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.
- package/.github/workflows/ci.yml +17 -2
- package/.release-plan.json +84 -4
- package/CHANGELOG.md +68 -0
- package/README.md +29 -0
- package/package.json +4 -2
- package/packages/build-tooling-utils/README.md +37 -0
- package/packages/build-tooling-utils/index.ts +2 -0
- package/packages/build-tooling-utils/package.json +42 -0
- package/packages/build-tooling-utils/rolldown.config.js +18 -0
- package/packages/build-tooling-utils/src/extensions.ts +18 -0
- package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
- package/packages/rolldown/README.md +115 -0
- package/packages/rolldown/index.ts +56 -0
- package/packages/rolldown/package.json +59 -0
- package/packages/rolldown/rolldown.config.js +21 -0
- package/packages/rolldown/src/babel.ts +136 -0
- package/packages/rolldown/src/define-config.ts +32 -0
- package/packages/rolldown/src/externals.ts +83 -0
- package/packages/rolldown/src/isolated-declarations.ts +65 -0
- package/packages/rolldown/src/transform.ts +162 -0
- package/packages/vite/README.md +7 -21
- package/packages/vite/index.ts +1 -1
- package/packages/vite/package.json +4 -6
- package/packages/vite/src/ember.ts +129 -15
- package/packages/vite/src/test-html.ts +86 -0
- package/src/bases/minimal-app/files/app/router.ts +1 -0
- package/src/bases/minimal-app/files/tsconfig.json +1 -0
- package/src/bases/minimal-app/index.js +2 -2
- package/src/bases/minimal-library/files/README.md +41 -0
- package/src/bases/minimal-library/files/package.json +49 -0
- package/src/bases/minimal-library/files/tsconfig.json +10 -0
- package/src/bases/minimal-library/files/tsdown.config.js +6 -0
- package/src/bases/minimal-library/index.js +158 -2
- package/src/cli/args.js +7 -1
- package/src/cli/index.js +90 -16
- package/src/cli/questions/layers.js +5 -2
- package/src/cli/questions/project-type.js +0 -5
- package/src/cli/questions/write.js +169 -0
- package/src/index.js +1 -0
- package/src/layers/are-the-types-wrong/index.js +74 -0
- package/src/layers/git/files/gitignore +14 -1
- package/src/layers/git/index.js +6 -5
- package/src/layers/github-actions/index.js +12 -8
- package/src/layers/index.js +1 -1
- package/src/layers/inspector-support/index.js +101 -0
- package/src/layers/prettier/files/.prettierignore +4 -0
- package/src/layers/publint/index.js +68 -0
- package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
- package/src/layers/qunit/index.js +107 -7
- package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
- package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
- package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
- package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
- package/src/layers/qunit/library-files/vite.config.mjs +6 -0
- package/src/layers/typescript/index.js +43 -7
- package/src/layers/vitest/files/vitest.config.mjs +11 -11
- package/src/layers/vitest/index.js +70 -32
- package/src/utils/babel.js +9 -6
- package/src/utils/fs.js +44 -3
- package/src/utils/inspector-support.js +196 -0
- package/src/utils/npm.js +21 -7
- package/src/utils/remove-types.js +5 -0
- package/src/utils/rewrite-imports.js +157 -0
- package/src/utils/stage.js +317 -0
- package/src/utils/tsdown-config.js +85 -0
- package/src/utils/types.ts +5 -1
- package/test/ember.nvp/base-library.test.ts +232 -0
- package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
- package/test/ember.nvp/inspector-support.test.ts +179 -0
- package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
- package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
- package/test/ember.nvp/layer-qunit.test.ts +376 -15
- package/test/ember.nvp/layer-vitest.test.ts +250 -0
- package/test/ember.nvp/layers.test.ts +16 -0
- package/test/ember.nvp/library-src-fixtures.ts +81 -0
- package/test/ember.nvp/permutations/app-1.test.ts +3 -0
- package/test/ember.nvp/permutations/app-2.test.ts +3 -0
- package/test/ember.nvp/permutations/library-1.test.ts +3 -0
- package/test/ember.nvp/permutations/library-2.test.ts +3 -0
- package/test/ember.nvp/permutations/shared.ts +239 -0
- package/test/ember.nvp/rewrite-imports.test.ts +235 -0
- package/test/ember.nvp/stage.test.ts +188 -0
- package/test/test-helpers/permutate.test.ts +17 -0
- package/test/test-helpers.ts +20 -7
- package/tsconfig.json +1 -0
- package/packages/vite/src/maybe-babel.ts +0 -219
- package/src/layers/qunit/files/run-tests.mjs +0 -30
- package/src/layers/vitest/files/tests/test-helper.ts +0 -5
- package/test/ember.nvp/permutations.test.ts +0 -200
- /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
- /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 {
|
|
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
|
|
8
|
-
*
|
|
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
|
|
144
|
+
await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
21
145
|
}
|
|
22
146
|
});
|
|
23
147
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
expect(install.exitCode).toBe(0);
|
|
389
|
+
let output = await read(project, "dist/index.js");
|
|
30
390
|
|
|
31
|
-
|
|
32
|
-
|
|
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
|
});
|