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
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { execSync } from "node:child_process";
6
+
7
+ import { Stage } from "ember.nvp";
8
+ import { mktemp } from "#test-helpers";
9
+
10
+ async function makeTargetProject() {
11
+ const target = join(await mktemp("stage-target"), "my-app");
12
+
13
+ await mkdir(join(target, "app"), { recursive: true });
14
+ await mkdir(join(target, "node_modules/some-dep"), { recursive: true });
15
+ await writeFile(join(target, "package.json"), `{ "name": "my-app" }`);
16
+ await writeFile(join(target, "app/app.js"), `export default class App {}`);
17
+ await writeFile(join(target, "tsconfig.json"), `{}`);
18
+ await writeFile(join(target, "node_modules/some-dep/index.js"), `module.exports = 1;`);
19
+
20
+ return target;
21
+ }
22
+
23
+ describe("Stage", () => {
24
+ describe("a new project (target does not exist)", () => {
25
+ it("stages writes without touching the target, then commits them", async () => {
26
+ const target = join(await mktemp("stage-new"), "my-app");
27
+ const stage = await Stage.create(target);
28
+
29
+ expect(stage.isNew).toBe(true);
30
+
31
+ await mkdir(join(stage.directory, "app"), { recursive: true });
32
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "my-app" }`);
33
+ await writeFile(join(stage.directory, "app/app.js"), `export default class App {}`);
34
+
35
+ expect(existsSync(target), "target is untouched while staged").toBe(false);
36
+
37
+ const changes = await stage.changes();
38
+
39
+ expect(changes).toEqual([
40
+ { path: "app/app.js", status: "added" },
41
+ { path: "package.json", status: "added" },
42
+ ]);
43
+
44
+ await stage.commit();
45
+
46
+ expect(await readFile(join(target, "package.json"), "utf-8")).toBe(`{ "name": "my-app" }`);
47
+ expect(existsSync(join(target, "app/app.js"))).toBe(true);
48
+ expect(existsSync(stage.directory), "stage is cleaned up after commit").toBe(false);
49
+ });
50
+
51
+ it("carries a git repo initialized in the stage over to the target", async () => {
52
+ const target = join(await mktemp("stage-git"), "my-app");
53
+ const stage = await Stage.create(target);
54
+
55
+ await writeFile(join(stage.directory, "readme.md"), `hi`);
56
+ execSync("git init --initial-branch=main", { cwd: stage.directory, stdio: "pipe" });
57
+
58
+ await stage.commit();
59
+
60
+ expect(existsSync(join(target, ".git"))).toBe(true);
61
+ expect(existsSync(join(target, "readme.md"))).toBe(true);
62
+ });
63
+ });
64
+
65
+ describe("an existing project (seeded stage)", () => {
66
+ it("seeds the target's files, excluding node_modules and .git", async () => {
67
+ const target = await makeTargetProject();
68
+ execSync("git init --initial-branch=main", { cwd: target, stdio: "pipe" });
69
+
70
+ const stage = await Stage.create(target);
71
+
72
+ expect(stage.isNew).toBe(false);
73
+ expect(existsSync(join(stage.directory, "package.json"))).toBe(true);
74
+ expect(existsSync(join(stage.directory, "app/app.js"))).toBe(true);
75
+ expect(existsSync(join(stage.directory, "node_modules"))).toBe(false);
76
+ expect(existsSync(join(stage.directory, ".git"))).toBe(false);
77
+
78
+ await stage.discard();
79
+ });
80
+
81
+ it("detects added, modified, and deleted files", async () => {
82
+ const target = await makeTargetProject();
83
+ const stage = await Stage.create(target);
84
+
85
+ await writeFile(join(stage.directory, ".prettierrc.js"), `export default {};`);
86
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "renamed" }`);
87
+ await rm(join(stage.directory, "tsconfig.json"));
88
+
89
+ const changes = await stage.changes();
90
+
91
+ expect(changes).toEqual([
92
+ { path: ".prettierrc.js", status: "added" },
93
+ { path: "package.json", status: "modified" },
94
+ { path: "tsconfig.json", status: "deleted" },
95
+ ]);
96
+
97
+ await stage.discard();
98
+ });
99
+
100
+ it("commit only touches changed paths, preserving node_modules and unrelated files", async () => {
101
+ const target = await makeTargetProject();
102
+ const stage = await Stage.create(target);
103
+
104
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "renamed" }`);
105
+ await rm(join(stage.directory, "tsconfig.json"));
106
+
107
+ await stage.commit();
108
+
109
+ expect(await readFile(join(target, "package.json"), "utf-8")).toBe(`{ "name": "renamed" }`);
110
+ expect(
111
+ existsSync(join(target, "tsconfig.json")),
112
+ "deleted in stage -> deleted in target",
113
+ ).toBe(false);
114
+ expect(existsSync(join(target, "app/app.js")), "unchanged files are untouched").toBe(true);
115
+ expect(
116
+ existsSync(join(target, "node_modules/some-dep/index.js")),
117
+ "node_modules survives commit",
118
+ ).toBe(true);
119
+ });
120
+
121
+ it("commit applies only the given subset of changes", async () => {
122
+ const target = await makeTargetProject();
123
+ const stage = await Stage.create(target);
124
+
125
+ await writeFile(join(stage.directory, ".prettierrc.js"), `export default {};`);
126
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "renamed" }`);
127
+ await rm(join(stage.directory, "tsconfig.json"));
128
+
129
+ const changes = await stage.changes();
130
+ const accepted = changes.filter((change) => change.path !== "package.json");
131
+
132
+ await stage.commit(accepted);
133
+
134
+ expect(existsSync(join(target, ".prettierrc.js")), "accepted addition is written").toBe(true);
135
+ expect(existsSync(join(target, "tsconfig.json")), "accepted deletion is applied").toBe(false);
136
+ expect(
137
+ await readFile(join(target, "package.json"), "utf-8"),
138
+ "rejected change is not applied",
139
+ ).toBe(`{ "name": "my-app" }`);
140
+ });
141
+
142
+ it("discard leaves the target exactly as it was", async () => {
143
+ const target = await makeTargetProject();
144
+ const stage = await Stage.create(target);
145
+
146
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "renamed" }`);
147
+
148
+ await stage.discard();
149
+
150
+ expect(await readFile(join(target, "package.json"), "utf-8")).toBe(`{ "name": "my-app" }`);
151
+ expect(existsSync(stage.directory)).toBe(false);
152
+ });
153
+
154
+ it("reports no changes when nothing was modified", async () => {
155
+ const target = await makeTargetProject();
156
+ const stage = await Stage.create(target);
157
+
158
+ expect(await stage.changes()).toEqual([]);
159
+
160
+ await stage.discard();
161
+ });
162
+ });
163
+
164
+ describe("diff", () => {
165
+ it("renders a unified diff for added, modified, and deleted files", async () => {
166
+ const target = await makeTargetProject();
167
+ const stage = await Stage.create(target);
168
+
169
+ await writeFile(join(stage.directory, ".prettierrc.js"), `export default {};`);
170
+ await writeFile(join(stage.directory, "package.json"), `{ "name": "renamed" }`);
171
+ await rm(join(stage.directory, "tsconfig.json"));
172
+
173
+ const diff = await stage.diff();
174
+
175
+ expect(diff).toContain(`+++ b/.prettierrc.js`);
176
+ expect(diff).toContain(`+export default {};`);
177
+
178
+ expect(diff).toContain(`--- a/package.json`);
179
+ expect(diff).toContain(`-{ "name": "my-app" }`);
180
+ expect(diff).toContain(`+{ "name": "renamed" }`);
181
+
182
+ expect(diff).toContain(`--- a/tsconfig.json`);
183
+ expect(diff).toContain(`+++ /dev/null`);
184
+
185
+ await stage.discard();
186
+ });
187
+ });
188
+ });
@@ -51,6 +51,23 @@ describe("permutate", () => {
51
51
  expect(hasOneEslint).toBe(true);
52
52
  });
53
53
 
54
+ it("enforces mutual exclusivity for testing layers", () => {
55
+ const result = permutate(["qunit", "vitest", "other"]);
56
+
57
+ for (const permutation of result) {
58
+ const testingCount = permutation.filter(
59
+ (item) => item === "qunit" || item === "vitest",
60
+ ).length;
61
+ expect(testingCount).toBeLessThanOrEqual(1);
62
+ }
63
+
64
+ const hasQunit = result.some((perm) => perm.includes("qunit"));
65
+ const hasVitest = result.some((perm) => perm.includes("vitest"));
66
+
67
+ expect(hasQunit).toBe(true);
68
+ expect(hasVitest).toBe(true);
69
+ });
70
+
54
71
  it("generates valid permutations with mixed items", () => {
55
72
  const result = permutate(["a", "eslint-foo", "eslint-bar", "b"]);
56
73
 
@@ -16,10 +16,22 @@ export const bases = [minimalApp, minimalAddon];
16
16
 
17
17
  export const layers = await discoverLayers();
18
18
 
19
+ /**
20
+ * Layers in the same group are alternatives to each other (one eslint
21
+ * config, one test framework): selecting two of them would produce a
22
+ * broken project, so permutations never combine them.
23
+ */
24
+ function exclusivityGroup(name: string): string | undefined {
25
+ if (name.startsWith("eslint-")) return "eslint";
26
+ if (name === "qunit" || name === "vitest") return "testing";
27
+
28
+ return undefined;
29
+ }
30
+
19
31
  export function permutate(toPermutate: string[]): string[][] {
20
32
  const out: string[][] = [];
21
33
 
22
- function backtrack(startIndex: number, prefix: string[], hasEslint: boolean) {
34
+ function backtrack(startIndex: number, prefix: string[], usedGroups: Set<string>) {
23
35
  if (prefix.length > 0) out.push(prefix.slice());
24
36
 
25
37
  for (let i = startIndex; i < toPermutate.length; i++) {
@@ -34,19 +46,20 @@ export function permutate(toPermutate: string[]): string[][] {
34
46
  // (silly TS not understanding loop bounds)
35
47
  if (!option) continue;
36
48
 
37
- const isEslint = option.startsWith("eslint-");
49
+ const group = exclusivityGroup(option);
38
50
 
39
- // Skip this option if it's an eslint entry and we already have one
40
- // (enforce mutual exclusivity for eslint-prefixed entries)
41
- if (isEslint && hasEslint) continue;
51
+ // Skip this option if we already have one from its exclusivity group
52
+ if (group && usedGroups.has(group)) continue;
42
53
 
54
+ if (group) usedGroups.add(group);
43
55
  prefix.push(option);
44
- backtrack(i + 1, prefix, hasEslint || isEslint);
56
+ backtrack(i + 1, prefix, usedGroups);
45
57
  prefix.pop();
58
+ if (group) usedGroups.delete(group);
46
59
  }
47
60
  }
48
61
 
49
- backtrack(0, [], false);
62
+ backtrack(0, [], new Set());
50
63
  return out;
51
64
  }
52
65
 
package/tsconfig.json CHANGED
@@ -10,6 +10,7 @@
10
10
  "dist",
11
11
  "packages",
12
12
  "src/**/layers/*/files/**",
13
+ "src/**/layers/*/library-files/**",
13
14
  "src/**/bases/*/files/**",
14
15
  "my-app"
15
16
  ]
@@ -1,219 +0,0 @@
1
- /**
2
- * Reasons we have to have babel
3
- * - decorators
4
- * - ember-concurrency
5
- * - scoped-css
6
- * - template compilation optimization
7
- */
8
- import { extensions } from "@embroider/vite";
9
- import { babel } from "@rollup/plugin-babel";
10
- import { join, resolve } from "node:path";
11
- import { parse as oxcParse } from "oxc-parser";
12
- import { transform } from "oxc-transform";
13
- import { walk } from "zimmerframe";
14
-
15
- /**
16
- * if a file imports any of these, it must be optimize
17
- */
18
- const babelTemplateImports = new Set([
19
- // Templates
20
- "@ember/template-compiler",
21
- "@ember/template-compilation",
22
- "ember-cli-htmlbars",
23
- "ember-cli-htmlbars-inline-precompile",
24
- "htmlbars-inline-precompile",
25
- ]);
26
-
27
- const babelMacroImports = new Set([
28
- // Macros
29
- "@embroider/macros",
30
- "@glimmer/env",
31
- "@ember/application/deprecations",
32
- // Macros only needed in prod
33
- "@ember/debug",
34
- ]); // Babel plugins required from libraries
35
- const babelOtherImports = new Set([
36
- "ember-concurrency",
37
- "ember-scoped-css",
38
- "ember-intl/helpers/format-message",
39
- ]);
40
-
41
- const babelRequiredImports = [...babelTemplateImports, ...babelMacroImports, ...babelOtherImports];
42
-
43
- const counts = {
44
- total: 0,
45
- babel: 0,
46
- why: {
47
- decorators: 0,
48
- formatMessage: 0,
49
- imports: {
50
- templates: 0,
51
- macros: 0,
52
- other: 0,
53
- },
54
- initializeRuntimeMacrosConfig: 0,
55
- },
56
- };
57
- export function maybeBabel(options: { parallel?: boolean; configFile: string; env: any }) {
58
- const { env, parallel, ...restOptions } = options;
59
- const original = (() => {
60
- const babelPath = resolve(join(process.cwd(), "./babel.config.js"));
61
-
62
- return babel({
63
- babelHelpers: "runtime",
64
- extensions,
65
- skipPreflightCheck: true,
66
- ...restOptions,
67
- configFile: babelPath,
68
- parallel: parallel ?? true,
69
- });
70
- })();
71
-
72
- /**
73
- * In @rollup/plugin-babel v7+, the `transform` hook is the object form
74
- * `{ filter, handler }` rather than a plain function. Normalize to the
75
- * underlying handler so we can invoke it directly.
76
- */
77
- const originalTransform = (
78
- typeof original.transform === "function" ? original.transform : original.transform?.handler
79
- )!;
80
-
81
- const babelMacros = new Set(babelRequiredImports);
82
- if (env.mode === "development") {
83
- babelMacros.delete("@ember/debug");
84
- }
85
-
86
- async function doTransform(this: any, code: string, id: string) {
87
- counts.total++;
88
-
89
- const ext = id.split(".").at(-1);
90
- const lang = (ext === "gjs" ? "js" : ext === "gts" ? "ts" : ext) as
91
- | "js"
92
- | "ts"
93
- | "jsx"
94
- | "tsx"
95
- | "dts"
96
- | undefined;
97
-
98
- let needsBabel = false;
99
-
100
- const estree = await oxcParse(id, code, { lang });
101
-
102
- walk(
103
- estree.program as any,
104
- /* state */ {},
105
- {
106
- Decorator(_node: any, { stop }: any) {
107
- needsBabel = true;
108
- counts.why.decorators++;
109
- stop();
110
- },
111
- MemberExpression(node: any, { stop }: any) {
112
- if (node.property?.name === "formatMessage" && node.object?.name === "intl") {
113
- needsBabel = true;
114
- counts.why.formatMessage++;
115
- stop();
116
- }
117
- },
118
- ImportDeclaration(node: any, { stop }: any) {
119
- const value = node.source.value;
120
- if (babelMacros.has(value)) {
121
- needsBabel = true;
122
-
123
- if (babelTemplateImports.has(value)) {
124
- counts.why.imports.templates++;
125
- } else if (babelMacroImports.has(value)) {
126
- counts.why.imports.macros++;
127
- } else {
128
- counts.why.imports.other++;
129
- }
130
-
131
- stop();
132
- }
133
- },
134
- },
135
- );
136
-
137
- if (!needsBabel && code.includes("initializeRuntimeMacrosConfig")) {
138
- counts.why.initializeRuntimeMacrosConfig++;
139
- needsBabel = true;
140
- }
141
-
142
- if (needsBabel) {
143
- counts.babel++;
144
-
145
- const result = await originalTransform.call(this, code, id);
146
-
147
- return result;
148
- }
149
-
150
- if (ext === "json") {
151
- return;
152
- }
153
-
154
- if (ext === "js" || ext === "gjs") {
155
- // We don't need to process JS
156
- return null;
157
- }
158
-
159
- const result = await transform(id, code, {
160
- lang,
161
- typescript: {
162
- onlyRemoveTypeImports: true,
163
- /**
164
- * We should work towards disabling this.
165
- */
166
- allowNamespaces: true,
167
- removeClassFieldsWithoutInitializer: false,
168
- rewriteImportExtensions: false,
169
- },
170
- });
171
-
172
- if (result.errors?.length) {
173
- console.error(`Errors during oxc-transform of ${id}:`);
174
- for (const err of result.errors) {
175
- if (err.labels) {
176
- console.error(err.labels);
177
- }
178
- console.error(err.codeframe || err.message);
179
- }
180
- throw result.errors;
181
- }
182
-
183
- return result;
184
- }
185
-
186
- return {
187
- enforce: "pre",
188
- buildEnd() {
189
- if (process.env.INSPECT) {
190
- console.debug(`Babel usage: ${counts.babel} / ${counts.total}`, counts.why);
191
- }
192
- },
193
- ...original,
194
- name: "nullvoxpopuli:babel",
195
- transform: {
196
- filter: {
197
- id: {
198
- include: [/\.js/, /\.gjs/, /\.ts/, /\.gts/],
199
- exclude: [/\.json/],
200
- },
201
- // Enabling this opts us out of template compilation
202
- // it's an AND instead of OR
203
- // code: {
204
- // include: [/initializeRuntimeMacrosConfig/, /precompileTemplate/],
205
- // },
206
- },
207
- handler(this: any, code: string, id: string) {
208
- try {
209
- return doTransform.call(this, code, id);
210
- } catch (e) {
211
- // Use VSCode JavaScript Debug Terminal
212
- // eslint-disable-next-line no-debugger
213
- debugger;
214
- throw e;
215
- }
216
- },
217
- },
218
- };
219
- }
@@ -1,30 +0,0 @@
1
- import { chromium } from "playwright";
2
-
3
- const browser = await chromium.launch();
4
- const page = await browser.newPage();
5
-
6
- page.on("console", (msg) => console.log(msg.text()));
7
-
8
- await page.goto("http://localhost:4173/tests");
9
-
10
- await page.waitForFunction(
11
- () => {
12
- const elem = document.getElementById("qunit-testresult");
13
- return elem?.classList.contains("complete");
14
- },
15
- { timeout: 30000 },
16
- );
17
-
18
- const failed = await page.evaluate(() => {
19
- const elem = document.getElementById("qunit-testresult");
20
- return elem?.querySelector(".failed")?.textContent;
21
- });
22
-
23
- await browser.close();
24
-
25
- if (failed && parseInt(failed) > 0) {
26
- console.error(`❌ ${failed} test(s) failed`);
27
- process.exit(1);
28
- }
29
-
30
- console.log("✅ All tests passed!");
@@ -1,5 +0,0 @@
1
- import Application from "#app/app.ts";
2
- import config from "#app/config.ts";
3
- import { setApplication } from "@ember/test-helpers";
4
-
5
- setApplication(Application.create(config));