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.
Files changed (85) hide show
  1. package/.github/workflows/ci.yml +17 -2
  2. package/.release-plan.json +88 -4
  3. package/CHANGELOG.md +57 -0
  4. package/package.json +3 -2
  5. package/packages/build-tooling-utils/README.md +37 -0
  6. package/packages/build-tooling-utils/index.ts +2 -0
  7. package/packages/build-tooling-utils/package.json +42 -0
  8. package/packages/build-tooling-utils/rolldown.config.js +18 -0
  9. package/packages/build-tooling-utils/src/extensions.ts +18 -0
  10. package/packages/build-tooling-utils/src/maybe-babel.ts +146 -0
  11. package/packages/rolldown/README.md +115 -0
  12. package/packages/rolldown/index.ts +56 -0
  13. package/packages/rolldown/package.json +59 -0
  14. package/packages/rolldown/rolldown.config.js +21 -0
  15. package/packages/rolldown/src/babel.ts +136 -0
  16. package/packages/rolldown/src/define-config.ts +32 -0
  17. package/packages/rolldown/src/externals.ts +83 -0
  18. package/packages/rolldown/src/isolated-declarations.ts +65 -0
  19. package/packages/rolldown/src/transform.ts +162 -0
  20. package/packages/vite/README.md +7 -21
  21. package/packages/vite/index.ts +1 -1
  22. package/packages/vite/package.json +4 -6
  23. package/packages/vite/src/ember.ts +129 -15
  24. package/packages/vite/src/test-html.ts +86 -0
  25. package/src/bases/minimal-app/files/app/router.ts +1 -0
  26. package/src/bases/minimal-app/files/tsconfig.json +1 -0
  27. package/src/bases/minimal-app/index.js +2 -2
  28. package/src/bases/minimal-library/files/README.md +41 -0
  29. package/src/bases/minimal-library/files/package.json +49 -0
  30. package/src/bases/minimal-library/files/tsconfig.json +10 -0
  31. package/src/bases/minimal-library/files/tsdown.config.js +6 -0
  32. package/src/bases/minimal-library/index.js +158 -2
  33. package/src/cli/index.js +21 -2
  34. package/src/cli/questions/layers.js +5 -2
  35. package/src/cli/questions/project-type.js +0 -5
  36. package/src/layers/are-the-types-wrong/index.js +74 -0
  37. package/src/layers/git/files/gitignore +14 -1
  38. package/src/layers/git/index.js +6 -5
  39. package/src/layers/github-actions/index.js +12 -8
  40. package/src/layers/index.js +1 -1
  41. package/src/layers/inspector-support/index.js +101 -0
  42. package/src/layers/prettier/files/.prettierignore +4 -0
  43. package/src/layers/publint/index.js +68 -0
  44. package/src/layers/qunit/files/tests/unit/macros-test.ts +2 -2
  45. package/src/layers/qunit/index.js +107 -7
  46. package/src/layers/qunit/library-files/config/test/babel.config.js +41 -0
  47. package/src/layers/qunit/library-files/config/test/testem.cjs +26 -0
  48. package/src/layers/qunit/library-files/tests/test-helper.ts +36 -0
  49. package/src/layers/qunit/library-files/tests/unit/.gitkeep +0 -0
  50. package/src/layers/qunit/library-files/vite.config.mjs +6 -0
  51. package/src/layers/typescript/index.js +43 -7
  52. package/src/layers/vitest/files/vitest.config.mjs +11 -11
  53. package/src/layers/vitest/index.js +70 -32
  54. package/src/utils/babel.js +9 -6
  55. package/src/utils/fs.js +44 -3
  56. package/src/utils/inspector-support.js +196 -0
  57. package/src/utils/npm.js +21 -7
  58. package/src/utils/remove-types.js +5 -0
  59. package/src/utils/rewrite-imports.js +157 -0
  60. package/src/utils/tsdown-config.js +85 -0
  61. package/src/utils/types.ts +5 -1
  62. package/test/ember.nvp/base-library.test.ts +232 -0
  63. package/test/ember.nvp/generator-replace-or-update.test.ts +2 -3
  64. package/test/ember.nvp/inspector-support.test.ts +179 -0
  65. package/test/ember.nvp/layer-inspector-support.test.ts +69 -0
  66. package/test/ember.nvp/layer-publish-checks.test.ts +103 -0
  67. package/test/ember.nvp/layer-qunit.test.ts +376 -15
  68. package/test/ember.nvp/layer-vitest.test.ts +250 -0
  69. package/test/ember.nvp/layers.test.ts +16 -0
  70. package/test/ember.nvp/library-src-fixtures.ts +81 -0
  71. package/test/ember.nvp/permutations/app-1.test.ts +3 -0
  72. package/test/ember.nvp/permutations/app-2.test.ts +3 -0
  73. package/test/ember.nvp/permutations/library-1.test.ts +3 -0
  74. package/test/ember.nvp/permutations/library-2.test.ts +3 -0
  75. package/test/ember.nvp/permutations/shared.ts +239 -0
  76. package/test/ember.nvp/rewrite-imports.test.ts +235 -0
  77. package/test/test-helpers/permutate.test.ts +17 -0
  78. package/test/test-helpers.ts +20 -7
  79. package/tsconfig.json +1 -0
  80. package/packages/vite/src/maybe-babel.ts +0 -219
  81. package/src/layers/qunit/files/run-tests.mjs +0 -30
  82. package/src/layers/vitest/files/tests/test-helper.ts +0 -5
  83. package/test/ember.nvp/permutations.test.ts +0 -200
  84. /package/src/{layers/vitest/files/tests/integration/.gitkeep → bases/minimal-library/files/src/index.ts} +0 -0
  85. /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,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-app", { index: 0, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-app", { index: 1, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-library", { index: 0, total: 2 });
@@ -0,0 +1,3 @@
1
+ import { testPermutations } from "./shared.ts";
2
+
3
+ testPermutations("minimal-library", { index: 1, total: 2 });
@@ -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
+ }