Skip to content

Commit 563798e

Browse files
authored
Merge pull request #20862 from Windvis/strict-component-blueprint-support
2 parents 9ff1736 + 9f04031 commit 563798e

File tree

19 files changed

+679
-331
lines changed

19 files changed

+679
-331
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from '<%= modulePrefix %>/tests/helpers';
3+
import { render } from '@ember/test-helpers';
4+
import <%= componentName %> from '<%= pkgName %>/components/<%= componentPathName %>';
5+
6+
module('<%= friendlyTestDescription %>', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test('it renders', async function (assert) {
10+
// Updating values is achieved using autotracking, just like in app code. For example:
11+
// class State { @tracked myProperty = 0; }; const state = new State();
12+
// and update using state.myProperty = 1; await rerender();
13+
// Handle any actions with function myAction(val) { ... };
14+
15+
await render(<template><%= selfCloseComponent(componentName) %></template>);
16+
17+
assert.dom().hasText('');
18+
19+
// Template block usage:
20+
await render(<template>
21+
<%= openComponent(componentName) %>
22+
template block text
23+
<%= closeComponent(componentName) %>
24+
</template>);
25+
26+
assert.dom().hasText('template block text');
27+
});
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { module, test } from 'qunit';
2+
import { setupRenderingTest } from '<%= modulePrefix %>/tests/helpers';
3+
import { render } from '@ember/test-helpers';
4+
import <%= componentName %> from '<%= pkgName %>/components/<%= componentPathName %>';
5+
6+
module('<%= friendlyTestDescription %>', function (hooks) {
7+
setupRenderingTest(hooks);
8+
9+
test('it renders', async function (assert) {
10+
// Updating values is achieved using autotracking, just like in app code. For example:
11+
// class State { @tracked myProperty = 0; }; const state = new State();
12+
// and update using state.myProperty = 1; await rerender();
13+
// Handle any actions with function myAction(val) { ... };
14+
15+
await render(<template><%= selfCloseComponent(componentName) %></template>);
16+
17+
assert.dom().hasText('');
18+
19+
// Template block usage:
20+
await render(<template>
21+
<%= openComponent(componentName) %>
22+
template block text
23+
<%= closeComponent(componentName) %>
24+
</template>);
25+
26+
assert.dom().hasText('template block text');
27+
});
28+
});

blueprints/component-test/index.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ function invocationFor(options) {
1414
return parts.map((p) => stringUtil.classify(p)).join('::');
1515
}
1616

17+
function invocationForStrictComponentAuthoringFormat(options) {
18+
let parts = options.entity.name.split('/');
19+
let componentName = parts[parts.length - 1];
20+
return stringUtil.classify(componentName);
21+
}
22+
1723
module.exports = {
1824
description: 'Generates a component integration or unit test.',
1925

@@ -36,6 +42,17 @@ module.exports = {
3642
{ unit: 'unit' },
3743
],
3844
},
45+
{
46+
name: 'component-authoring-format',
47+
type: ['loose', 'strict'],
48+
default: 'loose',
49+
aliases: [
50+
{ loose: 'loose' },
51+
{ strict: 'strict' },
52+
{ 'template-tag': 'strict' },
53+
{ tt: 'strict' },
54+
],
55+
},
3956
],
4057

4158
fileMapTokens: function () {
@@ -55,6 +72,23 @@ module.exports = {
5572
};
5673
},
5774

