ember.nvp 1.1.0 → 1.3.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.
@@ -0,0 +1,195 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import { generate } from "#test-helpers";
3
+ import { rm } from "node:fs/promises";
4
+ import type { Project } from "ember.nvp";
5
+
6
+ describe("layer: readme", () => {
7
+ const dirs: string[] = [];
8
+
9
+ afterAll(async () => {
10
+ if (process.env.CI) return;
11
+
12
+ for (const dir of dirs) {
13
+ await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
14
+ }
15
+ });
16
+
17
+ describe("App project README generation", () => {
18
+ let project: Project;
19
+
20
+ beforeAll(async () => {
21
+ project = await generate({
22
+ type: "app",
23
+ name: "my-app",
24
+ packageManager: "pnpm",
25
+ layers: ["readme", "git", "prettier", "typescript"],
26
+ });
27
+ dirs.push(project.directory);
28
+ });
29
+
30
+ it("generates README.md with project info and selected layer docs", async () => {
31
+ expect(await project.read("README.md")).toMatchInlineSnapshot(`
32
+ "# my-app
33
+
34
+ An Ember application created with \`ember.nvp\`.
35
+
36
+ ## Getting Started
37
+
38
+ ### Prerequisites
39
+
40
+ - Node.js >= 24
41
+ - pnpm
42
+
43
+ ### Installation
44
+
45
+ ~~~sh
46
+ pnpm install
47
+ ~~~
48
+
49
+ ### Development
50
+
51
+ To start the local development server:
52
+
53
+ ~~~sh
54
+ pnpm dev
55
+ ~~~
56
+
57
+ or
58
+
59
+ ~~~sh
60
+ pnpm start
61
+ ~~~
62
+
63
+ ## Features & Tooling
64
+
65
+ ### Prettier
66
+
67
+ Code formatting is managed with [Prettier](https://prettier.io/).
68
+
69
+ - \`pnpm format\` - Format code
70
+ - \`pnpm lint:prettier\` - Check code formatting
71
+
72
+ ### TypeScript
73
+
74
+ This project uses TypeScript and Glint for static type checking.
75
+
76
+ - \`pnpm lint:types\` - Typecheck code with Glint/TypeScript
77
+ "
78
+ `);
79
+ });
80
+ });
81
+
82
+ describe("Library project README generation with npm", () => {
83
+ let project: Project;
84
+
85
+ beforeAll(async () => {
86
+ project = await generate({
87
+ type: "library",
88
+ name: "my-lib",
89
+ packageManager: "npm",
90
+ layers: ["readme", "prettier"],
91
+ });
92
+ dirs.push(project.directory);
93
+ });
94
+
95
+ it("generates README.md with npm-specific commands for library", async () => {
96
+ expect(await project.read("README.md")).toMatchInlineSnapshot(`
97
+ "# my-lib
98
+
99
+ An Ember library/addon created with \`ember.nvp\`.
100
+
101
+ ## Getting Started
102
+
103
+ ### Prerequisites
104
+
105
+ - Node.js >= 24
106
+ - npm
107
+
108
+ ### Development & Building
109
+
110
+ To build the library:
111
+
112
+ ~~~sh
113
+ npm run build
114
+ ~~~
115
+
116
+ ## Features & Tooling
117
+
118
+ ### Prettier
119
+
120
+ Code formatting is managed with [Prettier](https://prettier.io/).
121
+
122
+ - \`npm run format\` - Format code
123
+ - \`npm run lint:prettier\` - Check code formatting
124
+ "
125
+ `);
126
+ });
127
+ });
128
+
129
+ describe("Extension project README generation", () => {
130
+ let project: Project;
131
+
132
+ beforeAll(async () => {
133
+ project = await generate({
134
+ type: "extension",
135
+ name: "my-extension",
136
+ packageManager: "pnpm",
137
+ layers: ["readme", "prettier"],
138
+ });
139
+ dirs.push(project.directory);
140
+ });
141
+
142
+ it("generates README.md for browser extension project", async () => {
143
+ expect(await project.read("README.md")).toMatchInlineSnapshot(`
144
+ "# my-extension
145
+
146
+ A browser extension using Ember created with \`ember.nvp\`.
147
+
148
+ ## Getting Started
149
+
150
+ ### Prerequisites
151
+
152
+ - Node.js >= 24
153
+ - pnpm
154
+
155
+ ### Development
156
+
157
+ To start development:
158
+
159
+ ~~~sh
160
+ pnpm dev
161
+ ~~~
162
+
163
+ or
164
+
165
+ ~~~sh
166
+ pnpm start
167
+ ~~~
168
+
169
+ ### Building
170
+
171
+ To build the library:
172
+
173
+ ~~~sh
174
+ pnpm build
175
+ ~~~
176
+
177
+ or
178
+
179
+ ~~~sh
180
+ pnpm build:watch
181
+ ~~~
182
+
183
+ ## Features & Tooling
184
+
185
+ ### Prettier
186
+
187
+ Code formatting is managed with [Prettier](https://prettier.io/).
188
+
189
+ - \`pnpm format\` - Format code
190
+ - \`pnpm lint:prettier\` - Check code formatting
191
+ "
192
+ `);
193
+ });
194
+ });
195
+ });
@@ -19,6 +19,7 @@ describe("discoverLayers", () => {
19
19
  "prettier",
20
20
  "publint",
21
21
  "qunit",
22
+ "readme",
22
23
  "renovate",
23
24
  "typescript",
24
25
  "vitest",
@@ -0,0 +1,211 @@
1
+ import { describe, it, beforeAll, afterAll, expect } from "vitest";
2
+ import { generate, listFiles } from "#test-helpers";
3
+ import { execa } from "execa";
4
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
5
+ import { dirname, join } from "node:path";
6
+
7
+ import type { Project } from "ember.nvp";
8
+
9
+ /**
10
+ * Integration test for building a library that uses ember-scoped-css through
11
+ * `@nullvoxpopuli/ember-rolldown` (tsdown):
12
+ *
13
+ * - the `scopedCSS()` unplugin (ember-scoped-css/rollup) resolves and rewrites
14
+ * the `?scoped=` CSS requests the template transform injects
15
+ * - the template transform + babel plugin ride along via `ember()`'s
16
+ * `babel.templateTransforms` / `babel.plugins` options
17
+ * - `@tsdown/css` with `css.inject` bundles the scoped CSS into
18
+ * `dist/style.css` and keeps the import in `dist/index.js`, so consuming
19
+ * apps load the styles through the module graph
20
+ *
21
+ * Covers all three authoring modes: co-located `.css`, inline
22
+ * `<style scoped>`, and the `scopedClass` pseudo-helper in module code.
23
+ */
24
+
25
+ const source = {
26
+ "tsdown.config.js": `import { defineConfig } from "tsdown";
27
+ import { ember } from "@nullvoxpopuli/ember-rolldown";
28
+ import { scopedCSS } from "ember-scoped-css/rollup";
29
+ import { scopedCSS as scopedCssBabel } from "ember-scoped-css/babel";
30
+
31
+ export default defineConfig({
32
+ entry: ["./src/index.ts"],
33
+ css: { inject: true },
34
+ plugins: [
35
+ ember({
36
+ babel: {
37
+ plugins: [scopedCssBabel()],
38
+ templateTransforms: [scopedCssBabel.template({})],
39
+ },
40
+ }),
41
+ scopedCSS(),
42
+ ],
43
+ });
44
+ `,
45
+ "src/index.ts": `export { Badge, type BadgeSignature } from "./components/badge.gts";
46
+ export { Banner, type BannerSignature } from "./components/banner.gts";
47
+ export { Chip, type ChipSignature, chipClass } from "./components/chip.gts";
48
+ `,
49
+ // co-located CSS
50
+ "src/components/badge.gts": `import type { TOC } from "@ember/component/template-only";
51
+
52
+ export interface BadgeSignature {
53
+ Element: HTMLSpanElement;
54
+ Blocks: {
55
+ default: [];
56
+ };
57
+ }
58
+
59
+ export const Badge: TOC<BadgeSignature> = <template>
60
+ <span class="badge" ...attributes>{{yield}}</span>
61
+ </template>;
62
+ `,
63
+ "src/components/badge.css": `.badge {
64
+ border-radius: 0.25rem;
65
+ padding: 0.25rem 0.5rem;
66
+ }
67
+ `,
68
+ // inline <style scoped>
69
+ "src/components/banner.gts": `import type { TOC } from "@ember/component/template-only";
70
+
71
+ export interface BannerSignature {
72
+ Element: HTMLDivElement;
73
+ Blocks: {
74
+ default: [];
75
+ };
76
+ }
77
+
78
+ export const Banner: TOC<BannerSignature> = <template>
79
+ <style scoped>
80
+ .banner {
81
+ border: 1px solid;
82
+ }
83
+ </style>
84
+ <div class="banner" ...attributes>{{yield}}</div>
85
+ </template>;
86
+ `,
87
+ // scopedClass pseudo-helper in module code
88
+ "src/components/chip.gts": `import type { TOC } from "@ember/component/template-only";
89
+ import { scopedClass } from "ember-scoped-css";
90
+
91
+ export const chipClass: string = scopedClass("chip");
92
+
93
+ export interface ChipSignature {
94
+ Element: HTMLSpanElement;
95
+ }
96
+
97
+ export const Chip: TOC<ChipSignature> = <template>
98
+ <span class="chip" ...attributes></span>
99
+ </template>;
100
+ `,
101
+ "src/components/chip.css": `.chip {
102
+ display: inline-flex;
103
+ }
104
+ `,
105
+ };
106
+
107
+ async function writeSource(project: Project) {
108
+ for (let [filePath, contents] of Object.entries(source)) {
109
+ let target = join(project.directory, filePath);
110
+
111
+ await mkdir(dirname(target), { recursive: true });
112
+ await writeFile(target, contents);
113
+ }
114
+ }
115
+
116
+ async function addDependencies(project: Project) {
117
+ let manifestPath = join(project.directory, "package.json");
118
+ let manifest = JSON.parse(await readFile(manifestPath, "utf8"));
119
+
120
+ // @tsdown/css releases in lockstep with tsdown; both ranges track 0.22.x
121
+ manifest.devDependencies["@tsdown/css"] = "^0.22.13";
122
+ manifest.devDependencies["ember-scoped-css"] = "^3.1.0";
123
+
124
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
125
+ }
126
+
127
+ describe("library with ember-scoped-css", () => {
128
+ let project: Project;
129
+
130
+ beforeAll(async () => {
131
+ project = await generate({ type: "library", name: "scoped-lib", layers: ["typescript"] });
132
+
133
+ await writeSource(project);
134
+ await addDependencies(project);
135
+
136
+ let install = await execa("pnpm install", { cwd: project.directory, shell: true });
137
+ expect(install.exitCode).toBe(0);
138
+
139
+ let build = await execa("pnpm build", { cwd: project.directory, shell: true });
140
+ expect(build.exitCode).toBe(0);
141
+ });
142
+
143
+ afterAll(async () => {
144
+ if (process.env.CI) return;
145
+
146
+ await rm(project.directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
147
+ });
148
+
149
+ it("emits the expected files", async () => {
150
+ expect(await listFiles(join(project.directory, "dist"))).toMatchInlineSnapshot(`
151
+ [
152
+ "index.d.ts",
153
+ "index.d.ts.map",
154
+ "index.js",
155
+ "index.js.map",
156
+ "style.css",
157
+ ]
158
+ `);
159
+ });
160
+
161
+ it("scopes classes in the precompiled templates and keeps the CSS import", async () => {
162
+ let output = await project.read("dist/index.js");
163
+
164
+ expect(output).toMatchInlineSnapshot(`
165
+ "import './style.css';
166
+ import { precompileTemplate } from "@ember/template-compilation";
167
+ import { setComponentTemplate } from "@ember/component";
168
+ import templateOnly from "@ember/component/template-only";
169
+ //#region src/components/badge.ts
170
+ const Badge = setComponentTemplate(precompileTemplate("<span class=\\"badge_e1025e732\\" ...attributes>{{yield}}</span>", { strictMode: true }), templateOnly());
171
+ //#endregion
172
+ //#region src/components/banner.ts
173
+ const Banner = setComponentTemplate(precompileTemplate("\\n<div class=\\"banner_e88952290\\" ...attributes>{{yield}}</div>", { strictMode: true }), templateOnly());
174
+ //#endregion
175
+ //#region src/components/chip.ts
176
+ const chipClass = "chip_ec34dd962";
177
+ const Chip = setComponentTemplate(precompileTemplate("<span class=\\"chip_ec34dd962\\" ...attributes></span>", { strictMode: true }), templateOnly());
178
+ //#endregion
179
+ export { Badge, Banner, Chip, chipClass };
180
+
181
+ //# sourceMappingURL=index.js.map"
182
+ `);
183
+
184
+ // the scopedClass import is compiled away; nothing of ember-scoped-css
185
+ // remains at runtime
186
+ expect(output).not.toContain("ember-scoped-css");
187
+ });
188
+
189
+ it("rewrites and bundles all three CSS sources", async () => {
190
+ expect(await project.read("dist/style.css")).toMatchInlineSnapshot(`
191
+ ".badge_e1025e732 {
192
+ border-radius: .25rem;
193
+ padding: .25rem .5rem;
194
+ }
195
+ .banner_e88952290 {
196
+ border: 1px solid;
197
+ }
198
+ .chip_ec34dd962 {
199
+ display: inline-flex;
200
+ }
201
+ "
202
+ `);
203
+ });
204
+
205
+ it("emits declarations untouched by scoping", async () => {
206
+ let declarations = await project.read("dist/index.d.ts");
207
+
208
+ expect(declarations).toContain("declare const Badge: TOC<BadgeSignature>");
209
+ expect(declarations).not.toContain("badge_");
210
+ });
211
+ });