ember.nvp 0.5.0 → 0.7.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 (91) hide show
  1. package/.github/workflows/ci.yml +17 -2
  2. package/.release-plan.json +84 -4
  3. package/CHANGELOG.md +68 -0
  4. package/README.md +29 -0
  5. package/package.json +4 -2
  6. package/packages/build-tooling-utils/README.md +37 -0
  7. package/packages/build-tooling-utils/index.ts +2 -0
  8. package/packages/build-tooling-utils/package.json +42 -0
  9. package/packages/build-tooling-utils/rolldown.config.js +18 -0
  10. package/packages/build-tooling-utils/src/extensions.ts +18 -0
  11. package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
  12. package/packages/rolldown/README.md +115 -0
  13. package/packages/rolldown/index.ts +56 -0
  14. package/packages/rolldown/package.json +59 -0
  15. package/packages/rolldown/rolldown.config.js +21 -0
  16. package/packages/rolldown/src/babel.ts +136 -0
  17. package/packages/rolldown/src/define-config.ts +32 -0
  18. package/packages/rolldown/src/externals.ts +83 -0
  19. package/packages/rolldown/src/isolated-declarations.ts +65 -0
  20. package/packages/rolldown/src/transform.ts +162 -0
  21. package/packages/vite/README.md +7 -21
  22. package/packages/vite/index.ts +1 -1
  23. package/packages/vite/package.json +4 -6
  24. package/packages/vite/src/ember.ts +129 -15
  25. package/packages/vite/src/test-html.ts +86 -0
  26. package/src/bases/minimal-app/files/app/router.ts +1 -0
  27. package/src/bases/minimal-app/files/tsconfig.json +1 -0
  28. package/src/bases/minimal-app/index.js +2 -2
  29. package/src/bases/minimal-library/files/README.md +41 -0
  30. package/src/bases/minimal-library/files/package.json +49 -0
  31. package/src/bases/minimal-library/files/tsconfig.json +10 -0
  32. package/src/bases/minimal-library/files/tsdown.config.js +6 -0
  33. package/src/bases/minimal-library/index.js +158 -2
  34. package/src/cli/args.js +7 -1
  35. package/src/cli/index.js +90 -16
  36. package/src/cli/questions/layers.js +5 -2
  37. package/src/cli/questions/project-type.js +0 -5
  38. package/src/cli/questions/write.js +169 -0
  39. package/src/index.js +1 -0
  40. package/src/layers/are-the-types-wrong/index.js +74 -0
  41. package/src/layers/git/files/gitignore +14 -1
  42. package/src/layers/git/index.js +6 -5
  43. package/src/layers/github-actions/index.js +12 -8
  44. package/src/layers/index.js +1 -1
  45. package/src/layers/inspector-support/index.js +101 -0
  46. package/src/layers/prettier/files/.prettierignore +4 -0
  47. package/src/layers/publint/index.js +68 -0
  48. package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
  49. package/src/layers/qunit/index.js +107 -7
  50. package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
  51. package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
  52. package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
  53. package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
  54. package/src/layers/qunit/library-files/vite.config.mjs +6 -0
  55. package/src/layers/typescript/index.js +43 -7
  56. package/src/layers/vitest/files/vitest.config.mjs +11 -11
  57. package/src/layers/vitest/index.js +70 -32
  58. package/src/utils/babel.js +9 -6
  59. package/src/utils/fs.js +44 -3
  60. package/src/utils/inspector-support.js +196 -0
  61. package/src/utils/npm.js +21 -7
  62. package/src/utils/remove-types.js +5 -0
  63. package/src/utils/rewrite-imports.js +157 -0
  64. package/src/utils/stage.js +317 -0
  65. package/src/utils/tsdown-config.js +85 -0
  66. package/src/utils/types.ts +5 -1
  67. package/test/ember.nvp/base-library.test.ts +232 -0
  68. package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
  69. package/test/ember.nvp/inspector-support.test.ts +179 -0
  70. package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
  71. package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
  72. package/test/ember.nvp/layer-qunit.test.ts +376 -15
  73. package/test/ember.nvp/layer-vitest.test.ts +250 -0
  74. package/test/ember.nvp/layers.test.ts +16 -0
  75. package/test/ember.nvp/library-src-fixtures.ts +81 -0
  76. package/test/ember.nvp/permutations/app-1.test.ts +3 -0
  77. package/test/ember.nvp/permutations/app-2.test.ts +3 -0
  78. package/test/ember.nvp/permutations/library-1.test.ts +3 -0
  79. package/test/ember.nvp/permutations/library-2.test.ts +3 -0
  80. package/test/ember.nvp/permutations/shared.ts +239 -0
  81. package/test/ember.nvp/rewrite-imports.test.ts +235 -0
  82. package/test/ember.nvp/stage.test.ts +188 -0
  83. package/test/test-helpers/permutate.test.ts +17 -0
  84. package/test/test-helpers.ts +20 -7
  85. package/tsconfig.json +1 -0
  86. package/packages/vite/src/maybe-babel.ts +0 -219
  87. package/src/layers/qunit/files/run-tests.mjs +0 -30
  88. package/src/layers/vitest/files/tests/test-helper.ts +0 -5
  89. package/test/ember.nvp/permutations.test.ts +0 -200
  90. /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
  91. /package/src/layers/{vitest/files/tests/unit → qunit/library-files/tests/rendering}/.gitkeep +0 -0
