@abgov/nx-adsp 13.31.0 → 13.31.2

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.
Files changed (69) hide show
  1. package/README.md +41 -32
  2. package/package.json +1 -1
  3. package/src/generators/angular-app/angular-app.js +2 -2
  4. package/src/generators/angular-app/files/AGENTS.md__tmpl__ +16 -0
  5. package/src/generators/express-service/express-service.spec.ts +26 -13
  6. package/src/generators/express-service/schema.json +1 -1
  7. package/src/generators/react-app/files/AGENTS.md__tmpl__ +16 -0
  8. package/src/generators/react-app/react-app.js +2 -2
  9. package/src/generators/vue-admin-crud/files/src/views/__editViewFileName__.vue__tmpl__ +3 -5
  10. package/src/generators/vue-admin-crud/files/src/views/__listViewFileName__.vue__tmpl__ +33 -4
  11. package/src/generators/vue-admin-crud/schema.d.ts +2 -0
  12. package/src/generators/vue-admin-crud/schema.json +7 -8
  13. package/src/generators/vue-admin-crud/vue-admin-crud.js +6 -1
  14. package/src/generators/vue-admin-crud/vue-admin-crud.js.map +1 -1
  15. package/src/generators/vue-admin-crud/vue-admin-crud.spec.ts +96 -13
  16. package/src/generators/vue-app/files/AGENTS.md__tmpl__ +56 -11
  17. package/src/generators/vue-app/files/src/App.vue__tmpl__ +2 -1
  18. package/src/generators/vue-app/files/src/composables/useApi.spec.ts__tmpl__ +5 -3
  19. package/src/generators/vue-app/files/src/composables/useApi.ts__tmpl__ +14 -2
  20. package/src/generators/vue-app/vue-app.js +33 -6
  21. package/src/generators/vue-app/vue-app.js.map +1 -1
  22. package/src/generators/vue-app/vue-app.spec.ts +133 -13
  23. package/src/generators/vue-components/files/AGENTS.md__tmpl__ +36 -17
  24. package/src/generators/vue-components/files/src/index.ts__tmpl__ +3 -0
  25. package/src/generators/vue-components/files/src/lib/formatters.ts__tmpl__ +58 -0
  26. package/src/generators/vue-components/files/src/lib/patterns/AppSideMenu.vue__tmpl__ +14 -3
  27. package/src/generators/vue-components/files/src/lib/patterns/FilterBar.vue__tmpl__ +7 -1
  28. package/src/generators/vue-components/files/src/lib/patterns/WorkspaceTable.vue__tmpl__ +7 -1
  29. package/src/generators/vue-components/vue-components.d.ts +1 -0
  30. package/src/generators/vue-components/vue-components.js +18 -39
  31. package/src/generators/vue-components/vue-components.js.map +1 -1
  32. package/src/generators/vue-components/vue-components.spec.ts +94 -2
  33. package/src/generators/vue-detail-view/files/src/views/__viewFileName__.vue__tmpl__ +51 -2
  34. package/src/generators/vue-detail-view/schema.d.ts +18 -0
  35. package/src/generators/vue-detail-view/schema.json +2 -2
  36. package/src/generators/vue-detail-view/vue-detail-view.js +4 -1
  37. package/src/generators/vue-detail-view/vue-detail-view.js.map +1 -1
  38. package/src/generators/vue-detail-view/vue-detail-view.spec.ts +36 -6
  39. package/src/generators/vue-intake-view/files/shared/src/views/__confirmationViewFileName__.vue__tmpl__ +60 -6
  40. package/src/generators/vue-intake-view/files/shared/src/views/__reviewViewFileName__.vue__tmpl__ +37 -1
  41. package/src/generators/vue-intake-view/files/steps/src/views/__stepViewFileName__.vue__tmpl__ +1 -1
  42. package/src/generators/vue-intake-view/schema.d.ts +10 -0
  43. package/src/generators/vue-intake-view/schema.json +12 -9
  44. package/src/generators/vue-intake-view/vue-intake-view.js +9 -3
  45. package/src/generators/vue-intake-view/vue-intake-view.js.map +1 -1
  46. package/src/generators/vue-intake-view/vue-intake-view.spec.ts +113 -12
  47. package/src/generators/vue-workspace-view/files/src/views/__viewFileName__.vue__tmpl__ +49 -1
  48. package/src/generators/vue-workspace-view/schema.d.ts +22 -0
  49. package/src/generators/vue-workspace-view/schema.json +8 -9
  50. package/src/generators/vue-workspace-view/vue-workspace-view.js +7 -1
  51. package/src/generators/vue-workspace-view/vue-workspace-view.js.map +1 -1
  52. package/src/generators/vue-workspace-view/vue-workspace-view.spec.ts +73 -7
  53. package/src/utils/paired-project.d.ts +10 -0
  54. package/src/utils/paired-project.js +125 -18
  55. package/src/utils/paired-project.js.map +1 -1
  56. package/src/utils/paired-project.spec.ts +126 -0
  57. package/src/utils/quality.js +43 -9
  58. package/src/utils/quality.js.map +1 -1
  59. package/src/utils/quality.spec.ts +35 -2
  60. package/src/utils/vue-eslint.d.ts +2 -0
  61. package/src/utils/vue-eslint.js +48 -0
  62. package/src/utils/vue-eslint.js.map +1 -0
  63. package/src/utils/vue-side-menu.d.ts +13 -1
  64. package/src/utils/vue-side-menu.js +50 -5
  65. package/src/utils/vue-side-menu.js.map +1 -1
  66. package/src/utils/vue-side-menu.spec.ts +100 -0
  67. package/src/utils/vue-tsconfig.d.ts +26 -0
  68. package/src/utils/vue-tsconfig.js +49 -0
  69. package/src/utils/vue-tsconfig.js.map +1 -0
