From 7602d25ac9e86bd628634e982ecf711657b1cc01 Mon Sep 17 00:00:00 2001 From: Alex LaFroscia Date: Wed, 24 Jun 2020 14:59:44 -0400 Subject: [PATCH] chore: re-write tests to use modern Ember features BREAKING CHANGE: Error about missing component is no longer swallowed in Production --- .eslintrc.js | 8 + addon/components/ember-islands.js | 4 +- addon/utils/get-render-component.js | 6 +- addon/utils/reconciler.js | 3 +- .../ember-islands/missing-component.js | 4 +- tests/acceptance/inner-content-test.js | 33 +-- tests/acceptance/missing-component-test.js | 64 ++--- tests/acceptance/rendering-components-test.js | 98 ++----- .../dummy/app/components/dummy-application.js | 4 +- .../app/components/inner-content-component.js | 4 +- .../dummy/app/components/js-only-component.js | 4 +- .../dummy/app/components/nested-component.js | 4 +- .../app/components/stateful-component.js | 5 +- .../app/components/top-level-component.js | 4 +- .../templates/components/nested-component.hbs | 2 +- .../components/top-level-component.hbs | 2 +- .../components/ember-islands-test.js | 145 ++++------ .../integration/components/reconcile-test.js | 266 ------------------ .../components/rerendering-test.js | 263 +++++++++++++++++ 19 files changed, 418 insertions(+), 505 deletions(-) delete mode 100644 tests/integration/components/reconcile-test.js create mode 100644 tests/integration/components/rerendering-test.js diff --git a/.eslintrc.js b/.eslintrc.js index 09708a8..90cdd3e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,5 +49,13 @@ module.exports = { plugins: ["node"], extends: ["plugin:node/recommended"], }, + // test files + { + files: ["tests/**"], + rules: { + "ember/closure-actions": "off", + "ember/no-jquery": "off", + }, + }, ], }; diff --git a/addon/components/ember-islands.js b/addon/components/ember-islands.js index 1ca92d2..e125885 100644 --- a/addon/components/ember-islands.js +++ b/addon/components/ember-islands.js @@ -1,11 +1,11 @@ -import Ember from "ember"; +import Component from "@ember/component"; import Reconciler from "ember-islands/utils/reconciler"; import queryIslandComponents from "ember-islands/utils/query-island-components"; import getRenderComponent from "ember-islands/utils/get-render-component"; let eiInstance; -export default Ember.Component.extend({ +export default Component.extend({ tagName: "", init() { diff --git a/addon/utils/get-render-component.js b/addon/utils/get-render-component.js index 237ec54..e37cb1b 100644 --- a/addon/utils/get-render-component.js +++ b/addon/utils/get-render-component.js @@ -1,14 +1,14 @@ +import { getOwner } from "@ember/application"; +import Component from "@ember/component"; import Ember from "ember"; -const { getOwner, Component, Logger } = Ember; +const { Logger } = Ember; export default function getRenderComponent(emberObject) { let owner = getOwner(emberObject); return function renderComponent({ name, attrs, element }) { let { component, layout } = lookupComponent(owner, name); - Ember.assert(missingComponentMessage(name), component); - // This can only be true in production mode where assert is a no-op. if (!component) { ({ component, layout } = provideMissingComponentInProductionMode( owner, diff --git a/addon/utils/reconciler.js b/addon/utils/reconciler.js index 53c8238..7a78fa4 100644 --- a/addon/utils/reconciler.js +++ b/addon/utils/reconciler.js @@ -1,5 +1,4 @@ -import Ember from "ember"; -const { guidFor } = Ember; +import { guidFor } from "@ember/object/internals"; export default class Reconciler { /** diff --git a/app/components/ember-islands/missing-component.js b/app/components/ember-islands/missing-component.js index 62051cd..e142122 100644 --- a/app/components/ember-islands/missing-component.js +++ b/app/components/ember-islands/missing-component.js @@ -1,3 +1,3 @@ -import Ember from "ember"; +import Component from "@ember/component"; -export default Ember.Component.extend(); +export default Component.extend(); diff --git a/tests/acceptance/inner-content-test.js b/tests/acceptance/inner-content-test.js index 211a2ba..fe09cee 100644 --- a/tests/acceptance/inner-content-test.js +++ b/tests/acceptance/inner-content-test.js @@ -1,12 +1,12 @@ -import Ember from "ember"; import { module, test } from "qunit"; -import startApp from "../helpers/start-app"; +import { setupApplicationTest } from "ember-qunit"; +import { getRootElement, findAll, visit } from "@ember/test-helpers"; -var application; +module("Acceptance | Using Inner Content", function (hooks) { + setupApplicationTest(hooks); -module("Acceptance: Using Inner Content", { - beforeEach: function () { - document.getElementById("ember-testing").innerHTML = ` + hooks.beforeEach(function () { + getRootElement().innerHTML = `
@@ -14,28 +14,19 @@ module("Acceptance: Using Inner Content", {
`; + }); - application = startApp(); - }, - - afterEach: function () { - Ember.run(application, "destroy"); - document.getElementById("ember-testing").innerHTML = ""; - }, -}); - -test("extracting innerContent", function (assert) { - assert.expect(2); - visit("/"); + test("extracting innerContent", async function (assert) { + assert.expect(2); + await visit("/"); - andThen(function () { assert.equal( - find("#element-with-inner-content > #inner-content").length, + findAll("#element-with-inner-content > #inner-content").length, 0, "The inner content of the server-rendered element is replaced" ); assert.equal( - find(".inner-content-component > #inner-content").length, + findAll(".inner-content-component > #inner-content").length, 1, "The innerContent is passed to components" ); diff --git a/tests/acceptance/missing-component-test.js b/tests/acceptance/missing-component-test.js index 25f6e70..2f9e218 100644 --- a/tests/acceptance/missing-component-test.js +++ b/tests/acceptance/missing-component-test.js @@ -1,52 +1,46 @@ -import Ember from "ember"; import { module, test } from "qunit"; -import startApp from "../helpers/start-app"; - -let application, originalAssert, originalError, errors; - -module("Acceptance: Dealing with missing components in production", { - beforeEach: function () { - // Put some static content on the page before the Ember application loads. - // This mimics server-rendered content. - document.getElementById("ember-testing").innerHTML = ` -
-
- `; +import { setupApplicationTest } from "ember-qunit"; +import { getRootElement, visit } from "@ember/test-helpers"; +import Ember from "ember"; - // Replace Ember's `assert` function with a no-op. This mirrors Ember's - // behavior in production mode. - originalAssert = Ember.assert; - Ember.assert = () => {}; +let originalError, errors; +module("Acceptance | Dealing with missing components in production", function ( + hooks +) { + hooks.beforeEach(function () { // Replace Ember's Logger.error with a fake that records the errors. originalError = Ember.Logger.error; errors = []; Ember.Logger.error = (message) => { errors.push(message); }; + }); - application = startApp(); - }, + setupApplicationTest(hooks); - afterEach: function () { - Ember.assert = originalAssert; - Ember.Logger.error = originalError; + hooks.beforeEach(function () { + // Put some static content on the page before the Ember application loads. + // This mimics server-rendered content. + getRootElement().innerHTML = ` +
+
+ `; + }); - Ember.run(application, "destroy"); - document.getElementById("ember-testing").innerHTML = ""; - }, -}); + hooks.afterEach(function () { + Ember.Logger.error = originalError; + }); -test("rendering the found component", function (assert) { - assert.expect(2); - visit("/"); + test("rendering the found component", async function (assert) { + await visit("/"); - andThen(function () { - assert.equal( - find("p:contains(top level component)").length, - 1, - "The top level component was rendered" - ); + assert + .dom("p") + .includesText( + "top level component", + "The top level component was rendered" + ); assert.deepEqual( errors, [ diff --git a/tests/acceptance/rendering-components-test.js b/tests/acceptance/rendering-components-test.js index 38f8af3..c975162 100644 --- a/tests/acceptance/rendering-components-test.js +++ b/tests/acceptance/rendering-components-test.js @@ -1,93 +1,49 @@ -import Ember from "ember"; import { module, test } from "qunit"; -import startApp from "../helpers/start-app"; +import { setupApplicationTest } from "ember-qunit"; +import { click, getRootElement, visit } from "@ember/test-helpers"; -var application; +module("Acceptance | Rendering Components", function (hooks) { + setupApplicationTest(hooks); -module("Acceptance: Rendering Components", { - beforeEach: function () { + hooks.beforeEach(function () { // Put some static content on the page before the Ember application loads. // This mimics server-rendered content. - document.getElementById("ember-testing").innerHTML = ` + getRootElement().innerHTML = `

server-rendered content top

server-rendered content bottom

`; - - application = startApp(); - }, - - afterEach: function () { - Ember.run(application, "destroy"); - document.getElementById("ember-testing").innerHTML = ""; - }, -}); - -test("rendering a component with an attribute", function (assert) { - assert.expect(2); - visit("/"); - - andThen(function () { - assert.equal( - find("p:contains(top level component)").length, - 1, - "The top level component was rendered" - ); - assert.equal( - find("#component-title:contains(Component Title)").length, - 1, - "Passed in attributes can be used" - ); }); -}); -test("using component events", function (assert) { - assert.expect(2); - visit("/"); + test("rendering a component with an attribute", async function (assert) { + await visit("/"); + + assert + .dom("[data-test-top-level-component-message]") + .exists("The top level component was rendered"); - andThen(function () { - assert.equal( - find("#expanded-content").length, - 0, - "Expanded content is hidden at first" - ); + assert + .dom("#component-title") + .includesText("Component Title", "Passed in attributes can be used"); }); - click("#toggle-expanded"); + test("using component events", async function (assert) { + await visit("/"); - andThen(function () { - assert.equal( - find("#expanded-content").length, - 1, - "The expanded content is showing" - ); - }); -}); + assert + .dom("#expanded-content") + .doesNotExist("Expanded content is hidden at first"); -test("using nested components", function (assert) { - assert.expect(3); - visit("/"); + await click("#toggle-expanded"); - andThen(function () { - assert.equal( - find("p:contains(A nested component)").length, - 1, - "The nested component was rendered" - ); - assert.equal( - find("#expanded-content").length, - 0, - "Expanded content is hidden at first" - ); + assert.dom("#expanded-content").exists("The expanded content is showing"); }); - click("#nested-component-toggle-expanded"); + test("using nested components", async function (assert) { + await visit("/"); - andThen(function () { - assert.equal( - find("#expanded-content").length, - 1, - "The expanded content is showing" - ); + assert + .dom("[data-test-nested-component-message]") + .exists("The nested component was rendered"); }); }); diff --git a/tests/dummy/app/components/dummy-application.js b/tests/dummy/app/components/dummy-application.js index 140e3e8..0dce0ed 100644 --- a/tests/dummy/app/components/dummy-application.js +++ b/tests/dummy/app/components/dummy-application.js @@ -1,6 +1,6 @@ -import Ember from "ember"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ init() { this._super(...arguments); this.isShowingIslandComponents = true; diff --git a/tests/dummy/app/components/inner-content-component.js b/tests/dummy/app/components/inner-content-component.js index 9903c30..01310e7 100644 --- a/tests/dummy/app/components/inner-content-component.js +++ b/tests/dummy/app/components/inner-content-component.js @@ -1,5 +1,5 @@ -import Ember from "ember"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: [":inner-content-component"], }); diff --git a/tests/dummy/app/components/js-only-component.js b/tests/dummy/app/components/js-only-component.js index fbb482f..0c55e2e 100644 --- a/tests/dummy/app/components/js-only-component.js +++ b/tests/dummy/app/components/js-only-component.js @@ -1,6 +1,4 @@ -import Ember from "ember"; - -const { Component } = Ember; +import Component from "@ember/component"; export default Component.extend({ classNames: ["js-only-component"], diff --git a/tests/dummy/app/components/nested-component.js b/tests/dummy/app/components/nested-component.js index b3e7dc7..1c3d36d 100644 --- a/tests/dummy/app/components/nested-component.js +++ b/tests/dummy/app/components/nested-component.js @@ -1,6 +1,6 @@ -import Ember from "ember"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ isExpanded: false, actions: { diff --git a/tests/dummy/app/components/stateful-component.js b/tests/dummy/app/components/stateful-component.js index c0c141b..8a2fa8e 100644 --- a/tests/dummy/app/components/stateful-component.js +++ b/tests/dummy/app/components/stateful-component.js @@ -1,6 +1,5 @@ -import Ember from "ember"; - -const { Component, computed } = Ember; +import Component from "@ember/component"; +import { computed } from "@ember/object"; export default Component.extend({ tagName: "button", diff --git a/tests/dummy/app/components/top-level-component.js b/tests/dummy/app/components/top-level-component.js index 4e1bb53..0519c11 100644 --- a/tests/dummy/app/components/top-level-component.js +++ b/tests/dummy/app/components/top-level-component.js @@ -1,6 +1,6 @@ -import Ember from "ember"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ isExpanded: false, classNameBindings: [":top-level-component"], diff --git a/tests/dummy/app/templates/components/nested-component.hbs b/tests/dummy/app/templates/components/nested-component.hbs index 1375941..6c15bb9 100644 --- a/tests/dummy/app/templates/components/nested-component.hbs +++ b/tests/dummy/app/templates/components/nested-component.hbs @@ -1,3 +1,3 @@ -

A nested component

+

A nested component

diff --git a/tests/dummy/app/templates/components/top-level-component.hbs b/tests/dummy/app/templates/components/top-level-component.hbs index 99e5cf3..916fa65 100644 --- a/tests/dummy/app/templates/components/top-level-component.hbs +++ b/tests/dummy/app/templates/components/top-level-component.hbs @@ -1,6 +1,6 @@

{{title}}

-

top level component

+

top level component

diff --git a/tests/integration/components/ember-islands-test.js b/tests/integration/components/ember-islands-test.js index d337ce5..1bacb2f 100644 --- a/tests/integration/components/ember-islands-test.js +++ b/tests/integration/components/ember-islands-test.js @@ -1,104 +1,75 @@ -import $ from "jquery"; -import Ember from "ember"; -const { Component } = Ember; -import { moduleForComponent, test, skip } from "ember-qunit"; -import hbs from "htmlbars-inline-precompile"; - -moduleForComponent("ember-islands", "Integration | Component | ember islands", { - integration: true, -}); - -test("it renders an island component", function (assert) { - document.getElementById("ember-testing").innerHTML = ` -
- `; - - this.render(hbs` - {{ember-islands}} - `); - - assert.equal($(".top-level-component").length, 1); -}); - -test("rendering a component that only has an hbs template file", function (assert) { - document.getElementById("ember-testing").innerHTML = ` -
- `; - - this.render(hbs` - {{ember-islands}} - `); - - assert.equal($(".hbs-only-component").length, 1); -}); - -test("rendering a component that only has a JavaScript file", function (assert) { - document.getElementById("ember-testing").innerHTML = ` -
- `; - - this.render(hbs` - {{ember-islands}} - `); +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { render } from "@ember/test-helpers"; +import { hbs } from "ember-cli-htmlbars"; +import Component from "@ember/component"; + +module("Integration | Component | ember islands", function (hooks) { + setupRenderingTest(hooks); + + test("it renders an island component", async function (assert) { + await render(hbs` +
+ {{ember-islands}} + `); - assert.equal($(".js-only-component").length, 1); -}); + assert.dom(".top-level-component").exists(); + }); -test("it tears down an island component", function (assert) { - let teardownCalls = []; + test("rendering a component that only has an hbs template file", async function (assert) { + await render(hbs` +
+ {{ember-islands}} + `); - const IslandComponent = Component.extend({ - classNames: ["island-component"], + assert.dom(".hbs-only-component").exists(); + }); - willDestroyElement() { - teardownCalls.push("willDestroyElement"); - }, + test("rendering a component that only has a JavaScript file", async function (assert) { + await render(hbs` +
+ {{ember-islands}} + `); - willDestroy() { - teardownCalls.push("willDestroy"); - }, + assert.dom(".js-only-component").exists(); }); - this.register("component:island-component", IslandComponent); + test("it tears down an island component", async function (assert) { + let teardownCalls = []; - document.getElementById("ember-testing").innerHTML = ` -
- `; + const IslandComponent = Component.extend({ + classNames: ["island-component"], - this.set("isShowing", true); + willDestroyElement() { + teardownCalls.push("willDestroyElement"); + }, - this.render(hbs` - {{#if isShowing}} - {{ember-islands}} - {{/if}} - `); + willDestroy() { + teardownCalls.push("willDestroy"); + }, + }); - assert.equal($(".island-component").length, 1, "Has component in DOM"); + this.owner.register("component:island-component", IslandComponent); - this.set("isShowing", false); + this.set("isShowing", true); - assert.equal($(".island-compoment").length, 0, "Component removed from DOM"); + await render(hbs` +
+ {{#if isShowing}} + {{ember-islands}} + {{/if}} + `); - assert.deepEqual( - teardownCalls, - ["willDestroyElement", "willDestroy"], - "All component teardown hooks called" - ); -}); + assert.dom(".island-component").exists("Has component in DOM"); -// Related issue: https://github.com/emberjs/ember.js/issues/15013 -skip("Provides usefull error message when a component can't be found", function (assert) { - document.getElementById("ember-testing").innerHTML = ` -
- `; + this.set("isShowing", false); - assert.throws( - () => { - this.render(hbs` - {{ember-islands}} - `); - }, - /could not find a component/, - "Threw the correct error message" - ); + assert.dom(".island-compoment").doesNotExist("Component removed from DOM"); + + assert.deepEqual( + teardownCalls, + ["willDestroyElement", "willDestroy"], + "All component teardown hooks called" + ); + }); }); diff --git a/tests/integration/components/reconcile-test.js b/tests/integration/components/reconcile-test.js deleted file mode 100644 index c2133ad..0000000 --- a/tests/integration/components/reconcile-test.js +++ /dev/null @@ -1,266 +0,0 @@ -import $ from "jquery"; -import Ember from "ember"; -import { moduleForComponent, test } from "ember-qunit"; -import wait from "ember-test-helpers/wait"; -import hbs from "htmlbars-inline-precompile"; -import { - pleaseDontUseThisExportToGetTheEmberIslandsInstance as getInstance, - reconcile, -} from "ember-islands"; -const { run } = Ember; - -moduleForComponent("ember-islands", "Integration | Component | rerendering", { - integration: true, - - beforeEach() { - this.testContainer = $('
'); - $("#ember-testing").append(this.testContainer); - }, -}); - -test("when the DOM does not change", function (assert) { - this.testContainer.html(` -
- Inner Content -
- `); - - this.render(hbs` - {{ember-islands}} - `); - - // Click the component to increment its count to 1 - this.testContainer.find("button").click(); - - let componentElement = this.testContainer.find("button"); - - assert.equal( - componentElement.text().trim(), - "Title, Inner Content, 1", - "Precondition: Rendered inside the stable element" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "Precondition: tracking 1 rendered component" - ); - - reconcile(); - - assert.equal( - componentElement.text().trim(), - "Title, Inner Content, 1", - "Rendered content and state does not change" - ); - - assert.strictEqual( - this.testContainer.find("button")[0], - componentElement[0], - "The component element stays stable" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "still tracking 1 rendered component" - ); -}); - -test("when a placeholder is removed", function (assert) { - this.testContainer.html(` -
- Inner Content -
- `); - - this.render(hbs` - {{ember-islands}} - `); - - assert.equal( - this.testContainer.text().trim(), - "Title, Inner Content, 0", - "Precondition: Rendered" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "Precondition: tracking 1 rendered component" - ); - - let renderedComponent = getInstance().getRenderedComponents()[0]; - - this.testContainer.html(` -
- All new DOM! -
- `); - - run(() => { - reconcile(); - }); - - return wait().then(function () { - assert.equal( - renderedComponent.isDestroying, - true, - "The previously rendered component has been destroyed" - ); - - assert.notEqual( - renderedComponent.destroyCallCount, - 0, - "destroy called at least once" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 0, - "tracking no rendered components" - ); - }); -}); - -test("when a new component placeholder is added", function (assert) { - this.render(hbs` - {{ember-islands}} - `); - - assert.equal( - this.testContainer.text().trim(), - "", - "Precondition: Nothing rendered" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 0, - "Precondition: tracking 0 rendered components" - ); - - this.testContainer.html(` -
- Inner Content -
- `); - - run(() => { - reconcile(); - }); - - assert.equal( - this.testContainer.text().trim(), - "Title, Inner Content, 0", - "Renders a component for the added placeholder" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "started tracking 1 component" - ); -}); - -test("when attributes of a placeholder change", function (assert) { - this.testContainer.html(` -
- Inner Content -
- `); - - this.render(hbs` - {{ember-islands}} - `); - - let componentElement = this.testContainer.find("button"); - - // Click the component to increment its count to 1 - this.testContainer.find("button").click(); - - assert.equal( - componentElement.text().trim(), - "Title, Inner Content, 1", - "Precondition: Rendered inside the stable element" - ); - - this.testContainer - .find("[data-component=stateful-component]") - .attr("data-attrs", '{"title": "New Title"}'); - - run(() => { - reconcile(); - }); - - assert.equal( - componentElement.text().trim(), - "New Title, Inner Content, 1", - "Attributes are updated and state is stable" - ); -}); - -test("when the data-component property of a placeholder changes", function (assert) { - this.testContainer.html(` -
- Inner Content -
- `); - - this.render(hbs` - {{ember-islands}} - `); - - let componentElement = this.testContainer.find("button"); - - assert.equal( - componentElement.text().trim(), - "Title, Inner Content, 0", - "Precondition: Rendered initial component" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "Precondition: tracking 1 rendered component" - ); - - this.testContainer - .find("[data-component=stateful-component]") - .attr("data-component", "top-level-component"); - - let previouslyRenderedComponent = getInstance().getRenderedComponents()[0]; - - run(() => { - reconcile(); - }); - - assert.equal( - previouslyRenderedComponent.isDestroying, - true, - "previously rendered component has been destroyed" - ); - - assert.notEqual( - previouslyRenderedComponent.destroyCallCount, - 0, - "previously rendered component destroy called at least once" - ); - - assert.notEqual( - this.testContainer.text().indexOf("top level component"), - -1, - "Renders the new component in the placeholder" - ); - - assert.equal( - getInstance().getRenderedComponents().length, - 1, - "tracking 1 new rendered component" - ); -}); diff --git a/tests/integration/components/rerendering-test.js b/tests/integration/components/rerendering-test.js new file mode 100644 index 0000000..7b97241 --- /dev/null +++ b/tests/integration/components/rerendering-test.js @@ -0,0 +1,263 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { hbs } from "ember-cli-htmlbars"; +import { click, find, render, settled } from "@ember/test-helpers"; +import { run } from "@ember/runloop"; +import { + pleaseDontUseThisExportToGetTheEmberIslandsInstance as getInstance, + reconcile, +} from "ember-islands"; + +module("Integration | Component | rerendering", function (hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function () { + // this.testContainer = $('
'); + // getRootElement().append(this.testContainer); + }); + + test("when the DOM does not change", async function (assert) { + await render(hbs` +
+ Inner Content +
+ {{ember-islands}} + `); + + // Click the component to increment its count to 1 + await click("button"); + + let componentElement = find("button"); + + assert + .dom(componentElement) + .hasText( + "Title, Inner Content, 1", + "Precondition: Rendered inside the stable element" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "Precondition: tracking 1 rendered component" + ); + + reconcile(); + + assert + .dom(componentElement) + .hasText( + "Title, Inner Content, 1", + "Rendered content and state does not change" + ); + + assert.strictEqual( + find("button"), + componentElement, + "The component element stays stable" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "still tracking 1 rendered component" + ); + }); + + test("when a placeholder is removed", async function (assert) { + await render(hbs` +
+
+ Inner Content +
+
+ {{ember-islands}} + `); + + assert.dom().hasText("Title, Inner Content, 0", "Precondition: Rendered"); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "Precondition: tracking 1 rendered component" + ); + + let renderedComponent = getInstance().getRenderedComponents()[0]; + + find("#container").innerHTML = ` +
+ All new DOM! +
+ `; + + run(() => { + reconcile(); + }); + + await settled(); + + assert.equal( + renderedComponent.isDestroying, + true, + "The previously rendered component has been destroyed" + ); + + assert.notEqual( + renderedComponent.destroyCallCount, + 0, + "destroy called at least once" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 0, + "tracking no rendered components" + ); + }); + + test("when a new component placeholder is added", async function (assert) { + await render(hbs` +
+ {{ember-islands}} + `); + + assert.dom().hasText("", "Precondition: Nothing rendered"); + + assert.equal( + getInstance().getRenderedComponents().length, + 0, + "Precondition: tracking 0 rendered components" + ); + + find("#container").innerHTML = ` +
+ Inner Content +
+ `; + + run(() => { + reconcile(); + }); + + assert + .dom() + .hasText( + "Title, Inner Content, 0", + "Renders a component for the added placeholder" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "started tracking 1 component" + ); + }); + + test("when attributes of a placeholder change", async function (assert) { + await render(hbs` +
+ Inner Content +
+ {{ember-islands}} + `); + + let componentElement = find("button"); + + // Click the component to increment its count to 1 + await click("button"); + + assert + .dom(componentElement) + .hasText( + "Title, Inner Content, 1", + "Precondition: Rendered inside the stable element" + ); + + find("[data-test-stateful-component]").setAttribute( + "data-attrs", + '{"title": "New Title"}' + ); + + run(() => { + reconcile(); + }); + + assert + .dom(componentElement) + .hasText( + "New Title, Inner Content, 1", + "Attributes are updated and state is stable" + ); + }); + + test("when the data-component property of a placeholder changes", async function (assert) { + await render(hbs` +
+ Inner Content +
+ {{ember-islands}} + `); + + let componentElement = find("button"); + + assert + .dom(componentElement) + .hasText( + "Title, Inner Content, 0", + "Precondition: Rendered initial component" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "Precondition: tracking 1 rendered component" + ); + + find("[data-test-stateful-component]").setAttribute( + "data-component", + "top-level-component" + ); + + let previouslyRenderedComponent = getInstance().getRenderedComponents()[0]; + + run(() => { + reconcile(); + }); + + assert.equal( + previouslyRenderedComponent.isDestroying, + true, + "previously rendered component has been destroyed" + ); + + assert.notEqual( + previouslyRenderedComponent.destroyCallCount, + 0, + "previously rendered component destroy called at least once" + ); + + assert + .dom() + .includesText( + "top level component", + "Renders the new component in the placeholder" + ); + + assert.equal( + getInstance().getRenderedComponents().length, + 1, + "tracking 1 new rendered component" + ); + }); +});