75+
files() {
76+
let files = this._super.files.apply(this, arguments);
77+
78+
if (this.options.componentAuthoringFormat === 'strict') {
79+
const strictFilesToRemove =
80+
this.options.isTypeScriptProject || this.options.typescript ? '.gjs' : '.gts';
81+
files = files.filter(
82+
(file) =>
83+
!(file.endsWith('.js') || file.endsWith('.ts') || file.endsWith(strictFilesToRemove))
84+
);
85+
} else {
86+
files = files.filter((file) => !(file.endsWith('.gjs') || file.endsWith('.gts')));
87+
}
88+
89+
return files;
90+
},
91+
5892
locals: function (options) {
5993
let dasherizedModuleName = stringUtil.dasherize(options.entity.name);
6094
let componentPathName = dasherizedModuleName;
@@ -74,7 +108,10 @@ module.exports = {
74108
? "import { hbs } from 'ember-cli-htmlbars';"
75109
: "import hbs from 'htmlbars-inline-precompile';";
76110

77-
let templateInvocation = invocationFor(options);
111+
let templateInvocation =
112+
this.options.componentAuthoringFormat === 'strict'
113+
? invocationForStrictComponentAuthoringFormat(options)
114+
: invocationFor(options);
78115
let componentName = templateInvocation;
79116
let openComponent = (descriptor) => `<${descriptor}>`;
80117
let closeComponent = (descriptor) => `</${descriptor}>`;
@@ -92,6 +129,7 @@ module.exports = {
92129
selfCloseComponent,
93130
friendlyTestDescription,
94131
hbsImportStatement,
132+
pkgName: options.project.pkg.name,
95133
};
96134
},
97135

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<% if (componentClass === '@glimmer/component') {%>import Component from '@glimmer/component';
2+
3+
export default class <%= classifiedModuleName %> extends Component {
4+
<template>
5+
{{yield}}
6+
</template>
7+
}<%} else {%><template>
8+
{{yield}}
9+
</template><%}%>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<% if (componentClass === '@glimmer/component') {%>import Component from '@glimmer/component';
2+
3+
<%= componentSignature %>
4+
export default class <%= classifiedModuleName %> extends Component<<%= classifiedModuleName %>Signature> {
5+
<template>
6+
{{yield}}
7+
</template>
8+
}<%} else {%>import type { TOC } from '@ember/component/template-only';
9+
10+
<%= componentSignature %>
11+
<template>
12+
{{yield}}
13+
</template> satisfies TOC<<%= classifiedModuleName %>Signature>;<%}%>

blueprints/component/index.js

+49-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const chalk = require('chalk');
44
const stringUtil = require('ember-cli-string-utils');
55
const getPathOption = require('ember-cli-get-component-path-option');
66
const normalizeEntityName = require('ember-cli-normalize-entity-name');
7+
const SilentError = require('silent-error');
78
const { generateComponentSignature } = require('../-utils');
89

910
const typescriptBlueprintPolyfill = require('ember-cli-typescript-blueprint-polyfill');
@@ -40,6 +41,17 @@ module.exports = {
4041
default: 'flat',
4142
aliases: [{ fs: 'flat' }, { ns: 'nested' }],
4243
},
44+
{
45+
name: 'component-authoring-format',
46+
type: ['loose', 'strict'],
47+
default: 'loose',
48+
aliases: [
49+
{ loose: 'loose' },
50+
{ strict: 'strict' },
51+
{ 'template-tag': 'strict' },
52+
{ tt: 'strict' },
53+
],
54+
},
4355
],
4456

4557
init() {
@@ -58,6 +70,18 @@ module.exports = {
5870
options.componentClass = '';
5971
}
6072

73+
if (options.componentAuthoringFormat === 'strict') {
74+
if (options.componentClass === '@ember/component') {
75+
throw new SilentError(
76+
'The "@ember/component" component class cannot be used in combination with the "--strict" flag'
77+
);
78+
}
79+
80+
if (options.componentClass === '') {
81+
options.componentClass = '@ember/component/template-only';
82+
}
83+
}
84+
6185
return this._super.install.apply(this, arguments);
6286
},
6387

