create-koppajs 1.0.1 → 1.2.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 (80) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +166 -131
  3. package/bin/create-koppajs.js +346 -175
  4. package/package.json +55 -34
  5. package/template/AI_CONSTITUTION.md +50 -0
  6. package/template/ARCHITECTURE.md +86 -0
  7. package/template/CHANGELOG.md +34 -0
  8. package/template/CONTRIBUTING.md +92 -0
  9. package/template/DECISION_HIERARCHY.md +32 -0
  10. package/template/DEVELOPMENT_RULES.md +57 -0
  11. package/template/LICENSE +1 -1
  12. package/template/README.md +241 -49
  13. package/template/RELEASE.md +230 -0
  14. package/template/ROADMAP.md +34 -0
  15. package/template/TESTING_STRATEGY.md +93 -0
  16. package/template/_editorconfig +12 -0
  17. package/template/_gitattributes +1 -0
  18. package/template/_github/instructions/ai-workflow.md +33 -0
  19. package/template/_github/workflows/ci.yml +38 -0
  20. package/template/_github/workflows/release.yml +58 -0
  21. package/template/_gitignore +5 -0
  22. package/template/_husky/commit-msg +8 -0
  23. package/template/_husky/pre-commit +1 -0
  24. package/template/_npmrc +1 -0
  25. package/template/_prettierignore +7 -0
  26. package/template/commitlint.config.mjs +6 -0
  27. package/template/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  28. package/template/docs/adr/0002-adopt-a-living-meta-layer.md +30 -0
  29. package/template/docs/adr/0003-normalize-kpa-plugin-output.md +24 -0
  30. package/template/docs/adr/0004-adopt-an-automated-quality-baseline.md +31 -0
  31. package/template/docs/adr/0005-adopt-a-tag-driven-release-baseline.md +45 -0
  32. package/template/docs/adr/0006-adopt-commit-message-conventions.md +39 -0
  33. package/template/docs/adr/README.md +37 -0
  34. package/template/docs/adr/TEMPLATE.md +18 -0
  35. package/template/docs/architecture/module-boundaries.md +48 -0
  36. package/template/docs/meta/maintenance.md +40 -0
  37. package/template/docs/quality/quality-gates.md +39 -0
  38. package/template/docs/specs/README.md +36 -0
  39. package/template/docs/specs/TEMPLATE.md +34 -0
  40. package/template/docs/specs/app-bootstrap.md +46 -0
  41. package/template/docs/specs/counter-component.md +48 -0
  42. package/template/docs/specs/quality-workflow.md +62 -0
  43. package/template/eslint.config.mjs +54 -0
  44. package/template/package.json +57 -6
  45. package/template/playwright.config.ts +31 -0
  46. package/template/pnpm-lock.yaml +3784 -0
  47. package/template/prettier.config.mjs +6 -0
  48. package/template/src/app-view.kpa +35 -36
  49. package/template/src/counter-component.kpa +87 -87
  50. package/template/src/style.css +5 -5
  51. package/template/tests/e2e/app.spec.ts +18 -0
  52. package/template/tests/integration/main-bootstrap.test.ts +33 -0
  53. package/template/tests/unit/normalize-kpa-module-export.test.ts +46 -0
  54. package/template/tsconfig.json +13 -2
  55. package/template/vite.config.d.mts +7 -0
  56. package/template/vite.config.mjs +39 -4
  57. package/template/vitest.config.mjs +19 -0
  58. package/template-overlays/router/ARCHITECTURE.md +86 -0
  59. package/template-overlays/router/CHANGELOG.md +44 -0
  60. package/template-overlays/router/DEVELOPMENT_RULES.md +57 -0
  61. package/template-overlays/router/README.md +243 -0
  62. package/template-overlays/router/ROADMAP.md +34 -0
  63. package/template-overlays/router/TESTING_STRATEGY.md +67 -0
  64. package/template-overlays/router/docs/adr/0001-keep-the-starter-minimal.md +32 -0
  65. package/template-overlays/router/docs/architecture/module-boundaries.md +39 -0
  66. package/template-overlays/router/docs/meta/maintenance.md +38 -0
  67. package/template-overlays/router/docs/specs/README.md +19 -0
  68. package/template-overlays/router/docs/specs/app-bootstrap.md +42 -0
  69. package/template-overlays/router/docs/specs/router-navigation.md +41 -0
  70. package/template-overlays/router/index.html +14 -0
  71. package/template-overlays/router/package.json +74 -0
  72. package/template-overlays/router/pnpm-lock.yaml +3793 -0
  73. package/template-overlays/router/src/app-view.kpa +128 -0
  74. package/template-overlays/router/src/home-page.kpa +100 -0
  75. package/template-overlays/router/src/main.ts +89 -0
  76. package/template-overlays/router/src/not-found-page.kpa +69 -0
  77. package/template-overlays/router/src/router-page.kpa +102 -0
  78. package/template-overlays/router/src/style.css +51 -0
  79. package/template-overlays/router/tests/e2e/app.spec.ts +38 -0
  80. package/template-overlays/router/tests/integration/main-bootstrap.test.ts +150 -0
