ember.nvp 0.6.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 +88 -4
- package/CHANGELOG.md +57 -0
- package/package.json +3 -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/index.js +21 -2
- package/src/cli/questions/layers.js +5 -2
- package/src/cli/questions/project-type.js +0 -5
- 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/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/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
|
@@ -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
|
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { Project } from "ember.nvp";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The library base generates an empty `src/`; tests that build or test a
|
|
8
|
+
* library write this example source (a class component, a template-only
|
|
9
|
+
* component, and a plain module) into the generated project first.
|
|
10
|
+
*/
|
|
11
|
+
export const librarySource = {
|
|
12
|
+
typescript: {
|
|
13
|
+
"src/index.ts": `export { Badge, type BadgeSignature } from "./components/badge.gts";
|
|
14
|
+
export { default as Greeting, type GreetingSignature } from "./components/greeting.gts";
|
|
15
|
+
export { add } from "./utils/math.ts";
|
|
16
|
+
`,
|
|
17
|
+
"src/components/greeting.gts": `import Component from "@glimmer/component";
|
|
18
|
+
|
|
19
|
+
export interface GreetingSignature {
|
|
20
|
+
Element: HTMLParagraphElement;
|
|
21
|
+
Args: {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default class Greeting extends Component<GreetingSignature> {
|
|
27
|
+
<template>
|
|
28
|
+
<p ...attributes>Hello, {{@name}}!</p>
|
|
29
|
+
</template>
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
"src/components/badge.gts": `import type { TOC } from "@ember/component/template-only";
|
|
33
|
+
|
|
34
|
+
export interface BadgeSignature {
|
|
35
|
+
Element: HTMLSpanElement;
|
|
36
|
+
Blocks: {
|
|
37
|
+
default: [];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Badge: TOC<BadgeSignature> = <template>
|
|
42
|
+
<span class="badge" ...attributes>{{yield}}</span>
|
|
43
|
+
</template>;
|
|
44
|
+
`,
|
|
45
|
+
"src/utils/math.ts": `export function add(a: number, b: number): number {
|
|
46
|
+
return a + b;
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
},
|
|
50
|
+
javascript: {
|
|
51
|
+
"src/index.js": `export { Badge } from "./components/badge.gjs";
|
|
52
|
+
export { default as Greeting } from "./components/greeting.gjs";
|
|
53
|
+
export { add } from "./utils/math.js";
|
|
54
|
+
`,
|
|
55
|
+
"src/components/greeting.gjs": `import Component from "@glimmer/component";
|
|
56
|
+
|
|
57
|
+
export default class Greeting extends Component {
|
|
58
|
+
<template>
|
|
59
|
+
<p ...attributes>Hello, {{@name}}!</p>
|
|
60
|
+
</template>
|
|
61
|
+
}
|
|
62
|
+
`,
|
|
63
|
+
"src/components/badge.gjs": `export const Badge = <template>
|
|
64
|
+
<span class="badge" ...attributes>{{yield}}</span>
|
|
65
|
+
</template>;
|
|
66
|
+
`,
|
|
67
|
+
"src/utils/math.js": `export function add(a, b) {
|
|
68
|
+
return a + b;
|
|
69
|
+
}
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export async function writeLibrarySource(project: Project, flavor: keyof typeof librarySource) {
|
|
75
|
+
for (let [filePath, contents] of Object.entries(librarySource[flavor])) {
|
|
76
|
+
let target = join(project.directory, filePath);
|
|
77
|
+
|
|
78
|
+
await mkdir(dirname(target), { recursive: true });
|
|
79
|
+
await writeFile(target, contents);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { beforeAll, describe, it, expect, afterAll } from "vitest";
|
|
2
|
+
import { generate, permutate, bases, layers, reapply } from "#test-helpers";
|
|
3
|
+
import { TODO } from "#layers";
|
|
4
|
+
|
|
5
|
+
import { rm } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
import type { Project } from "ember.nvp";
|
|
8
|
+
import type { ProjectType } from "#types";
|
|
9
|
+
|
|
10
|
+
const baseline = "<baseline>";
|
|
11
|
+
|
|
12
|
+
const RE_APPLY_ONLY = new Set<string>([
|
|
13
|
+
// "typescript"
|
|
14
|
+
// "renovate",
|
|
15
|
+
// "github-actions",
|
|
16
|
+
]);
|
|
17
|
+
const INITIAL_ONLY = new Set<string>([
|
|
18
|
+
// baseline,
|
|
19
|
+
// "eslint-bundled-nvp",
|
|
20
|
+
// "typescript"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Layers that don't yet generate a working setup for a project type.
|
|
25
|
+
* Every layer is *supposed* to support every base eventually -- shrink
|
|
26
|
+
* these as layers learn.
|
|
27
|
+
*/
|
|
28
|
+
const NOT_YET_SUPPORTED: Record<ProjectType, Set<string>> = {
|
|
29
|
+
// publish checks: apps aren't published
|
|
30
|
+
app: new Set(["publint", "are-the-types-wrong"]),
|
|
31
|
+
// libraries have no Application to wire inspector support into
|
|
32
|
+
library: new Set(["inspector-support"]),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const eachBase = bases.map((base) => {
|
|
36
|
+
const type: ProjectType = base === "minimal-app" ? "app" : "library";
|
|
37
|
+
|
|
38
|
+
const applicableLayers = layers.filter((layer) => !NOT_YET_SUPPORTED[type].has(layer.name));
|
|
39
|
+
|
|
40
|
+
const permutations = permutate(applicableLayers.map((layer) => layer.name));
|
|
41
|
+
permutations.push([baseline]);
|
|
42
|
+
|
|
43
|
+
const eachPermutation = permutations
|
|
44
|
+
.filter((permutation) => {
|
|
45
|
+
if (INITIAL_ONLY.size > 0 && !permutation.some((x) => INITIAL_ONLY.has(x))) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Not implemented yet
|
|
50
|
+
if (permutation.some((x) => TODO.has(x))) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return true;
|
|
55
|
+
})
|
|
56
|
+
.map((permutation) => ({ permutation, name: permutation.join(", ") }));
|
|
57
|
+
|
|
58
|
+
return { name: base, type, applicableLayers, eachPermutation };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The permutation matrix grows exponentially with the layer count, so
|
|
63
|
+
* each base runs from its own test file(s) -- CI runs them as separate,
|
|
64
|
+
* parallel jobs. A base whose matrix outgrows one job's budget runs from
|
|
65
|
+
* several files, each taking a deterministic 1-of-N slice.
|
|
66
|
+
*
|
|
67
|
+
* @param base which base's permutations this file runs
|
|
68
|
+
* @param slice which 1-of-N slice of the permutations this file runs
|
|
69
|
+
*/
|
|
70
|
+
export function testPermutations(
|
|
71
|
+
base: (typeof bases)[number],
|
|
72
|
+
slice: { index: number; total: number } = { index: 0, total: 1 },
|
|
73
|
+
) {
|
|
74
|
+
const {
|
|
75
|
+
type,
|
|
76
|
+
applicableLayers,
|
|
77
|
+
eachPermutation: allPermutations,
|
|
78
|
+
} = eachBase.find((b) => b.name === base)!;
|
|
79
|
+
|
|
80
|
+
const eachPermutation = allPermutations.filter((_, i) => i % slice.total === slice.index);
|
|
81
|
+
|
|
82
|
+
describe(base, () => {
|
|
83
|
+
describe.each(eachPermutation)("layers: $name", ({ permutation }) => {
|
|
84
|
+
let project: Project;
|
|
85
|
+
let layerNames = permutation.filter((x) => x !== baseline);
|
|
86
|
+
let startingLayers = layers.filter((layer) => layerNames.includes(layer.name));
|
|
87
|
+
|
|
88
|
+
beforeAll(async () => {
|
|
89
|
+
project = await generate({
|
|
90
|
+
type,
|
|
91
|
+
layers: layerNames,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterAll(async () => {
|
|
96
|
+
if (process.env.CI) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (project?.directory) {
|
|
101
|
+
await rm(project.directory, {
|
|
102
|
+
recursive: true,
|
|
103
|
+
force: true,
|
|
104
|
+
maxRetries: 3,
|
|
105
|
+
retryDelay: 100,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("successfully setup the layers", async () => {
|
|
111
|
+
if (startingLayers.length === 0) {
|
|
112
|
+
// TODO: test baseline stuff more?
|
|
113
|
+
expect(true, "No layers to verify").toBe(true);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (let layer of startingLayers) {
|
|
117
|
+
expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
|
|
118
|
+
|
|
119
|
+
let result = await layer.isSetup(project);
|
|
120
|
+
|
|
121
|
+
expect(result, `${layer.name} is setup`).toBe(true);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Are these valuable? no one would invoke the layers like this
|
|
127
|
+
*/
|
|
128
|
+
// describe.skip("(re)applying", () => {
|
|
129
|
+
// for (let layer of layers) {
|
|
130
|
+
// if (RE_APPLY_ONLY.size > 0 && !RE_APPLY_ONLY.has(layer.name)) {
|
|
131
|
+
// continue;
|
|
132
|
+
// }
|
|
133
|
+
|
|
134
|
+
// if (TODO.has(layer.name)) {
|
|
135
|
+
// continue;
|
|
136
|
+
// }
|
|
137
|
+
|
|
138
|
+
// describe(layer.name, () => {
|
|
139
|
+
// let expected = startingLayers.find((l) => l.name === layer.name);
|
|
140
|
+
|
|
141
|
+
// beforeAll(async () => {
|
|
142
|
+
// await layer.run(project);
|
|
143
|
+
// });
|
|
144
|
+
|
|
145
|
+
// if (expected) {
|
|
146
|
+
// it("is applied", async () => {
|
|
147
|
+
// expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
|
|
148
|
+
|
|
149
|
+
// let result = await layer.isSetup(project, true);
|
|
150
|
+
|
|
151
|
+
// if (typeof result === "object") {
|
|
152
|
+
// expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
|
|
153
|
+
// expect(result.isSetup, `${layer.name} is setup`).toBe(true);
|
|
154
|
+
// return;
|
|
155
|
+
// }
|
|
156
|
+
|
|
157
|
+
// expect(result, `${layer.name} is setup`).toBe(true);
|
|
158
|
+
|
|
159
|
+
// return;
|
|
160
|
+
// });
|
|
161
|
+
// } else {
|
|
162
|
+
// it("is not applied", async () => {
|
|
163
|
+
// expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
|
|
164
|
+
|
|
165
|
+
// let result = await layer.isSetup(project, true);
|
|
166
|
+
|
|
167
|
+
// if (typeof result === "object") {
|
|
168
|
+
// expect(result.isSetup, `${layer.name} is not setup`).toBe(false);
|
|
169
|
+
// hardExpect(result.reasons.length, `${layer.name} is not setup`).toBeGreaterThan(0);
|
|
170
|
+
// return;
|
|
171
|
+
// }
|
|
172
|
+
|
|
173
|
+
// expect(result, `${layer.name} is not setup`).toBe(false);
|
|
174
|
+
// });
|
|
175
|
+
// }
|
|
176
|
+
// });
|
|
177
|
+
// }
|
|
178
|
+
// });
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Simulates running the CLI again with different options selected
|
|
182
|
+
* on the same project
|
|
183
|
+
*/
|
|
184
|
+
describe("apply anew", () => {
|
|
185
|
+
let newLayers = applicableLayers
|
|
186
|
+
.filter((layer) => {
|
|
187
|
+
if (RE_APPLY_ONLY.size > 0 && !RE_APPLY_ONLY.has(layer.name)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (TODO.has(layer.name)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
})
|
|
197
|
+
.map((layer) => ({ name: layer.name, layer }));
|
|
198
|
+
|
|
199
|
+
describe.each(newLayers)("$name", ({ layer }) => {
|
|
200
|
+
beforeAll(async () => {
|
|
201
|
+
// Simulate running the CLI again, adding one more option while
|
|
202
|
+
// keeping the previously-selected layers.
|
|
203
|
+
await reapply(project, [...new Set([...layerNames, layer.name])]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it.for(startingLayers)("starting layers still present: $name", async (layer) => {
|
|
207
|
+
expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
|
|
208
|
+
|
|
209
|
+
let result = await layer.isSetup(project, true);
|
|
210
|
+
|
|
211
|
+
if (typeof result === "object") {
|
|
212
|
+
expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
|
|
213
|
+
expect(result.isSetup, `${layer.name} is setup`).toBe(true);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
expect(result, `${layer.name} is setup`).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("is applied", async () => {
|
|
221
|
+
expect(layer.isSetup, `has isSetup for ${layer.name}`).toBeInstanceOf(Function);
|
|
222
|
+
|
|
223
|
+
let result = await layer.isSetup(project, true);
|
|
224
|
+
|
|
225
|
+
if (typeof result === "object") {
|
|
226
|
+
expect(result.reasons, `${layer.name} is setup`).deep.equal([]);
|
|
227
|
+
expect(result.isSetup, `${layer.name} is setup`).toBe(true);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
expect(result, `${layer.name} is setup`).toBe(true);
|
|
232
|
+
|
|
233
|
+
return;
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|