@@ -1,15 +1,139 @@
1
- import { describe, it, afterAll, expect } from "vitest";
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
2
  import { generate } from "#test-helpers";
3
- import { rimraf } from "rimraf";
3
+ import { writeLibrarySource } from "./library-src-fixtures.ts";
4
4
  import { execa } from "execa";
5
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
6
+ import { dirname, join, relative, sep } from "node:path";
7
+
8
+ import type { Project } from "ember.nvp";
9
+
10
+ async function listFiles(directory: string): Promise<string[]> {
11
+ const entries = await readdir(directory, { withFileTypes: true, recursive: true });
12
+
13
+ return entries
14
+ .filter((entry) => entry.isFile())
15
+ .map((entry) => relative(directory, join(entry.parentPath, entry.name)))
16
+ .filter((path) => !path.split(sep).includes("node_modules"))
17
+ .filter((path) => !path.startsWith(`dist${sep}`))
18
+ .sort();
19
+ }
20
+
21
+ async function read(project: Project, filePath: string): Promise<string> {
22
+ return readFile(join(project.directory, filePath), "utf-8");
23
+ }
24
+
25
+ async function installAndTest(project: Project) {
26
+ let install = await execa("pnpm install", { cwd: project.directory, shell: true });
27
+ expect(install.exitCode).toBe(0);
28
+
29
+ let test = await execa("pnpm test", { cwd: project.directory, shell: true });
30
+ expect(test.exitCode).toBe(0);
31
+ }
5
32
 
6
33
  /**
7
- * The replace-or-update question only matters when a project already exists
8
- * at the target path. These tests generate a project, assert the baseline
9
- * state, then regenerate over it the way the CLI does once the user has
10
- * answered the question, and assert the resulting project output is correct
11
- * for each answer.
34
+ * The layer ships testing infrastructure only; the tests exercising the
35
+ * base's example exports are generated here, in both flavors.
12
36
  */