@@ -0,0 +1,150 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ type FakeLink = {
4
+ getAttribute: (name: string) => string | null;
5
+ setAttribute: (name: string, value: string) => void;
6
+ };
7
+
8
+ type RouterCtorOptions = {
9
+ outlet: HTMLElement;
10
+ root: Document;
11
+ routes: Array<{
12
+ path: string;
13
+ componentTag: string;
14
+ }>;
15
+ };
16
+
17
+ const outlet = {} as HTMLElement;
18
+
19
+ function createFakeLink(initialRoute: string, initialHref: string): FakeLink {
20
+ const attributes = new Map([
21
+ ["data-route", initialRoute],
22
+ ["href", initialHref],
23
+ ]);
24
+
25
+ return {
26
+ getAttribute(name: string) {
27
+ return attributes.get(name) ?? null;
28
+ },
29
+ setAttribute(name: string, value: string) {
30
+ attributes.set(name, value);
31
+ },
32
+ };
33
+ }
34
+
35
+ const { core, take, routerCtor, routerHrefFor, routerInit } = vi.hoisted(() => {
36
+ const take = vi.fn();
37
+ const core = Object.assign(vi.fn(), { take });
38
+ const routerInit = vi.fn();
39
+ const routerHrefFor = vi.fn((target: string) => target);
40
+ const routerCtor = vi.fn(function RouterCtor(options: RouterCtorOptions) {
41
+ void options;
42
+
43
+ return {
44
+ hrefFor: routerHrefFor,
45
+ init: routerInit,
46
+ };
47
+ });
48
+
49
+ return {
50
+ core,
51
+ take,
52
+ routerCtor,
53
+ routerHrefFor,
54
+ routerInit,
55
+ };
56
+ });
57
+
58
+ vi.mock("@koppajs/koppajs-core", () => ({
59
+ Core: core,
60
+ }));
61
+
62
+ vi.mock("@koppajs/koppajs-router", () => ({
63
+ KOPPAJS_ROUTE_CHANGE_EVENT: "koppajs-route-change",
64
+ KoppajsRouter: routerCtor,
65
+ }));
66
+
67
+ describe("main bootstrap", () => {
68
+ const querySelector = vi.fn((selector: string) =>
69
+ selector === "#app-outlet" ? outlet : null,
70
+ );
71
+ const querySelectorAll = vi.fn(() => [] as FakeLink[]);
72
+ const addEventListener = vi.fn();
73
+ const requestAnimationFrame = vi.fn();
74
+ let links: FakeLink[] = [];
75
+
76
+ beforeEach(() => {
77
+ links = [createFakeLink("/", "/"), createFakeLink("/router", "/router")];
78
+
79
+ querySelector.mockImplementation((selector: string) =>
80
+ selector === "#app-outlet" ? outlet : null,
81
+ );
82
+ querySelectorAll.mockImplementation(() => links);
83
+ addEventListener.mockReset();
84
+ requestAnimationFrame.mockReset();
85
+
86
+ vi.stubGlobal("document", {
87
+ querySelector,
88
+ querySelectorAll,
89
+ } as unknown as Document);
90
+ vi.stubGlobal("window", {
91
+ addEventListener,
92
+ requestAnimationFrame,
93
+ } as unknown as Window);
94
+ });
95
+
96
+ afterEach(() => {
97
+ core.mockClear();
98
+ take.mockClear();
99
+ routerCtor.mockClear();
100
+ routerHrefFor.mockClear();
101
+ routerInit.mockClear();
102
+ vi.resetModules();
103
+ vi.unstubAllGlobals();
104
+ });
105
+
106
+ it("registers app components, boots core, and starts the router once", async () => {
107
+ await import("../../src/main");
108
+
109
+ expect(take).toHaveBeenCalledTimes(5);
110
+ expect(take).toHaveBeenNthCalledWith(1, expect.anything(), "app-view");
111
+ expect(take).toHaveBeenNthCalledWith(
112
+ 2,
113
+ expect.anything(),
114
+ "counter-component",
115
+ );
116
+ expect(take).toHaveBeenNthCalledWith(3, expect.anything(), "home-page");
117
+ expect(take).toHaveBeenNthCalledWith(4, expect.anything(), "router-page");
118
+ expect(take).toHaveBeenNthCalledWith(
119
+ 5,
120
+ expect.anything(),
121
+ "not-found-page",
122
+ );
123
+ expect(core).toHaveBeenCalledTimes(1);
124
+
125
+ expect(routerCtor).toHaveBeenCalledTimes(1);
126
+ const [routerOptions] = routerCtor.mock.calls[0] ?? [];
127
+
128
+ expect(routerOptions).toBeDefined();
129
+ expect(routerOptions?.outlet).toBe(outlet);
130
+ expect(routerOptions?.root).toBe(globalThis.document);
131
+ expect(
132
+ routerOptions?.routes.map((route) => ({
133
+ path: route.path,
134
+ componentTag: route.componentTag,
135
+ })),
136
+ ).toEqual(
137
+ expect.arrayContaining([
138
+ { path: "/", componentTag: "home-page" },
139
+ { path: "/router", componentTag: "router-page" },
140
+ { path: "*", componentTag: "not-found-page" },
141
+ ]),
142
+ );
143
+ expect(addEventListener).toHaveBeenCalledTimes(1);
144
+ expect(routerInit).toHaveBeenCalledTimes(1);
145
+ expect(routerHrefFor).toHaveBeenNthCalledWith(1, "/");
146
+ expect(routerHrefFor).toHaveBeenNthCalledWith(2, "/router");
147
+ expect(links[0]?.getAttribute("href")).toBe("/");
148
+ expect(links[1]?.getAttribute("href")).toBe("/router");
149
+ });
150
+ });