@jskit-ai/ui-generator 0.1.14 → 0.1.16

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.
@@ -1,307 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
- import path from "node:path";
4
- import { tmpdir } from "node:os";
5
- import test from "node:test";
6
- import { runGeneratorSubcommand } from "../src/server/subcommands/container.js";
7
-
8
- async function withTempApp(run) {
9
- const appRoot = await mkdtemp(path.join(tmpdir(), "ui-generator-container-"));
10
- try {
11
- return await run(appRoot);
12
- } finally {
13
- await rm(appRoot, { recursive: true, force: true });
14
- }
15
- }
16
-
17
- async function writeAppFixture(appRoot) {
18
- await mkdir(path.join(appRoot, "config"), { recursive: true });
19
- await mkdir(path.join(appRoot, "src", "components"), { recursive: true });
20
- await mkdir(path.join(appRoot, "src"), { recursive: true });
21
- await mkdir(path.join(appRoot, "packages", "main", "src", "client", "providers"), { recursive: true });
22
-
23
- await writeFile(
24
- path.join(appRoot, "config", "public.js"),
25
- `export const config = {
26
- surfaceDefinitions: {
27
- app: { id: "app", pagesRoot: "", enabled: true, requiresAuth: false, requiresWorkspace: false },
28
- admin: { id: "admin", pagesRoot: "w/[workspaceSlug]/admin", enabled: true, requiresAuth: true, requiresWorkspace: true }
29
- }
30
- };
31
- `,
32
- "utf8"
33
- );
34
- await writeFile(
35
- path.join(appRoot, "src", "components", "ShellLayout.vue"),
36
- `<template>
37
- <div>
38
- <ShellOutlet host="shell-layout" position="primary-menu" default />
39
- <ShellOutlet host="shell-layout" position="top-right" />
40
- </div>
41
- </template>
42
- `,
43
- "utf8"
44
- );
45
- await writeFile(
46
- path.join(appRoot, "src", "placement.js"),
47
- `function addPlacement() {}
48
-
49
- export { addPlacement };
50
- export default function getPlacements() {
51
- return [];
52
- }
53
- `,
54
- "utf8"
55
- );
56
- await writeFile(
57
- path.join(appRoot, "packages", "main", "src", "client", "providers", "MainClientProvider.js"),
58
- `const mainClientComponents = [];
59
-
60
- function registerMainClientComponent(componentToken, resolveComponent) {
61
- const token = String(componentToken || "").trim();
62
- if (!token || typeof resolveComponent !== "function") {
63
- return;
64
- }
65
- mainClientComponents.push(
66
- Object.freeze({
67
- token,
68
- resolveComponent
69
- })
70
- );
71
- }
72
-
73
- class MainClientProvider {}
74
-
75
- export { MainClientProvider, registerMainClientComponent };
76
- `,
77
- "utf8"
78
- );
79
- }
80
-
81
- test("ui-generator container subcommand creates parent route container with ShellOutlet and RouterView", async () => {
82
- await withTempApp(async (appRoot) => {
83
- await writeAppFixture(appRoot);
84
-
85
- const result = await runGeneratorSubcommand({
86
- appRoot,
87
- subcommand: "container",
88
- options: {
89
- name: "Practice",
90
- surface: "admin"
91
- }
92
- });
93
-
94
- assert.deepEqual(result.touchedFiles, [
95
- "packages/main/src/client/providers/MainClientProvider.js",
96
- "src/components/SectionContainerShell.vue",
97
- "src/components/TabLinkItem.vue",
98
- "src/pages/w/[workspaceSlug]/admin/practice.vue"
99
- ]);
100
-
101
- const containerSource = await readFile(
102
- path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin", "practice.vue"),
103
- "utf8"
104
- );
105
- assert.match(containerSource, /<SectionContainerShell/);
106
- assert.match(containerSource, /host="practice"/);
107
- assert.match(containerSource, /<RouterView \/>/);
108
- assert.match(containerSource, /"surface": "admin"/);
109
- assert.match(containerSource, /"placements": \{/);
110
- assert.match(containerSource, /"outlets": \[/);
111
- assert.match(containerSource, /"host": "practice"/);
112
- assert.match(containerSource, /"position": "sub-pages"/);
113
-
114
- const sectionShellSource = await readFile(path.join(appRoot, "src", "components", "SectionContainerShell.vue"), "utf8");
115
- assert.match(sectionShellSource, /<ShellOutlet :host="props\.host" :position="props\.position" \/>/);
116
-
117
- const tabLinkSource = await readFile(path.join(appRoot, "src", "components", "TabLinkItem.vue"), "utf8");
118
- assert.match(tabLinkSource, /@jskit-ai\/users-web\/client\/support\/menuLinkTarget/);
119
- assert.match(tabLinkSource, /resolveMenuLinkTarget/);
120
- assert.match(tabLinkSource, /normalizeMenuLinkPathname/);
121
- assert.match(tabLinkSource, /class="tab-link-item text-none"/);
122
-
123
- const providerSource = await readFile(
124
- path.join(appRoot, "packages", "main", "src", "client", "providers", "MainClientProvider.js"),
125
- "utf8"
126
- );
127
- assert.match(
128
- providerSource,
129
- /registerMainClientComponent\("local\.main\.ui\.tab-link-item", \(\) => TabLinkItem\);/
130
- );
131
-
132
- const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
133
- assert.doesNotMatch(placementSource, /id: "ui-generator\.container\.practice\.menu"/);
134
- });
135
- });
136
-
137
- test("ui-generator container preserves bracket route params in directory-prefix", async () => {
138
- await withTempApp(async (appRoot) => {
139
- await writeAppFixture(appRoot);
140
-
141
- const result = await runGeneratorSubcommand({
142
- appRoot,
143
- subcommand: "container",
144
- options: {
145
- name: "Contact Tools",
146
- surface: "admin",
147
- "directory-prefix": "contacts/[contactId]"
148
- }
149
- });
150
-
151
- assert.deepEqual(result.touchedFiles, [
152
- "packages/main/src/client/providers/MainClientProvider.js",
153
- "src/components/SectionContainerShell.vue",
154
- "src/components/TabLinkItem.vue",
155
- "src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/contact-tools.vue"
156
- ]);
157
-
158
- const containerSource = await readFile(
159
- path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin", "contacts", "[contactId]", "contact-tools.vue"),
160
- "utf8"
161
- );
162
- assert.match(containerSource, /host="contact-tools"/);
163
- assert.match(containerSource, /"host": "contact-tools"/);
164
- assert.match(containerSource, /"position": "sub-pages"/);
165
-
166
- const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
167
- assert.doesNotMatch(placementSource, /jskit:ui-generator\.container\.menu:admin:contacts\/\[contactId\]\/contact-tools/);
168
- });
169
- });
170
-
171
- test("ui-generator container supports explicit route-path for dynamic container files", async () => {
172
- await withTempApp(async (appRoot) => {
173
- await writeAppFixture(appRoot);
174
-
175
- const result = await runGeneratorSubcommand({
176
- appRoot,
177
- subcommand: "container",
178
- options: {
179
- name: "Contact",
180
- surface: "admin",
181
- "directory-prefix": "contacts",
182
- "route-path": "[contactId]"
183
- }
184
- });
185
-
186
- assert.deepEqual(result.touchedFiles, [
187
- "packages/main/src/client/providers/MainClientProvider.js",
188
- "src/components/SectionContainerShell.vue",
189
- "src/components/TabLinkItem.vue",
190
- "src/pages/w/[workspaceSlug]/admin/contacts/[contactId].vue"
191
- ]);
192
-
193
- const containerSource = await readFile(
194
- path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin", "contacts", "[contactId].vue"),
195
- "utf8"
196
- );
197
- assert.match(containerSource, /host="contact"/);
198
- assert.match(containerSource, /<RouterView \/>/);
199
- });
200
- });
201
-
202
- test("ui-generator container rejects invalid explicit route-path values", async () => {
203
- await withTempApp(async (appRoot) => {
204
- await writeAppFixture(appRoot);
205
-
206
- await assert.rejects(
207
- runGeneratorSubcommand({
208
- appRoot,
209
- subcommand: "container",
210
- options: {
211
- name: "Contact",
212
- surface: "admin",
213
- "route-path": "---"
214
- }
215
- }),
216
- /ui-generator container requires a valid --route-path when provided\./
217
- );
218
- });
219
- });
220
-
221
- test("ui-generator container appends menu placement only when --placement is provided", async () => {
222
- await withTempApp(async (appRoot) => {
223
- await writeAppFixture(appRoot);
224
-
225
- const result = await runGeneratorSubcommand({
226
- appRoot,
227
- subcommand: "container",
228
- options: {
229
- name: "Practice",
230
- surface: "admin",
231
- placement: "shell-layout:top-right"
232
- }
233
- });
234
-
235
- assert.deepEqual(result.touchedFiles, [
236
- "packages/main/src/client/providers/MainClientProvider.js",
237
- "src/components/SectionContainerShell.vue",
238
- "src/components/TabLinkItem.vue",
239
- "src/pages/w/[workspaceSlug]/admin/practice.vue",
240
- "src/placement.js"
241
- ]);
242
-
243
- const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
244
- assert.match(placementSource, /id: "ui-generator\.container\.practice\.menu"/);
245
- assert.match(placementSource, /host: "shell-layout"/);
246
- assert.match(placementSource, /position: "top-right"/);
247
- assert.match(placementSource, /workspaceSuffix: "\/practice"/);
248
- });
249
- });
250
-
251
- test("ui-generator container backfills route meta placements on existing container page", async () => {
252
- await withTempApp(async (appRoot) => {
253
- await writeAppFixture(appRoot);
254
-
255
- await mkdir(path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin"), { recursive: true });
256
- await writeFile(
257
- path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin", "practice.vue"),
258
- `<script setup>
259
- import { RouterView } from "vue-router";
260
- import SectionContainerShell from "/src/components/SectionContainerShell.vue";
261
- </script>
262
-
263
- <template>
264
- <SectionContainerShell
265
- title="Practice"
266
- subtitle="Manage practice modules."
267
- host="practice"
268
- position="sub-pages"
269
- >
270
- <RouterView />
271
- </SectionContainerShell>
272
- </template>
273
-
274
- <route lang="json">
275
- {
276
- "meta": {
277
- "jskit": {
278
- "surface": "admin"
279
- }
280
- }
281
- }
282
- </route>
283
- `,
284
- "utf8"
285
- );
286
-
287
- const result = await runGeneratorSubcommand({
288
- appRoot,
289
- subcommand: "container",
290
- options: {
291
- name: "Practice",
292
- surface: "admin"
293
- }
294
- });
295
-
296
- assert.match(result.touchedFiles.join("\n"), /src\/pages\/w\/\[workspaceSlug\]\/admin\/practice\.vue/);
297
-
298
- const containerSource = await readFile(
299
- path.join(appRoot, "src", "pages", "w", "[workspaceSlug]", "admin", "practice.vue"),
300
- "utf8"
301
- );
302
- assert.match(containerSource, /"placements": \{/);
303
- assert.match(containerSource, /"outlets": \[/);
304
- assert.match(containerSource, /"host": "practice"/);
305
- assert.match(containerSource, /"position": "sub-pages"/);
306
- });
307
- });