@abgov/nx-adsp 13.28.1 → 13.29.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.28.1",
3
+ "version": "13.29.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const path = require("path");
7
7
  const vue_router_1 = require("../../utils/vue-router");
8
+ const vue_side_menu_1 = require("../../utils/vue-side-menu");
8
9
  const vue_components_1 = require("../vue-components/vue-components");
9
10
  // Nx's own CLI option coercion (coerceTypesInOptions in nx/src/utils/params)
10
11
  // only knows how to split an array-typed option on commas -- it has no JSON
@@ -51,6 +52,11 @@ function default_1(host, options) {
51
52
  requiresAuth: normalizedOptions.requiresAuth,
52
53
  layout: 'wide',
53
54
  });
55
+ // The list is the nav destination; the edit form is reached from it.
56
+ (0, vue_side_menu_1.insertSideMenuItem)(host, normalizedOptions.projectRoot, {
57
+ label: normalizedOptions.heading,
58
+ to: normalizedOptions.route,
59
+ });
54
60
  yield (0, devkit_1.formatFiles)(host);
55
61
  });
56
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":";;AAkDA,4BAmCC;;AArFD,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,iEAAiE;AACjE,SAAS,WAAW,CAAC,MAAwB;IAC3C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IAChD,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,OAAO,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,uCACK,OAAO,KACV,WAAW,EACX,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EACnC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,OAAO,EACP,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,OAAO,EAC/C,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI,IAC1C;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,WAAW,kCAExB,iBAAiB,KACpB,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,IAEX,CAAC;QAEF,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,MAAM;YACtC,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QACH,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,iBAAiB,CAAC,KAAK;YAC7B,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
1
+ {"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":"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"}
@@ -234,34 +234,31 @@ mark what's meant to be edited.
234
234
  { path: '/queue', component: () => import('../views/QueueView.vue'), meta: { layout: 'wide' } }
235
235
  { path: '/apply', component: () => import('../views/ApplyView.vue'), meta: { layout: 'form' } }
236
236
  ```
237
- 3. **For the `internal` layout: add a nav link to `src/App.vue`.** The generated shell only
238
- populates `accountItems`; primary navigation lives in `primaryItems` (or `secondaryItems`).
239
- `AppSideMenu` emits one `itemClick` for all slots the generated `onItemClick` already
240
- dispatches by `item.to`: items with a `to` route, items without sign in/out. Add only
241
- `primaryItems`:
237
+ 3. **For the `internal` layout: check the side-menu nav.** `src/App.vue` has a plain
238
+ `primaryItems` array — the app's own navigation list, seeded with Home. Each `vue-*-view`
239
+ generator that adds a staff screen appends its entry automatically, so a generated route is
240
+ navigable without you doing anything.
241
+
242
+ It is a literal array, not derived from the router, on purpose: nav order, grouping and
243
+ labels are decisions this app owns. Reorder, relabel, group into `secondaryItems`, or prune
244
+ freely — the generators only ever append, and never touch an entry that already exists for
245
+ that route.
242
246
 
243
247
  ```typescript
