@norskvideo/ctl-dev-kit 0.2.78 → 0.2.79
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.
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// directory below is not a free choice.
|
|
14
14
|
import type { GeneratedFile, ShapeContext } from "../create-product.ts";
|
|
15
15
|
import { EXAMPLE_ID } from "./components.ts";
|
|
16
|
+
import { TEST_SETUP_TS, UI_TEST_DEV_DEPENDENCIES, VITEST_CONFIG_TS } from "./ui-tests.ts";
|
|
16
17
|
|
|
17
18
|
/** The tar ships workflow.yml at its root, so Studio knows the workflow by this name. */
|
|
18
19
|
export const WORKFLOW_NAME = "workflow";
|
|
@@ -39,6 +40,7 @@ function packageJson(ctx: ShapeContext): string {
|
|
|
39
40
|
dev: "bunx vite build --watch",
|
|
40
41
|
clean: `rm -rf ../dashboards/${WORKFLOW_NAME}`,
|
|
41
42
|
typecheck: "bunx tsc --noEmit",
|
|
43
|
+
test: "vitest run",
|
|
42
44
|
},
|
|
43
45
|
dependencies: {
|
|
44
46
|
react: "^19.0.0",
|
|
@@ -51,6 +53,7 @@ function packageJson(ctx: ShapeContext): string {
|
|
|
51
53
|
"@vitejs/plugin-react": "^6.0.1",
|
|
52
54
|
typescript: "^5.7.2",
|
|
53
55
|
vite: "^8.0.0",
|
|
56
|
+
...UI_TEST_DEV_DEPENDENCIES,
|
|
54
57
|
},
|
|
55
58
|
},
|
|
56
59
|
null,
|
|
@@ -209,6 +212,38 @@ export default function App({ env }: { env: Env }) {
|
|
|
209
212
|
}
|
|
210
213
|
`;
|
|
211
214
|
|
|
215
|
+
// A stand-in for the component's state socket: what a dashboard test drives
|
|
216
|
+
// instead of a running Studio.
|
|
217
|
+
const APP_TEST_TSX = `import { act, render, screen } from "@testing-library/react";
|
|
218
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
219
|
+
import App from "./App.tsx";
|
|
220
|
+
|
|
221
|
+
class FakeSocket {
|
|
222
|
+
static last: FakeSocket | undefined;
|
|
223
|
+
onmessage: ((event: { data: string }) => void) | null = null;
|
|
224
|
+
constructor(readonly url: string) {
|
|
225
|
+
FakeSocket.last = this;
|
|
226
|
+
}
|
|
227
|
+
close() {}
|
|
228
|
+
deliver(message: unknown) {
|
|
229
|
+
this.onmessage?.({ data: JSON.stringify(message) });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
afterEach(() => {
|
|
234
|
+
vi.unstubAllGlobals();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("shows the component's state as it arrives", () => {
|
|
238
|
+
vi.stubGlobal("WebSocket", FakeSocket);
|
|
239
|
+
render(<App env={{ apiBasePath: "/api", wsBasePath: "/ws" }} />);
|
|
240
|
+
expect(screen.getByText(/waiting for the component's first state/)).toBeInTheDocument();
|
|
241
|
+
|
|
242
|
+
act(() => FakeSocket.last?.deliver({ type: "state-update", state: { ticks: 3 } }));
|
|
243
|
+
expect(screen.getByText("3 ticks")).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
`;
|
|
246
|
+
|
|
212
247
|
const MAIN_TSX = `import React from "react";
|
|
213
248
|
import ReactDOM from "react-dom/client";
|
|
214
249
|
import App from "./App.tsx";
|
|
@@ -263,9 +298,12 @@ export function dashboardFiles(ctx: ShapeContext): GeneratedFile[] {
|
|
|
263
298
|
{ path: "dashboard/package.json", content: packageJson(ctx) },
|
|
264
299
|
{ path: "dashboard/tsconfig.json", content: TSCONFIG },
|
|
265
300
|
{ path: "dashboard/vite.config.ts", content: VITE_CONFIG_TS },
|
|
301
|
+
{ path: "dashboard/vitest.config.ts", content: VITEST_CONFIG_TS },
|
|
266
302
|
{ path: "dashboard/index.html", content: indexHtml(ctx) },
|
|
267
303
|
{ path: "dashboard/src/main.tsx", content: MAIN_TSX },
|
|
268
304
|
{ path: "dashboard/src/App.tsx", content: APP_TSX },
|
|
305
|
+
{ path: "dashboard/src/App.test.tsx", content: APP_TEST_TSX },
|
|
306
|
+
{ path: "dashboard/src/test/setup.ts", content: TEST_SETUP_TS },
|
|
269
307
|
{ path: "dashboard/src/api.ts", content: API_TS },
|
|
270
308
|
{ path: "dashboard/src/index.css", content: INDEX_CSS },
|
|
271
309
|
{ path: "backend/src/lib/dashboards.ts", content: BACKEND_LIB_DASHBOARDS_TS },
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// three independent edits, and why the dev script builds dist before starting
|
|
14
14
|
// the backend that serves it.
|
|
15
15
|
import type { GeneratedFile, ShapeContext } from "../create-product.ts";
|
|
16
|
+
import { TEST_SETUP_TS, UI_TEST_DEV_DEPENDENCIES, VITEST_CONFIG_TS } from "./ui-tests.ts";
|
|
16
17
|
|
|
17
18
|
export const CONFIG_SCREEN_URL = "/configure";
|
|
18
19
|
|
|
@@ -31,6 +32,7 @@ function packageJson(ctx: ShapeContext): string {
|
|
|
31
32
|
build: "bunx vite build",
|
|
32
33
|
clean: "rm -rf dist",
|
|
33
34
|
typecheck: "bunx tsc --noEmit",
|
|
35
|
+
test: "vitest run",
|
|
34
36
|
},
|
|
35
37
|
dependencies: {
|
|
36
38
|
[`${ctx.scope}/shared`]: "workspace:*",
|
|
@@ -44,6 +46,7 @@ function packageJson(ctx: ShapeContext): string {
|
|
|
44
46
|
"@vitejs/plugin-react": "^6.0.1",
|
|
45
47
|
typescript: "^5.7.2",
|
|
46
48
|
vite: "^8.0.0",
|
|
49
|
+
...UI_TEST_DEV_DEPENDENCIES,
|
|
47
50
|
},
|
|
48
51
|
},
|
|
49
52
|
null,
|
|
@@ -141,6 +144,18 @@ export default function App() {
|
|
|
141
144
|
`;
|
|
142
145
|
}
|
|
143
146
|
|
|
147
|
+
function appTestTsx(ctx: ShapeContext): string {
|
|
148
|
+
return `import { render, screen } from "@testing-library/react";
|
|
149
|
+
import { expect, test } from "vitest";
|
|
150
|
+
import App from "./App.tsx";
|
|
151
|
+
|
|
152
|
+
test("names the product", () => {
|
|
153
|
+
render(<App />);
|
|
154
|
+
expect(screen.getByRole("heading", { name: "${ctx.name}" })).toBeInTheDocument();
|
|
155
|
+
});
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
const INDEX_CSS = `:root {
|
|
145
160
|
color-scheme: light dark;
|
|
146
161
|
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
@@ -157,9 +172,12 @@ export function frontendFiles(ctx: ShapeContext): GeneratedFile[] {
|
|
|
157
172
|
{ path: "frontend/package.json", content: packageJson(ctx) },
|
|
158
173
|
{ path: "frontend/tsconfig.json", content: TSCONFIG },
|
|
159
174
|
{ path: "frontend/vite.config.ts", content: VITE_CONFIG_TS },
|
|
175
|
+
{ path: "frontend/vitest.config.ts", content: VITEST_CONFIG_TS },
|
|
160
176
|
{ path: "frontend/index.html", content: indexHtml(ctx) },
|
|
161
177
|
{ path: "frontend/src/main.tsx", content: MAIN_TSX },
|
|
162
178
|
{ path: "frontend/src/App.tsx", content: appTsx(ctx) },
|
|
179
|
+
{ path: "frontend/src/App.test.tsx", content: appTestTsx(ctx) },
|
|
180
|
+
{ path: "frontend/src/test/setup.ts", content: TEST_SETUP_TS },
|
|
163
181
|
{ path: "frontend/src/index.css", content: INDEX_CSS },
|
|
164
182
|
];
|
|
165
183
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The test runner the two screen workspaces (frontend/, dashboard/) share:
|
|
2
|
+
// vitest under jsdom with testing-library, run by the root `test:unit`, so the
|
|
3
|
+
// product's one test command covers the screens too.
|
|
4
|
+
|
|
5
|
+
export const UI_TEST_DEV_DEPENDENCIES = {
|
|
6
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
7
|
+
"@testing-library/react": "^16.3.2",
|
|
8
|
+
jsdom: "^29.0.1",
|
|
9
|
+
vitest: "^4.1.3",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Like vite.config.ts, kept out of the tsconfig `include`: it imports the
|
|
13
|
+
// bundler.
|
|
14
|
+
export const VITEST_CONFIG_TS = `import { createRequire } from "node:module";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
|
+
import react from "@vitejs/plugin-react";
|
|
17
|
+
import { defineConfig } from "vitest/config";
|
|
18
|
+
|
|
19
|
+
// One React instance: alias to the pair react-dom loads, so testing-library
|
|
20
|
+
// cannot pull in a second copy (every hook then throws).
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
plugins: [react()],
|
|
25
|
+
resolve: {
|
|
26
|
+
alias: { react: dirname(require.resolve("react")), "react-dom": dirname(require.resolve("react-dom")) },
|
|
27
|
+
dedupe: ["react", "react-dom"],
|
|
28
|
+
},
|
|
29
|
+
test: {
|
|
30
|
+
include: ["src/**/*.test.{ts,tsx}"],
|
|
31
|
+
environment: "jsdom",
|
|
32
|
+
setupFiles: ["./src/test/setup.ts"],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
// Without vitest globals, testing-library cannot register its own cleanup, so
|
|
38
|
+
// renders would pile up across tests in one document.
|
|
39
|
+
export const TEST_SETUP_TS = `import "@testing-library/jest-dom/vitest";
|
|
40
|
+
import { cleanup } from "@testing-library/react";
|
|
41
|
+
import { afterEach } from "vitest";
|
|
42
|
+
|
|
43
|
+
afterEach(cleanup);
|
|
44
|
+
`;
|
|
@@ -158,6 +158,9 @@ shared core.
|
|
|
158
158
|
|
|
159
159
|
function claudeTail(ctx: ShapeContext): string {
|
|
160
160
|
const workspaceNotes = featureNotes(ctx);
|
|
161
|
+
const screens = (["dashboard", "frontend"] as const).filter((f) => ctx.features.has(f));
|
|
162
|
+
const screenTests =
|
|
163
|
+
screens.length === 0 ? "" : `, then the vitest suites of ${screens.map((s) => `\`${s}/\``).join(" and ")} (jsdom)`;
|
|
161
164
|
return `${workspaceNotes}## Getting norsk-ctl + the dev loop
|
|
162
165
|
|
|
163
166
|
\`norsk-ctl\` (the daemon/CLI) is **not** an npm dep — it's the released binary,
|
|
@@ -285,7 +288,7 @@ refreshed \`~/.norsk-ctl/product-templates/<name>/compose.yml\` carries the pins
|
|
|
285
288
|
Byte-snapshots (\`tests/unit/workflow-snapshot.test.ts\`) pin the emitted wire
|
|
286
289
|
bytes; \`UPDATE_SNAPSHOTS=1\` refreshes deliberately.
|
|
287
290
|
|
|
288
|
-
- \`bun run test\` / \`test:unit\` — shared/backend units + tests/unit scaffolds.
|
|
291
|
+
- \`bun run test\` / \`test:unit\` — shared/backend units + tests/unit scaffolds${screenTests}.
|
|
289
292
|
- \`bun run test:image\` — image smoke test (needs Docker + \`bun run build:image\`):
|
|
290
293
|
runs the built container and asserts the surface \`product add\` probes.
|
|
291
294
|
- \`bun run check:drift\` — the shared-conventions gate; CI runs it plus
|
|
@@ -322,7 +325,11 @@ function rootPackageJson(ctx: ShapeContext): string {
|
|
|
322
325
|
lint: "biome check .",
|
|
323
326
|
"lint:fix": "biome check --write .",
|
|
324
327
|
test: "bun run test:unit",
|
|
325
|
-
"test:unit":
|
|
328
|
+
"test:unit": [
|
|
329
|
+
`bun test shared/src/ backend/src/ ${hasComponents ? "components/src/ " : ""}tests/unit/`,
|
|
330
|
+
...(hasDashboard ? ["bun run --cwd dashboard test"] : []),
|
|
331
|
+
...(hasFrontend ? ["bun run --cwd frontend test"] : []),
|
|
332
|
+
].join(" && "),
|
|
326
333
|
"test:image": "bun test tests/image/",
|
|
327
334
|
"test:smoke": "NORSK_CTL_ENV=test bun test tests/smoke.spec.ts",
|
|
328
335
|
"check:drift": "bun run node_modules/@norskvideo/ctl-dev-kit/conventions/check-drift.ts",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-dev-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.79",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./create-product": "./create-product/create-product.ts",
|
|
@@ -44,16 +44,22 @@
|
|
|
44
44
|
"@norskvideo/norsk-studio-builder": "1.30.0-2026-10-12-34c09bda",
|
|
45
45
|
"@norskvideo/norsk-studio-built-ins": "1.30.0-2026-10-12-34c09bda",
|
|
46
46
|
"@playwright/test": "^1.59.0",
|
|
47
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
48
|
+
"@testing-library/react": "^16.3.2",
|
|
47
49
|
"@types/bun": "latest",
|
|
48
50
|
"@types/cors": "^2.8.17",
|
|
49
51
|
"@types/express": "5",
|
|
50
52
|
"@types/node": "^22.10.0",
|
|
51
53
|
"@types/react": "^19.0.10",
|
|
52
54
|
"@types/react-dom": "^19.0.3",
|
|
55
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
53
56
|
"cors": "^2.8.5",
|
|
54
57
|
"express": "5",
|
|
58
|
+
"jsdom": "^29.0.1",
|
|
55
59
|
"react": "^19.0.0",
|
|
56
60
|
"react-dom": "^19.0.0",
|
|
61
|
+
"vite": "^8.0.0",
|
|
62
|
+
"vitest": "^4.1.3",
|
|
57
63
|
"yaml": "^2.8.0",
|
|
58
64
|
"zod": "4.4.3"
|
|
59
65
|
},
|