@jskit-ai/ui-generator 0.1.16 → 0.1.18
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.
- package/package.descriptor.mjs +19 -16
- package/package.json +3 -2
- package/src/server/buildTemplateContext.js +2 -2
- package/src/server/subcommands/addSubpages.js +4 -5
- package/src/server/subcommands/element.js +1 -2
- package/src/server/subcommands/outlet.js +15 -21
- package/src/server/subcommands/page.js +3 -3
- package/src/server/subcommands/pageSupport.js +57 -114
- package/src/server/subcommands/support.js +5 -31
- package/test/addSubpagesSubcommand.test.js +40 -31
- package/test/buildTemplateContext.test.js +82 -28
- package/test/elementSubcommand.test.js +11 -18
- package/test/outletSubcommand.test.js +18 -18
- package/test/pageSubcommand.test.js +66 -14
- package/README.md +0 -214
|
@@ -3,6 +3,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import test from "node:test";
|
|
6
|
+
import { readLocalLinkItemComponentSource } from "@jskit-ai/shell-web/server/support/localLinkItemScaffolds";
|
|
6
7
|
import { runGeneratorSubcommand } from "../src/server/subcommands/addSubpages.js";
|
|
7
8
|
|
|
8
9
|
async function withTempApp(run) {
|
|
@@ -45,17 +46,8 @@ export default function getPlacements() {
|
|
|
45
46
|
path.join(appRoot, "packages", "main", "src", "client", "providers", "MainClientProvider.js"),
|
|
46
47
|
`const mainClientComponents = [];
|
|
47
48
|
|
|
48
|
-
function registerMainClientComponent(
|
|
49
|
-
|
|
50
|
-
if (!token || typeof resolveComponent !== "function") {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
mainClientComponents.push(
|
|
54
|
-
Object.freeze({
|
|
55
|
-
token,
|
|
56
|
-
resolveComponent
|
|
57
|
-
})
|
|
58
|
-
);
|
|
49
|
+
function registerMainClientComponent(token, resolveComponent) {
|
|
50
|
+
mainClientComponents.push({ token, resolveComponent });
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
class MainClientProvider {}
|
|
@@ -105,14 +97,21 @@ test("ui-generator add-subpages derives the default target from an index-route p
|
|
|
105
97
|
|
|
106
98
|
assert.deepEqual(result.touchedFiles, [
|
|
107
99
|
"packages/main/src/client/providers/MainClientProvider.js",
|
|
100
|
+
"src/components/menus/TabLinkItem.vue",
|
|
108
101
|
"src/components/SectionContainerShell.vue",
|
|
109
|
-
"src/components/TabLinkItem.vue",
|
|
110
102
|
`src/pages/${targetFile}`
|
|
111
103
|
]);
|
|
112
104
|
|
|
113
105
|
const pageSource = await readPageFile(appRoot, targetFile);
|
|
114
|
-
assert.match(
|
|
106
|
+
assert.match(
|
|
107
|
+
pageSource,
|
|
108
|
+
/<ShellOutlet target="practice:sub-pages" default-link-component-token="local\.main\.ui\.tab-link-item" \/>/
|
|
109
|
+
);
|
|
115
110
|
assert.match(pageSource, /<RouterView \/>/);
|
|
111
|
+
assert.equal(
|
|
112
|
+
await readFile(path.join(appRoot, "src", "components", "menus", "TabLinkItem.vue"), "utf8"),
|
|
113
|
+
await readLocalLinkItemComponentSource("local.main.ui.tab-link-item")
|
|
114
|
+
);
|
|
116
115
|
});
|
|
117
116
|
});
|
|
118
117
|
|
|
@@ -131,7 +130,10 @@ test("ui-generator add-subpages derives the default target from a dynamic file-r
|
|
|
131
130
|
});
|
|
132
131
|
|
|
133
132
|
const pageSource = await readPageFile(appRoot, targetFile);
|
|
134
|
-
assert.match(
|
|
133
|
+
assert.match(
|
|
134
|
+
pageSource,
|
|
135
|
+
/<ShellOutlet target="contacts-contact-id:sub-pages" default-link-component-token="local\.main\.ui\.tab-link-item" \/>/
|
|
136
|
+
);
|
|
135
137
|
});
|
|
136
138
|
});
|
|
137
139
|
|
|
@@ -150,28 +152,31 @@ test("ui-generator add-subpages derives the default target from a nested route p
|
|
|
150
152
|
});
|
|
151
153
|
|
|
152
154
|
const pageSource = await readPageFile(appRoot, targetFile);
|
|
153
|
-
assert.match(
|
|
155
|
+
assert.match(
|
|
156
|
+
pageSource,
|
|
157
|
+
/<ShellOutlet target="catalog-products:sub-pages" default-link-component-token="local\.main\.ui\.tab-link-item" \/>/
|
|
158
|
+
);
|
|
154
159
|
});
|
|
155
160
|
});
|
|
156
161
|
|
|
157
|
-
test("ui-generator add-subpages
|
|
162
|
+
test("ui-generator add-subpages rejects explicit target shorthand without a position", async () => {
|
|
158
163
|
await withTempApp(async (appRoot) => {
|
|
159
164
|
await writeAppFixture(appRoot);
|
|
160
165
|
|
|
161
166
|
const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
|
|
162
167
|
await writePageFile(appRoot, targetFile);
|
|
163
168
|
|
|
164
|
-
await
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
await assert.rejects(
|
|
170
|
+
runGeneratorSubcommand({
|
|
171
|
+
appRoot,
|
|
172
|
+
subcommand: "add-subpages",
|
|
173
|
+
args: [targetFile],
|
|
174
|
+
options: {
|
|
175
|
+
target: "practice-hub"
|
|
176
|
+
}
|
|
177
|
+
}),
|
|
178
|
+
/option "target" must be a target in "host:position" format/
|
|
179
|
+
);
|
|
175
180
|
});
|
|
176
181
|
});
|
|
177
182
|
|
|
@@ -192,7 +197,10 @@ test("ui-generator add-subpages supports explicit target host:position", async (
|
|
|
192
197
|
});
|
|
193
198
|
|
|
194
199
|
const pageSource = await readPageFile(appRoot, targetFile);
|
|
195
|
-
assert.match(
|
|
200
|
+
assert.match(
|
|
201
|
+
pageSource,
|
|
202
|
+
/<ShellOutlet target="practice-hub:secondary-tabs" default-link-component-token="local\.main\.ui\.tab-link-item" \/>/
|
|
203
|
+
);
|
|
196
204
|
});
|
|
197
205
|
});
|
|
198
206
|
|
|
@@ -209,8 +217,9 @@ test("ui-generator add-subpages does not rewrite existing scaffold support compo
|
|
|
209
217
|
customSectionShellSource,
|
|
210
218
|
"utf8"
|
|
211
219
|
);
|
|
220
|
+
await mkdir(path.join(appRoot, "src", "components", "menus"), { recursive: true });
|
|
212
221
|
await writeFile(
|
|
213
|
-
path.join(appRoot, "src", "components", "TabLinkItem.vue"),
|
|
222
|
+
path.join(appRoot, "src", "components", "menus", "TabLinkItem.vue"),
|
|
214
223
|
customTabLinkSource,
|
|
215
224
|
"utf8"
|
|
216
225
|
);
|
|
@@ -233,7 +242,7 @@ test("ui-generator add-subpages does not rewrite existing scaffold support compo
|
|
|
233
242
|
customSectionShellSource
|
|
234
243
|
);
|
|
235
244
|
assert.equal(
|
|
236
|
-
await readFile(path.join(appRoot, "src", "components", "TabLinkItem.vue"), "utf8"),
|
|
245
|
+
await readFile(path.join(appRoot, "src", "components", "menus", "TabLinkItem.vue"), "utf8"),
|
|
237
246
|
customTabLinkSource
|
|
238
247
|
);
|
|
239
248
|
});
|
|
@@ -299,7 +308,7 @@ test("ui-generator add-subpages validates target format", async () => {
|
|
|
299
308
|
target: "practice:"
|
|
300
309
|
}
|
|
301
310
|
}),
|
|
302
|
-
/option "target" must be
|
|
311
|
+
/option "target" must be a target in "host:position" format/
|
|
303
312
|
);
|
|
304
313
|
});
|
|
305
314
|
});
|
|
@@ -31,8 +31,12 @@ async function writeShellLayout(appRoot, source = "") {
|
|
|
31
31
|
source ||
|
|
32
32
|
`<template>
|
|
33
33
|
<div>
|
|
34
|
-
<ShellOutlet
|
|
35
|
-
<ShellOutlet
|
|
34
|
+
<ShellOutlet target="shell-layout:top-right" />
|
|
35
|
+
<ShellOutlet
|
|
36
|
+
target="shell-layout:primary-menu"
|
|
37
|
+
default
|
|
38
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
39
|
+
/>
|
|
36
40
|
</div>
|
|
37
41
|
</template>
|
|
38
42
|
`
|
|
@@ -57,16 +61,44 @@ test("buildUiPageTemplateContext resolves link placement from default app ShellO
|
|
|
57
61
|
targetFile: "admin/reports/index.vue",
|
|
58
62
|
options: {}
|
|
59
63
|
});
|
|
60
|
-
assert.equal(context.
|
|
61
|
-
assert.equal(context.
|
|
62
|
-
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "users.web.shell.surface-aware-menu-link-item");
|
|
64
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "shell-layout:primary-menu");
|
|
65
|
+
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.surface-aware-menu-link-item");
|
|
63
66
|
assert.equal(context.__JSKIT_UI_LINK_WORKSPACE_SUFFIX__, "/reports");
|
|
64
67
|
assert.equal(context.__JSKIT_UI_LINK_NON_WORKSPACE_SUFFIX__, "/reports");
|
|
68
|
+
assert.equal(context.__JSKIT_UI_LINK_WHEN_LINE__, "");
|
|
65
69
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, "");
|
|
66
70
|
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_ID__, "ui-generator.page.admin.reports.link");
|
|
67
71
|
});
|
|
68
72
|
});
|
|
69
73
|
|
|
74
|
+
test("buildUiPageTemplateContext derives an auth guard from an authenticated surface policy", async () => {
|
|
75
|
+
await withTempApp(async (appRoot) => {
|
|
76
|
+
await writeConfig(
|
|
77
|
+
appRoot,
|
|
78
|
+
`export const config = {
|
|
79
|
+
surfaceAccessPolicies: {
|
|
80
|
+
authenticated: {
|
|
81
|
+
requireAuth: true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
surfaceDefinitions: {
|
|
85
|
+
app: { id: "app", pagesRoot: "app", enabled: true, accessPolicyId: "authenticated" }
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
`
|
|
89
|
+
);
|
|
90
|
+
await writeShellLayout(appRoot);
|
|
91
|
+
|
|
92
|
+
const context = await buildUiPageTemplateContext({
|
|
93
|
+
appRoot,
|
|
94
|
+
targetFile: "app/reports/index.vue",
|
|
95
|
+
options: {}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
assert.equal(context.__JSKIT_UI_LINK_WHEN_LINE__, " when: ({ auth }) => Boolean(auth?.authenticated)\n");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
70
102
|
test("buildUiPageTemplateContext supports explicit link placement override", async () => {
|
|
71
103
|
await withTempApp(async (appRoot) => {
|
|
72
104
|
await writeConfig(
|
|
@@ -87,8 +119,8 @@ test("buildUiPageTemplateContext supports explicit link placement override", asy
|
|
|
87
119
|
"link-placement": "shell-layout:top-right"
|
|
88
120
|
}
|
|
89
121
|
});
|
|
90
|
-
assert.equal(context.
|
|
91
|
-
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "
|
|
122
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "shell-layout:top-right");
|
|
123
|
+
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.surface-aware-menu-link-item");
|
|
92
124
|
});
|
|
93
125
|
});
|
|
94
126
|
|
|
@@ -133,7 +165,11 @@ test("buildUiPageTemplateContext supports explicit package outlet link placement
|
|
|
133
165
|
ui: {
|
|
134
166
|
placements: {
|
|
135
167
|
outlets: [
|
|
136
|
-
{
|
|
168
|
+
{
|
|
169
|
+
target: "workspace-tools:primary-menu",
|
|
170
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
171
|
+
source: "src/client/components/UsersWorkspaceToolsWidget.vue"
|
|
172
|
+
}
|
|
137
173
|
]
|
|
138
174
|
}
|
|
139
175
|
}
|
|
@@ -149,8 +185,7 @@ test("buildUiPageTemplateContext supports explicit package outlet link placement
|
|
|
149
185
|
"link-placement": "workspace-tools:primary-menu"
|
|
150
186
|
}
|
|
151
187
|
});
|
|
152
|
-
assert.equal(context.
|
|
153
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "primary-menu");
|
|
188
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "workspace-tools:primary-menu");
|
|
154
189
|
});
|
|
155
190
|
});
|
|
156
191
|
|
|
@@ -226,7 +261,7 @@ test("buildUiPageTemplateContext infers subpage link placement, tab token, and l
|
|
|
226
261
|
`<template>
|
|
227
262
|
<SectionContainerShell>
|
|
228
263
|
<template #tabs>
|
|
229
|
-
<ShellOutlet
|
|
264
|
+
<ShellOutlet target="contact-view:sub-pages" />
|
|
230
265
|
</template>
|
|
231
266
|
<RouterView />
|
|
232
267
|
</SectionContainerShell>
|
|
@@ -240,8 +275,7 @@ test("buildUiPageTemplateContext infers subpage link placement, tab token, and l
|
|
|
240
275
|
options: {}
|
|
241
276
|
});
|
|
242
277
|
|
|
243
|
-
assert.equal(context.
|
|
244
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "sub-pages");
|
|
278
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "contact-view:sub-pages");
|
|
245
279
|
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.tab-link-item");
|
|
246
280
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, " to: \"./notes\",\n");
|
|
247
281
|
});
|
|
@@ -265,7 +299,7 @@ test("buildUiPageTemplateContext inherits a file-route parent host for deeper de
|
|
|
265
299
|
`<template>
|
|
266
300
|
<SectionContainerShell>
|
|
267
301
|
<template #tabs>
|
|
268
|
-
<ShellOutlet
|
|
302
|
+
<ShellOutlet target="contact-view:sub-pages" />
|
|
269
303
|
</template>
|
|
270
304
|
<RouterView />
|
|
271
305
|
</SectionContainerShell>
|
|
@@ -279,8 +313,7 @@ test("buildUiPageTemplateContext inherits a file-route parent host for deeper de
|
|
|
279
313
|
options: {}
|
|
280
314
|
});
|
|
281
315
|
|
|
282
|
-
assert.equal(context.
|
|
283
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "sub-pages");
|
|
316
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "contact-view:sub-pages");
|
|
284
317
|
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.tab-link-item");
|
|
285
318
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, " to: \"./notes/history\",\n");
|
|
286
319
|
});
|
|
@@ -304,7 +337,7 @@ test("buildUiPageTemplateContext infers subpage link placement from an index-rou
|
|
|
304
337
|
`<template>
|
|
305
338
|
<SectionContainerShell>
|
|
306
339
|
<template #tabs>
|
|
307
|
-
<ShellOutlet
|
|
340
|
+
<ShellOutlet target="catalog:sub-pages" />
|
|
308
341
|
</template>
|
|
309
342
|
<RouterView />
|
|
310
343
|
</SectionContainerShell>
|
|
@@ -318,8 +351,7 @@ test("buildUiPageTemplateContext infers subpage link placement from an index-rou
|
|
|
318
351
|
options: {}
|
|
319
352
|
});
|
|
320
353
|
|
|
321
|
-
assert.equal(context.
|
|
322
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "sub-pages");
|
|
354
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "catalog:sub-pages");
|
|
323
355
|
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.tab-link-item");
|
|
324
356
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, " to: \"./products\",\n");
|
|
325
357
|
});
|
|
@@ -343,7 +375,7 @@ test("buildUiPageTemplateContext finds the nearest index-route parent host", asy
|
|
|
343
375
|
`<template>
|
|
344
376
|
<SectionContainerShell>
|
|
345
377
|
<template #tabs>
|
|
346
|
-
<ShellOutlet
|
|
378
|
+
<ShellOutlet target="catalog:sub-pages" />
|
|
347
379
|
</template>
|
|
348
380
|
<RouterView />
|
|
349
381
|
</SectionContainerShell>
|
|
@@ -356,7 +388,7 @@ test("buildUiPageTemplateContext finds the nearest index-route parent host", asy
|
|
|
356
388
|
`<template>
|
|
357
389
|
<SectionContainerShell>
|
|
358
390
|
<template #tabs>
|
|
359
|
-
<ShellOutlet
|
|
391
|
+
<ShellOutlet target="catalog-products:sub-pages" />
|
|
360
392
|
</template>
|
|
361
393
|
<RouterView />
|
|
362
394
|
</SectionContainerShell>
|
|
@@ -370,8 +402,7 @@ test("buildUiPageTemplateContext finds the nearest index-route parent host", asy
|
|
|
370
402
|
options: {}
|
|
371
403
|
});
|
|
372
404
|
|
|
373
|
-
assert.equal(context.
|
|
374
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "sub-pages");
|
|
405
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "catalog-products:sub-pages");
|
|
375
406
|
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.tab-link-item");
|
|
376
407
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, " to: \"./variants\",\n");
|
|
377
408
|
});
|
|
@@ -395,7 +426,7 @@ test("buildUiPageTemplateContext infers subpage link placement from an index rou
|
|
|
395
426
|
`<template>
|
|
396
427
|
<SectionContainerShell>
|
|
397
428
|
<template #tabs>
|
|
398
|
-
<ShellOutlet
|
|
429
|
+
<ShellOutlet target="customer-view:sub-pages" />
|
|
399
430
|
</template>
|
|
400
431
|
<RouterView />
|
|
401
432
|
</SectionContainerShell>
|
|
@@ -409,8 +440,7 @@ test("buildUiPageTemplateContext infers subpage link placement from an index rou
|
|
|
409
440
|
options: {}
|
|
410
441
|
});
|
|
411
442
|
|
|
412
|
-
assert.equal(context.
|
|
413
|
-
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_POSITION__, "sub-pages");
|
|
443
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_TARGET__, "customer-view:sub-pages");
|
|
414
444
|
assert.equal(context.__JSKIT_UI_LINK_COMPONENT_TOKEN__, "local.main.ui.tab-link-item");
|
|
415
445
|
assert.equal(context.__JSKIT_UI_LINK_TO_PROP_LINE__, " to: \"./pets\",\n");
|
|
416
446
|
assert.equal(context.__JSKIT_UI_LINK_WORKSPACE_SUFFIX__, "/customers/[customerId]/pets");
|
|
@@ -468,11 +498,35 @@ test("buildUiPageTemplateContext rejects target files with a leading src segment
|
|
|
468
498
|
targetFile: "src/components/ReportsPanel.vue",
|
|
469
499
|
options: {}
|
|
470
500
|
}),
|
|
471
|
-
/must be relative to src\/pages
|
|
501
|
+
/must be relative to src\/pages\/ or start with src\/pages\/:/
|
|
472
502
|
);
|
|
473
503
|
});
|
|
474
504
|
});
|
|
475
505
|
|
|
506
|
+
test("buildUiPageTemplateContext accepts target files with a src/pages prefix", async () => {
|
|
507
|
+
await withTempApp(async (appRoot) => {
|
|
508
|
+
await writeConfig(
|
|
509
|
+
appRoot,
|
|
510
|
+
`export const config = {
|
|
511
|
+
surfaceDefinitions: {
|
|
512
|
+
admin: { id: "admin", pagesRoot: "admin", enabled: true }
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
`
|
|
516
|
+
);
|
|
517
|
+
await writeShellLayout(appRoot);
|
|
518
|
+
|
|
519
|
+
const context = await buildUiPageTemplateContext({
|
|
520
|
+
appRoot,
|
|
521
|
+
targetFile: "src/pages/admin/reports/index.vue",
|
|
522
|
+
options: {}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
assert.equal(context.__JSKIT_UI_LINK_PLACEMENT_ID__, "ui-generator.page.admin.reports.link");
|
|
526
|
+
assert.equal(context.__JSKIT_UI_LINK_WORKSPACE_SUFFIX__, "/reports");
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
476
530
|
test("buildUiPageTemplateContext fails when the target file matches no surface", async () => {
|
|
477
531
|
await withTempApp(async (appRoot) => {
|
|
478
532
|
await writeConfig(
|
|
@@ -546,7 +600,7 @@ test("buildUiPageTemplateContext validates link placement format", async () => {
|
|
|
546
600
|
"link-placement": "invalid-placement"
|
|
547
601
|
}
|
|
548
602
|
}),
|
|
549
|
-
/option "placement" must be in "host:position" format/
|
|
603
|
+
/option "placement" must be a target in "host:position" format/
|
|
550
604
|
);
|
|
551
605
|
});
|
|
552
606
|
});
|
|
@@ -23,8 +23,12 @@ async function writeAppFixture(appRoot) {
|
|
|
23
23
|
path.join(appRoot, "src", "components", "ShellLayout.vue"),
|
|
24
24
|
`<template>
|
|
25
25
|
<div>
|
|
26
|
-
<ShellOutlet
|
|
27
|
-
|
|
26
|
+
<ShellOutlet
|
|
27
|
+
target="shell-layout:primary-menu"
|
|
28
|
+
default
|
|
29
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
30
|
+
/>
|
|
31
|
+
<ShellOutlet target="shell-layout:top-right" />
|
|
28
32
|
</div>
|
|
29
33
|
</template>
|
|
30
34
|
`,
|
|
@@ -34,7 +38,7 @@ async function writeAppFixture(appRoot) {
|
|
|
34
38
|
path.join(appRoot, "src", "pages", "admin", "workspace", "settings", "index.vue"),
|
|
35
39
|
`<template>
|
|
36
40
|
<section>
|
|
37
|
-
<ShellOutlet
|
|
41
|
+
<ShellOutlet target="admin-settings:forms" />
|
|
38
42
|
</section>
|
|
39
43
|
</template>
|
|
40
44
|
`,
|
|
@@ -55,17 +59,8 @@ export default function getPlacements() {
|
|
|
55
59
|
path.join(appRoot, "packages", "main", "src", "client", "providers", "MainClientProvider.js"),
|
|
56
60
|
`const mainClientComponents = [];
|
|
57
61
|
|
|
58
|
-
function registerMainClientComponent(
|
|
59
|
-
|
|
60
|
-
if (!token || typeof resolveComponent !== "function") {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
mainClientComponents.push(
|
|
64
|
-
Object.freeze({
|
|
65
|
-
token,
|
|
66
|
-
resolveComponent
|
|
67
|
-
})
|
|
68
|
-
);
|
|
62
|
+
function registerMainClientComponent(token, resolveComponent) {
|
|
63
|
+
mainClientComponents.push({ token, resolveComponent });
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
class MainClientProvider {}
|
|
@@ -104,8 +99,7 @@ test("ui-generator placed-element subcommand creates component and outlet placem
|
|
|
104
99
|
|
|
105
100
|
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
106
101
|
assert.match(placementSource, /id: "ui-generator\.element\.ops-panel"/);
|
|
107
|
-
assert.match(placementSource, /
|
|
108
|
-
assert.match(placementSource, /position: "top-right"/);
|
|
102
|
+
assert.match(placementSource, /target: "shell-layout:top-right"/);
|
|
109
103
|
assert.match(placementSource, /componentToken: "local\.main\.ui\.element\.ops-panel"/);
|
|
110
104
|
});
|
|
111
105
|
});
|
|
@@ -125,8 +119,7 @@ test("ui-generator placed-element subcommand supports explicit placement overrid
|
|
|
125
119
|
});
|
|
126
120
|
|
|
127
121
|
const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
|
|
128
|
-
assert.match(placementSource, /
|
|
129
|
-
assert.match(placementSource, /position: "primary-menu"/);
|
|
122
|
+
assert.match(placementSource, /target: "shell-layout:primary-menu"/);
|
|
130
123
|
});
|
|
131
124
|
});
|
|
132
125
|
|
|
@@ -40,7 +40,7 @@ import { computed } from "vue";
|
|
|
40
40
|
subcommand: "outlet",
|
|
41
41
|
args: [targetFile],
|
|
42
42
|
options: {
|
|
43
|
-
target: "contact-view"
|
|
43
|
+
target: "contact-view:sub-pages"
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ import { computed } from "vue";
|
|
|
48
48
|
|
|
49
49
|
const output = await readFile(targetPath, "utf8");
|
|
50
50
|
assert.match(output, /import ShellOutlet from "@jskit-ai\/shell-web\/client\/components\/ShellOutlet";/);
|
|
51
|
-
assert.match(output, /<ShellOutlet
|
|
51
|
+
assert.match(output, /<ShellOutlet target="contact-view:sub-pages" \/>/);
|
|
52
52
|
assert.doesNotMatch(output, /RouterView/);
|
|
53
53
|
assert.doesNotMatch(output, /jskit:ui-generator\.outlet:/);
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ import { computed } from "vue";
|
|
|
57
57
|
subcommand: "outlet",
|
|
58
58
|
args: [targetFile],
|
|
59
59
|
options: {
|
|
60
|
-
target: "contact-view"
|
|
60
|
+
target: "contact-view:sub-pages"
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
63
|
|
|
@@ -79,7 +79,7 @@ import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
|
|
|
79
79
|
|
|
80
80
|
<template>
|
|
81
81
|
<section>
|
|
82
|
-
<ShellOutlet
|
|
82
|
+
<ShellOutlet target="contact-view:sub-pages" />
|
|
83
83
|
</section>
|
|
84
84
|
</template>
|
|
85
85
|
`,
|
|
@@ -91,12 +91,12 @@ import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
|
|
|
91
91
|
subcommand: "outlet",
|
|
92
92
|
args: [targetFile],
|
|
93
93
|
options: {
|
|
94
|
-
target: "contact-view"
|
|
94
|
+
target: "contact-view:sub-pages"
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
const output = await readFile(targetPath, "utf8");
|
|
99
|
-
assert.equal((output.match(/<ShellOutlet
|
|
99
|
+
assert.equal((output.match(/<ShellOutlet target="contact-view:sub-pages" \/>/g) || []).length, 1);
|
|
100
100
|
assert.equal(
|
|
101
101
|
(output.match(/import ShellOutlet from "@jskit-ai\/shell-web\/client\/components\/ShellOutlet";/g) || []).length,
|
|
102
102
|
1
|
|
@@ -124,7 +124,7 @@ test("ui-generator outlet creates script setup when missing", async () => {
|
|
|
124
124
|
subcommand: "outlet",
|
|
125
125
|
args: [targetFile],
|
|
126
126
|
options: {
|
|
127
|
-
target: "contact-view"
|
|
127
|
+
target: "contact-view:sub-pages"
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
|
|
@@ -161,7 +161,7 @@ test("ui-generator outlet inserts generated script after existing route block",
|
|
|
161
161
|
subcommand: "outlet",
|
|
162
162
|
args: [targetFile],
|
|
163
163
|
options: {
|
|
164
|
-
target: "contact-view"
|
|
164
|
+
target: "contact-view:sub-pages"
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
|
|
@@ -203,12 +203,12 @@ test("ui-generator outlet keeps indentation when injected into nested template b
|
|
|
203
203
|
subcommand: "outlet",
|
|
204
204
|
args: [targetFile],
|
|
205
205
|
options: {
|
|
206
|
-
target: "contact-view"
|
|
206
|
+
target: "contact-view:sub-pages"
|
|
207
207
|
}
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
const output = await readFile(targetPath, "utf8");
|
|
211
|
-
assert.match(output, /\n\s{2}<\/section>\n\s{2}<ShellOutlet
|
|
211
|
+
assert.match(output, /\n\s{2}<\/section>\n\s{2}<ShellOutlet target="contact-view:sub-pages" \/>\n<\/template>/);
|
|
212
212
|
assert.match(output, /<template v-else-if="view\.isLoading">\n\s*<v-skeleton-loader type="heading, text@2, article" \/>\n\s*<\/template>/);
|
|
213
213
|
assert.doesNotMatch(output, /jskit:ui-generator\.outlet:/);
|
|
214
214
|
});
|
|
@@ -226,11 +226,11 @@ test("ui-generator outlet rejects unsupported options", async () => {
|
|
|
226
226
|
runGeneratorSubcommand({
|
|
227
227
|
appRoot,
|
|
228
228
|
subcommand: "outlet",
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
args: [targetFile],
|
|
230
|
+
options: {
|
|
231
|
+
target: "contact-view:sub-pages",
|
|
232
|
+
bogus: "routed"
|
|
233
|
+
}
|
|
234
234
|
}),
|
|
235
235
|
/ui-generator outlet received unsupported option: --bogus\./
|
|
236
236
|
);
|
|
@@ -255,7 +255,7 @@ test("ui-generator outlet supports explicit target host:position", async () => {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
const output = await readFile(targetPath, "utf8");
|
|
258
|
-
assert.match(output, /<ShellOutlet
|
|
258
|
+
assert.match(output, /<ShellOutlet target="customer-view:summary-actions" \/>/);
|
|
259
259
|
});
|
|
260
260
|
});
|
|
261
261
|
|
|
@@ -274,7 +274,7 @@ test("ui-generator outlet rejects non-vue target files without changing them", a
|
|
|
274
274
|
subcommand: "outlet",
|
|
275
275
|
args: [targetFile],
|
|
276
276
|
options: {
|
|
277
|
-
target: "vet-view"
|
|
277
|
+
target: "vet-view:sub-pages"
|
|
278
278
|
}
|
|
279
279
|
}),
|
|
280
280
|
/ui-generator outlet target file must be an existing Vue SFC \(\.vue\): src\/pages\/w\/\[workspaceSlug\]\/admin\/practice\/vets\/_components\/VetAddEditFormFields\.js\./
|
|
@@ -302,7 +302,7 @@ test("ui-generator outlet validates target format", async () => {
|
|
|
302
302
|
target: "customer-view:"
|
|
303
303
|
}
|
|
304
304
|
}),
|
|
305
|
-
/ui-generator outlet option "target" must be
|
|
305
|
+
/ui-generator outlet option "target" must be a target in "host:position" format\./
|
|
306
306
|
);
|
|
307
307
|
});
|
|
308
308
|
});
|