@@ -78,14 +102,16 @@ module.exports = {
78102
afterInstall(options) {
79103
this._super.afterInstall.apply(this, arguments);
80104

81-
this.skippedJsFiles.forEach((file) => {
82-
let mapped = this.mapFile(file, this.savedLocals);
83-
this.ui.writeLine(` ${chalk.yellow('skip')} ${mapped}`);
84-
});
105+
if (options.componentAuthoringFormat === 'loose') {
106+
this.skippedJsFiles.forEach((file) => {
107+
let mapped = this.mapFile(file, this.savedLocals);
108+
this.ui.writeLine(` ${chalk.yellow('skip')} ${mapped}`);
109+
});
85110

86-
if (this.skippedJsFiles.size > 0) {
87-
let command = `ember generate component-class ${options.entity.name}`;
88-
this.ui.writeLine(` ${chalk.cyan('tip')} to add a class, run \`${command}\``);
111+
if (this.skippedJsFiles.size > 0) {
112+
let command = `ember generate component-class ${options.entity.name}`;
113+
this.ui.writeLine(` ${chalk.cyan('tip')} to add a class, run \`${command}\``);
114+
}
89115
}
90116
},
91117

@@ -135,6 +161,21 @@ module.exports = {
135161
}
136162
});
137163
}
164+
if (this.options.componentAuthoringFormat === 'strict') {
165+
const strictFilesToRemove =
166+
this.options.isTypeScriptProject || this.options.typescript ? '.gjs' : '.gts';
167+
files = files.filter(
168+
(file) =>
169+
!(
170+
file.endsWith('.js') ||
171+
file.endsWith('.ts') ||
172+
file.endsWith('.hbs') ||
173+
file.endsWith(strictFilesToRemove)
174+
)
175+
);
176+
} else {
177+
files = files.filter((file) => !(file.endsWith('.gjs') || file.endsWith('.gts')));
178+
}
138179

139180
return files;
140181
},
@@ -172,6 +213,7 @@ module.exports = {
172213
}
173214

174215
return {
216+
classifiedModuleName,
175217
importTemplate,
176218
importComponent,
177219
componentSignature,

node-tests/blueprints/component-test-test.js

+38
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ describe('Blueprint: component-test', function () {
3131
});
3232
});
3333

34+
it('component-test foo --strict', function () {
35+
return emberGenerateDestroy(['component-test', 'foo', '--strict'], (_file) => {
36+
expect(_file('tests/integration/components/foo-test.gjs')).to.equal(
37+
fixture('component-test/app.gjs')
38+
);
39+
});
40+
});
41+
42+
it('component-test foo --strict --typescript', function () {
43+
return emberGenerateDestroy(
44+
['component-test', 'foo', '--strict', '--typescript'],
45+
(_file) => {
46+
expect(_file('tests/integration/components/foo-test.gts')).to.equal(
47+
fixture('component-test/app.gts')
48+
);
49+
}
50+
);
51+
});
52+
3453
it('component-test x-foo --unit', function () {
3554
return emberGenerateDestroy(['component-test', 'x-foo', '--unit'], (_file) => {
3655
expect(_file('tests/unit/components/x-foo-test.js')).to.equal(
@@ -65,6 +84,25 @@ describe('Blueprint: component-test', function () {
6584
);
6685
});
6786
});
87+
88+
it('component-test foo --strict', function () {
89+
return emberGenerateDestroy(['component-test', 'foo', '--strict'], (_file) => {
90+
expect(_file('tests/integration/components/foo-test.gjs')).to.equal(
91+
fixture('component-test/addon.gjs')
92+
);
93+
});
94+
});
95+
96+
it('component-test foo --strict --typescript', function () {
97+
return emberGenerateDestroy(
98+
['component-test', 'foo', '--strict', '--typescript'],
99+
(_file) => {
100+
expect(_file('tests/integration/components/foo-test.gts')).to.equal(
101+
fixture('component-test/addon.gts')
102+
);
103+
}
104+
);
105+
});
68106
});
69107

70108
describe('in in-repo-addon', function () {

0 commit comments

Comments
 (0)