244
- // In <script setup> — route, router, and onItemClick are already in the generated App.vue:
245
- // Icon names from the GoA icon set: design.alberta.ca/components/icons
246
- const primaryItems = computed(() => [
247
- { label: 'Home', to: '/', icon: 'home', current: route.path === '/' },
248
- { label: 'My Feature', to: '/my-feature', icon: 'list', current: route.path.startsWith('/my-feature') },
249
- ])
248
+ const primaryItems = [
249
+ { label: 'Home', to: '/', icon: 'home' },
250
+ { label: 'Review queue', to: '/queue' }, // added by vue-workspace-view
251
+ ];
250
252
  ```
251
253
 
252
- ```html
253
- <!-- In <template>, add :primary-items to <AppSideMenu>
254
- (@item-click="onItemClick" is already wired in the generated shell): -->
255
- <AppSideMenu
256
- heading="..."
257
- :primary-items="primaryItems"
258
- :account-items="accountItems"
259
- @item-click="onItemClick"
260
- >
261
- ```
254
+ Two things worth adding by hand: **`icon`** is effectively required —
255
+ `goa-work-side-menu-item` renders a blank item without one, and the generators can't guess it
256
+ (icon names: [design.alberta.ca/components/icons](https://design.alberta.ca/components/icons)).
257
+ And **`current`** for active highlighting, e.g. `current: route.path.startsWith('/queue')`,
258
+ which needs the `route` already in scope.
262
259
 
263
- The `header` layout has no side menu add route-level breadcrumbs or a secondary nav
264
- directly inside the view component instead.
260
+ The `header` layout has no side menu, so it has no `primaryItems` array and the generators
261
+ skip the step silently — use route-level breadcrumbs or a nav inside the view instead.
265
262
 
266
263
  ## Backend API calls (proxy setup)
267
264
 
@@ -32,6 +32,14 @@ function signInAgain() {
32
32
  }
33
33
  <% if (layout === 'internal') { %>
34
34
 
35
+ // The app's main navigation. Every `vue-*-view` generator that adds a staff
36
+ // screen appends an entry here, and it is yours to reorder, relabel, group or
37
+ // prune -- a generated app is a starting point, not a fixed shell. Items are
38
+ // `{ label, to }`; add `icon` for a leading icon or `badge` for a count.
39
+ const primaryItems = [
40
+ { label: 'Home', to: '/' },
41
+ ];
42
+
35
43
  // AppSideMenu's items are presentational — it doesn't know about Keycloak or
36
44
  // routing; it just renders whatever's passed and emits itemClick on click.
37
45
  const accountItems = computed(() => [
@@ -56,6 +64,7 @@ function onItemClick(item: { to?: string }) {
56
64
  <% if (layout === 'internal') { %>
57
65
  <AppSideMenu
58
66
  heading="<%= projectName %>"
67
+ :primary-items="primaryItems"
59
68
  :account-items="accountItems"
60
69
  @item-click="onItemClick"
61
70
  >
@@ -193,6 +193,12 @@ describe('Vue App Generator', () => {
193
193
  expect(app).toContain('<AppFooter />');
194
194
  }, 30000);
195
195
 
196
+ it('the header layout has no side-menu nav array (there is no side menu)', async () => {
197
+ await generator(host, options);
198
+ const app = host.read('apps/test/src/App.vue').toString();
199
+ expect(app).not.toContain('primaryItems');
200
+ });
201
+
196
202
  it('--layout=internal generates an AppSideMenu shell instead of AppHeader/AppFooter', async () => {
197
203
  await generator(host, { ...options, layout: 'internal' });
198
204
 
@@ -206,6 +212,12 @@ describe('Vue App Generator', () => {
206
212
  expect(app).toContain('<AppSideMenu');
207
213
  expect(app).toContain('heading="test"');
208
214
  expect(app).toContain(':account-items="accountItems"');
215
+ // A plain, editable nav array -- the app's own list, seeded with Home and
216
+ // appended to by each staff view generator. Not derived from the router:
217
+ // nav order, grouping and labels are the owning team's decisions.
218
+ expect(app).toContain(':primary-items="primaryItems"');
219
+ expect(app).toContain('const primaryItems = [');
220
+ expect(app).toContain("{ label: 'Home', to: '/' },");
209
221
  expect(app).toContain('@item-click="onItemClick"');
210
222
  // The content gutter is shared regardless of shell choice.
211
223
  expect(app).toContain('<AppLayout');
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const devkit_1 = require("@nx/devkit");
6
6
  const path = require("path");
7
7
  const vue_router_1 = require("../../utils/vue-router");
8
+ const vue_side_menu_1 = require("../../utils/vue-side-menu");
8
9
  const vue_components_1 = require("../vue-components/vue-components");
9
10
  // Nx's own CLI option coercion (coerceTypesInOptions in nx/src/utils/params)
10
11
  // only knows how to split an array-typed option on commas -- it has no JSON
@@ -62,6 +63,11 @@ function default_1(host, options) {
62
63
  // A multi-column table at the 1000px default wraps every cell.
63
64
  layout: 'wide',
64
65
  });
66
+ // No-op on a header-layout app, which has no side menu.
67
+ (0, vue_side_menu_1.insertSideMenuItem)(host, normalizedOptions.projectRoot, {
68
+ label: normalizedOptions.heading,
69
+ to: normalizedOptions.route,
70
+ });
65
71
  yield (0, devkit_1.formatFiles)(host);
66
72
  });
67
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,13 @@ import { Schema } from './schema';
9
9
 
10
10
  // Mirrors the shape vue-app's own template generates -- vue-workspace-view
11
11
  // retrofits into this file, so the fixture must match what it actually looks for.
12
+ const APP_VUE_INTERNAL_FIXTURE = `<script setup lang="ts">
13
+ const primaryItems = [
14
+ { label: 'Home', to: '/' },
15
+ ];
16
+ </script>
17
+ `;
18
+
12
19
  const ROUTER_FIXTURE = `import { createRouter, createWebHistory } from 'vue-router';
