Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ test/tmp
test/version_tmp
test_*.html
/tests/ember-tests.js
/smoke-tests/scenarios/output/
tmp
tmp*.gem
tmp.bpm
Expand Down
6 changes: 6 additions & 0 deletions packages/@ember/-internals/glimmer/lib/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ class ClassicRootState {

let result = (this.result = iterator.sync());

associateDestroyableChild(owner, result);

// override .render function after initial render
this.render = errorLoopTransaction(() => result.rerender({ alwaysRevalidate: false }));
});
Expand Down Expand Up @@ -648,6 +650,10 @@ export function renderComponent(

let innerResult = renderer.render(component, { into, args }).result;

if (innerResult) {
associateDestroyableChild(owner, innerResult);
}

let result = {
destroy() {
if (innerResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { array, concat, fn, get, hash, on } from '@glimmer/runtime';
import GlimmerishComponent from '../../utils/glimmerish-component';

import { run } from '@ember/runloop';
import { associateDestroyableChild, registerDestructor } from '@glimmer/destroyable';
import { destroy, associateDestroyableChild, registerDestructor } from '@glimmer/destroyable';
import { renderComponent, type RenderResult } from '../../../lib/renderer';
import { trackedObject } from '@ember/reactive/collections';
import { cached, tracked } from '@glimmer/tracking';
Expand Down Expand Up @@ -80,17 +80,126 @@ class RenderComponentTestCase extends AbstractStrictTestCase {
}
}

moduleFor(
'Strict Mode - RenderComponentTestCase',
class extends RenderComponentTestCase {
afterEach() {
if (this.component) {
runDestroy(this);
}
}

'@test destroy cleans up dom via destrying the test context'() {
let Foo = defComponent('Hello, world!');
let Root = defComponent('<Foo/>', { scope: { Foo } });

this.renderComponent(Root, { expect: 'Hello, world!' });

run(() => destroy(this));

assertHTML('');
}

'@test destroy of the owner cleans up dom via destrying the test context'() {
let Foo = defComponent('Hello, world!');
let Root = defComponent('<Foo/>', { scope: { Foo } });

this.renderComponent(Root, { expect: 'Hello, world!' });

run(() => destroy(this.owner));

assertHTML('');
}
}
);

moduleFor(
'Strict Mode - renderComponent (direct)',
class extends AbstractStrictTestCase {
get element() {
return document.querySelector('#qunit-fixture')!;
}

'@test manually calling destroy cleans up the DOM'() {
let Foo = defComponent('Hello, world!');

let owner = buildOwner({});
let manualDestroy: () => void;

run(() => {
let result = renderComponent(Foo, {
owner,
into: this.element,
});
manualDestroy = result.destroy;
this.component = {
...result,
rerender() {
// unused, but asserted against
},
};
});

assertHTML('Hello, world!');
this.assertStableRerender();

run(() => manualDestroy());

assertHTML('');
this.assertStableRerender();

run(() => destroy(owner));
}

'@test destroying the owner cleans up the DOM'() {
let Foo = defComponent('Hello, world!');

let owner = buildOwner({});

run(() => {
let result = renderComponent(Foo, {
owner,
into: this.element,
});
this.component = {
...result,
rerender() {
// unused, but asserted against
},
};
});

assertHTML('Hello, world!');
this.assertStableRerender();

run(() => destroy(owner));

assertHTML('');
this.assertStableRerender();
}
}
);

moduleFor(
'Strict Mode - renderComponent',
class extends RenderComponentTestCase {
afterEach() {
if (this.component) {
// runDestroy(this.component);
// runDestroy(this.owner);
runDestroy(this);
}
}

'@test destroy cleans up dom via destroying the owner'() {
let Foo = defComponent('Hello, world!');
let Root = defComponent('<Foo/>', { scope: { Foo } });

this.renderComponent(Root, { expect: 'Hello, world!' });

run(() => destroy(this.owner));

assertHTML('');
}

'@test Can use a component in scope'() {
let Foo = defComponent('Hello, world!');
let Root = defComponent('<Foo/>', { scope: { Foo } });
Expand Down Expand Up @@ -133,6 +242,24 @@ moduleFor(
this.renderComponent(Root, { expect: 'foobar' });
}

'@test multiple components have independent lifetimes'() {
class State {
@tracked showSecond = true;
}
let state = new State();
let Foo = defComponent('Hello, world!');
let Root = defComponent('<Foo />{{#if state.showSecond}}<Foo />{{/if}}', {
scope: { state, Foo },
});

this.renderComponent(Root, { expect: 'Hello, world!Hello, world!' });

this.assertChange({
change: () => (state.showSecond = false),
expect: 'Hello, world!<!---->',
});
}

'@test Can use a dynamic component definition'() {
let Foo = defComponent('Hello, world!');
let Root = defComponent('<this.Foo/>', {
Expand Down
78 changes: 78 additions & 0 deletions smoke-tests/scenarios/basic-test.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any place @glimmer/component was tested, so I just added some tests here

Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,84 @@ function basicTest(scenarios: Scenarios, appName: string) {
`,
},
integration: {
'destruction-test.gjs': `
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test is passing in 6.7

import { module, test } from 'qunit';
import { clearRender, render } from '@ember/test-helpers';
import { setupRenderingTest } from 'ember-qunit';
import { destroy, registerDestructor } from '@ember/destroyable';

import Component from '@glimmer/component';

class WillDestroy extends Component {
willDestroy() {
super.willDestroy();
this.args.onDestroy();
}
}

class Destructor extends Component {
constructor(...args) {
super(...args);

let onDestroy = this.args.onDestroy;
registerDestructor(this, () => onDestroy());
}
}

module('@glimmer/component Destruction', function (hooks) {
setupRenderingTest(hooks);

module('after', function (hooks) {
hooks.after(function (assert) {
assert.verifySteps(['WillDestroy destroyed']);
});

test('it calls "@onDestroy"', async function (assert) {
const onDestroy = () => assert.step('WillDestroy destroyed');

await render(
<template><WillDestroy @onDestroy={{onDestroy}} /></template>
);
});
});

module('afterEach', function (hooks) {
hooks.afterEach(function (assert) {
assert.verifySteps(['WillDestroy destroyed']);
});

test('it calls "@onDestroy"', async function (assert) {
const onDestroy = () => assert.step('WillDestroy destroyed');

await render(
<template><WillDestroy @onDestroy={{onDestroy}} /></template>
);

destroy(this.owner);
});
});

test('it calls "@onDestroy"', async function (assert) {
const onDestroy = () => assert.step('destroyed');

await render(<template><WillDestroy @onDestroy={{onDestroy}} /></template>);

await clearRender();

assert.verifySteps(['destroyed']);
});

test('it calls "registerDestructor"', async function (assert) {
const onDestroy = () => assert.step('destroyed');

await render(<template><Destructor @onDestroy={{onDestroy}} /></template>);

await clearRender();

assert.verifySteps(['destroyed']);
});
});
`,
'interactive-example-test.js': `
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
Expand Down