37
+ const exampleTests = {
38
+ typescript: {
39
+ "tests/rendering/greeting-test.gts": `import { render } from "@ember/test-helpers";
40
+ import { module, test } from "qunit";
41
+ import { setupRenderingTest } from "ember-qunit";
42
+
43
+ import Greeting from "#src/components/greeting.gts";
44
+
45
+ module("Rendering | Greeting", function (hooks) {
46
+ setupRenderingTest(hooks);
47
+
48
+ test("it renders", async function (assert) {
49
+ await render(<template><Greeting @name="Tomster" /></template>);
50
+
51
+ assert.dom("p").hasText("Hello, Tomster!");
52
+ });
53
+ });
54
+ `,
55
+ "tests/rendering/badge-test.gts": `import { render } from "@ember/test-helpers";
56
+ import { module, test } from "qunit";
57
+ import { setupRenderingTest } from "ember-qunit";
58
+
59
+ import { Badge } from "#src/components/badge.gts";
60
+
61
+ module("Rendering | Badge", function (hooks) {
62
+ setupRenderingTest(hooks);
63
+
64
+ test("it renders its content", async function (assert) {
65
+ await render(<template><Badge>New</Badge></template>);
66
+
67
+ assert.dom("span.badge").hasText("New");
68
+ });
69
+ });
70
+ `,
71
+ "tests/unit/math-test.ts": `import { module, test } from "qunit";
72
+
73
+ import { add } from "#src/utils/math.ts";
74
+
75
+ module("Unit | add", function () {
76
+ test("adds two numbers", function (assert) {
77
+ assert.strictEqual(add(1, 2), 3);
78
+ });
79
+ });
80
+ `,
81
+ },
82
+ javascript: {
83
+ "tests/rendering/greeting-test.gjs": `import { render } from "@ember/test-helpers";
84
+ import { module, test } from "qunit";
85
+ import { setupRenderingTest } from "ember-qunit";
86
+
87
+ import Greeting from "#src/components/greeting.gjs";
88
+
89
+ module("Rendering | Greeting", function (hooks) {
90
+ setupRenderingTest(hooks);
91
+
92
+ test("it renders", async function (assert) {
93
+ await render(<template><Greeting @name="Tomster" /></template>);
94
+
95
+ assert.dom("p").hasText("Hello, Tomster!");
96
+ });
97
+ });
98
+ `,
99
+ "tests/rendering/badge-test.gjs": `import { render } from "@ember/test-helpers";
100
+ import { module, test } from "qunit";
101
+ import { setupRenderingTest } from "ember-qunit";
102
+
103
+ import { Badge } from "#src/components/badge.gjs";
104
+
105
+ module("Rendering | Badge", function (hooks) {
106
+ setupRenderingTest(hooks);
107
+
108
+ test("it renders its content", async function (assert) {
109
+ await render(<template><Badge>New</Badge></template>);
110
+
111
+ assert.dom("span.badge").hasText("New");
112
+ });
113
+ });
114
+ `,
115
+ "tests/unit/math-test.js": `import { module, test } from "qunit";
116
+
117
+ import { add } from "#src/utils/math.js";
118
+
119
+ module("Unit | add", function () {
120
+ test("adds two numbers", function (assert) {
121
+ assert.strictEqual(add(1, 2), 3);
122
+ });
123
+ });
124
+ `,
125
+ },
126
+ };
127
+
128
+ async function writeExampleTests(project: Project, flavor: keyof typeof exampleTests) {
129
+ for (let [filePath, contents] of Object.entries(exampleTests[flavor])) {
130
+ let target = join(project.directory, filePath);
131
+
132
+ await mkdir(dirname(target), { recursive: true });
133
+ await writeFile(target, contents);
134
+ }
135
+ }
136
+
13
137
  describe("layer: qunit", () => {
14
138
  const dirs: string[] = [];
15
139
 
@@ -17,18 +141,255 @@ describe("layer: qunit", () => {
17
141
  if (process.env.CI) return;
18
142
 
19
143
  for (const dir of dirs) {
20
- await rimraf(dir, { maxRetries: 3, retryDelay: 100 });
144
+ await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
21
145
  }
22
146
  });
23
147
 
24
- it("it boots and run tests", async () => {
25
- const project = await generate({ type: "app", layers: ["qunit"] });
26
- dirs.push(project.directory);
148
+ describe("app", () => {
149
+ it("it boots and run tests", async () => {
150
+ const project = await generate({ type: "app", layers: ["qunit"] });
151
+ dirs.push(project.directory);
152
+
153
+ await installAndTest(project);
154
+ });
155
+ });
156
+
157
+ describe("library (TypeScript)", () => {
158
+ let project: Project;
159
+
160
+ beforeAll(async () => {
161
+ project = await generate({
162
+ type: "library",
163
+ name: "my-lib",
164
+ layers: ["qunit", "typescript"],
165
+ });
166
+ dirs.push(project.directory);
167
+ });
168
+
169
+ it("generates the expected files, without any index.html", async () => {
170
+ let files = await listFiles(project.directory);
171
+
172
+ expect(files.some((file) => file.endsWith("index.html"))).toBe(false);
173
+ expect(files).toMatchInlineSnapshot(`
174
+ [
175
+ ".gitignore",
176
+ "README.md",
177
+ "config/test/babel.config.js",
178
+ "config/test/testem.cjs",
179
+ "package.json",
180
+ "src/index.ts",
181
+ "tests/rendering/.gitkeep",
182
+ "tests/test-helper.ts",
183
+ "tests/unit/.gitkeep",
184
+ "tsconfig.json",
185
+ "tsdown.config.js",
186
+ "vite.config.mjs",
187
+ ]
188
+ `);
189
+ });
190
+
191
+ it("generates the test build config", async () => {
192
+ expect(await read(project, "vite.config.mjs")).toMatchInlineSnapshot(`
193
+ "import { defineConfig } from "vite";
194
+ import { ember } from "@nullvoxpopuli/ember-vite";
195
+
196
+ export default defineConfig({
197
+ plugins: [ember({ babel: { configFile: "./config/test/babel.config.js" } })],
198
+ });
199
+ "
200
+ `);
201
+ expect(await read(project, "config/test/babel.config.js")).toMatchInlineSnapshot(`
202
+ "import { buildMacros } from "@embroider/macros/babel";
203
+
204
+ const macros = buildMacros({
205
+ configure(config) {
206
+ if (process.env.EMBER_ENV === "test") {
207
+ config.enableRuntimeMode();
208
+ }
209
+ },
210
+ });
211
+
212
+ export default {
213
+ plugins: [
214
+ [
215
+ "@babel/plugin-transform-typescript",
216
+ {
217
+ allExtensions: true,
218
+ onlyRemoveTypeImports: true,
219
+ allowDeclareFields: true,
220
+ },
221
+ ],
222
+ [
223
+ "babel-plugin-ember-template-compilation",
224
+ {
225
+ transforms: [...macros.templateMacros],
226
+ },
227
+ ],
228
+ [
229
+ "module:decorator-transforms",
230
+ {
231
+ runtime: {
232
+ import: import.meta.resolve("decorator-transforms/runtime-esm"),
233
+ },
234
+ },
235
+ ],
236
+ ...macros.babelMacros,
237
+ ],
238
+
239
+ generatorOpts: {
240
+ compact: false,
241
+ },
242
+ };
243
+ "
244
+ `);
245
+ expect(await read(project, "config/test/testem.cjs")).toMatchInlineSnapshot(`
246
+ ""use strict";
247
+
248
+ if (typeof module !== "undefined") {
249
+ module.exports = {
250
+ test_page: "tests/index.html?hidepassed",
251
+ cwd: "dist",
252
+ disable_watching: true,
253
+ launch_in_ci: ["Chrome"],
254
+ launch_in_dev: ["Chrome"],
255
+ browser_start_timeout: 120,
256
+ browser_args: {
257
+ Chrome: {
258
+ ci: [
259
+ // --no-sandbox is needed when running Chrome inside a container
260
+ process.env.CI ? "--no-sandbox" : null,
261
+ "--headless",
262
+ "--disable-dev-shm-usage",
263
+ "--disable-software-rasterizer",
264
+ "--mute-audio",
265
+ "--remote-debugging-port=0",
266
+ "--window-size=1440,900",
267
+ ].filter(Boolean),
268
+ },
269
+ },
270
+ };
271
+ }
272
+ "
273
+ `);
274
+ });
275
+
276
+ it("leaves the publish build config alone", async () => {
277
+ expect(await read(project, "tsdown.config.js")).toMatchInlineSnapshot(`
278
+ "import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
279
+
280
+ export default defineConfig({
281
+ entry: ["./src/index.ts"],
282
+ plugins: [ember()],
283
+ });
284
+ "
285
+ `);
286
+ });
287
+
288
+ it("installs, and tests generated against the library's exports pass", async () => {
289
+ await writeLibrarySource(project, "typescript");
290
+ await writeExampleTests(project, "typescript");
291
+ await installAndTest(project);
292
+ });
293
+
294
+ it("the publish build still ships precompileTemplate", async () => {
295
+ let build = await execa("pnpm build", { cwd: project.directory, shell: true });
296
+ expect(build.exitCode).toBe(0);
297
+
298
+ let output = await read(project, "dist/index.js");
299
+
300
+ expect(output).toContain("precompileTemplate");
301
+ expect(output).not.toContain("createTemplateFactory");
302
+ });
303
+ });
304
+
305
+ describe("library (JavaScript)", () => {
306
+ let project: Project;
307
+
308
+ beforeAll(async () => {
309
+ project = await generate({ type: "library", name: "my-lib", layers: ["qunit"] });
310
+ dirs.push(project.directory);
311
+ });
312
+
313
+ it("generates the expected files, without any index.html", async () => {
314
+ let files = await listFiles(project.directory);
315
+
316
+ expect(files.some((file) => file.endsWith("index.html"))).toBe(false);
317
+ expect(files).toMatchInlineSnapshot(`
318
+ [
319
+ ".gitignore",
320
+ "README.md",
321
+ "config/test/babel.config.js",
322
+ "config/test/testem.cjs",
323
+ "package.json",
324
+ "src/index.js",
325
+ "tests/rendering/.gitkeep",
326
+ "tests/test-helper.js",
327
+ "tests/unit/.gitkeep",
328
+ "tsdown.config.js",
329
+ "vite.config.mjs",
330
+ ]
331
+ `);
332
+ });
333
+
334
+ it("has no TypeScript leftovers in the test build config", async () => {
335
+ expect(await read(project, "config/test/babel.config.js")).toMatchInlineSnapshot(`
336
+ "import { buildMacros } from "@embroider/macros/babel";
337
+
338
+ const macros = buildMacros({
339
+ configure(config) {
340
+ if (process.env.EMBER_ENV === "test") {
341
+ config.enableRuntimeMode();
342
+ }
343
+ },
344
+ });
345
+
346
+ export default {
347
+ plugins: [[
348
+ "babel-plugin-ember-template-compilation",
349
+ {
350
+ transforms: [...macros.templateMacros],
351
+ },
352
+ ], [
353
+ "module:decorator-transforms",
354
+ {
355
+ runtime: {
356
+ import: import.meta.resolve("decorator-transforms/runtime-esm"),
357
+ },
358
+ },
359
+ ], ...macros.babelMacros],
360
+
361
+ generatorOpts: {
362
+ compact: false,
363
+ },
364
+ };
365
+ "
366
+ `);
367
+ expect(await read(project, "tsdown.config.js")).toMatchInlineSnapshot(`
368
+ "import { defineConfig, ember } from "@nullvoxpopuli/ember-rolldown";
369
+
370
+ export default defineConfig({
371
+ entry: ["./src/index.js"],
372
+ dts: false,
373
+ plugins: [ember()],
374
+ });
375
+ "
376
+ `);
377
+ });
378
+
379
+ it("installs, and tests generated against the library's exports pass", async () => {
380
+ await writeLibrarySource(project, "javascript");
381
+ await writeExampleTests(project, "javascript");
382
+ await installAndTest(project);
383
+ });
384
+
385
+ it("the publish build still ships precompileTemplate", async () => {
386
+ let build = await execa("pnpm build", { cwd: project.directory, shell: true });
387
+ expect(build.exitCode).toBe(0);
27
388
 
28
- let install = await execa("pnpm install", { cwd: project.directory, shell: true });
29
- expect(install.exitCode).toBe(0);
389
+ let output = await read(project, "dist/index.js");
30
390
 
31
- let test = await execa("pnpm test", { cwd: project.directory, shell: true });
32
- expect(test.exitCode).toBe(0);
391
+ expect(output).toContain("precompileTemplate");
392
+ expect(output).not.toContain("createTemplateFactory");
393
+ });
33
394
  });
34
395
  });
@@ -0,0 +1,250 @@
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
+ import { generate } from "#test-helpers";
3
+ import { writeLibrarySource } from "./library-src-fixtures.ts";
4
+ import { execa } from "execa";
5
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
6
+ import { dirname, join, relative, sep } from "node:path";
7
+
8
+ import type { Project } from "ember.nvp";
9
+
10
+ /**
11
+ * The vitest layer ships infrastructure only (config, deps, scripts).
12
+ * These tests generate projects with the layer, emit specs that exercise
13
+ * each project's real exports, and run the real `pnpm test` (vitest in
14
+ * headless-browser mode), asserting the specs genuinely run and pass.
15
+ */
16
+
17
+ async function listFiles(directory: string): Promise<string[]> {
18
+ const entries = await readdir(directory, { withFileTypes: true, recursive: true });
19
+
20
+ return entries
21
+ .filter((entry) => entry.isFile())
22
+ .map((entry) => relative(directory, join(entry.parentPath, entry.name)))
23
+ .filter((path) => !path.split(sep).includes("node_modules"))
24
+ .sort();
25
+ }
26
+
27
+ async function read(project: Project, filePath: string): Promise<string> {
28
+ return readFile(join(project.directory, filePath), "utf-8");
29
+ }
30
+
31
+ async function emit(project: Project, files: Record<string, string>) {
32
+ for (let [path, contents] of Object.entries(files)) {
33
+ let filePath = join(project.directory, path);
34
+
35
+ await mkdir(dirname(filePath), { recursive: true });
36
+ await writeFile(filePath, contents);
37
+ }
38
+ }
39
+
40
+ async function installAndTest(project: Project) {
41
+ let install = await execa("pnpm install", { cwd: project.directory, shell: true });
42
+ expect(install.exitCode).toBe(0);
43
+
44
+ let test = await execa("pnpm test", { cwd: project.directory, shell: true });
45
+ expect(test.exitCode).toBe(0);
46
+ }
47
+
48
+ /**
49
+ * Specs against the library base's real exports, in the flavor matching
50
+ * the generated project (these files are emitted by this test, not
51
+ * shipped by the layer, so no automatic conversion applies).
52
+ */
53
+ function libraryTests(ext: "ts" | "js") {
54
+ return {
55
+ [`tests/rendering/greeting-test.g${ext}`]: `import { describe, test, expect } from "vitest";
56
+ import { setupRenderingContext } from "ember-vitest";
57
+
58
+ import { Greeting } from "../../src/index.${ext}";
59
+
60
+ describe("Greeting", () => {
61
+ test("greets by name", async () => {
62
+ using ctx = await setupRenderingContext();
63
+
64
+ await ctx.render(<template><Greeting @name="World" /></template>);
65
+
66
+ expect(ctx.element.textContent).toContain("Hello, World!");
67
+ });
68
+ });
69
+ `,
70
+ [`tests/rendering/badge-test.g${ext}`]: `import { describe, test, expect } from "vitest";
71
+ import { setupRenderingContext } from "ember-vitest";
72
+
73
+ import { Badge } from "../../src/index.${ext}";
74
+
75
+ describe("Badge", () => {
76
+ test("renders its block", async () => {
77
+ using ctx = await setupRenderingContext();
78
+
79
+ await ctx.render(<template><Badge>New</Badge></template>);
80
+
81
+ expect(ctx.find(".badge")?.textContent).toBe("New");
82
+ });
83
+ });
84
+ `,
85
+ [`tests/unit/math-test.${ext}`]: `import { describe, test, expect } from "vitest";
86
+
87
+ import { add } from "../../src/utils/math.${ext}";
88
+
89
+ describe("add", () => {
90
+ test("sums two numbers", () => {
91
+ expect(add(2, 3)).toBe(5);
92
+ });
93
+ });
94
+ `,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Specs against the app base's real modules: boots an app made from the
100
+ * app's own router class (pointed at a test-friendly location) and
101
+ * application template, and visits its application route.
102
+ */
103
+ function appTests(ext: "ts" | "js") {
104
+ return {
105
+ [`tests/application/welcome-test.g${ext}`]: `import { describe, expect } from "vitest";
106
+ import { applicationTest } from "ember-vitest";
107
+ import { visit } from "@ember/test-helpers";
108
+ import Application from "ember-strict-application-resolver";
109
+
110
+ import Router from "#app/router.${ext}";
111
+ import ApplicationTemplate from "#app/templates/application.g${ext}";
112
+
113
+ class TestRouter extends Router {
114
+ location = "none";
115
+ }
116
+
117
+ class TestApp extends Application {
118
+ modules = {
119
+ "./router": TestRouter,
120
+ "./templates/application": ApplicationTemplate,
121
+ };
122
+ }
123
+
124
+ describe("application", () => {
125
+ applicationTest.override({ app: ({}, use) => use(TestApp) });
126
+
127
+ applicationTest("renders the welcome page", async ({ element }) => {
128
+ await visit("/");
129
+
130
+ expect(element.textContent).toContain("Welcome to Ember");
131
+ });
132
+ });
133
+ `,
134
+ };
135
+ }
136
+
137
+ describe("layer: vitest", () => {
138
+ const dirs: string[] = [];
139
+
140
+ afterAll(async () => {
141
+ if (process.env.CI) return;
142
+
143
+ for (const dir of dirs) {
144
+ await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
145
+ }
146
+ });
147
+
148
+ describe("JavaScript library", () => {
149
+ let project: Project;
150
+
151
+ beforeAll(async () => {
152
+ project = await generate({ type: "library", name: "my-lib", layers: ["vitest"] });
153
+ dirs.push(project.directory);
154
+ });
155
+
156
+ it("generates the expected files", async () => {
157
+ expect(await listFiles(project.directory)).toMatchInlineSnapshot(`
158
+ [
159
+ ".gitignore",
160
+ "README.md",
161
+ "package.json",
162
+ "src/index.js",
163
+ "tsdown.config.js",
164
+ "vitest.config.mjs",
165
+ ]
166
+ `);
167
+ });
168
+
169
+ it("runs and passes emitted example tests", { timeout: 300_000 }, async () => {
170
+ await writeLibrarySource(project, "javascript");
171
+ await emit(project, libraryTests("js"));
172
+ await installAndTest(project);
173
+ });
174
+ });
175
+
176
+ describe("TypeScript library", () => {
177
+ let project: Project;
178
+
179
+ beforeAll(async () => {
180
+ project = await generate({
181
+ type: "library",
182
+ name: "my-lib",
183
+ layers: ["typescript", "vitest"],
184
+ });
185
+ dirs.push(project.directory);
186
+ });
187
+
188
+ it("generates the expected files", async () => {
189
+ expect(await listFiles(project.directory)).toMatchInlineSnapshot(`
190
+ [
191
+ ".gitignore",
192
+ "README.md",
193
+ "package.json",
194
+ "src/index.ts",
195
+ "tsconfig.json",
196
+ "tsdown.config.js",
197
+ "vitest.config.mjs",
198
+ ]
199
+ `);
200
+ });
201
+
202
+ it("generates the vitest config", async () => {
203
+ expect(await read(project, "vitest.config.mjs")).toMatchInlineSnapshot(`
204
+ "import { ember } from "@nullvoxpopuli/ember-vite";
205
+ import { webdriverio } from "@vitest/browser-webdriverio";
206
+ import { defineConfig } from "vitest/config";
207
+
208
+ export default defineConfig({
209
+ plugins: [ember()],
210
+ test: {
211
+ include: ["tests/**/*-test.{js,ts,gjs,gts}"],
212
+ maxConcurrency: 1,
213
+ passWithNoTests: true,
214
+ browser: {
215
+ provider: webdriverio(),
216
+ enabled: true,
217
+ headless: true,
218
+ instances: [{ browser: "chrome" }],
219
+ },
220
+ },
221
+ });
222
+ "
223
+ `);
224
+ });
225
+
226
+ it("runs and passes emitted example tests", { timeout: 300_000 }, async () => {
227
+ await writeLibrarySource(project, "typescript");
228
+ await emit(project, libraryTests("ts"));
229
+ await installAndTest(project);
230
+ });
231
+ });
232
+
233
+ describe("TypeScript app", () => {
234
+ let project: Project;
235
+
236
+ beforeAll(async () => {
237
+ project = await generate({
238
+ type: "app",
239
+ name: "my-app",
240
+ layers: ["typescript", "vitest"],
241
+ });
242
+ dirs.push(project.directory);
243
+ });
244
+
245
+ it("runs and passes emitted example tests", { timeout: 300_000 }, async () => {
246
+ await emit(project, appTests("ts"));
247
+ await installAndTest(project);
248
+ });
249
+ });
250
+ });
@@ -10,14 +10,18 @@ describe("discoverLayers", () => {
10
10
 
11
11
  expect(layerNames).toMatchInlineSnapshot(`
12
12
  [
13
+ "are-the-types-wrong",
13
14
  "eslint-bundled-ember",
14
15
  "eslint-bundled-nvp",
15
16
  "git",
16
17
  "github-actions",
18
+ "inspector-support",
17
19
  "prettier",
20
+ "publint",
18
21
  "qunit",
19
22
  "renovate",
20
23
  "typescript",
24
+ "vitest",
21
25
  ]
22
26
  `);
23
27
  });
@@ -32,4 +36,16 @@ describe("discoverLayers", () => {
32
36
  });
33
37
  }
34
38
  });
39
+
40
+ describe("default selections", () => {
41
+ const typescript = layers.find((layer) => layer.name === "typescript")!;
42
+
43
+ it("typescript is on by default for libraries", async () => {
44
+ expect(await typescript.defaultValue?.("library")).toBe(true);
45
+ });
46
+
47
+ it("typescript is opt-in for apps", async () => {
48
+ expect(await typescript.defaultValue?.("app")).toBeFalsy();
49
+ });
50
+ });
35
51
  });