13
20
  import HomeView from '../views/HomeView.vue';
14
21
 
@@ -41,6 +48,7 @@ describe('Vue Workspace View Generator', () => {
41
48
  host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
42
49
  addProjectConfiguration(host, 'test', { root: 'apps/test' });
43
50
  host.write('apps/test/src/router/index.ts', ROUTER_FIXTURE);
51
+ host.write('apps/test/src/App.vue', APP_VUE_INTERNAL_FIXTURE);
44
52
  });
45
53
 
46
54
  it('throws when --project does not exist', async () => {
@@ -263,6 +271,13 @@ describe('Vue Workspace View Generator', () => {
263
271
  expect(view).toContain(':per-page-count="50"');
264
272
  }, 30000);
265
273
 
274
+ it('adds nothing to the side menu when the app has no App.vue', async () => {
275
+ host.delete('apps/test/src/App.vue');
276
+ // A header-layout app has no side menu; adding a staff view to one must not
277
+ // fail, it simply has no nav to join.
278
+ await expect(generator(host, baseOptions)).resolves.not.toThrow();
279
+ });
280
+
266
281
  it('inserts the route into router/index.ts, requiring auth by default', async () => {
267
282
  await generator(host, baseOptions);
268
283
 
@@ -274,6 +289,10 @@ describe('Vue Workspace View Generator', () => {
274
289
  expect(routerTs).toContain('requiresAuth: true');
275
290
  // A data table asks for the wide variant -- the generator knows it emitted one.
276
291
  expect(routerTs).toContain("layout: 'wide'");
292
+ // A staff queue is a nav destination, so the generator adds it to the
293
+ // app's side-menu list rather than leaving the route reachable only by URL.
294
+ const appVue = host.read('apps/test/src/App.vue').toString();
295
+ expect(appVue).toContain("to: '/applications'");
277
296
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
278
297
  }, 30000);
279
298
 
@@ -0,0 +1,19 @@
1
+ import { Tree } from '@nx/devkit';
2
+ export interface SideMenuItemSpec {
3
+ /** Nav label shown in the side menu, e.g. 'Review queue'. */
4
+ label: string;
5
+ /** Route path the item navigates to, e.g. '/queue'. */
6
+ to: string;
7
+ }
8
+ /**
9
+ * Adds a nav item to a generated app's side menu.
10
+ *
11
+ * A no-op when the app has no `primaryItems` array. That is the signal the app
12
+ * was generated with `--layout=header` (a public app has no side menu at all),
13
+ * so adding a staff view to it should not fail or warn -- there is simply no nav
14
+ * to add to.
15
+ *
16
+ * Idempotent: re-running a generator, or two generators claiming the same path,
17
+ * leaves one entry.
18
+ */
19
+ export declare function insertSideMenuItem(host: Tree, projectRoot: string, item: SideMenuItemSpec): void;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.insertSideMenuItem = insertSideMenuItem;
4
+ // Deterministic text insertion into vue-app's `primaryItems` array, mirroring
5
+ // insertVueRoute's approach for the same reason: the template's shape is fixed,
6
+ // so a targeted anchor beats pulling in an AST library for one array entry.
7
+ //
8
+ // The array is a plain literal in App.vue rather than something derived from the
9
+ // router, deliberately: a generated app is an opinionated starting point, and
10
+ // nav order, grouping and labels are decisions the owning team should be able to
11
+ // read and change in one obvious place -- not behaviour resolved at runtime by
12
+ // the shell that every app then has to work around.
13
+ const ANCHOR = 'const primaryItems = [';
14
+ /**
15
+ * Adds a nav item to a generated app's side menu.
16
+ *
17
+ * A no-op when the app has no `primaryItems` array. That is the signal the app
18
+ * was generated with `--layout=header` (a public app has no side menu at all),
19
+ * so adding a staff view to it should not fail or warn -- there is simply no nav
20
+ * to add to.
21
+ *
22
+ * Idempotent: re-running a generator, or two generators claiming the same path,
23
+ * leaves one entry.
24
+ */
25
+ function insertSideMenuItem(host, projectRoot, item) {
26
+ var _a;
27
+ const appPath = `${projectRoot}/src/App.vue`;
28
+ const content = (_a = host.read(appPath)) === null || _a === void 0 ? void 0 : _a.toString();
29
+ if (content === undefined)
30
+ return;
31
+ const anchorIndex = content.indexOf(ANCHOR);
32
+ if (anchorIndex === -1)
33
+ return;
34
+ if (content.includes(`to: '${item.to}'`))
35
+ return;
36
+ const insertAt = anchorIndex + ANCHOR.length;
37
+ host.write(appPath, content.slice(0, insertAt) +
38
+ `\n { label: '${item.label}', to: '${item.to}' },` +
39
+ content.slice(insertAt));
40
+ }
41
+ //# sourceMappingURL=vue-side-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue-side-menu.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/vue-side-menu.ts"],"names":[],"mappings":";;AA+BA,gDAqBC;AA3CD,8EAA8E;AAC9E,gFAAgF;AAChF,4EAA4E;AAC5E,EAAE;AACF,iFAAiF;AACjF,8EAA8E;AAC9E,iFAAiF;AACjF,+EAA+E;AAC/E,oDAAoD;AACpD,MAAM,MAAM,GAAG,wBAAwB,CAAC;AAExC;;;;;;;;;;GAUG;AACH,SAAgB,kBAAkB,CAChC,IAAU,EACV,WAAmB,EACnB,IAAsB;;IAEtB,MAAM,OAAO,GAAG,GAAG,WAAW,cAAc,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAA,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,0CAAE,QAAQ,EAAE,CAAC;IAC/C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAElC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,CAAC,CAAC;QAAE,OAAO;IAE/B,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,EAAE,GAAG,CAAC;QAAE,OAAO;IAEjD,MAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7C,IAAI,CAAC,KAAK,CACR,OAAO,EACP,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;QACxB,iBAAiB,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,EAAE,MAAM;QACnD,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
3
+ import { insertSideMenuItem } from './vue-side-menu';
4
+
5
+ const INTERNAL_APP = `<script setup lang="ts">
6
+ const primaryItems = [
7
+ { label: 'Home', to: '/' },
8
+ ];
9
+ </script>
10
+ `;
11
+
12
+ // A header-layout app has no side menu at all, so no array to insert into.
13
+ const HEADER_APP = `<script setup lang="ts">
14
+ const accountItems = computed(() => []);
15
+ </script>
16
+ `;
17
+
18
+ describe('insertSideMenuItem', () => {
19
+ let host: Tree;
20
+
21
+ beforeEach(() => {
22
+ host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
23
+ });
24
+
25
+ it('inserts an item into the app nav array', () => {
26
+ host.write('apps/staff/src/App.vue', INTERNAL_APP);
27
+ insertSideMenuItem(host, 'apps/staff', {
28
+ label: 'Review queue',
29
+ to: '/queue',
30
+ });
31
+ const app = host.read('apps/staff/src/App.vue').toString();
32
+ expect(app).toContain("{ label: 'Review queue', to: '/queue' },");
33
+ // The seeded Home entry survives.
34
+ expect(app).toContain("{ label: 'Home', to: '/' },");
35
+ });
36
+
37
+ it('is idempotent on the same route', () => {
38
+ host.write('apps/staff/src/App.vue', INTERNAL_APP);
39
+ const item = { label: 'Review queue', to: '/queue' };
40
+ insertSideMenuItem(host, 'apps/staff', item);
41
+ insertSideMenuItem(host, 'apps/staff', item);
42
+ const app = host.read('apps/staff/src/App.vue').toString();
43
+ expect(app.split("to: '/queue'").length - 1).toBe(1);
44
+ });
45
+
46
+ it('does not re-add a route already present under a different label', () => {
47
+ host.write('apps/staff/src/App.vue', INTERNAL_APP);
48
+ insertSideMenuItem(host, 'apps/staff', { label: 'Queue', to: '/queue' });
49
+ insertSideMenuItem(host, 'apps/staff', { label: 'Renamed', to: '/queue' });
50
+ const app = host.read('apps/staff/src/App.vue').toString();
51
+ expect(app).toContain("label: 'Queue'");
52
+ expect(app).not.toContain("label: 'Renamed'");
53
+ });
54
+
55
+ it('is a silent no-op on a header-layout app with no side menu', () => {
56
+ host.write('apps/public/src/App.vue', HEADER_APP);
57
+ expect(() =>
58
+ insertSideMenuItem(host, 'apps/public', {
59
+ label: 'Review queue',
60
+ to: '/queue',
61
+ }),
62
+ ).not.toThrow();
63
+ expect(host.read('apps/public/src/App.vue').toString()).toBe(HEADER_APP);
64
+ });
65
+
66
+ it('is a silent no-op when there is no App.vue at all', () => {
67
+ expect(() =>
68
+ insertSideMenuItem(host, 'apps/missing', { label: 'X', to: '/x' }),
69
+ ).not.toThrow();
70
+ });
71
+ });