@@ -92,8 +92,12 @@ describe('Vue Intake View Generator', () => {
92
92
  expect(step1).toContain(
93
93
  "import { Stepper, StepErrorSummary, GoabInput } from '@proj/vue-components';",
94
94
  );
95
- expect(step1).toContain("{ key: 'personal-info', label: 'Personal information' }");
96
- expect(step1).toContain("{ key: 'contact-info', label: 'Contact information' }");
95
+ expect(step1).toContain(
96
+ "{ key: 'personal-info', label: 'Personal information' }",
97
+ );
98
+ expect(step1).toContain(
99
+ "{ key: 'contact-info', label: 'Contact information' }",
100
+ );
97
101
  // Own step is 'incomplete' when active and not yet completed, per the real
98
102
  // goa-form-step status enum (complete/incomplete/not-started -- no "current").
99
103
  expect(step1).toContain("step.key === 'personal-info'");
@@ -125,13 +129,19 @@ describe('Vue Intake View Generator', () => {
125
129
  it('generates a review view listing every step with an Edit link, and a declaration gate on Submit', async () => {
126
130
  await generator(host, baseOptions);
127
131
 
128
- const review = host.read('apps/test/src/views/ApplicationReviewView.vue').toString();
132
+ const review = host
133
+ .read('apps/test/src/views/ApplicationReviewView.vue')
134
+ .toString();
129
135
  expect(review).toContain("editStep('personal-info')");
130
136
  expect(review).toContain("editStep('contact-info')");
131
137
  expect(review).toContain("record['fullName'] ?? '—'");
132
138
  expect(review).toContain("record['email'] ?? '—'");
133
- expect(review).toContain(':disabled="!declared || submitting || undefined"');
134
- expect(review).toContain("await action('applications', idParam.value, 'submit')");
139
+ expect(review).toContain(
140
+ ':disabled="!declared || submitting || undefined"',
141
+ );
142
+ expect(review).toContain(
143
+ "await action('applications', idParam.value, 'submit')",
144
+ );
135
145
  expect(review).not.toContain('apiFetch');
136
146
  expect(review).toContain('watch(idParam, load, { immediate: true })');
137
147
  expect(review).not.toContain('onMounted(');
@@ -175,8 +185,12 @@ describe('Vue Intake View Generator', () => {
175
185
  const step = host
176
186
  .read('apps/test/src/views/DetailsStepView.vue')
177
187
  .toString();
178
- expect(step).toContain("count: form.count === '' ? null : Number(form.count)");
179
- expect(step).toContain('occurred: toIsoDateString(form.occurred) || null');
188
+ expect(step).toContain(
189
+ "count: form.count === '' ? null : Number(form.count)",
190
+ );
191
+ expect(step).toContain(
192
+ 'occurred: toIsoDateString(form.occurred) || null',
193
+ );
180
194
  expect(step).toContain("fromIsoDateString(data['occurred'])");
181
195
  });
182
196
 
@@ -210,12 +224,39 @@ describe('Vue Intake View Generator', () => {
210
224
  });
211
225
  });
212
226
 
213
- it('generates a confirmation view showing the reference number', async () => {
227
+ // The previous version of this test asserted `{{ route.params.id }}` -- it
228
+ // encoded the defect rather than the requirement, so the confirmation page
229
+ // shipped presenting the storage id as "your reference number" ("Your
230
+ // reference number is 1001" in a real build).
231
+ it('generates a confirmation view showing the business reference, not the route id', async () => {
214
232
  await generator(host, baseOptions);
215
233
  const confirmation = host
216
234
  .read('apps/test/src/views/ApplicationConfirmationView.vue')
217
235
  .toString();
218
- expect(confirmation).toContain('{{ route.params.id }}');
236
+ // Fetches the record and reads the reference field off it.
237
+ expect(confirmation).toContain("record.value?.['reference']");
238
+ expect(confirmation).toContain("{{ loading ? '…' : reference }}");
239
+ // The route id survives only as a fallback, never as the presented value.
240
+ expect(confirmation).not.toContain('{{ route.params.id }}');
241
+ }, 30000);
242
+
243
+ it('honours --referenceField', async () => {
244
+ await generator(host, { ...baseOptions, referenceField: 'claimNumber' });
245
+ expect(
246
+ host
247
+ .read('apps/test/src/views/ApplicationConfirmationView.vue')
248
+ .toString(),
249
+ ).toContain("record.value?.['claimNumber']");
250
+ }, 30000);
251
+
252
+ it('gives the confirmation page an h1 and a route out of the flow', async () => {
253
+ await generator(host, baseOptions);
254
+ const confirmation = host
255
+ .read('apps/test/src/views/ApplicationConfirmationView.vue')
256
+ .toString();
257
+ expect(confirmation).toContain('<h1');
258
+ expect(confirmation).toContain('What happens next');
259
+ expect(confirmation).toContain('<router-link to="/">');
219
260
  }, 30000);
220
261
 
221
262
  it('inserts a route for every step plus review and confirmation, requiring auth by default', async () => {
@@ -235,9 +276,7 @@ describe('Vue Intake View Generator', () => {
235
276
  expect(routerTs).toContain(
236
277
  "component: () => import('../views/ApplicationConfirmationView.vue')",
237
278
  );
238
- expect(
239
- routerTs.split('requiresAuth: true').length - 1,
240
- ).toBe(4);
279
+ expect(routerTs.split('requiresAuth: true').length - 1).toBe(4);
241
280
  // The existing route is untouched, not replaced.
242
281
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
243
282
  }, 30000);
@@ -274,4 +313,66 @@ describe('Vue Intake View Generator', () => {
274
313
  const after = readProjectConfiguration(host, 'test');
275
314
  expect(after).toEqual(before);
276
315
  }, 30000);
316
+
317
+ // The check-your-answers page exists so the person can read back what they
318
+ // entered; a stored code ('cattle-mature', 'northwest') is not that. The
319
+ // generator is handed the label for every option and used to discard it.
320
+ it('renders coded answers as labels on the review page', async () => {
321
+ await generator(host, {
322
+ ...baseOptions,
323
+ steps: [
324
+ {
325
+ key: 'losses',
326
+ label: 'Losses',
327
+ fields: [
328
+ {
329
+ key: 'species',
330
+ label: 'Species',
331
+ type: 'select',
332
+ options: [{ value: 'cattle-mature', label: 'Cattle (mature)' }],
333
+ },
334
+ ],
335
+ },
336
+ ],
337
+ });
338
+ const review = host
339
+ .read('apps/test/src/views/ApplicationReviewView.vue')
340
+ .toString();
341
+ expect(review).toContain(
342
+ "optionLabel(fieldOptions['species'], record['species'])",
343
+ );
344
+ expect(review).toContain('Cattle (mature)');
345
+ }, 30000);
346
+
347
+ it('spaces stacked form items in each step', async () => {
348
+ await generator(host, baseOptions);
349
+ const step = host
350
+ .read('apps/test/src/views/PersonalInfoStepView.vue')
351
+ .toString();
352
+ // Counted per tag rather than as a contiguous string: formatFiles reflows a
353
+ // multi-attribute tag across lines, so `<goa-form-item mb="l"` is not a
354
+ // reliable substring.
355
+ const tags = step
356
+ .split('<goa-form-item')
357
+ .slice(1)
358
+ .map((fragment) => fragment.slice(0, fragment.indexOf('>')));
359
+ expect(tags.length).toBeGreaterThan(0);
360
+ for (const tag of tags) {
361
+ expect(tag).toContain('mb="l"');
362
+ }
363
+ }, 30000);
364
+
365
+ // Regression: the review page's <dl> was unstyled, so terms and values stacked
366
+ // flush -- every answer ran into the next on the page whose entire purpose is
367
+ // being read back before submitting. vue-detail-view already styled its
368
+ // equivalent list; this is the same fix, not a new idea.
369
+ it('gives the review page label/value list real spacing', async () => {
370
+ await generator(host, baseOptions);
371
+ const review = host
372
+ .read('apps/test/src/views/ApplicationReviewView.vue')
373
+ .toString();
374
+ expect(review).toContain('<dl class="review-fields">');
375
+ expect(review).toContain('grid-template-columns: auto 1fr');
376
+ expect(review).toContain('gap: var(--goa-space-xs) var(--goa-space-l)');
377
+ }, 30000);
277
378
  });
@@ -7,7 +7,23 @@ import {
7
7
  <% } -%>
8
8
  formatCurrency,
9
9
  formatDate,
10
+ <% if (hasCodedColumns || hasBadgeColumns) { -%>
11
+ optionLabel,
12
+ <% } -%>
13
+ <% if (hasBadgeColumns) { -%>
14
+ badgeType,
15
+ <% } -%>
16
+ } from '<%= goaImportPath %>';
17
+ <% if (hasCodedColumns || hasBadgeColumns) { -%>
18
+ import type {
19
+ <% if (hasCodedColumns || hasBadgeColumns) { -%>
20
+ DisplayOption,
21
+ <% } -%>
22
+ <% if (hasBadgeColumns) { -%>
23
+ BadgeType,
24
+ <% } -%>
10
25
  } from '<%= goaImportPath %>';
26
+ <% } -%>
11
27
  import { useApi } from '../composables/useApi';
12
28
 
13
29
  const { list } = useApi();
@@ -17,6 +33,31 @@ const columns = [
17
33
  { key: '<%= column.key %>', label: '<%= column.label %>', sortable: <%= !!column.sortable %> },
18
34
  <% }); -%>
19
35
  ];
36
+ <% if (hasCodedColumns || hasBadgeColumns) { -%>
37
+
38
+ // Value -> label for coded columns, so the table shows 'Cattle (mature)' rather
39
+ // than the stored 'cattle-mature'. Keyed by column so one lookup covers them all.
40
+ const columnOptions: Record<string, DisplayOption[]> = {
41
+ <% columns.forEach(function (column) { -%>
42
+ <% if (column.options && column.options.length) { -%>
43
+ '<%= column.key %>': <%- JSON.stringify(column.options) %>,
44
+ <% } -%>
45
+ <% }); -%>
46
+ };
47
+ <% } -%>
48
+ <% if (hasBadgeColumns) { -%>
49
+
50
+ // Value -> badge type, so a status carries the right colour. Anything unmapped
51
+ // falls back to the neutral 'information' rather than being asserted as good or
52
+ // bad.
53
+ const columnBadgeTypes: Record<string, Record<string, BadgeType>> = {
54
+ <% columns.forEach(function (column) { -%>
55
+ <% if (column.type === 'badge') { -%>
56
+ '<%= column.key %>': <%- JSON.stringify(column.badgeMap || {}) %>,
57
+ <% } -%>
58
+ <% }); -%>
59
+ };
60
+ <% } -%>
20
61
 
21
62
  // The fetched rows' shape isn't known to this generator -- read fields
22
63
  // defensively rather than declaring (and likely getting wrong) a fake interface.
@@ -186,7 +227,14 @@ onUnmounted(() => {
186
227
  <% columns.forEach(function (column) { -%>
187
228
  <% if (column.type === 'badge') { -%>
188
229
  <template #cell-<%= column.key %>="{ row }">
189
- <goa-badge type="information" :content="String(row['<%= column.key %>'] ?? '—')" />
230
+ <goa-badge
231
+ :type="badgeType(columnBadgeTypes['<%= column.key %>'], row['<%= column.key %>'])"
232
+ :content="optionLabel(columnOptions['<%= column.key %>'], row['<%= column.key %>'])"
233
+ />
234
+ </template>
235
+ <% } else if (column.options && column.options.length) { -%>
236
+ <template #cell-<%= column.key %>="{ row }">
237
+ {{ optionLabel(columnOptions['<%= column.key %>'], row['<%= column.key %>']) }}
190
238
  </template>
191
239
  <% } else if (column.type === 'date') { -%>
192
240
  <template #cell-<%= column.key %>="{ row }">
@@ -3,6 +3,26 @@ export interface WorkspaceViewColumn {
3
3
  label: string;
4
4
  type?: 'text' | 'date' | 'currency' | 'badge';
5
5
  sortable?: boolean;
6
+ /**
7
+ * Value -> label for a coded column. Supply this whenever the stored value is
8
+ * a code rather than prose, or the table renders the raw code.
9
+ */
10
+ options?: { value: string; label: string }[];
11
+ /**
12
+ * `badge` only. Value -> goa-badge type, e.g.
13
+ * `{ approved: 'success', declined: 'emergency' }`. Unmapped values render
14
+ * neutral.
15
+ */
16
+ badgeMap?: Record<
17
+ string,
18
+ | 'information'
19
+ | 'success'
20
+ | 'important'
21
+ | 'emergency'
22
+ | 'dark'
23
+ | 'midtone'
24
+ | 'light'
25
+ >;
6
26
  }
7
27
 
8
28
  export interface WorkspaceViewFilter {
@@ -32,6 +52,8 @@ export interface Schema {
32
52
  heading?: string;
33
53
  pageSize?: number;
34
54
  filterable?: boolean;
55
+ /** Ionicon name for the side-menu entry. Defaults per generator. */
56
+ icon?: string;
35
57
  requiresAuth?: boolean;
36
58
  }
37
59
 
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "resource": {
27
27
  "type": "string",
28
- "description": "API resource path segment -- the view fetches /api/<resource>?page=&limit=&search=&sortBy=&sortDir=."
28
+ "description": "API resource path segment -- the view fetches /api/v1/<resource>?page=&limit=&search=&sortBy=&sortDir=."
29
29
  },
30
30
  "route": {
31
31
  "type": "string",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "columns": {
35
35
  "type": "string",
36
- "description": "JSON array of table columns, in display order -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"badge\",\"sortable\":true}]'. Each item: { key, label, type?: \"text\"|\"date\"|\"currency\"|\"badge\" (default \"text\"), sortable?: boolean }. A plain array is also accepted when this generator is invoked programmatically. Nx's CLI option coercion only supports comma-separated primitive lists for array-typed schema properties, not JSON -- a JSON string is the only CLI syntax that actually survives Nx's own arg parsing for a structured list like this."
36
+ "description": "JSON array of table columns, in display order -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"badge\",\"sortable\":true}]'. Each item: { key, label, type?: \"text\"|\"date\"|\"currency\"|\"badge\" (default \"text\"), sortable?: boolean, options?: [{value,label}], badgeMap?: {value: badgeType} }. Supply `options` for any column whose stored value is a code, or the table renders the raw code. Supply `badgeMap` on a `badge` column to give each status its colour (e.g. {\"approved\":\"success\",\"declined\":\"emergency\"}); unmapped values render neutral. A plain array is also accepted when this generator is invoked programmatically. Nx's CLI option coercion only supports comma-separated primitive lists for array-typed schema properties, not JSON -- a JSON string is the only CLI syntax that actually survives Nx's own arg parsing for a structured list like this."
37
37
  },
38
38
  "filters": {
39
39
  "type": "string",
@@ -57,18 +57,17 @@
57
57
  "description": "Whether to generate a debounced search input above the table.",
58
58
  "default": true
59
59
  },
60
+ "icon": {
61
+ "type": "string",
62
+ "description": "Ionicon name for the generated side-menu entry, e.g. `list`. goa-work-side-menu-item renders a blank item without one.",
63
+ "default": "list"
64
+ },
60
65
  "requiresAuth": {
61
66
  "type": "boolean",
62
67
  "description": "Whether the generated route requires authentication.",
63
68
  "default": true
64
69
  }
65
70
  },
66
- "required": [
67
- "project",
68
- "name",
69
- "resource",
70
- "route",
71
- "columns"
72
- ],
71
+ "required": ["project", "name", "resource", "route", "columns"],
73
72
  "additionalProperties": false
74
73
  }
@@ -51,11 +51,16 @@ function normalizeOptions(host, options) {
51
51
  }
52
52
  function default_1(host, options) {
53
53
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
54
+ var _a;
54
55
  const normalizedOptions = normalizeOptions(host, options);
55
56
  // Idempotent -- ensures WorkspaceTable exists even in a project scaffolded
56
57
  // before vue-components carried it.
57
58
  yield (0, vue_components_1.default)(host);
58
- (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host), tmpl: '' }));
59
+ (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files'), normalizedOptions.projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath: (0, vue_components_1.vueComponentsImportPath)(host),
60
+ // Derived here rather than tested inline in the template: EJS throws a
61
+ // ReferenceError on a bare undefined variable, so a flag the template
62
+ // reads must always be defined.
63
+ hasCodedColumns: normalizedOptions.columns.some((column) => { var _a; return (_a = column.options) === null || _a === void 0 ? void 0 : _a.length; }), hasBadgeColumns: normalizedOptions.columns.some((column) => column.type === 'badge'), tmpl: '' }));
59
64
  (0, vue_router_1.insertVueRoute)(host, normalizedOptions.projectRoot, normalizedOptions.project, {
60
65
  path: normalizedOptions.route,
61
66
  componentImportPath: `../views/${normalizedOptions.viewFileName}.vue`,
@@ -67,6 +72,7 @@ function default_1(host, options) {
67
72
  (0, vue_side_menu_1.insertSideMenuItem)(host, normalizedOptions.projectRoot, {
68
73
  label: normalizedOptions.heading,
69
74
  to: normalizedOptions.route,
75
+ icon: (_a = normalizedOptions.icon) !== null && _a !== void 0 ? _a : 'list',
70
76
  });
71
77
  yield (0, devkit_1.formatFiles)(host);
72
78
  });
@@ -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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"}
@@ -125,7 +125,9 @@ describe('Vue Workspace View Generator', () => {
125
125
  expect(view).toContain('let loadSequence = 0');
126
126
  expect(view).toContain('const sequence = ++loadSequence');
127
127
  expect(view).toContain('if (sequence !== loadSequence) return;');
128
- expect(view).toContain('if (sequence === loadSequence) loading.value = false;');
128
+ expect(view).toContain(
129
+ 'if (sequence === loadSequence) loading.value = false;',
130
+ );
129
131
 
130
132
  // The debounce timer must not outlive the component.
131
133
  expect(view).toContain('onUnmounted(');
@@ -138,7 +140,7 @@ describe('Vue Workspace View Generator', () => {
138
140
  expect(view).not.toContain('Intl.');
139
141
  expect(view).not.toContain('function formatCurrency');
140
142
  expect(view).toContain(
141
- "<goa-badge type=\"information\" :content=\"String(row['status'] ?? '')\" />",
143
+ "badgeType(columnBadgeTypes['status'], row['status'])",
142
144
  );
143
145
  // A type: 'date' column is a calendar date -- formatDate, not
144
146
  // formatDateTime, which would show an invented midnight.
@@ -205,11 +207,11 @@ describe('Vue Workspace View Generator', () => {
205
207
  await expect(
206
208
  generator(host, {
207
209
  ...baseOptions,
208
- filters: JSON.stringify([
209
- { key: 'x', label: 'X', type: 'checkbox' },
210
- ]),
210
+ filters: JSON.stringify([{ key: 'x', label: 'X', type: 'checkbox' }]),
211
211
  }),
212
- ).rejects.toThrow('--filters[].type must be "dropdown" or "date"; got "checkbox" for "x".');
212
+ ).rejects.toThrow(
213
+ '--filters[].type must be "dropdown" or "date"; got "checkbox" for "x".',
214
+ );
213
215
  });
214
216
 
215
217
  it('accepts a real array, the form a programmatic caller passes', async () => {
@@ -265,7 +267,11 @@ describe('Vue Workspace View Generator', () => {
265
267
  }, 30000);
266
268
 
267
269
  it('respects an explicit --heading and --pageSize', async () => {
268
- await generator(host, { ...baseOptions, heading: 'Grant Applications', pageSize: 50 });
270
+ await generator(host, {
271
+ ...baseOptions,
272
+ heading: 'Grant Applications',
273
+ pageSize: 50,
274
+ });
269
275
  const view = host
270
276
  .read('apps/test/src/views/ApplicationsListView.vue')
271
277
  .toString();
@@ -318,4 +324,64 @@ describe('Vue Workspace View Generator', () => {
318
324
  const after = readProjectConfiguration(host, 'test');
319
325
  expect(after).toEqual(before);
320
326
  }, 30000);
327
+
328
+ // Regression: every badge column was hardcoded type="information", so a
329
+ // declined claim rendered neutral blue -- a badge's colour is the fastest thing
330
+ // a reader takes in, so the wrong one actively misinforms.
331
+ it('maps badge values to semantic types and labels', async () => {
332
+ await generator(host, {
333
+ ...baseOptions,
334
+ columns: [
335
+ { key: 'reference', label: 'Reference' },
336
+ {
337
+ key: 'status',
338
+ label: 'Status',
339
+ type: 'badge',
340
+ options: [{ value: 'declined', label: 'Declined' }],
341
+ badgeMap: { approved: 'success', declined: 'emergency' },
342
+ },
343
+ ],
344
+ });
345
+ const view = host
346
+ .read('apps/test/src/views/ApplicationsListView.vue')
347
+ .toString();
348
+ // Quote style depends on whether formatFiles ran, so match the pairs.
349
+ expect(view).toMatch(/["']?declined["']?\s*:\s*["']emergency["']/);
350
+ expect(view).toMatch(/["']?approved["']?\s*:\s*["']success["']/);
351
+ expect(view).toContain("badgeType(columnBadgeTypes['status']");
352
+ expect(view).toContain("optionLabel(columnOptions['status']");
353
+ }, 30000);
354
+
355
+ it('renders a coded non-badge column using its label', async () => {
356
+ await generator(host, {
357
+ ...baseOptions,
358
+ columns: [
359
+ {
360
+ key: 'region',
361
+ label: 'Region',
362
+ options: [{ value: 'northwest', label: 'North west' }],
363
+ },
364
+ ],
365
+ });
366
+ const view = host
367
+ .read('apps/test/src/views/ApplicationsListView.vue')
368
+ .toString();
369
+ expect(view).toContain(
370
+ "optionLabel(columnOptions['region'], row['region'])",
371
+ );
372
+ expect(view).toContain('North west');
373
+ }, 30000);
374
+
375
+ it('omits the lookup maps entirely when no column needs them', async () => {
376
+ await generator(host, {
377
+ ...baseOptions,
378
+ columns: [{ key: 'reference', label: 'Reference' }],
379
+ });
380
+ const view = host
381
+ .read('apps/test/src/views/ApplicationsListView.vue')
382
+ .toString();
383
+ expect(view).not.toContain('columnOptions');
384
+ expect(view).not.toContain('columnBadgeTypes');
385
+ expect(view).not.toContain('optionLabel');
386
+ }, 30000);
321
387
  });
@@ -11,6 +11,16 @@ type DevProxyEntry = {
11
11
  pathRewrite: Record<string, string>;
12
12
  };
13
13
  export declare function buildDevProxyConf(nginxProxies: NginxProxyConfiguration[]): Record<string, DevProxyEntry>;
14
+ export declare const VITE_PROXY_FILENAME = "vite.proxy.cjs";
15
+ export interface ViteProxyRoute {
16
+ location: string;
17
+ target: string;
18
+ secure: boolean;
19
+ rewriteTo?: string;
20
+ }
21
+ export declare function parseViteDevProxyRoutes(source: string): ViteProxyRoute[];
22
+ export declare function viteDevProxyRoutes(nginxProxies: NginxProxyConfiguration[]): ViteProxyRoute[];
23
+ export declare function buildViteDevProxyModule(routes: ViteProxyRoute[]): string;
14
24
  export declare function resolvePairedProjectProxy(host: Tree, pairedProject: string | undefined): PairedProjectProxy | undefined;
15
25
  export interface PairedFrontendApp {
16
26
  tag: string;