@jskit-ai/ui-generator 0.1.14 → 0.1.15

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.
@@ -14,7 +14,7 @@ async function withTempApp(run) {
14
14
  }
15
15
  }
16
16
 
17
- test("ui-generator outlet injects ShellOutlet and RouterView into existing page", async () => {
17
+ test("ui-generator outlet injects a generic ShellOutlet into an existing page", async () => {
18
18
  await withTempApp(async (appRoot) => {
19
19
  const targetFile = "src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue";
20
20
  const targetPath = path.join(appRoot, targetFile);
@@ -38,9 +38,9 @@ import { computed } from "vue";
38
38
  const result = await runGeneratorSubcommand({
39
39
  appRoot,
40
40
  subcommand: "outlet",
41
+ args: [targetFile],
41
42
  options: {
42
- file: targetFile,
43
- host: "contact-view"
43
+ target: "contact-view"
44
44
  }
45
45
  });
46
46
 
@@ -48,17 +48,16 @@ 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, /import \{ RouterView \} from "vue-router";/);
52
51
  assert.match(output, /<ShellOutlet host="contact-view" position="sub-pages" \/>/);
53
- assert.equal((output.match(/<RouterView \/>/g) || []).length, 1);
52
+ assert.doesNotMatch(output, /RouterView/);
54
53
  assert.doesNotMatch(output, /jskit:ui-generator\.outlet:/);
55
54
 
56
55
  const rerun = await runGeneratorSubcommand({
57
56
  appRoot,
58
57
  subcommand: "outlet",
58
+ args: [targetFile],
59
59
  options: {
60
- file: targetFile,
61
- host: "contact-view"
60
+ target: "contact-view"
62
61
  }
63
62
  });
64
63
 
@@ -66,7 +65,7 @@ import { computed } from "vue";
66
65
  });
67
66
  });
68
67
 
69
- test("ui-generator outlet does not add second RouterView when one already exists", async () => {
68
+ test("ui-generator outlet does not inject a second matching outlet", async () => {
70
69
  await withTempApp(async (appRoot) => {
71
70
  const targetFile = "src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue";
72
71
  const targetPath = path.join(appRoot, targetFile);
@@ -75,12 +74,12 @@ test("ui-generator outlet does not add second RouterView when one already exists
75
74
  await writeFile(
76
75
  targetPath,
77
76
  `<script setup>
78
- import { RouterView } from "vue-router";
77
+ import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";
79
78
  </script>
80
79
 
81
80
  <template>
82
81
  <section>
83
- <RouterView />
82
+ <ShellOutlet host="contact-view" position="sub-pages" />
84
83
  </section>
85
84
  </template>
86
85
  `,
@@ -90,20 +89,22 @@ import { RouterView } from "vue-router";
90
89
  await runGeneratorSubcommand({
91
90
  appRoot,
92
91
  subcommand: "outlet",
92
+ args: [targetFile],
93
93
  options: {
94
- file: targetFile,
95
- host: "contact-view"
94
+ target: "contact-view"
96
95
  }
97
96
  });
98
97
 
99
98
  const output = await readFile(targetPath, "utf8");
100
- assert.match(output, /<ShellOutlet host="contact-view" position="sub-pages" \/>/);
101
- assert.equal((output.match(/<RouterView \/>/g) || []).length, 1);
102
- assert.equal((output.match(/import \{ RouterView \} from "vue-router";/g) || []).length, 1);
99
+ assert.equal((output.match(/<ShellOutlet host="contact-view" position="sub-pages" \/>/g) || []).length, 1);
100
+ assert.equal(
101
+ (output.match(/import ShellOutlet from "@jskit-ai\/shell-web\/client\/components\/ShellOutlet";/g) || []).length,
102
+ 1
103
+ );
103
104
  });
104
105
  });
105
106
 
106
- test("ui-generator outlet-only mode injects only ShellOutlet and creates script setup when missing", async () => {
107
+ test("ui-generator outlet creates script setup when missing", async () => {
107
108
  await withTempApp(async (appRoot) => {
108
109
  const targetFile = "src/components/ContactDetailsPanel.vue";
109
110
  const targetPath = path.join(appRoot, targetFile);
@@ -121,19 +122,16 @@ test("ui-generator outlet-only mode injects only ShellOutlet and creates script
121
122
  await runGeneratorSubcommand({
122
123
  appRoot,
123
124
  subcommand: "outlet",
125
+ args: [targetFile],
124
126
  options: {
125
- file: targetFile,
126
- host: "contact-view",
127
- position: "sub-pages",
128
- mode: "outlet-only"
127
+ target: "contact-view"
129
128
  }
130
129
  });
131
130
 
132
131
  const output = await readFile(targetPath, "utf8");
133
132
  assert.match(output, /<script setup>/);
134
133
  assert.match(output, /import ShellOutlet from "@jskit-ai\/shell-web\/client\/components\/ShellOutlet";/);
135
- assert.doesNotMatch(output, /import \{ RouterView \} from "vue-router";/);
136
- assert.doesNotMatch(output, /<RouterView \/>/);
134
+ assert.doesNotMatch(output, /RouterView/);
137
135
  });
138
136
  });
139
137
 
@@ -161,10 +159,9 @@ test("ui-generator outlet inserts generated script after existing route block",
161
159
  await runGeneratorSubcommand({
162
160
  appRoot,
163
161
  subcommand: "outlet",
162
+ args: [targetFile],
164
163
  options: {
165
- file: targetFile,
166
- host: "contact-view",
167
- mode: "outlet-only"
164
+ target: "contact-view"
168
165
  }
169
166
  });
170
167
 
@@ -204,16 +201,82 @@ test("ui-generator outlet keeps indentation when injected into nested template b
204
201
  await runGeneratorSubcommand({
205
202
  appRoot,
206
203
  subcommand: "outlet",
204
+ args: [targetFile],
207
205
  options: {
208
- file: targetFile,
209
- host: "contact-view",
210
- mode: "routed"
206
+ target: "contact-view"
211
207
  }
212
208
  });
213
209
 
214
210
  const output = await readFile(targetPath, "utf8");
215
- assert.match(output, /\n\s{2}<\/section>\n\s{2}<ShellOutlet host="contact-view" position="sub-pages" \/>\n\s{2}<RouterView \/>\n<\/template>/);
211
+ assert.match(output, /\n\s{2}<\/section>\n\s{2}<ShellOutlet host="contact-view" position="sub-pages" \/>\n<\/template>/);
216
212
  assert.match(output, /<template v-else-if="view\.isLoading">\n\s*<v-skeleton-loader type="heading, text@2, article" \/>\n\s*<\/template>/);
217
213
  assert.doesNotMatch(output, /jskit:ui-generator\.outlet:/);
218
214
  });
219
215
  });
216
+
217
+ test("ui-generator outlet rejects unsupported options", async () => {
218
+ await withTempApp(async (appRoot) => {
219
+ const targetFile = "src/components/ContactDetailsPanel.vue";
220
+ const targetPath = path.join(appRoot, targetFile);
221
+
222
+ await mkdir(path.dirname(targetPath), { recursive: true });
223
+ await writeFile(targetPath, "<template><div /></template>\n", "utf8");
224
+
225
+ await assert.rejects(
226
+ runGeneratorSubcommand({
227
+ appRoot,
228
+ subcommand: "outlet",
229
+ args: [targetFile],
230
+ options: {
231
+ target: "contact-view",
232
+ bogus: "routed"
233
+ }
234
+ }),
235
+ /ui-generator outlet received unsupported option: --bogus\./
236
+ );
237
+ });
238
+ });
239
+
240
+ test("ui-generator outlet supports explicit target host:position", async () => {
241
+ await withTempApp(async (appRoot) => {
242
+ const targetFile = "src/components/ContactDetailsPanel.vue";
243
+ const targetPath = path.join(appRoot, targetFile);
244
+
245
+ await mkdir(path.dirname(targetPath), { recursive: true });
246
+ await writeFile(targetPath, "<template><div /></template>\n", "utf8");
247
+
248
+ await runGeneratorSubcommand({
249
+ appRoot,
250
+ subcommand: "outlet",
251
+ args: [targetFile],
252
+ options: {
253
+ target: "customer-view:summary-actions"
254
+ }
255
+ });
256
+
257
+ const output = await readFile(targetPath, "utf8");
258
+ assert.match(output, /<ShellOutlet host="customer-view" position="summary-actions" \/>/);
259
+ });
260
+ });
261
+
262
+ test("ui-generator outlet validates target format", async () => {
263
+ await withTempApp(async (appRoot) => {
264
+ const targetFile = "src/components/ContactDetailsPanel.vue";
265
+ const targetPath = path.join(appRoot, targetFile);
266
+
267
+ await mkdir(path.dirname(targetPath), { recursive: true });
268
+ await writeFile(targetPath, "<template><div /></template>\n", "utf8");
269
+
270
+ await assert.rejects(
271
+ runGeneratorSubcommand({
272
+ appRoot,
273
+ subcommand: "outlet",
274
+ args: [targetFile],
275
+ options: {
276
+ target: "customer-view:"
277
+ }
278
+ }),
279
+ /ui-generator outlet option "target" must be "host" or "host:position"\./
280
+ );
281
+ });
282
+ });
@@ -0,0 +1,10 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import descriptor from "../package.descriptor.mjs";
4
+
5
+ test("ui-generator surface options validate against enabled surface ids", () => {
6
+ assert.equal(descriptor.kind, "generator");
7
+ assert.equal(descriptor.options?.surface?.validationType, "enabled-surface-id");
8
+ assert.equal(descriptor.metadata?.generatorSubcommands?.["placed-element"]?.optionNames?.includes("surface"), true);
9
+ assert.equal(descriptor.metadata?.generatorSubcommands?.page?.optionNames?.includes("force"), true);
10
+ });
@@ -0,0 +1,352 @@
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/page.js";
7
+
8
+ async function withTempApp(run) {
9
+ const appRoot = await mkdtemp(path.join(tmpdir(), "ui-generator-page-"));
10
+ try {
11
+ return await run(appRoot);
12
+ } finally {
13
+ await rm(appRoot, { recursive: true, force: true });
14
+ }
15
+ }
16
+
17
+ function toPagePath(targetFile = "") {
18
+ return path.join("src/pages", targetFile);
19
+ }
20
+
21
+ async function writeAppFixture(appRoot, { configSource = "" } = {}) {
22
+ await mkdir(path.join(appRoot, "config"), { recursive: true });
23
+ await mkdir(path.join(appRoot, "src", "components"), { recursive: true });
24
+ await mkdir(path.join(appRoot, "src"), { recursive: true });
25
+
26
+ await writeFile(
27
+ path.join(appRoot, "config", "public.js"),
28
+ configSource ||
29
+ `export const config = {
30
+ surfaceDefinitions: {
31
+ admin: { id: "admin", pagesRoot: "w/[workspaceSlug]/admin", enabled: true, requiresAuth: true, requiresWorkspace: true }
32
+ }
33
+ };
34
+ `,
35
+ "utf8"
36
+ );
37
+ await writeFile(
38
+ path.join(appRoot, "src", "components", "ShellLayout.vue"),
39
+ `<template>
40
+ <div>
41
+ <ShellOutlet host="shell-layout" position="primary-menu" default />
42
+ <ShellOutlet host="shell-layout" position="top-right" />
43
+ </div>
44
+ </template>
45
+ `,
46
+ "utf8"
47
+ );
48
+ await writeFile(
49
+ path.join(appRoot, "src", "placement.js"),
50
+ `function addPlacement() {}
51
+
52
+ export { addPlacement };
53
+ export default function getPlacements() {
54
+ return [];
55
+ }
56
+ `,
57
+ "utf8"
58
+ );
59
+ }
60
+
61
+ test("ui-generator page subcommand creates an index page from an explicit target file", async () => {
62
+ await withTempApp(async (appRoot) => {
63
+ await writeAppFixture(appRoot);
64
+
65
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
66
+ const result = await runGeneratorSubcommand({
67
+ appRoot,
68
+ subcommand: "page",
69
+ args: [targetFile],
70
+ options: {
71
+ name: "Practice"
72
+ }
73
+ });
74
+
75
+ assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
76
+
77
+ const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
78
+ assert.match(pageSource, /<h1 class="text-h5 mb-2">Practice<\/h1>/);
79
+
80
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
81
+ assert.match(placementSource, /id: "ui-generator\.page\.admin\.practice\.link"/);
82
+ assert.match(placementSource, /workspaceSuffix: "\/practice"/);
83
+ assert.match(placementSource, /label: "Practice"/);
84
+ });
85
+ });
86
+
87
+ test("ui-generator page subcommand creates a file route and derives label from the file path", async () => {
88
+ await withTempApp(async (appRoot) => {
89
+ await writeAppFixture(appRoot);
90
+
91
+ const targetFile = "w/[workspaceSlug]/admin/contacts/[contactId].vue";
92
+ const result = await runGeneratorSubcommand({
93
+ appRoot,
94
+ subcommand: "page",
95
+ args: [targetFile],
96
+ options: {}
97
+ });
98
+
99
+ assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
100
+
101
+ const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
102
+ assert.match(pageSource, /<h1 class="text-h5 mb-2">Contact Id<\/h1>/);
103
+
104
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
105
+ assert.match(placementSource, /workspaceSuffix: "\/contacts\/\[contactId\]"/);
106
+ assert.match(placementSource, /id: "ui-generator\.page\.admin\.contacts\.contact-id\.link"/);
107
+ assert.match(placementSource, /label: "Contact Id"/);
108
+ });
109
+ });
110
+
111
+ test("ui-generator page subcommand supports link placement options", async () => {
112
+ await withTempApp(async (appRoot) => {
113
+ await writeAppFixture(appRoot);
114
+
115
+ const targetFile = "w/[workspaceSlug]/admin/contacts/[contactId]/index/notes/index.vue";
116
+ await runGeneratorSubcommand({
117
+ appRoot,
118
+ subcommand: "page",
119
+ args: [targetFile],
120
+ options: {
121
+ "link-placement": "shell-layout:top-right",
122
+ "link-component-token": "local.main.ui.tab-link-item",
123
+ "link-to": "./notes"
124
+ }
125
+ });
126
+
127
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
128
+ assert.match(placementSource, /host: "shell-layout"/);
129
+ assert.match(placementSource, /position: "top-right"/);
130
+ assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
131
+ assert.match(placementSource, /to: "\.\/notes"/);
132
+ });
133
+ });
134
+
135
+ test("ui-generator page subcommand infers subpage link placement, tab token, and link-to from the nearest parent host", async () => {
136
+ await withTempApp(async (appRoot) => {
137
+ await writeAppFixture(appRoot);
138
+
139
+ const parentFile = "w/[workspaceSlug]/admin/contacts/[contactId].vue";
140
+ await mkdir(path.dirname(path.join(appRoot, toPagePath(parentFile))), { recursive: true });
141
+ await writeFile(
142
+ path.join(appRoot, toPagePath(parentFile)),
143
+ `<template>
144
+ <SectionContainerShell>
145
+ <template #tabs>
146
+ <ShellOutlet host="contact-view" position="sub-pages" />
147
+ </template>
148
+ <RouterView />
149
+ </SectionContainerShell>
150
+ </template>
151
+ `,
152
+ "utf8"
153
+ );
154
+
155
+ const targetFile = "w/[workspaceSlug]/admin/contacts/[contactId]/notes/index.vue";
156
+ await runGeneratorSubcommand({
157
+ appRoot,
158
+ subcommand: "page",
159
+ args: [targetFile],
160
+ options: {}
161
+ });
162
+
163
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
164
+ assert.match(placementSource, /host: "contact-view"/);
165
+ assert.match(placementSource, /position: "sub-pages"/);
166
+ assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
167
+ assert.match(placementSource, /to: "\.\/notes"/);
168
+ });
169
+ });
170
+
171
+ test("ui-generator page subcommand prefers the nearest index-route parent host", async () => {
172
+ await withTempApp(async (appRoot) => {
173
+ await writeAppFixture(appRoot);
174
+
175
+ await mkdir(path.join(appRoot, "src/pages/w/[workspaceSlug]/admin/catalog/index/products"), {
176
+ recursive: true
177
+ });
178
+ await writeFile(
179
+ path.join(appRoot, "src/pages/w/[workspaceSlug]/admin/catalog/index.vue"),
180
+ `<template>
181
+ <SectionContainerShell>
182
+ <template #tabs>
183
+ <ShellOutlet host="catalog" position="sub-pages" />
184
+ </template>
185
+ <RouterView />
186
+ </SectionContainerShell>
187
+ </template>
188
+ `,
189
+ "utf8"
190
+ );
191
+ await writeFile(
192
+ path.join(appRoot, "src/pages/w/[workspaceSlug]/admin/catalog/index/products/index.vue"),
193
+ `<template>
194
+ <SectionContainerShell>
195
+ <template #tabs>
196
+ <ShellOutlet host="catalog-products" position="sub-pages" />
197
+ </template>
198
+ <RouterView />
199
+ </SectionContainerShell>
200
+ </template>
201
+ `,
202
+ "utf8"
203
+ );
204
+
205
+ const targetFile =
206
+ "w/[workspaceSlug]/admin/catalog/index/products/index/variants/index.vue";
207
+ await runGeneratorSubcommand({
208
+ appRoot,
209
+ subcommand: "page",
210
+ args: [targetFile],
211
+ options: {}
212
+ });
213
+
214
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
215
+ assert.match(placementSource, /host: "catalog-products"/);
216
+ assert.match(placementSource, /position: "sub-pages"/);
217
+ assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
218
+ assert.match(placementSource, /to: "\.\/variants"/);
219
+ });
220
+ });
221
+
222
+ test("ui-generator page subcommand chooses the most specific matching surface pagesRoot", async () => {
223
+ await withTempApp(async (appRoot) => {
224
+ await writeAppFixture(appRoot, {
225
+ configSource: `export const config = {
226
+ surfaceDefinitions: {
227
+ app: { id: "app", pagesRoot: "", enabled: true, requiresAuth: false, requiresWorkspace: false },
228
+ admin: { id: "admin", pagesRoot: "w/[workspaceSlug]/admin", enabled: true, requiresAuth: true, requiresWorkspace: true }
229
+ }
230
+ };
231
+ `
232
+ });
233
+
234
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
235
+ const result = await runGeneratorSubcommand({
236
+ appRoot,
237
+ subcommand: "page",
238
+ args: [targetFile],
239
+ options: {}
240
+ });
241
+
242
+ assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
243
+
244
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
245
+ assert.match(placementSource, /id: "ui-generator\.page\.admin\.practice\.link"/);
246
+ assert.match(placementSource, /workspaceSuffix: "\/practice"/);
247
+ });
248
+ });
249
+
250
+ test("ui-generator page subcommand rejects unsupported options", async () => {
251
+ await withTempApp(async (appRoot) => {
252
+ await writeAppFixture(appRoot);
253
+
254
+ await assert.rejects(
255
+ runGeneratorSubcommand({
256
+ appRoot,
257
+ subcommand: "page",
258
+ args: ["w/[workspaceSlug]/admin/practice/index.vue"],
259
+ options: {
260
+ bogus: "true"
261
+ }
262
+ }),
263
+ /ui-generator page received unsupported option: --bogus\./
264
+ );
265
+ });
266
+ });
267
+
268
+ test("ui-generator page subcommand rejects target files with a src/pages prefix", async () => {
269
+ await withTempApp(async (appRoot) => {
270
+ await writeAppFixture(appRoot);
271
+
272
+ await assert.rejects(
273
+ runGeneratorSubcommand({
274
+ appRoot,
275
+ subcommand: "page",
276
+ args: ["src/pages/w/[workspaceSlug]/admin/practice/index.vue"],
277
+ options: {}
278
+ }),
279
+ /must be relative to src\/pages\/, without the src\/pages\/ prefix/
280
+ );
281
+ });
282
+ });
283
+
284
+ test("ui-generator page subcommand refuses to overwrite an existing page without --force", async () => {
285
+ await withTempApp(async (appRoot) => {
286
+ await writeAppFixture(appRoot);
287
+
288
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
289
+ await mkdir(path.join(appRoot, "src/pages/w/[workspaceSlug]/admin/practice"), {
290
+ recursive: true
291
+ });
292
+ await writeFile(
293
+ path.join(appRoot, toPagePath(targetFile)),
294
+ `<template>
295
+ <div>custom practice page</div>
296
+ </template>
297
+ `,
298
+ "utf8"
299
+ );
300
+
301
+ await assert.rejects(
302
+ runGeneratorSubcommand({
303
+ appRoot,
304
+ subcommand: "page",
305
+ args: [targetFile],
306
+ options: {
307
+ name: "Practice"
308
+ }
309
+ }),
310
+ /ui-generator page will not overwrite existing page src\/pages\/w\/\[workspaceSlug\]\/admin\/practice\/index\.vue\. Re-run with --force to overwrite it\./
311
+ );
312
+
313
+ const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
314
+ assert.match(pageSource, /custom practice page/);
315
+ });
316
+ });
317
+
318
+ test("ui-generator page subcommand overwrites an existing page when --force is passed", async () => {
319
+ await withTempApp(async (appRoot) => {
320
+ await writeAppFixture(appRoot);
321
+
322
+ const targetFile = "w/[workspaceSlug]/admin/practice/index.vue";
323
+ await mkdir(path.join(appRoot, "src/pages/w/[workspaceSlug]/admin/practice"), {
324
+ recursive: true
325
+ });
326
+ await writeFile(
327
+ path.join(appRoot, toPagePath(targetFile)),
328
+ `<template>
329
+ <div>custom practice page</div>
330
+ </template>
331
+ `,
332
+ "utf8"
333
+ );
334
+
335
+ const result = await runGeneratorSubcommand({
336
+ appRoot,
337
+ subcommand: "page",
338
+ args: [targetFile],
339
+ options: {
340
+ name: "Practice",
341
+ force: "true"
342
+ }
343
+ });
344
+
345
+ assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
346
+ assert.equal(result.summary, 'Regenerated UI page "/practice" at src/pages/w/[workspaceSlug]/admin/practice/index.vue.');
347
+
348
+ const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
349
+ assert.match(pageSource, /<h1 class="text-h5 mb-2">Practice<\/h1>/);
350
+ assert.doesNotMatch(pageSource, /custom practice page/);
351
+ });
352
+ });