diff --git a/blueprints-js/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gjs b/blueprints-js/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gjs
new file mode 100644
index 00000000000..33449bb5877
--- /dev/null
+++ b/blueprints-js/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gjs
@@ -0,0 +1,38 @@
+<% if (testType === 'integration') { %>import { module, test } from 'qunit';
+import { setupRenderingTest } from '<%= modulePrefix %>/tests/helpers';
+import { render } from '@ember/test-helpers';
+import <%= componentName %> from '<%= modulePrefix %>/components/<%= componentPathName %>';
+
+module('<%= friendlyTestDescription %>', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Updating values is achieved using autotracking, just like in app code. For example:
+ // class State { @tracked myProperty = 0; }; const state = new State();
+ // and update using state.myProperty = 1; await rerender();
+ // Handle any actions with function myAction(val) { ... };
+
+ await render(<%= selfCloseComponent(componentName) %>);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(
+ <%= openComponent(componentName) %>
+ template block text
+ <%= closeComponent(componentName) %>
+ );
+
+ assert.dom().hasText('template block text');
+ });
+});<% } else if (testType === 'unit') { %>import { module, test } from 'qunit';
+import { setupTest } from '<%= modulePrefix %>/tests/helpers';
+
+module('<%= friendlyTestDescription %>', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let component = this.owner.factoryFor('component:<%= componentPathName %>').create();
+ assert.ok(component);
+ });
+}); <% } %>
diff --git a/blueprints-js/component/files/__root__/__templatepath__/__templatename__.gjs b/blueprints-js/component/files/__root__/__templatepath__/__templatename__.gjs
new file mode 100644
index 00000000000..e8d9d219abd
--- /dev/null
+++ b/blueprints-js/component/files/__root__/__templatepath__/__templatename__.gjs
@@ -0,0 +1,3 @@
+
+ {{yield}}
+
diff --git a/blueprints/component-test/index.js b/blueprints/component-test/index.js
index 1e89db6e6b0..9f95319b582 100644
--- a/blueprints/component-test/index.js
+++ b/blueprints/component-test/index.js
@@ -15,6 +15,12 @@ function invocationFor(options) {
return parts.map((p) => stringUtil.classify(p)).join('::');
}
+function invocationForStrictComponentAuthoringFormat(options) {
+ let parts = options.entity.name.split('/');
+ let componentName = parts[parts.length - 1];
+ return stringUtil.classify(componentName);
+}
+
module.exports = useTestFrameworkDetector({
description: 'Generates a component integration or unit test.',
@@ -37,6 +43,17 @@ module.exports = useTestFrameworkDetector({
{ unit: 'unit' },
],
},
+ {
+ name: 'component-authoring-format',
+ type: ['loose', 'strict'],
+ default: 'loose',
+ aliases: [
+ { loose: 'loose' },
+ { strict: 'strict' },
+ { 'template-tag': 'strict' },
+ { tt: 'strict' },
+ ],
+ },
],
fileMapTokens: function () {
@@ -56,6 +73,19 @@ module.exports = useTestFrameworkDetector({
};
},
+ files() {
+ let files = this._super.files.apply(this, arguments);
+
+ if (this.options.componentAuthoringFormat === 'strict') {
+ files = files.filter((file) => !(file.endsWith('.js') || file.endsWith('.ts')));
+ }
+ if (this.options.componentAuthoringFormat === 'loose') {
+ files = files.filter((file) => !(file.endsWith('.gjs') || file.endsWith('.gts')));
+ }
+
+ return files;
+ },
+
locals: function (options) {
let dasherizedModuleName = stringUtil.dasherize(options.entity.name);
let componentPathName = dasherizedModuleName;
@@ -75,7 +105,10 @@ module.exports = useTestFrameworkDetector({
? "import { hbs } from 'ember-cli-htmlbars';"
: "import hbs from 'htmlbars-inline-precompile';";
- let templateInvocation = invocationFor(options);
+ let templateInvocation =
+ this.options.componentAuthoringFormat === 'strict'
+ ? invocationForStrictComponentAuthoringFormat(options)
+ : invocationFor(options);
let componentName = templateInvocation;
let openComponent = (descriptor) => `<${descriptor}>`;
let closeComponent = (descriptor) => `${descriptor}>`;
diff --git a/blueprints/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gts b/blueprints/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gts
new file mode 100644
index 00000000000..33449bb5877
--- /dev/null
+++ b/blueprints/component-test/qunit-rfc-232-files/__root__/__testType__/__path__/__test__.gts
@@ -0,0 +1,38 @@
+<% if (testType === 'integration') { %>import { module, test } from 'qunit';
+import { setupRenderingTest } from '<%= modulePrefix %>/tests/helpers';
+import { render } from '@ember/test-helpers';
+import <%= componentName %> from '<%= modulePrefix %>/components/<%= componentPathName %>';
+
+module('<%= friendlyTestDescription %>', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Updating values is achieved using autotracking, just like in app code. For example:
+ // class State { @tracked myProperty = 0; }; const state = new State();
+ // and update using state.myProperty = 1; await rerender();
+ // Handle any actions with function myAction(val) { ... };
+
+ await render(<%= selfCloseComponent(componentName) %>);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(
+ <%= openComponent(componentName) %>
+ template block text
+ <%= closeComponent(componentName) %>
+ );
+
+ assert.dom().hasText('template block text');
+ });
+});<% } else if (testType === 'unit') { %>import { module, test } from 'qunit';
+import { setupTest } from '<%= modulePrefix %>/tests/helpers';
+
+module('<%= friendlyTestDescription %>', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let component = this.owner.factoryFor('component:<%= componentPathName %>').create();
+ assert.ok(component);
+ });
+}); <% } %>
diff --git a/blueprints/component/files/__root__/__templatepath__/__templatename__.gts b/blueprints/component/files/__root__/__templatepath__/__templatename__.gts
new file mode 100644
index 00000000000..e8d9d219abd
--- /dev/null
+++ b/blueprints/component/files/__root__/__templatepath__/__templatename__.gts
@@ -0,0 +1,3 @@
+
+ {{yield}}
+
diff --git a/blueprints/component/index.js b/blueprints/component/index.js
index fffa4961bea..8d04d928d4b 100644
--- a/blueprints/component/index.js
+++ b/blueprints/component/index.js
@@ -52,6 +52,17 @@ module.exports = {
default: OCTANE ? 'flat' : 'classic',
aliases: OCTANE ? [{ fs: 'flat' }, { ns: 'nested' }, { cs: 'classic' }] : [{ cs: 'classic' }],
},
+ {
+ name: 'component-authoring-format',
+ type: ['loose', 'strict'],
+ default: 'loose',
+ aliases: [
+ { loose: 'loose' },
+ { strict: 'strict' },
+ { 'template-tag': 'strict' },
+ { tt: 'strict' },
+ ],
+ },
],
/**
@@ -135,14 +146,16 @@ module.exports = {
afterInstall(options) {
this._super.afterInstall.apply(this, arguments);
- this.skippedJsFiles.forEach((file) => {
- let mapped = this.mapFile(file, this.savedLocals);
- this.ui.writeLine(` ${chalk.yellow('skip')} ${mapped}`);
- });
+ if (options.componentAuthoringFormat === 'loose') {
+ this.skippedJsFiles.forEach((file) => {
+ let mapped = this.mapFile(file, this.savedLocals);
+ this.ui.writeLine(` ${chalk.yellow('skip')} ${mapped}`);
+ });
- if (this.skippedJsFiles.size > 0) {
- let command = `ember generate component-class ${options.entity.name}`;
- this.ui.writeLine(` ${chalk.cyan('tip')} to add a class, run \`${command}\``);
+ if (this.skippedJsFiles.size > 0) {
+ let command = `ember generate component-class ${options.entity.name}`;
+ this.ui.writeLine(` ${chalk.cyan('tip')} to add a class, run \`${command}\``);
+ }
}
},
@@ -225,6 +238,14 @@ module.exports = {
}
});
}
+ if (this.options.componentAuthoringFormat === 'strict') {
+ files = files.filter(
+ (file) => !(file.endsWith('.js') || file.endsWith('.ts') || file.endsWith('.hbs'))
+ );
+ }
+ if (this.options.componentAuthoringFormat === 'loose') {
+ files = files.filter((file) => !(file.endsWith('.gjs') || file.endsWith('.gts')));
+ }
return files;
},
diff --git a/node-tests/blueprints/component-test-test.js b/node-tests/blueprints/component-test-test.js
index 4c0b903af83..e42211f6a98 100644
--- a/node-tests/blueprints/component-test-test.js
+++ b/node-tests/blueprints/component-test-test.js
@@ -38,6 +38,14 @@ describe('Blueprint: component-test', function () {
);
});
});
+
+ it('component-test x-foo --strict', function () {
+ return emberGenerateDestroy(['component-test', 'x-foo', '--strict'], (_file) => {
+ expect(_file('tests/integration/components/x-foo-test.gjs')).to.equal(
+ fixture('component-test/rfc232.gjs')
+ );
+ });
+ });
});
describe('with ember-cli-qunit@4.1.0', function () {
diff --git a/node-tests/blueprints/component-test.js b/node-tests/blueprints/component-test.js
index b8338928887..6be68c7c3fa 100644
--- a/node-tests/blueprints/component-test.js
+++ b/node-tests/blueprints/component-test.js
@@ -31,6 +31,11 @@ const templateOnlyContents = `import templateOnly from '@ember/component/templat
export default templateOnly();
`;
+const templateTagContents = `
+ {{yield}}
+
+`;
+
describe('Blueprint: component', function () {
setupTestHooks(this);
@@ -324,6 +329,12 @@ describe('Blueprint: component', function () {
);
});
+ it('component foo --strict', function () {
+ return emberGenerateDestroy(['component', 'foo', '--strict'], (_file) => {
+ expect(_file('app/components/foo.gjs')).to.equal(templateTagContents);
+ });
+ });
+
describe('with podModulePrefix', function () {
beforeEach(function () {
setupPodConfig({ podModulePrefix: true });
diff --git a/node-tests/fixtures/component-test/rfc232.gjs b/node-tests/fixtures/component-test/rfc232.gjs
new file mode 100644
index 00000000000..f030d391ba7
--- /dev/null
+++ b/node-tests/fixtures/component-test/rfc232.gjs
@@ -0,0 +1,28 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'my-app/tests/helpers';
+import { render } from '@ember/test-helpers';
+import XFoo from 'my-app/components/x-foo';
+
+module('Integration | Component | x-foo', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Updating values is achieved using autotracking, just like in app code. For example:
+ // class State { @tracked myProperty = 0; }; const state = new State();
+ // and update using state.myProperty = 1; await rerender();
+ // Handle any actions with function myAction(val) { ... };
+
+ await render();
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(
+
+ template block text
+
+ );
+
+ assert.dom().hasText('template block text');
+ });
+});