@norith/glimmer-component 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +42 -0
  2. package/addon/-private/base-component-manager.ts +35 -0
  3. package/addon/-private/component.ts +287 -0
  4. package/addon/-private/ember-component-manager.ts +99 -0
  5. package/addon/index.ts +34 -0
  6. package/config/environment.js +5 -0
  7. package/dist/commonjs/addon/-private/base-component-manager.d.ts +13 -0
  8. package/dist/commonjs/addon/-private/base-component-manager.js +20 -0
  9. package/dist/commonjs/addon/-private/component.d.ts +244 -0
  10. package/dist/commonjs/addon/-private/component.js +170 -0
  11. package/dist/commonjs/index.d.ts +1 -0
  12. package/dist/commonjs/index.js +9 -0
  13. package/dist/commonjs/src/component-manager.d.ts +12 -0
  14. package/dist/commonjs/src/component-manager.js +30 -0
  15. package/dist/commonjs/src/component.d.ts +4 -0
  16. package/dist/commonjs/src/component.js +23 -0
  17. package/dist/commonjs/test/index.d.ts +1 -0
  18. package/dist/commonjs/test/index.js +4 -0
  19. package/dist/commonjs/test/interactive/args-test.d.ts +1 -0
  20. package/dist/commonjs/test/interactive/args-test.js +47 -0
  21. package/dist/commonjs/test/interactive/index.d.ts +2 -0
  22. package/dist/commonjs/test/interactive/index.js +5 -0
  23. package/dist/commonjs/test/interactive/lifecycle-hook-test.d.ts +1 -0
  24. package/dist/commonjs/test/interactive/lifecycle-hook-test.js +92 -0
  25. package/dist/modules/addon/-private/base-component-manager.d.ts +13 -0
  26. package/dist/modules/addon/-private/base-component-manager.js +17 -0
  27. package/dist/modules/addon/-private/component.d.ts +244 -0
  28. package/dist/modules/addon/-private/component.js +167 -0
  29. package/dist/modules/index.d.ts +1 -0
  30. package/dist/modules/index.js +2 -0
  31. package/dist/modules/src/component-manager.d.ts +12 -0
  32. package/dist/modules/src/component-manager.js +24 -0
  33. package/dist/modules/src/component.d.ts +4 -0
  34. package/dist/modules/src/component.js +17 -0
  35. package/dist/modules/test/index.d.ts +1 -0
  36. package/dist/modules/test/index.js +2 -0
  37. package/dist/modules/test/interactive/args-test.d.ts +1 -0
  38. package/dist/modules/test/interactive/args-test.js +42 -0
  39. package/dist/modules/test/interactive/index.d.ts +2 -0
  40. package/dist/modules/test/interactive/index.js +3 -0
  41. package/dist/modules/test/interactive/lifecycle-hook-test.d.ts +1 -0
  42. package/dist/modules/test/interactive/lifecycle-hook-test.js +87 -0
  43. package/ember-addon-main.js +5 -0
  44. package/index.ts +1 -0
  45. package/package.json +115 -0
  46. package/src/component-manager.ts +24 -0
  47. package/src/component.ts +22 -0
  48. package/test/ember/dummy/app/app.js +14 -0
  49. package/test/ember/dummy/app/components/conference-speakers.js +22 -0
  50. package/test/ember/dummy/app/config/environment.d.ts +16 -0
  51. package/test/ember/dummy/app/index.html +25 -0
  52. package/test/ember/dummy/app/resolver.js +3 -0
  53. package/test/ember/dummy/app/router.js +13 -0
  54. package/test/ember/dummy/app/styles/app.css +0 -0
  55. package/test/ember/dummy/app/templates/application.hbs +3 -0
  56. package/test/ember/dummy/app/templates/components/conference-speakers.hbs +14 -0
  57. package/test/ember/dummy/app/templates/conference-speakers.hbs +1 -0
  58. package/test/ember/dummy/config/environment.js +51 -0
  59. package/test/ember/dummy/config/optional-features.json +6 -0
  60. package/test/ember/dummy/config/targets.js +14 -0
  61. package/test/ember/dummy/public/robots.txt +3 -0
  62. package/test/ember/index.html +40 -0
  63. package/test/ember/integration/components/glimmer-component-guide-test.js +42 -0
  64. package/test/ember/integration/components/glimmer-component-test.js +288 -0
  65. package/test/ember/test-helper.js +12 -0
  66. package/test/index.ts +1 -0
  67. package/test/interactive/args-test.ts +49 -0
  68. package/test/interactive/index.ts +2 -0
  69. package/test/interactive/lifecycle-hook-test.ts +99 -0
  70. package/types/@ember/component.d.ts +11 -0
  71. package/types/ember/index.d.ts +9 -0
  72. package/types/ember-compatibility-helpers.d.ts +1 -0
