{"action":"created","comment":{"url":"https://api.github.com/repos/emberjs/ember.js/pulls/comments/40644249","id":40644249,"diff_hunk":"@@ -90,49 +421,456 @@ if (isEnabled('ember-application-visit')) {\n     QUnit.expect(6);\n     QUnit.stop();\n \n-    var app;\n+    run(function() {\n+      createApplication();\n+\n+      App.register('template:application', compile('<h1>Hello world</h1> {{component \"x-child\"}}'));\n+\n+      App.register('view:application', View.extend({\n+        elementId: 'my-cool-app'\n+      }));\n+\n+      App.register('component:x-child', View.extend({\n+        elementId: 'child-view'\n+      }));\n+    });\n+\n+    assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n \n     run(function() {\n-      app = createApplication();\n-      app.instanceInitializer({\n-        name: 'register-application-template',\n-        initialize(app) {\n-          app.register('template:application', compile('<h1>Hello world</h1> {{component \"x-child\"}}'));\n-          app.register('view:application', View.extend({\n-            elementId: 'my-cool-app'\n-          }));\n-          app.register('component:x-child', View.extend({\n-            elementId: 'child-view'\n-          }));\n+      App.visit('/').then(function(instance) {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+        assert.equal(Ember.$('#qunit-fixture > #my-cool-app h1').text(), 'Hello world', 'the application was rendered once the promise resolves');\n+        assert.strictEqual(View.views['my-cool-app'], undefined, 'view was not registered globally');\n+\n+        function lookup(fullName) {\n+          if (isEnabled('ember-registry-container-reform')) {\n+            return instance.lookup(fullName);\n+          } else {\n+            return instance.container.lookup(fullName);\n+          }\n         }\n+\n+        assert.ok(lookup('-view-registry:main')['my-cool-app'] instanceof View, 'view was registered on the instance\\'s view registry');\n+        assert.ok(lookup('-view-registry:main')['child-view'] instanceof View, 'child view was registered on the instance\\'s view registry');\n+      }, function(error) {\n+        QUnit.start();\n+        assert.ok(false, 'The visit() promise was rejected: ' + error);\n       });\n     });\n+  });\n \n-    assert.equal(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+  QUnit.module('Ember.Application - visit() Integration Tests', {\n+    teardown() {\n+      if (instances) {\n+        run(instances, 'forEach', (i) => i.destroy());\n+        instances = [];\n+      }\n \n-    app.visit('/').then(function(instance) {\n-      QUnit.start();\n-      assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+      if (App) {\n+        run(App, 'destroy');\n+        App = null;\n+      }\n+    }\n+  });\n+\n+  QUnit.test('FastBoot-style setup', function(assert) {\n+    QUnit.expect(15);\n+    QUnit.stop();\n+\n+    let initCalled = false;\n+    let didInsertElementCalled = false;\n+\n+    run(function() {\n+      createApplication(true);\n+\n+      App.Router.map(function() {\n+        this.route('a');\n+        this.route('b');\n+      });\n+\n+      App.register('template:application', compile('<h1>Hello world</h1>\\n{{outlet}}'));\n+\n+      App.register('template:a', compile('<h2>Welcome to {{x-foo page=\"A\"}}</h2>'));\n+\n+      App.register('template:b', compile('<h2>{{x-foo page=\"B\"}}</h2>'));\n+\n+      App.register('template:components/x-foo', compile('Page {{page}}'));\n+\n+      App.register('component:x-foo', Component.extend({\n+        tagName: 'span',\n+        init() {\n+          this._super();\n+          initCalled = true;\n+        },\n+        didInsertElement() {\n+          didInsertElementCalled = true;\n+        }\n+      }));\n+    });\n+\n+    assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+\n+    function makeFakeDom() {\n+      // TODO: use simple-dom\n+      return document.implementation.createHTMLDocument();\n+    }\n+\n+    let a = run(function() {\n+      let dom = makeFakeDom();\n+\n+      return App.visit('/a', { browser: false, document: dom, rootElement: dom.body }).then(instance => {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+        assert.equal(instance.getURL(), '/a');\n+\n+        let serialized = dom.body.innerHTML;\n+        let $parsed = Ember.$(serialized);\n+\n+        assert.equal($parsed.find('h1').text(), 'Hello world');\n+        assert.equal($parsed.find('h2').text(), 'Welcome to Page A');\n+\n+        assert.ok(initCalled, 'Component#init should be called');\n+        assert.ok(!didInsertElementCalled, 'Component#didInsertElement should not be called');\n+\n+        assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+        QUnit.stop();\n+      });\n+    });\n+\n+    let b = run(function() {\n+      let dom = makeFakeDom();\n+\n+      return App.visit('/b', { browser: false, document: dom, rootElement: dom.body }).then(instance => {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+        assert.equal(instance.getURL(), '/b');\n+\n+        let serialized = dom.body.innerHTML;\n+        let $parsed = Ember.$(serialized);\n+\n+        assert.equal($parsed.find('h1').text(), 'Hello world');\n+        assert.equal($parsed.find('h2').text(), 'Page B');\n+\n+        assert.ok(initCalled, 'Component#init should be called');\n+        assert.ok(!didInsertElementCalled, 'Component#didInsertElement should not be called');\n+\n+        assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+        QUnit.stop();\n+      });\n+    });\n+\n+    Ember.RSVP.all([a, b]).finally(() => QUnit.start());\n+  });\n+\n+  QUnit.test('Ember Islands-style setup', function(assert) {\n+    QUnit.expect(14);\n+    QUnit.stop();\n+\n+    let xFooInitCalled = false;\n+    let xFooDidInsertElementCalled = false;\n+\n+    let xBarInitCalled = false;\n+    let xBarDidInsertElementCalled = false;\n+\n+    run(function() {\n+      createApplication(true);\n+\n+      App.Router.map(function() {\n+        this.route('show', { path: '/:component_name' });\n+      });\n+\n+      App.register('route:show', Route.extend({\n+        queryParams: {\n+          data: { refreshModel: true }\n+        },\n+\n+        model(params) {\n+          return {\n+            componentName: params.component_name,\n+            componentData: params.data ? JSON.parse(params.data) : undefined\n+          };\n+        }\n+      }));\n+\n+      let Counter = EmberObject.extend({\n+        value: 0,\n+\n+        increment() {\n+          this.incrementProperty('value');\n+        }\n+      });\n+\n+      App.register('service:isolated-counter', Counter);\n+      App.register('service:shared-counter', Counter.create(), { instantiate: false });\n+\n+      App.register('template:show', compile('{{component model.componentName model=model.componentData}}'));\n+\n+      App.register('template:components/x-foo', compile(`\n+        <h1>X-Foo</h1>\n+        <p>Hello {{model.name}}, I have been clicked {{isolatedCounter.value}} times ({{sharedCounter.value}} times combined)!</p>\n+      `));\n+\n+      App.register('component:x-foo', Component.extend({\n+        tagName: 'x-foo',\n+\n+        isolatedCounter: inject.service(),\n+        sharedCounter: inject.service(),\n+\n+        init() {\n+          this._super();\n+          xFooInitCalled = true;\n+        },\n+\n+        didInsertElement() {\n+          xFooDidInsertElementCalled = true;\n+        },\n+\n+        click() {\n+          this.get('isolatedCounter').increment();\n+          this.get('sharedCounter').increment();\n+        }\n+      }));\n+\n+      App.register('template:components/x-bar', compile(`\n+        <h1>X-Bar</h1>\n+        <button {{action \"incrementCounter\"}}>Join {{counter.value}} others in clicking me!</button>\n+      `));\n+\n+      App.register('component:x-bar', Component.extend({\n+        counter: inject.service('shared-counter'),\n+\n+        actions: {\n+          incrementCounter() {\n+            this.get('counter').increment();\n+          }\n+        },\n \n-      run(instance.view, 'appendTo', '#qunit-fixture');\n-      assert.equal(Ember.$('#qunit-fixture > #my-cool-app h1').text(), 'Hello world', 'the application was rendered once the promise resolves');\n-      assert.strictEqual(View.views['my-cool-app'], undefined, 'view was not registered globally');\n+        init() {\n+          this._super();\n+          xBarInitCalled = true;\n+        },\n \n-      function lookup(fullName) {\n-        if (isEnabled('ember-registry-container-reform')) {\n-          return instance.lookup(fullName);\n-        } else {\n-          return instance.container.lookup(fullName);\n+        didInsertElement() {\n+          xBarDidInsertElementCalled = true;\n         }\n+      }));\n+    });\n+\n+    let $foo = Ember.$('<div />').appendTo('#qunit-fixture');\n+    let $bar = Ember.$('<div />').appendTo('#qunit-fixture');\n+\n+    let data = encodeURIComponent(JSON.stringify({ name: 'Godfrey' }));\n+    run(function() {\n+      Ember.RSVP.all([\n+        App.visit(`/x-foo?data=${data}`, { rootElement: $foo[0] }),\n+        App.visit('/x-bar', { rootElement: $bar[0] })\n+      ]).then(() => {\n+        QUnit.start();\n+\n+        assert.ok(xFooInitCalled);\n+        assert.ok(xFooDidInsertElementCalled);\n+\n+        assert.ok(xBarInitCalled);\n+        assert.ok(xBarDidInsertElementCalled);\n+\n+        assert.equal($foo.find('h1').text(), 'X-Foo');\n+        assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 0 times (0 times combined)!');\n+        assert.ok($foo.text().indexOf('X-Bar') === -1);\n+\n+        assert.equal($bar.find('h1').text(), 'X-Bar');\n+        assert.equal($bar.find('button').text(), 'Join 0 others in clicking me!');\n+        assert.ok($bar.text().indexOf('X-Foo') === -1);\n+\n+        run(function() {\n+          $foo.find('x-foo').click();\n+        });\n+\n+        assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 1 times (1 times combined)!');\n+        assert.equal($bar.find('button').text(), 'Join 1 others in clicking me!');\n+\n+        run(function() {\n+          $bar.find('button').click();\n+          $bar.find('button').click();\n+        });\n+\n+        assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 1 times (3 times combined)!');\n+        assert.equal($bar.find('button').text(), 'Join 3 others in clicking me!');\n+      });\n+    });\n+  });\n+\n+  QUnit.test('Resource-discovery setup', function(assert) {\n+    QUnit.expect(26);\n+    QUnit.stop();\n+\n+    let xFooInstances = 0;\n+\n+    run(function() {\n+      createApplication(true);\n+\n+      App.Router.map(function() {\n+        this.route('a');\n+        this.route('b');\n+        this.route('c');\n+        this.route('d');\n+        this.route('e');\n+      });\n+\n+      let NetworkService = Ember.Object.extend({\n+        init() {\n+          this.set('requests', []);\n+        },\n+\n+        fetch(url) {\n+          this.get('requests').push(url);\n+          return Ember.RSVP.resolve();\n+        }\n+      });\n+\n+      App.register('service:network', NetworkService);\n+\n+      App.inject('route', 'network', 'service:network');\n+\n+      App.register('route:a', Route.extend({\n+        model() { return this.network.fetch('/a'); },\n+        afterModel() { this.replaceWith('b'); }\n+      }));\n+\n+      App.register('route:b', Route.extend({\n+        model() { return this.network.fetch('/b'); },\n+        afterModel() { this.replaceWith('c'); }\n+      }));\n+\n+      App.register('route:c', Route.extend({\n+        model() { return this.network.fetch('/c'); }\n+      }));\n+\n+      App.register('route:d', Route.extend({\n+        model() { return this.network.fetch('/d'); },\n+        afterModel() { this.replaceWith('e'); }\n+      }));\n+\n+      App.register('route:e', Route.extend({\n+        model() { return this.network.fetch('/e'); }\n+      }));\n+\n+      App.register('template:a', compile('{{x-foo}}'));\n+      App.register('template:b', compile('{{x-foo}}'));\n+      App.register('template:c', compile('{{x-foo}}'));\n+      App.register('template:d', compile('{{x-foo}}'));\n+      App.register('template:e', compile('{{x-foo}}'));\n+\n+      App.register('component:x-foo', Component.extend({\n+        init() {\n+          this._super();\n+          xFooInstances++;\n+        }\n+      }));\n+    });\n+\n+    assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+\n+    function lookup(instance, fullName) {\n+      if (isEnabled('ember-registry-container-reform')) {\n+        return instance.lookup(fullName);\n+      } else {\n+        return instance.container.lookup(fullName);\n       }\n+    }\n \n-      ok(lookup('-view-registry:main')['my-cool-app'] instanceof View, 'view was registered on the instance\\'s view registry');\n-      ok(lookup('-view-registry:main')['child-view'] instanceof View, 'child view was registered on the instance\\'s view registry');\n+    let a = run(function() {\n+      return App.visit('/a', { browser: false, render: false }).then((instance) => {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n \n-      instance.destroy();\n-    }, function(error) {\n-      QUnit.start();\n-      assert.ok(false, 'The visit() promise was rejected: ' + error);\n+        assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+        assert.strictEqual(xFooInstances, 0, 'did not create any x-foo components');\n+\n+        let viewRegistry = lookup(instance, '-view-registry:main');\n+        assert.strictEqual(Object.keys(viewRegistry).length, 0, 'did not create any views');\n+\n+        let networkService = lookup(instance, 'service:network');\n+        assert.deepEqual(networkService.get('requests'), ['/a', '/b', '/c']);\n+        QUnit.stop();\n+      });\n+    });\n+\n+    let b = run(function() {\n+      return App.visit('/b', { browser: false, render: false }).then((instance) => {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+\n+        assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+        assert.strictEqual(xFooInstances, 0, 'did not create any x-foo components');\n+\n+        let viewRegistry = lookup(instance, '-view-registry:main');\n+        assert.strictEqual(Object.keys(viewRegistry).length, 0, 'did not create any views');\n+\n+        let networkService = lookup(instance, 'service:network');\n+        assert.deepEqual(networkService.get('requests'), ['/b', '/c']);\n+        QUnit.stop();\n+      });\n+    });\n+\n+    let c = run(function() {\n+      return App.visit('/c', { browser: false, render: false }).then((instance) => {\n+        QUnit.start();\n+        assert.ok(instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance');\n+\n+        assert.strictEqual(Ember.$('#qunit-fixture').children().length, 0, 'there are no elements in the fixture element');\n+        assert.strictEqual(xFooInstances, 0, 'did not create any x-foo components');\n+\n+        let viewRegistry = lookup(instance, '-view-registry:main');\n+        assert.strictEqual(Object.keys(viewRegistry).length, 0, 'did not create any views');\n+\n+        let networkService = lookup(instance, 'service:network');\n+        assert.deepEqual(networkService.get('requests'), ['/c']);\n+        QUnit.stop();\n+      });\n     });\n+\n+    let d = run(function() {","path":"packages/ember-application/tests/system/visit_test.js","position":896,"original_position":896,"commit_id":"8e317ee6ddd10493382d6e2ec9477bb457ce873c","original_commit_id":"8e317ee6ddd10493382d6e2ec9477bb457ce873c","user":{"login":"chancancode","id":55829,"avatar_url":"https://avatars.githubusercontent.com/u/55829?v=3","gravatar_id":"","url":"https://api.github.com/users/chancancode","html_url":"https://github.com/chancancode","followers_url":"https://api.github.com/users/chancancode/followers","following_url":"https://api.github.com/users/chancancode/following{/other_user}","gists_url":"https://api.github.com/users/chancancode/gists{/gist_id}","starred_url":"https://api.github.com/users/chancancode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chancancode/subscriptions","organizations_url":"https://api.github.com/users/chancancode/orgs","repos_url":"https://api.github.com/users/chancancode/repos","events_url":"https://api.github.com/users/chancancode/events{/privacy}","received_events_url":"https://api.github.com/users/chancancode/received_events","type":"User","site_admin":false},"body":"I don't disagree. I admit that after working on this PR, whatever mental model I thought had about the runloop has been completely destroyed :P\r\n\r\nI think I mostly just inherited these patterns from copying the existing tests. But in this particular case I believe removing it would fail the test because the first `visit` here calls `boot`, which has a `run.once(\"actions\", ...);` line somewhere along the way.","created_at":"2015-09-29T08:07:41Z","updated_at":"2015-09-29T08:07:41Z","html_url":"https://github.com/emberjs/ember.js/pull/12394#discussion_r40644249","pull_request_url":"https://api.github.com/repos/emberjs/ember.js/pulls/12394","_links":{"self":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/comments/40644249"},"html":{"href":"https://github.com/emberjs/ember.js/pull/12394#discussion_r40644249"},"pull_request":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/12394"}}},"pull_request":{"url":"https://api.github.com/repos/emberjs/ember.js/pulls/12394","id":46079008,"html_url":"https://github.com/emberjs/ember.js/pull/12394","diff_url":"https://github.com/emberjs/ember.js/pull/12394.diff","patch_url":"https://github.com/emberjs/ember.js/pull/12394.patch","issue_url":"https://api.github.com/repos/emberjs/ember.js/issues/12394","number":12394,"state":"open","locked":false,"title":"Revisiting the visit API (aka FastBoot™)","user":{"login":"chancancode","id":55829,"avatar_url":"https://avatars.githubusercontent.com/u/55829?v=3","gravatar_id":"","url":"https://api.github.com/users/chancancode","html_url":"https://github.com/chancancode","followers_url":"https://api.github.com/users/chancancode/followers","following_url":"https://api.github.com/users/chancancode/following{/other_user}","gists_url":"https://api.github.com/users/chancancode/gists{/gist_id}","starred_url":"https://api.github.com/users/chancancode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chancancode/subscriptions","organizations_url":"https://api.github.com/users/chancancode/orgs","repos_url":"https://api.github.com/users/chancancode/repos","events_url":"https://api.github.com/users/chancancode/events{/privacy}","received_events_url":"https://api.github.com/users/chancancode/received_events","type":"User","site_admin":false},"body":":arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook: :arrow_right_hook:\r\n\r\nIt's time to put on some finishing touches for the `visit` API! Since this is a somewhat big patch, here is a detailed description.\r\n\r\n# Summary\r\n\r\nThe `visit` API exposes `ApplicationInstance` as a useful primitive for many scenarios, including FastBoot™, widgets, resource discover, testing, and more.\r\n\r\n## Background\r\n\r\nEmber 1.12 introduced the concept of `ApplicationInstance`s. While they are very well designed and could be very useful in many cases, there are no official APIs to create them. The `visit` API helps fill this void by exposing a supported way to boot and customize instances.\r\n\r\n## What's in the box :package: \r\n\r\n* [x] Refactoring\r\n* [x] New APIs\r\n* [x] Tests\r\n* [x] Documentation\r\n* [x] Bonus DLC Pack\r\n* [x] Emojis\r\n\r\n### Refactoring\r\n\r\n1. **A Clear Boot Phase**\r\n\r\n    Previously, we don't have a very clear concept of a boot phase, especially for `ApplictionInstance`. This creates a lot of implicit timing dependencies around when certain options can be set/changed. For example, the `ember-cli-fastboot` changes the `autoboot` flag in an application initializer (i.e. in the middle of the boot phase), which created a strange fork in the boot flow.\r\n\r\n    As part of this patch I did some work to solidify the boot phase. Options like `autoboot` should be set\r\n~~before `boot()` is called~~ at instantiation time. What I can do on `Application` is rather limited, but I tried to make that clear in the new `ApplicationInstance` boot phase, which takes a hash of boot-time options in favor of ad-hoc methods like `overrideRouterLocation`.\r\n\r\n2. **Asynchronous APIs**\r\n\r\n    Booting an `Application` (and to a lesser extent, an `ApplicationInstance`) is an inertially asynchronous task. Naturally, we would like to express the asynchrony here with promises/async functions. However, the architecture we have predated Ember's adoption of promises, and a lot of code assumes that it is a \"synchronous\". Specifically, a lot of tests assumes the last call to `app.advanceReadiness()` or `app.reset()` will complete the boot process when the same runloop ends, which makes it impossible to async-ify the process.\r\n\r\n    This wasn't clear to me when I started working on the code – I actually spent some time making them fully async, only to find a lot of tests failing. However, we should start embracing the asynchrony here going forward. I converted the primary (internal) APIs async, which we can start using for new code like the `visit` API. Internally, they wrap their synchronous counterparts which we will use for legacy paths.\r\n\r\n    This makes things a bit annoying in the short term, but it should help us iterate towards the right direction.\r\n\r\n3. **Globals Mode**\r\n\r\n    Previously, setting the `autoboot` flag also skipped some seemingly unrelated code paths in the boot process. It turns out that most of these code paths are there for the legacy \"globals mode\" (where the application object acts as a \"namespace\" for attaching classes, i.e. `App.MyComponent = ...`).\r\n\r\n   While it's fair to overload the `autoboot` flag as an opt-out for these features, they are ultimately unrelated concerns. To make the distinction clear, I added an internal `_globalsMode` flag to track these code paths. Its value is currently tied to the `autoboot` flag, but you can manually hardcode the `autoboot = true, _globalsMode = false` combo and run the tests, which should reveal that spots where we are relying on these behaviors. Since most of these features have already been deprecated, separating it from `autoboot` as a concept should help to review and remove them independently.\r\n\r\n4. **Expansion of the `environment` concept**\r\n\r\n    With HTMLBars, most of the initial render path has already been jQuery-free (which is why FastBoot works in node). The last bit I had to plumb was the `view.appendTo(rootElement)` call, which uses jQuery to resolve the `rootElement` selector into an Element.\r\n\r\n    The current `visit` implement works around this by exposing the \"root view\" without attaching its element. This isn't ideal since we already deprecated/removed views and it requires the use of other private APIs like `appendTo` and `renderToElement`.\r\n\r\n    In practice, if we were given an actual `Element` object as the `rootElement`, our renderer already knows how to append it into the DOM without jQuery. All I need to do is to tell plumb that through to the view layer so that it can skip the jQuery lookup if the user has opt-ed to not user jQuery. To do this, I expanded on the `environment` abstraction and made it instance specific through the `-environment:main` container key. I only did what I needed to do to make my scenarios work, but that should lay the ground work for the eventual \"optional jQuery\" world.\r\n\r\nSee also https://github.com/emberjs/ember.js/issues/11291 and https://github.com/emberjs/ember.js/pull/11289#issuecomment-105960488\r\n\r\n### New (Public) APIs\r\n\r\n* `Application.autoboot` flag\r\n* `Application#boot()`\r\n* `Application#visit()`\r\n* `ApplicationInstance#getURL()`\r\n* `ApplicationInstance#visit()`\r\n\r\n### Tests\r\n\r\nI expanded the unit tests coverage, and added some end-to-end integration tests for the a few target scenarios we intend to support.\r\n\r\n### Documentation\r\n\r\nYou can find them in the diff.\r\n\r\n### Bonus DLC Pack\r\n\r\nTo redeem, enter the following code into the Ember Data store: `F83J-7BD2-D16Z-3QA4`\r\n\r\n### Emojis\r\n\r\n:+1: :star: :point_right: :heart: :headphones: :key: :rocket: :japanese_goblin: \r\n\r\n## Differences from current implementation\r\n\r\n* `Application.autoboot` should only be set before boot time (e.g. at `create` time)\r\n* `Application#visit()` no longer rejects if a redirect occurred during the initial transition\r\n* `Application#visit()` no longer attach a `view` property to the instance it returns\r\n* `Application#visit()` no longer prevents the router from inserting the top-level view into the DOM (use `rootElement` to customize the insertion point as usual)\r\n* `Application#visit()` resolves only after rendering is complete and the root view is attached to the DOM\r\n* `Application#visit()` now takes an option hash for customizing the instance\r\n* Uncaught errors in application/instance initializers now causes `Application#visit()` to reject instead of synchronously throws\r\n* Removed `ApplicationView#overrideRouterLocation` (use the `location` boot option instead)\r\n* New flags that enables a much wider range of scenarios\r\n* Better documentation\r\n\r\n## TODOs\r\n\r\n* [ ] Add some node tests\r\n* [ ] Update the `ember-cli-fastboot` implementation\r\n* [ ] Move [`ember-cli-fastboot`-specific hacks](https://github.com/tildeio/ember-cli-fastboot/blob/master/app/initializers/fastboot.js#L12-L20) into the appropriate package; since the DOMHelper API is not public, we cannot ship until things works out of the box (see also https://github.com/krisselden/morph-range/pull/7)\r\n* [ ] Better error reporting: differentiate between a `BootError` and a `TransitionError`: for transition errors, it is useful to still return the instance, as there we might have rendered an error page into the DOM\r\n\r\n## Open questions\r\n\r\n* [ ] Various TODOs in the code comments that I have no answers to\r\n* [ ] Are we comfortable committing the the three scenarios I listed in the documentation? Do we need to add more?\r\n* [ ] Is the `ApplicationInstance#visit` implementation correct?\r\n* [ ] Can we consolidate `autoboot` with Ember CLI's `autorun` flag? (they do related but not identical things)\r\n\r\n## Future extensions\r\n\r\n* Formalize the API requirements for the fake DOM, DOMHelper, etc\r\n* Use these API for tests: boot a new instance in each test case by calling `visit()` and `destroy()` it in teardown, instead of calling `App.reset()`. Use `ApplicationInstance#visit` to implement the `visit` test helper, etc\r\n* Work with @mitchlloyd on Ember Islands type scenarios (see the [test](https://github.com/emberjs/ember.js/commit/b037894a2d278effb07f469d64cb68c7ec0739b8))\r\n* Flesh out the options to enable other scenarios\r\n* Plumb the `environment` through the rest of the view layer to make `jQuery` optional\r\n* Remove reliance on globals mode in tests\r\n* Add a mechanism to differentiate between the various types of redirects and common errors, so that a FastBoot server can return the right status code\r\n\r\ncc @wycats @tomdale @stefanpenner @krisselden @mitchlloyd @chadhietala @joshvfleming :pray: ","created_at":"2015-09-26T09:36:14Z","updated_at":"2015-09-29T08:07:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/emberjs/ember.js/pulls/12394/commits","review_comments_url":"https://api.github.com/repos/emberjs/ember.js/pulls/12394/comments","review_comment_url":"https://api.github.com/repos/emberjs/ember.js/pulls/comments{/number}","comments_url":"https://api.github.com/repos/emberjs/ember.js/issues/12394/comments","statuses_url":"https://api.github.com/repos/emberjs/ember.js/statuses/8e317ee6ddd10493382d6e2ec9477bb457ce873c","head":{"label":"chancancode:visit_api","ref":"visit_api","sha":"8e317ee6ddd10493382d6e2ec9477bb457ce873c","user":{"login":"chancancode","id":55829,"avatar_url":"https://avatars.githubusercontent.com/u/55829?v=3","gravatar_id":"","url":"https://api.github.com/users/chancancode","html_url":"https://github.com/chancancode","followers_url":"https://api.github.com/users/chancancode/followers","following_url":"https://api.github.com/users/chancancode/following{/other_user}","gists_url":"https://api.github.com/users/chancancode/gists{/gist_id}","starred_url":"https://api.github.com/users/chancancode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chancancode/subscriptions","organizations_url":"https://api.github.com/users/chancancode/orgs","repos_url":"https://api.github.com/users/chancancode/repos","events_url":"https://api.github.com/users/chancancode/events{/privacy}","received_events_url":"https://api.github.com/users/chancancode/received_events","type":"User","site_admin":false},"repo":{"id":9660972,"name":"ember.js","full_name":"chancancode/ember.js","owner":{"login":"chancancode","id":55829,"avatar_url":"https://avatars.githubusercontent.com/u/55829?v=3","gravatar_id":"","url":"https://api.github.com/users/chancancode","html_url":"https://github.com/chancancode","followers_url":"https://api.github.com/users/chancancode/followers","following_url":"https://api.github.com/users/chancancode/following{/other_user}","gists_url":"https://api.github.com/users/chancancode/gists{/gist_id}","starred_url":"https://api.github.com/users/chancancode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chancancode/subscriptions","organizations_url":"https://api.github.com/users/chancancode/orgs","repos_url":"https://api.github.com/users/chancancode/repos","events_url":"https://api.github.com/users/chancancode/events{/privacy}","received_events_url":"https://api.github.com/users/chancancode/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/chancancode/ember.js","description":"Ember.js - A JavaScript framework for creating ambitious web applications","fork":true,"url":"https://api.github.com/repos/chancancode/ember.js","forks_url":"https://api.github.com/repos/chancancode/ember.js/forks","keys_url":"https://api.github.com/repos/chancancode/ember.js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/chancancode/ember.js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/chancancode/ember.js/teams","hooks_url":"https://api.github.com/repos/chancancode/ember.js/hooks","issue_events_url":"https://api.github.com/repos/chancancode/ember.js/issues/events{/number}","events_url":"https://api.github.com/repos/chancancode/ember.js/events","assignees_url":"https://api.github.com/repos/chancancode/ember.js/assignees{/user}","branches_url":"https://api.github.com/repos/chancancode/ember.js/branches{/branch}","tags_url":"https://api.github.com/repos/chancancode/ember.js/tags","blobs_url":"https://api.github.com/repos/chancancode/ember.js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/chancancode/ember.js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/chancancode/ember.js/git/refs{/sha}","trees_url":"https://api.github.com/repos/chancancode/ember.js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/chancancode/ember.js/statuses/{sha}","languages_url":"https://api.github.com/repos/chancancode/ember.js/languages","stargazers_url":"https://api.github.com/repos/chancancode/ember.js/stargazers","contributors_url":"https://api.github.com/repos/chancancode/ember.js/contributors","subscribers_url":"https://api.github.com/repos/chancancode/ember.js/subscribers","subscription_url":"https://api.github.com/repos/chancancode/ember.js/subscription","commits_url":"https://api.github.com/repos/chancancode/ember.js/commits{/sha}","git_commits_url":"https://api.github.com/repos/chancancode/ember.js/git/commits{/sha}","comments_url":"https://api.github.com/repos/chancancode/ember.js/comments{/number}","issue_comment_url":"https://api.github.com/repos/chancancode/ember.js/issues/comments{/number}","contents_url":"https://api.github.com/repos/chancancode/ember.js/contents/{+path}","compare_url":"https://api.github.com/repos/chancancode/ember.js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/chancancode/ember.js/merges","archive_url":"https://api.github.com/repos/chancancode/ember.js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/chancancode/ember.js/downloads","issues_url":"https://api.github.com/repos/chancancode/ember.js/issues{/number}","pulls_url":"https://api.github.com/repos/chancancode/ember.js/pulls{/number}","milestones_url":"https://api.github.com/repos/chancancode/ember.js/milestones{/number}","notifications_url":"https://api.github.com/repos/chancancode/ember.js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/chancancode/ember.js/labels{/name}","releases_url":"https://api.github.com/repos/chancancode/ember.js/releases{/id}","created_at":"2013-04-25T01:00:39Z","updated_at":"2014-02-20T04:01:24Z","pushed_at":"2015-09-26T05:41:19Z","git_url":"git://github.com/chancancode/ember.js.git","ssh_url":"git@github.com:chancancode/ember.js.git","clone_url":"https://github.com/chancancode/ember.js.git","svn_url":"https://github.com/chancancode/ember.js","homepage":"http://www.emberjs.com","size":27213,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"emberjs:master","ref":"master","sha":"00ed2325709808ed6349b707da84ed44783d3ad0","user":{"login":"emberjs","id":1253363,"avatar_url":"https://avatars.githubusercontent.com/u/1253363?v=3","gravatar_id":"","url":"https://api.github.com/users/emberjs","html_url":"https://github.com/emberjs","followers_url":"https://api.github.com/users/emberjs/followers","following_url":"https://api.github.com/users/emberjs/following{/other_user}","gists_url":"https://api.github.com/users/emberjs/gists{/gist_id}","starred_url":"https://api.github.com/users/emberjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/emberjs/subscriptions","organizations_url":"https://api.github.com/users/emberjs/orgs","repos_url":"https://api.github.com/users/emberjs/repos","events_url":"https://api.github.com/users/emberjs/events{/privacy}","received_events_url":"https://api.github.com/users/emberjs/received_events","type":"Organization","site_admin":false},"repo":{"id":1801829,"name":"ember.js","full_name":"emberjs/ember.js","owner":{"login":"emberjs","id":1253363,"avatar_url":"https://avatars.githubusercontent.com/u/1253363?v=3","gravatar_id":"","url":"https://api.github.com/users/emberjs","html_url":"https://github.com/emberjs","followers_url":"https://api.github.com/users/emberjs/followers","following_url":"https://api.github.com/users/emberjs/following{/other_user}","gists_url":"https://api.github.com/users/emberjs/gists{/gist_id}","starred_url":"https://api.github.com/users/emberjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/emberjs/subscriptions","organizations_url":"https://api.github.com/users/emberjs/orgs","repos_url":"https://api.github.com/users/emberjs/repos","events_url":"https://api.github.com/users/emberjs/events{/privacy}","received_events_url":"https://api.github.com/users/emberjs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/emberjs/ember.js","description":"Ember.js - A JavaScript framework for creating ambitious web applications","fork":false,"url":"https://api.github.com/repos/emberjs/ember.js","forks_url":"https://api.github.com/repos/emberjs/ember.js/forks","keys_url":"https://api.github.com/repos/emberjs/ember.js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/emberjs/ember.js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/emberjs/ember.js/teams","hooks_url":"https://api.github.com/repos/emberjs/ember.js/hooks","issue_events_url":"https://api.github.com/repos/emberjs/ember.js/issues/events{/number}","events_url":"https://api.github.com/repos/emberjs/ember.js/events","assignees_url":"https://api.github.com/repos/emberjs/ember.js/assignees{/user}","branches_url":"https://api.github.com/repos/emberjs/ember.js/branches{/branch}","tags_url":"https://api.github.com/repos/emberjs/ember.js/tags","blobs_url":"https://api.github.com/repos/emberjs/ember.js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/emberjs/ember.js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/emberjs/ember.js/git/refs{/sha}","trees_url":"https://api.github.com/repos/emberjs/ember.js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/emberjs/ember.js/statuses/{sha}","languages_url":"https://api.github.com/repos/emberjs/ember.js/languages","stargazers_url":"https://api.github.com/repos/emberjs/ember.js/stargazers","contributors_url":"https://api.github.com/repos/emberjs/ember.js/contributors","subscribers_url":"https://api.github.com/repos/emberjs/ember.js/subscribers","subscription_url":"https://api.github.com/repos/emberjs/ember.js/subscription","commits_url":"https://api.github.com/repos/emberjs/ember.js/commits{/sha}","git_commits_url":"https://api.github.com/repos/emberjs/ember.js/git/commits{/sha}","comments_url":"https://api.github.com/repos/emberjs/ember.js/comments{/number}","issue_comment_url":"https://api.github.com/repos/emberjs/ember.js/issues/comments{/number}","contents_url":"https://api.github.com/repos/emberjs/ember.js/contents/{+path}","compare_url":"https://api.github.com/repos/emberjs/ember.js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/emberjs/ember.js/merges","archive_url":"https://api.github.com/repos/emberjs/ember.js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/emberjs/ember.js/downloads","issues_url":"https://api.github.com/repos/emberjs/ember.js/issues{/number}","pulls_url":"https://api.github.com/repos/emberjs/ember.js/pulls{/number}","milestones_url":"https://api.github.com/repos/emberjs/ember.js/milestones{/number}","notifications_url":"https://api.github.com/repos/emberjs/ember.js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/emberjs/ember.js/labels{/name}","releases_url":"https://api.github.com/repos/emberjs/ember.js/releases{/id}","created_at":"2011-05-25T23:39:40Z","updated_at":"2015-09-29T02:51:46Z","pushed_at":"2015-09-29T01:59:05Z","git_url":"git://github.com/emberjs/ember.js.git","ssh_url":"git@github.com:emberjs/ember.js.git","clone_url":"https://github.com/emberjs/ember.js.git","svn_url":"https://github.com/emberjs/ember.js","homepage":"http://www.emberjs.com","size":91899,"stargazers_count":14815,"watchers_count":14815,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3172,"mirror_url":null,"open_issues_count":300,"forks":3172,"open_issues":300,"watchers":14815,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/12394"},"html":{"href":"https://github.com/emberjs/ember.js/pull/12394"},"issue":{"href":"https://api.github.com/repos/emberjs/ember.js/issues/12394"},"comments":{"href":"https://api.github.com/repos/emberjs/ember.js/issues/12394/comments"},"review_comments":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/12394/comments"},"review_comment":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/emberjs/ember.js/pulls/12394/commits"},"statuses":{"href":"https://api.github.com/repos/emberjs/ember.js/statuses/8e317ee6ddd10493382d6e2ec9477bb457ce873c"}}}}