@abgov/nx-adsp 13.18.0 → 13.18.1

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.18.0",
3
+ "version": "13.18.1",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -236,31 +236,22 @@ mark what's meant to be edited.
236
236
  ```
237
237
  3. **For the `internal` layout: add a nav link to `src/App.vue`.** The generated shell only
238
238
  populates `accountItems`; primary navigation lives in `primaryItems` (or `secondaryItems`).
239
- `AppSideMenu` is purely presentational it doesn't know about routing, so `App.vue` owns
240
- `current` detection and the `itemClick` handler:
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`:
241
242
 
242
243
  ```typescript
243
- // In <script setup>:
244
- import { computed } from 'vue';
245
- import { RouterView, useRoute, useRouter } from 'vue-router';
246
- // ...existing imports...
247
-
248
- const route = useRoute(); // already in the generated App.vue
249
- const router = useRouter();
250
-
251
- // Icon names come from the GoA icon set: design.alberta.ca/components/icons
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
252
246
  const primaryItems = computed(() => [
253
- { label: 'Home', to: '/', icon: 'home', current: route.path === '/' },
254
- { label: 'My Feature', to: '/my-feature', icon: 'list', current: route.path.startsWith('/my-feature') },
255
- ]);
256
-
257
- function onItemClick(item: { to?: string }) {
258
- if (item.to) router.push(item.to);
259
- }
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
+ ])
260
250
  ```
261
251
 
262
252
  ```html
263
- <!-- In <template>, add :primary-items and @item-click to <AppSideMenu>: -->
253
+ <!-- In <template>, add :primary-items to <AppSideMenu>
254
+ (@item-click="onItemClick" is already wired in the generated shell): -->
264
255
  <AppSideMenu
265
256
  heading="..."
266
257
  :primary-items="primaryItems"
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue';
3
3
  import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
4
- import { RouterView, useRoute } from 'vue-router';
4
+ import { RouterView, useRoute<% if (layout === 'internal') { %>, useRouter<% } %> } from 'vue-router';
5
5
  <% if (layout === 'internal') { %>
6
6
  import { AppLayout, AppSideMenu, SessionExpiredBanner } from '<%= goaImportPath %>';
7
7
  <% } else { %>
@@ -32,16 +32,21 @@ function signInAgain() {
32
32
  }
33
33
  <% if (layout === 'internal') { %>
34
34
 
35
- // AppSideMenu's account items are presentational -- it doesn't know about
36
- // Keycloak, it just renders whatever's passed and emits itemClick on click.
35
+ // AppSideMenu's items are presentational it doesn't know about Keycloak or
36
+ // routing; it just renders whatever's passed and emits itemClick on click.
37
37
  const accountItems = computed(() => [
38
38
  kc.authenticated
39
39
  ? { label: kc.fullName ? `Sign out (${kc.fullName})` : 'Sign out' }
40
40
  : { label: 'Sign in' },
41
41
  ]);
42
42
 
43
- function onAccountItemClick() {
44
- if (kc.authenticated) logout();
43
+ const router = useRouter();
44
+
45
+ // AppSideMenu emits one itemClick for ALL slots (primary, secondary, account).
46
+ // Items with `to` are nav items — route. Items without are account actions — sign in/out.
47
+ function onItemClick(item: { to?: string }) {
48
+ if (item.to) router.push(item.to);
49
+ else if (kc.authenticated) logout();
45
50
  else login();
46
51
  }
47
52
  <% } %>
@@ -52,7 +57,7 @@ function onAccountItemClick() {
52
57
  <AppSideMenu
53
58
  heading="<%= projectName %>"
54
59
  :account-items="accountItems"
55
- @item-click="onAccountItemClick"
60
+ @item-click="onItemClick"
56
61
  >
57
62
  <SessionExpiredBanner
58
63
  v-model:show="session.expired"
@@ -3,9 +3,9 @@ import { ref, onMounted, watch } from 'vue'
3
3
  import { useKeycloak } from '@dsb-norge/vue-keycloak-js'
4
4
  import { useApi } from '../composables/useApi'
5
5
 
6
- // Keep the reactive instance — do NOT destructure (see App.vue): a destructured
7
- // `authenticated` freezes at its setup-time value, so the watch below would never
8
- // see the user sign in and apiFetch would always skip the token.
6
+ // Keep the useKeycloak() result as an object — do NOT destructure it (see App.vue):
7
+ // its fields are plain values in a reactive object and freeze at setup-time undefined
8
+ // when extracted. `apiFetch` from useApi() is a plain function and is fine to destructure.
9
9
  const kc = useKeycloak()
10
10
  const { apiFetch } = useApi()
11
11
  const publicResource = ref('Not retrieved — is the backend service running?')
@@ -206,7 +206,7 @@ describe('Vue App Generator', () => {
206
206
  expect(app).toContain('<AppSideMenu');
207
207
  expect(app).toContain('heading="test"');
208
208
  expect(app).toContain(':account-items="accountItems"');
209
- expect(app).toContain('@item-click="onAccountItemClick"');
209
+ expect(app).toContain('@item-click="onItemClick"');
210
210
  // The content gutter is shared regardless of shell choice.
211
211
  expect(app).toContain('<AppLayout');
212
212
  // App.vue itself must not add a second skip-to-main-content landmark —
@@ -353,6 +353,21 @@ describe('Vue App Generator', () => {
353
353
  expect(homeView).toContain('goa-hero-banner');
354
354
  }, 30000);
355
355
 
356
+ it('--layout=internal App.vue uses a unified onItemClick that routes nav items and signs in/out for account items', async () => {
357
+ // AppSideMenu emits one itemClick for all slots. A dedicated onAccountItemClick breaks
358
+ // once primaryItems are added — primary nav clicks fire login()/logout() instead of routing.
359
+ // The generated handler must dispatch by item.to.
360
+ await generator(host, { ...options, layout: 'internal' });
361
+ const app = host.read('apps/test/src/App.vue').toString();
362
+ expect(app).toContain('onItemClick');
363
+ expect(app).toContain('router.push');
364
+ expect(app).not.toContain('onAccountItemClick');
365
+ // Routing branch: items with `to` navigate
366
+ expect(app).toContain('item.to');
367
+ // Auth branch: items without `to` fall through to sign-in/out
368
+ expect(app).toContain('kc.authenticated');
369
+ }, 30000);
370
+
356
371
  it('--layout=internal has no hero banner anywhere, including on HomeView', async () => {
357
372
  await generator(host, { ...options, layout: 'internal' });
358
373
  const homeView = host.read('apps/test/src/views/HomeView.vue').toString();