@@ -0,0 +1,288 @@
1
+ import GlimmerComponent from '@norith/glimmer-component';
2
+ import { module, test } from 'qunit';
3
+ import { setupRenderingTest } from 'ember-qunit';
4
+ import { render, clearRender, click } from '@ember/test-helpers';
5
+ import { hbs } from 'ember-cli-htmlbars';
6
+ import { getOwner } from '@ember/application';
7
+ import { set, computed } from '@ember/object';
8
+
9
+ import { gte } from 'ember-compatibility-helpers';
10
+
11
+ module('Integration | Component | @glimmer/component', function (hooks) {
12
+ let InstrumentedComponent;
13
+
14
+ setupRenderingTest(hooks);
15
+
16
+ hooks.beforeEach(function (assert) {
17
+ InstrumentedComponent = class extends GlimmerComponent {
18
+ constructor() {
19
+ super(...arguments);
20
+ assert.step('constructor');
21
+ }
22
+
23
+ willDestroy() {
24
+ assert.step('willDestroy');
25
+ }
26
+ };
27
+ });
28
+
29
+ test('it can render with curlies (no args)', async function (assert) {
30
+ this.owner.register('component:under-test', InstrumentedComponent);
31
+
32
+ await render(hbs`{{under-test}}`);
33
+
34
+ assert.verifySteps(['constructor'], 'initial setup steps');
35
+
36
+ await clearRender();
37
+
38
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
39
+ });
40
+
41
+ test('it can render and update with curlies (args)', async function (assert) {
42
+ this.owner.register('component:under-test', InstrumentedComponent);
43
+ this.owner.register('template:components/under-test', hbs`<p>{{@text}}</p>`);
44
+
45
+ this.set('text', 'hello!');
46
+ await render(hbs`{{under-test text=this.text}}`);
47
+
48
+ assert.dom('p').hasText('hello!');
49
+ assert.verifySteps(['constructor'], 'initial render steps');
50
+
51
+ this.set('text', 'hello world!');
52
+
53
+ assert.dom('p').hasText('hello world!');
54
+ assert.verifySteps([], 'no rerender steps');
55
+
56
+ this.set('text', 'hello!');
57
+
58
+ assert.dom('p').hasText('hello!');
59
+ assert.verifySteps([], 'no rerender steps');
60
+
61
+ await clearRender();
62
+
63
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
64
+ });
65
+
66
+ test('it can render with angles (no args)', async function (assert) {
67
+ this.owner.register('component:under-test', InstrumentedComponent);
68
+
69
+ await render(hbs`<UnderTest />`);
70
+
71
+ assert.verifySteps(['constructor'], 'initial render steps');
72
+
73
+ await clearRender();
74
+
75
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
76
+ });
77
+
78
+ test('it can render and update with angles (args)', async function (assert) {
79
+ this.owner.register('component:under-test', InstrumentedComponent);
80
+ this.owner.register('template:components/under-test', hbs`<p>{{@text}}</p>`);
81
+
82
+ this.set('text', 'hello!');
83
+ await render(hbs`<UnderTest @text={{this.text}} />`);
84
+
85
+ assert.dom('p').hasText('hello!');
86
+ assert.verifySteps(['constructor'], 'initial render steps');
87
+
88
+ this.set('text', 'hello world!');
89
+
90
+ assert.dom('p').hasText('hello world!');
91
+ assert.verifySteps([], 'no rerender steps');
92
+
93
+ this.set('text', 'hello!');
94
+
95
+ assert.dom('p').hasText('hello!');
96
+ assert.verifySteps([], 'no rerender steps');
97
+
98
+ await clearRender();
99
+
100
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
101
+ });
102
+
103
+ test('it can use args in component', async function (assert) {
104
+ this.owner.register(
105
+ 'component:under-test',
106
+ class extends GlimmerComponent {
107
+ get text() {
108
+ return this.args.text.toUpperCase();
109
+ }
110
+ }
111
+ );
112
+ this.owner.register('template:components/under-test', hbs`<p>{{this.text}}</p>`);
113
+
114
+ this.set('text', 'hello!');
115
+ await render(hbs`<UnderTest @text={{this.text}} />`);
116
+ assert.dom('p').hasText('HELLO!');
117
+ });
118
+
119
+ test('it can use args in constructor', async function (assert) {
120
+ this.owner.register(
121
+ 'component:under-test',
122
+ class extends GlimmerComponent {
123
+ constructor() {
124
+ super(...arguments);
125
+
126
+ this.text = this.args.text.toUpperCase();
127
+ }
128
+ }
129
+ );
130
+ this.owner.register('template:components/under-test', hbs`<p>{{this.text}}</p>`);
131
+
132
+ this.set('text', 'hello!');
133
+ await render(hbs`<UnderTest @text={{this.text}} />`);
134
+ assert.dom('p').hasText('HELLO!');
135
+ });
136
+
137
+ test('it can use get/set to recompute for changes', async function (assert) {
138
+ this.owner.register(
139
+ 'component:under-test',
140
+ class extends GlimmerComponent {
141
+ constructor() {
142
+ super(...arguments);
143
+
144
+ this.count = 0;
145
+ }
146
+
147
+ increment() {
148
+ set(this, 'count', this.count + 1);
149
+ }
150
+ }
151
+ );
152
+ this.owner.register(
153
+ 'template:components/under-test',
154
+ hbs`<p>Count: {{this.count}}</p><button data-test="increment" onclick={{action this.increment}}>Increment</button>`
155
+ );
156
+
157
+ await render(hbs`<UnderTest />`);
158
+ assert.dom('p').hasText('Count: 0');
159
+
160
+ await click('button[data-test=increment]');
161
+ assert.dom('p').hasText('Count: 1');
162
+
163
+ await click('button[data-test=increment]');
164
+ assert.dom('p').hasText('Count: 2');
165
+ });
166
+
167
+ test('does not update for non-tracked property changes', async function (assert) {
168
+ this.owner.register(
169
+ 'component:under-test',
170
+ class extends GlimmerComponent {
171
+ constructor() {
172
+ super(...arguments);
173
+
174
+ this._count = 0;
175
+ }
176
+
177
+ get count() {
178
+ return this._count;
179
+ }
180
+
181
+ set count(value) {
182
+ this._count = value;
183
+ }
184
+
185
+ increment() {
186
+ this.count++;
187
+ }
188
+ }
189
+ );
190
+ this.owner.register(
191
+ 'template:components/under-test',
192
+ hbs`<p>Count: {{this.count}}</p><button data-test="increment" onclick={{action this.increment}}>Increment</button>`
193
+ );
194
+
195
+ await render(hbs`<UnderTest />`);
196
+ assert.dom('p').hasText('Count: 0');
197
+
198
+ await click('button[data-test=increment]');
199
+ assert.dom('p').hasText('Count: 0');
200
+
201
+ await click('button[data-test=increment]');
202
+ assert.dom('p').hasText('Count: 0');
203
+ });
204
+
205
+ test('it has an owner', async function (assert) {
206
+ this.owner.register(
207
+ 'component:under-test',
208
+ class extends GlimmerComponent {
209
+ get environment() {
210
+ return getOwner(this).resolveRegistration('config:environment').environment;
211
+ }
212
+ }
213
+ );
214
+ this.owner.register(
215
+ 'template:components/under-test',
216
+ hbs`<p>Environment: {{this.environment}}</p>`
217
+ );
218
+ await render(hbs`<UnderTest />`);
219
+ assert.dom('p').hasText('Environment: test');
220
+ });
221
+
222
+ test('computed properties can depend on args', async function (assert) {
223
+ this.owner.register(
224
+ 'component:under-test',
225
+ class extends InstrumentedComponent {
226
+ @computed('args.text')
227
+ get text() {
228
+ return this.args.text;
229
+ }
230
+ }
231
+ );
232
+ this.owner.register('template:components/under-test', hbs`<p>{{this.text}}</p>`);
233
+
234
+ this.set('text', 'hello!');
235
+ await render(hbs`<UnderTest @text={{this.text}}/>`);
236
+
237
+ assert.dom('p').hasText('hello!');
238
+ assert.verifySteps(['constructor'], 'initial render steps');
239
+
240
+ this.set('text', 'hello world!');
241
+
242
+ assert.dom('p').hasText('hello world!');
243
+ assert.verifySteps([], 'no rerender steps');
244
+
245
+ this.set('text', 'hello!');
246
+
247
+ assert.dom('p').hasText('hello!');
248
+ assert.verifySteps([], 'no rerender steps');
249
+
250
+ await clearRender();
251
+
252
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
253
+ });
254
+
255
+ if (gte('3.13.0-alpha.0')) {
256
+ test('args autotrack correctly', async function (assert) {
257
+ this.owner.register(
258
+ 'component:under-test',
259
+ class extends InstrumentedComponent {
260
+ get text() {
261
+ return this.args.text;
262
+ }
263
+ }
264
+ );
265
+ this.owner.register('template:components/under-test', hbs`<p>{{this.text}}</p>`);
266
+
267
+ this.set('text', 'hello!');
268
+ await render(hbs`<UnderTest @text={{this.text}}/>`);
269
+
270
+ assert.dom('p').hasText('hello!');
271
+ assert.verifySteps(['constructor'], 'initial render steps');
272
+
273
+ this.set('text', 'hello world!');
274
+
275
+ assert.dom('p').hasText('hello world!');
276
+ assert.verifySteps([], 'no rerender steps');
277
+
278
+ this.set('text', 'hello!');
279
+
280
+ assert.dom('p').hasText('hello!');
281
+ assert.verifySteps([], 'no rerender steps');
282
+
283
+ await clearRender();
284
+
285
+ assert.verifySteps(['willDestroy'], 'post destroy steps');
286
+ });
287
+ }
288
+ });
@@ -0,0 +1,12 @@
1
+ import Application from '../app';
2
+ import config from '../config/environment';
3
+ import { setApplication } from '@ember/test-helpers';
4
+ import { start } from 'ember-qunit';
5
+ import { setup } from 'qunit-dom';
6
+ import * as QUnit from 'qunit';
7
+
8
+ setup(QUnit.assert);
9
+
10
+ setApplication(Application.create(config.APP));
11
+
12
+ start();
package/test/index.ts ADDED
@@ -0,0 +1 @@
1
+ import './interactive';
@@ -0,0 +1,49 @@
1
+ import Component from '@norith/glimmer-component';
2
+ import { tracked } from '@norith/glimmer-tracking';
3
+
4
+ import { setComponentTemplate, precompileTemplate } from '@norith/glimmer-core';
5
+ import { test, render, settled } from '@norith/glimmer-core/test/utils';
6
+
7
+ QUnit.module('[@glimmer/component] Component Arguments', () => {
8
+ test('Getters that depend on `args` re-render correctly', async function (assert) {
9
+ assert.expect(2);
10
+
11
+ let parent: ParentComponent;
12
+
13
+ class ParentComponent extends Component {
14
+ @tracked firstName = 'Tom';
15
+ @tracked status = 'is dope';
16
+
17
+ constructor(owner: object, args: {}) {
18
+ super(owner, args);
19
+ parent = this;
20
+ }
21
+ }
22
+
23
+ class ChildComponent extends Component<{ firstName: string }> {
24
+ get name(): string {
25
+ return `${this.args.firstName} Dale`;
26
+ }
27
+ }
28
+
29
+ setComponentTemplate(
30
+ precompileTemplate(
31
+ '<ChildComponent @firstName={{this.firstName}} @status={{this.status}} />',
32
+ { strictMode: true, scope: { ChildComponent } }
33
+ ),
34
+ ParentComponent
35
+ );
36
+
37
+ setComponentTemplate(
38
+ precompileTemplate('{{this.name}} {{@status}}', { strictMode: true }),
39
+ ChildComponent
40
+ );
41
+
42
+ assert.equal(await render(ParentComponent), 'Tom Dale is dope');
43
+
44
+ parent!.firstName = 'Thom';
45
+ parent!.status = 'is dank';
46
+
47
+ assert.equal(await settled(), 'Thom Dale is dank');
48
+ });
49
+ });
@@ -0,0 +1,2 @@
1
+ import './args-test';
2
+ import './lifecycle-hook-test';
@@ -0,0 +1,99 @@
1
+ import Component from '@norith/glimmer-component';
2
+ import { tracked } from '@norith/glimmer-tracking';
3
+
4
+ import { test, render, settled } from '@norith/glimmer-core/test/utils';
5
+ import { setComponentTemplate, precompileTemplate } from '@norith/glimmer-core';
6
+
7
+ QUnit.module('[@glimmer/component] Lifecycle Hooks', () => {
8
+ test('Lifecycle hook ordering', async function (assert) {
9
+ assert.expect(2);
10
+
11
+ let invocations: [string, string][] = [];
12
+ let component1: Component1;
13
+
14
+ abstract class HookLoggerComponent extends Component<{ name: string }> {
15
+ constructor(owner: object, args: { name: string }) {
16
+ super(owner, args);
17
+ invocations.push([this.args.name, 'constructor']);
18
+ }
19
+
20
+ willDestroy(): void {
21
+ invocations.push([this.args.name, 'willDestroy']);
22
+ }
23
+ }
24
+
25
+ class Component1 extends HookLoggerComponent {
26
+ @tracked firstName = 'Chirag';
27
+ @tracked showChildren = true;
28
+
29
+ constructor(owner: object, args: { name: string }) {
30
+ super(owner, args);
31
+ component1 = this;
32
+ }
33
+ }
34
+ class Component2 extends HookLoggerComponent {}
35
+ class Component3 extends HookLoggerComponent {}
36
+ class Component4 extends HookLoggerComponent {}
37
+ class Component5 extends HookLoggerComponent {}
38
+
39
+ setComponentTemplate(
40
+ precompileTemplate(
41
+ `
42
+ {{#if this.showChildren}}
43
+ <Component2 @name="component2" @firstName={{this.firstName}} />
44
+ <Component3 @name="component3"/>
45
+ {{/if}}
46
+ `,
47
+ {
48
+ strictMode: true,
49
+ scope: { Component2, Component3 },
50
+ }
51
+ ),
52
+ Component1
53
+ );
54
+
55
+ setComponentTemplate(
56
+ precompileTemplate(
57
+ `
58
+ {{@firstName}}
59
+ <Component4 @name="component4"/>
60
+ <Component5 @name="component5"/>
61
+ `,
62
+ {
63
+ strictMode: true,
64
+ scope: { Component4, Component5 },
65
+ }
66
+ ),
67
+ Component2
68
+ );
69
+
70
+ const emptyTemplate = precompileTemplate('');
71
+
72
+ setComponentTemplate(emptyTemplate, Component3);
73
+ setComponentTemplate(emptyTemplate, Component4);
74
+ setComponentTemplate(emptyTemplate, Component5);
75
+
76
+ await render(Component1, { args: { name: 'component1' } });
77
+
78
+ assert.deepEqual(invocations, [
79
+ ['component1', 'constructor'],
80
+ ['component2', 'constructor'],
81
+ ['component4', 'constructor'],
82
+ ['component5', 'constructor'],
83
+ ['component3', 'constructor'],
84
+ ]);
85
+
86
+ invocations = [];
87
+
88
+ component1!.showChildren = false;
89
+
90
+ await settled();
91
+
92
+ assert.deepEqual(invocations, [
93
+ ['component2', 'willDestroy'],
94
+ ['component4', 'willDestroy'],
95
+ ['component5', 'willDestroy'],
96
+ ['component3', 'willDestroy'],
97
+ ]);
98
+ });
99
+ });
@@ -0,0 +1,11 @@
1
+ declare module '@ember/component' {
2
+ export function setComponentManager<T>(managerId: string, baseClass: T): T;
3
+ export function setComponentManager<T>(managerFactory: (owner: unknown) => {}, baseClass: T): T;
4
+ export function capabilities(
5
+ version: string,
6
+ opts?: {
7
+ destructor?: boolean;
8
+ asyncLifecycleCallbacks?: boolean;
9
+ }
10
+ ): unknown;
11
+ }
@@ -0,0 +1,9 @@
1
+ declare module 'ember' {
2
+ function meta(obj: object): Meta;
3
+ function destroy(obj: object);
4
+ }
5
+
6
+ declare class Meta {
7
+ setSourceDestroying();
8
+ setSourceDestroyed();
9
+ }
@@ -0,0 +1 @@
1
+ export function gte(version: string): boolean;