From 72c2bdfae722a225e25e74d1a5b779feb0f37ece Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Wed, 1 Jul 2026 13:02:29 +0200 Subject: [PATCH] test(portal-self-service): DigiD-guarded registration submit page (refs #67) Scaffold libs/ui (NL Design System via Utrecht components) and libs/auth (DigiD OIDC over angular-auth-oidc-client: mockable AuthService, provider, token interceptor, authenticated guard). Failing component + axe tests for the RegistrationPage: it must show the signed-in BSN, submit to the BFF (mocked api-client) and confirm, with no WCAG 2.1 AA violations. The page is a stub, so the behaviour tests fail; green follows. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../registration/registration-page.spec.ts | 51 +++++ .../src/app/registration/registration-page.ts | 9 + libs/auth/README.md | 7 + libs/auth/eslint.config.mjs | 34 +++ libs/auth/project.json | 13 ++ libs/auth/src/index.ts | 4 + libs/auth/src/lib/auth.service.ts | 16 ++ libs/auth/src/lib/authenticated.guard.ts | 13 ++ libs/auth/src/lib/digid-auth.providers.ts | 40 ++++ libs/auth/src/lib/digid-auth.service.ts | 29 +++ libs/auth/src/test-setup.ts | 5 + libs/auth/tsconfig.json | 31 +++ libs/auth/tsconfig.lib.json | 26 +++ libs/auth/tsconfig.spec.json | 29 +++ libs/auth/vite.config.mts | 28 +++ libs/ui/README.md | 7 + libs/ui/eslint.config.mjs | 34 +++ libs/ui/project.json | 13 ++ libs/ui/src/index.ts | 10 + libs/ui/src/test-setup.ts | 5 + libs/ui/tsconfig.json | 31 +++ libs/ui/tsconfig.lib.json | 26 +++ libs/ui/tsconfig.spec.json | 29 +++ libs/ui/vite.config.mts | 28 +++ package.json | 8 +- pnpm-lock.yaml | 199 +++++++++++++++++- tsconfig.base.json | 4 +- 27 files changed, 725 insertions(+), 4 deletions(-) create mode 100644 apps/self-service/src/app/registration/registration-page.spec.ts create mode 100644 apps/self-service/src/app/registration/registration-page.ts create mode 100644 libs/auth/README.md create mode 100644 libs/auth/eslint.config.mjs create mode 100644 libs/auth/project.json create mode 100644 libs/auth/src/index.ts create mode 100644 libs/auth/src/lib/auth.service.ts create mode 100644 libs/auth/src/lib/authenticated.guard.ts create mode 100644 libs/auth/src/lib/digid-auth.providers.ts create mode 100644 libs/auth/src/lib/digid-auth.service.ts create mode 100644 libs/auth/src/test-setup.ts create mode 100644 libs/auth/tsconfig.json create mode 100644 libs/auth/tsconfig.lib.json create mode 100644 libs/auth/tsconfig.spec.json create mode 100644 libs/auth/vite.config.mts create mode 100644 libs/ui/README.md create mode 100644 libs/ui/eslint.config.mjs create mode 100644 libs/ui/project.json create mode 100644 libs/ui/src/index.ts create mode 100644 libs/ui/src/test-setup.ts create mode 100644 libs/ui/tsconfig.json create mode 100644 libs/ui/tsconfig.lib.json create mode 100644 libs/ui/tsconfig.spec.json create mode 100644 libs/ui/vite.config.mts diff --git a/apps/self-service/src/app/registration/registration-page.spec.ts b/apps/self-service/src/app/registration/registration-page.spec.ts new file mode 100644 index 0000000..57e0628 --- /dev/null +++ b/apps/self-service/src/app/registration/registration-page.spec.ts @@ -0,0 +1,51 @@ +import { signal } from '@angular/core'; +import { fireEvent, render, screen } from '@testing-library/angular'; +import { of } from 'rxjs'; +import { AuthService } from 'auth'; +import { BffApiV1Service } from 'api-client'; +import { axe } from 'vitest-axe'; +import { RegistrationPage } from './registration-page'; + +class FakeAuth extends AuthService { + readonly isAuthenticated = signal(true); + readonly bsn = signal('123456782'); + login(): void { + /* noop */ + } + logout(): void { + /* noop */ + } +} + +function providers(post = vi.fn().mockReturnValue(of({ registrationId: 'reg-9', status: 'Ingediend' }))) { + return { + post, + providers: [ + { provide: AuthService, useClass: FakeAuth }, + { provide: BffApiV1Service, useValue: { postSelfServiceRegistrations: post } }, + ], + }; +} + +describe('RegistrationPage', () => { + it('shows the signed-in BSN', async () => { + await render(RegistrationPage, { providers: providers().providers }); + expect(screen.getByText(/123456782/)).toBeTruthy(); + }); + + it('submits the registration and confirms', async () => { + const { post, providers: p } = providers(); + await render(RegistrationPage, { providers: p }); + + fireEvent.click(screen.getByRole('button', { name: /indienen/i })); + + expect(post).toHaveBeenCalledTimes(1); + expect(await screen.findByText(/ontvangen/i)).toBeTruthy(); + }); + + it('has no WCAG 2.1 AA violations on the submit page', async () => { + const { container } = await render(RegistrationPage, { providers: providers().providers }); + const results = await axe(container); + expect(results.violations).toEqual([]); + }); +}); diff --git a/apps/self-service/src/app/registration/registration-page.ts b/apps/self-service/src/app/registration/registration-page.ts new file mode 100644 index 0000000..7a91ec1 --- /dev/null +++ b/apps/self-service/src/app/registration/registration-page.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +// STUB (red): no bsn, no submit — the component/axe tests fail until the green commit. +@Component({ + selector: 'app-registration-page', + imports: [], + template: '', +}) +export class RegistrationPage {} diff --git a/libs/auth/README.md b/libs/auth/README.md new file mode 100644 index 0000000..fa43269 --- /dev/null +++ b/libs/auth/README.md @@ -0,0 +1,7 @@ +# auth + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test auth` to execute the unit tests. diff --git a/libs/auth/eslint.config.mjs b/libs/auth/eslint.config.mjs new file mode 100644 index 0000000..0fc6acf --- /dev/null +++ b/libs/auth/eslint.config.mjs @@ -0,0 +1,34 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...nx.configs['flat/angular'], + ...nx.configs['flat/angular-template'], + ...baseConfig, + { + files: ['**/*.ts'], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'lib', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'lib', + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['**/*.html'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/auth/project.json b/libs/auth/project.json new file mode 100644 index 0000000..7a81d35 --- /dev/null +++ b/libs/auth/project.json @@ -0,0 +1,13 @@ +{ + "name": "auth", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/auth/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts new file mode 100644 index 0000000..81eef46 --- /dev/null +++ b/libs/auth/src/index.ts @@ -0,0 +1,4 @@ +export * from './lib/auth.service'; +export * from './lib/digid-auth.service'; +export * from './lib/digid-auth.providers'; +export * from './lib/authenticated.guard'; diff --git a/libs/auth/src/lib/auth.service.ts b/libs/auth/src/lib/auth.service.ts new file mode 100644 index 0000000..605608c --- /dev/null +++ b/libs/auth/src/lib/auth.service.ts @@ -0,0 +1,16 @@ +import { Signal } from '@angular/core'; + +/** + * The portal's view of the signed-in user. An abstraction over the OIDC library so components and + * guards depend on a small, mockable surface (the real implementation is DigiadAuthService). + */ +export abstract class AuthService { + /** Whether a DigiD session is active. */ + abstract readonly isAuthenticated: Signal; + /** The citizen-service number from the DigiD token, once authenticated. */ + abstract readonly bsn: Signal; + /** Start the DigiD login (redirects to Keycloak). */ + abstract login(): void; + /** End the session. */ + abstract logout(): void; +} diff --git a/libs/auth/src/lib/authenticated.guard.ts b/libs/auth/src/lib/authenticated.guard.ts new file mode 100644 index 0000000..37faacf --- /dev/null +++ b/libs/auth/src/lib/authenticated.guard.ts @@ -0,0 +1,13 @@ +import { inject } from '@angular/core'; +import { CanActivateFn } from '@angular/router'; +import { AuthService } from './auth.service'; + +/** Allow the route only when a DigiD session is active; otherwise start the login. */ +export const authenticatedGuard: CanActivateFn = () => { + const auth = inject(AuthService); + if (auth.isAuthenticated()) { + return true; + } + auth.login(); + return false; +}; diff --git a/libs/auth/src/lib/digid-auth.providers.ts b/libs/auth/src/lib/digid-auth.providers.ts new file mode 100644 index 0000000..1bbf9cc --- /dev/null +++ b/libs/auth/src/lib/digid-auth.providers.ts @@ -0,0 +1,40 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; +import { authInterceptor, LogLevel, provideAuth } from 'angular-auth-oidc-client'; +import { AuthService } from './auth.service'; +import { DigiadAuthService } from './digid-auth.service'; + +export interface DigiadAuthOptions { + /** The Keycloak `digid` realm issuer, as reachable from the browser. */ + authority: string; + /** Where Keycloak redirects back to after login (usually the app origin). */ + redirectUrl: string; + /** The BFF origin whose requests get the bearer token attached (secure route). */ + secureApiOrigin: string; +} + +/** + * Configure DigiD login (Keycloak `digid` realm, public client `big-portal`, auth-code + PKCE) and + * bind {@link AuthService} to the OIDC-backed implementation. Register {@link authInterceptor} in the + * app's HttpClient so BFF calls carry the token. + */ +export function provideDigiadAuth(options: DigiadAuthOptions): EnvironmentProviders { + return makeEnvironmentProviders([ + provideAuth({ + config: { + authority: options.authority, + redirectUrl: options.redirectUrl, + postLogoutRedirectUri: options.redirectUrl, + clientId: 'big-portal', + scope: 'openid profile', + responseType: 'code', + silentRenew: true, + useRefreshToken: true, + secureRoutes: [options.secureApiOrigin], + logLevel: LogLevel.Warn, + }, + }), + { provide: AuthService, useClass: DigiadAuthService }, + ]); +} + +export { authInterceptor }; diff --git a/libs/auth/src/lib/digid-auth.service.ts b/libs/auth/src/lib/digid-auth.service.ts new file mode 100644 index 0000000..c786a8c --- /dev/null +++ b/libs/auth/src/lib/digid-auth.service.ts @@ -0,0 +1,29 @@ +import { inject, Injectable, Signal } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { OidcSecurityService } from 'angular-auth-oidc-client'; +import { map } from 'rxjs'; +import { AuthService } from './auth.service'; + +/** DigiD-backed AuthService over angular-auth-oidc-client (Keycloak `digid` realm). */ +@Injectable() +export class DigiadAuthService extends AuthService { + private readonly oidc = inject(OidcSecurityService); + + readonly isAuthenticated: Signal = toSignal( + this.oidc.isAuthenticated$.pipe(map((result) => result.isAuthenticated)), + { initialValue: false }, + ); + + readonly bsn: Signal = toSignal( + this.oidc.userData$.pipe(map((data) => (data.userData as { bsn?: string } | null)?.bsn)), + { initialValue: undefined }, + ); + + override login(): void { + this.oidc.authorize(); + } + + override logout(): void { + this.oidc.logoff().subscribe(); + } +} diff --git a/libs/auth/src/test-setup.ts b/libs/auth/src/test-setup.ts new file mode 100644 index 0000000..46e1374 --- /dev/null +++ b/libs/auth/src/test-setup.ts @@ -0,0 +1,5 @@ +import '@angular/compiler'; +import '@analogjs/vitest-angular/setup-snapshots'; +import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed'; + +setupTestBed({ zoneless: false }); diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json new file mode 100644 index 0000000..67ecfd1 --- /dev/null +++ b/libs/auth/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "isolatedModules": true, + "target": "es2022", + "moduleResolution": "bundler", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "emitDecoratorMetadata": false, + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/auth/tsconfig.lib.json b/libs/auth/tsconfig.lib.json new file mode 100644 index 0000000..2cc024f --- /dev/null +++ b/libs/auth/tsconfig.lib.json @@ -0,0 +1,26 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ] +} diff --git a/libs/auth/tsconfig.spec.json b/libs/auth/tsconfig.spec.json new file mode 100644 index 0000000..19ef165 --- /dev/null +++ b/libs/auth/tsconfig.spec.json @@ -0,0 +1,29 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest" + ] + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] +} diff --git a/libs/auth/vite.config.mts b/libs/auth/vite.config.mts new file mode 100644 index 0000000..ca3bf6b --- /dev/null +++ b/libs/auth/vite.config.mts @@ -0,0 +1,28 @@ +/// +import { defineConfig } from 'vite'; +import angular from '@analogjs/vite-plugin-angular'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/libs/auth', + plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: () => [ nxViteTsPaths() ], + // }, + test: { + name: 'auth', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: ['src/test-setup.ts'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/libs/auth', + provider: 'v8' as const, + }, + }, +})); diff --git a/libs/ui/README.md b/libs/ui/README.md new file mode 100644 index 0000000..139ffca --- /dev/null +++ b/libs/ui/README.md @@ -0,0 +1,7 @@ +# ui + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test ui` to execute the unit tests. diff --git a/libs/ui/eslint.config.mjs b/libs/ui/eslint.config.mjs new file mode 100644 index 0000000..0fc6acf --- /dev/null +++ b/libs/ui/eslint.config.mjs @@ -0,0 +1,34 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...nx.configs['flat/angular'], + ...nx.configs['flat/angular-template'], + ...baseConfig, + { + files: ['**/*.ts'], + rules: { + '@angular-eslint/directive-selector': [ + 'error', + { + type: 'attribute', + prefix: 'lib', + style: 'camelCase', + }, + ], + '@angular-eslint/component-selector': [ + 'error', + { + type: 'element', + prefix: 'lib', + style: 'kebab-case', + }, + ], + }, + }, + { + files: ['**/*.html'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/libs/ui/project.json b/libs/ui/project.json new file mode 100644 index 0000000..60c3045 --- /dev/null +++ b/libs/ui/project.json @@ -0,0 +1,13 @@ +{ + "name": "ui", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/ui/src", + "prefix": "lib", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts new file mode 100644 index 0000000..6fe96e6 --- /dev/null +++ b/libs/ui/src/index.ts @@ -0,0 +1,10 @@ +// NL Design System building blocks — Utrecht is NL DS's reference Angular implementation +// (docs/frontend-decisions.md). The portals depend on the design system through this one lib. +// The design tokens/theme CSS is imported once, at the app level (apps/*/src/styles.css). +export { + UtrechtArticle, + UtrechtButtonAttr, + UtrechtDocument, + UtrechtHeading, + UtrechtParagraph, +} from '@utrecht/component-library-angular'; diff --git a/libs/ui/src/test-setup.ts b/libs/ui/src/test-setup.ts new file mode 100644 index 0000000..46e1374 --- /dev/null +++ b/libs/ui/src/test-setup.ts @@ -0,0 +1,5 @@ +import '@angular/compiler'; +import '@analogjs/vitest-angular/setup-snapshots'; +import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed'; + +setupTestBed({ zoneless: false }); diff --git a/libs/ui/tsconfig.json b/libs/ui/tsconfig.json new file mode 100644 index 0000000..67ecfd1 --- /dev/null +++ b/libs/ui/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "isolatedModules": true, + "target": "es2022", + "moduleResolution": "bundler", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "emitDecoratorMetadata": false, + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/ui/tsconfig.lib.json b/libs/ui/tsconfig.lib.json new file mode 100644 index 0000000..2cc024f --- /dev/null +++ b/libs/ui/tsconfig.lib.json @@ -0,0 +1,26 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ] +} diff --git a/libs/ui/tsconfig.spec.json b/libs/ui/tsconfig.spec.json new file mode 100644 index 0000000..19ef165 --- /dev/null +++ b/libs/ui/tsconfig.spec.json @@ -0,0 +1,29 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest" + ] + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] +} diff --git a/libs/ui/vite.config.mts b/libs/ui/vite.config.mts new file mode 100644 index 0000000..b059678 --- /dev/null +++ b/libs/ui/vite.config.mts @@ -0,0 +1,28 @@ +/// +import { defineConfig } from 'vite'; +import angular from '@analogjs/vite-plugin-angular'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/libs/ui', + plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: () => [ nxViteTsPaths() ], + // }, + test: { + name: 'ui', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: ['src/test-setup.ts'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/libs/ui', + provider: 'v8' as const, + }, + }, +})); diff --git a/package.json b/package.json index fc9a178..1b014db 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "@angular/forms": "21.2.9", "@angular/platform-browser": "21.2.9", "@angular/router": "21.2.9", + "@utrecht/component-library-angular": "^3.0.2", + "@utrecht/design-tokens": "^6.2.1", + "angular-auth-oidc-client": "^21.0.2", "rxjs": "~7.8.0", "zone.js": "0.16.0" }, @@ -40,11 +43,13 @@ "@swc-node/register": "1.11.1", "@swc/core": "1.15.8", "@swc/helpers": "0.5.18", + "@testing-library/angular": "^19.4.1", "@types/node": "20.19.9", "@typescript-eslint/utils": "^8.40.0", "@vitest/coverage-v8": "~4.1.0", "@vitest/ui": "~4.1.0", "angular-eslint": "21.3.1", + "axe-core": "^4.12.1", "esbuild": "^0.27.0", "eslint": "^9.8.0", "eslint-config-prettier": "^10.0.0", @@ -60,6 +65,7 @@ "typescript-eslint": "^8.40.0", "vite": "8.0.9", "vite-tsconfig-paths": "^5.1.4", - "vitest": "~4.1.0" + "vitest": "~4.1.0", + "vitest-axe": "^0.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12528a2..b49967f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,15 @@ importers: '@angular/router': specifier: 21.2.9 version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@utrecht/component-library-angular': + specifier: ^3.0.2 + version: 3.0.2 + '@utrecht/design-tokens': + specifier: ^6.2.1 + version: 6.2.1 + angular-auth-oidc-client: + specifier: ^21.0.2 + version: 21.0.2(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2))(rxjs@7.8.2) rxjs: specifier: ~7.8.0 version: 7.8.2 @@ -108,6 +117,9 @@ importers: '@swc/helpers': specifier: 0.5.18 version: 0.5.18 + '@testing-library/angular': + specifier: ^19.4.1 + version: 19.4.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2))(@testing-library/dom@10.4.1) '@types/node': specifier: 20.19.9 version: 20.19.9 @@ -123,6 +135,9 @@ importers: angular-eslint: specifier: 21.3.1 version: 21.3.1(@angular/cli@21.2.7(@types/node@20.19.9)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.4.2))(typescript-eslint@8.62.1(eslint@9.39.4(jiti@2.4.2))(typescript@5.9.3))(typescript@5.9.3) + axe-core: + specifier: ^4.12.1 + version: 4.12.1 esbuild: specifier: ^0.27.0 version: 0.27.7 @@ -171,6 +186,9 @@ importers: vitest: specifier: ~4.1.0 version: 4.1.9(@types/node@20.19.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@22.1.0)(vite@8.0.9(@types/node@20.19.9)(esbuild@0.27.7)(jiti@2.4.2)(less@4.5.1)(sass-embedded@1.100.0)(sass@1.97.3)(terser@5.48.0)(yaml@2.9.0)) + vitest-axe: + specifier: ^0.1.0 + version: 0.1.0(vitest@4.1.9) packages: @@ -1756,6 +1774,9 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fontsource-variable/noto-sans@5.2.10': + resolution: {integrity: sha512-wyFgKkFu7jki5kEL8qv7avjQ8rxHX0J/nhLWvbR9T0hOH1HRKZEvb9EW9lMjZfWHHfEzKkYf5J+NadwgCS7TXA==} + '@gar/promise-retry@1.0.3': resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} engines: {node: ^20.17.0 || >=22.9.0} @@ -3555,6 +3576,19 @@ packages: '@swc/types@0.1.27': resolution: {integrity: sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==} + '@testing-library/angular@19.4.1': + resolution: {integrity: sha512-C9gIfKf3cpkLqNoUy0pbz8/YpmxyFQTZP6Qzo9HrolQPyVSMdc0+ev3vewCouZDyQQwfRkJNlpgjaHHyhL9oqw==} + peerDependencies: + '@angular/common': '>= 21.0.0' + '@angular/core': '>= 21.0.0' + '@angular/platform-browser': '>= 21.0.0' + '@angular/router': '>= 21.0.0' + '@testing-library/dom': ^10.0.0 + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + '@tootallnate/once@2.0.1': resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} engines: {node: '>= 10'} @@ -3588,6 +3622,9 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3890,6 +3927,12 @@ packages: cpu: [x64] os: [win32] + '@utrecht/component-library-angular@3.0.2': + resolution: {integrity: sha512-SgCygnsyabXpI2UQPyU9KeuTLfgoEd9pskbmrqd6AwR5SfKEkyoxokZTw9qyDhepSyINvGGlhGSEn8LQ8rOU2A==} + + '@utrecht/design-tokens@6.2.1': + resolution: {integrity: sha512-wSh0/uT8FfHM/J3fcN7ZJ80OoJpOXjEdSqLu1higSW6AHW4LYm5ZyeQFFsGcIhGlIYPSjds8ksDpizdgjoab/g==} + '@vitejs/plugin-basic-ssl@2.1.4': resolution: {integrity: sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4096,6 +4139,14 @@ packages: resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==} engines: {node: '>= 14.0.0'} + angular-auth-oidc-client@21.0.2: + resolution: {integrity: sha512-e3S8/ylzPXgeiM14GFQ/rxV4q6S+UqYjefHhMooNEDF4FSvZYd/C8VGQUmJFbYSO5IQql9d9seVbDJ5yIxDzEA==} + peerDependencies: + '@angular/common': '>=20.0.0' + '@angular/core': '>=20.0.0' + '@angular/router': '>=20.0.0' + rxjs: ^6.5.3 || ^7.4.0 + angular-eslint@21.3.1: resolution: {integrity: sha512-VGQWTyuPAEO/AnZuqHxGBJMYSiZ0tbrHx/OgPCRTKHfbrFU4x+zivS84h9UWoDpDtius1RyD+ZReFjTAEWptiA==} peerDependencies: @@ -4154,6 +4205,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -4192,6 +4246,10 @@ packages: peerDependencies: postcss: ^8.1.0 + axe-core@4.12.1: + resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + engines: {node: '>=4'} + axios@1.16.0: resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} @@ -4819,6 +4877,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4847,6 +4909,9 @@ packages: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -5558,6 +5623,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -6114,6 +6183,9 @@ packages: resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} engines: {node: '>=20'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -6151,6 +6223,10 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -6260,6 +6336,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + mini-css-extract-plugin@2.10.0: resolution: {integrity: sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==} engines: {node: '>= 12.13.0'} @@ -6984,6 +7064,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@30.4.1: resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -7061,6 +7145,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -7089,6 +7176,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -7172,6 +7263,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfc4648@1.5.4: + resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -7664,6 +7758,10 @@ packages: resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} engines: {node: '>=18'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -7914,6 +8012,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -8168,6 +8269,11 @@ packages: yaml: optional: true + vitest-axe@0.1.0: + resolution: {integrity: sha512-jvtXxeQPg8R/2ANTY8QicA5pvvdRP4F0FsVUAHANJ46YCDASie/cuhlSzu0DGcLmZvGBSBNsNuK3HqfaeknyvA==} + peerDependencies: + vitest: '>=0.16.0' + vitest@4.1.9: resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -10757,6 +10863,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@fontsource-variable/noto-sans@5.2.10': {} + '@gar/promise-retry@1.0.3': {} '@gerrit0/mini-shiki@3.23.0': @@ -12885,6 +12993,26 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@testing-library/angular@19.4.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2))(@testing-library/dom@10.4.1)': + dependencies: + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/router': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@testing-library/dom': 10.4.1 + tslib: 2.8.1 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + '@tootallnate/once@2.0.1': {} '@ts-morph/common@0.22.0': @@ -12918,6 +13046,8 @@ snapshots: dependencies: tslib: 2.8.1 + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.7 @@ -13237,6 +13367,14 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true + '@utrecht/component-library-angular@3.0.2': + dependencies: + tslib: 2.6.3 + + '@utrecht/design-tokens@6.2.1': + dependencies: + '@fontsource-variable/noto-sans': 5.2.10 + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@20.19.9)(jiti@2.4.2)(less@4.4.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.97.3)(terser@5.46.0)(yaml@2.9.0))': dependencies: vite: 7.3.2(@types/node@20.19.9)(jiti@2.4.2)(less@4.4.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.97.3)(terser@5.46.0)(yaml@2.9.0) @@ -13503,6 +13641,15 @@ snapshots: '@algolia/requester-fetch': 5.48.1 '@algolia/requester-node-http': 5.48.1 + angular-auth-oidc-client@21.0.2(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2))(rxjs@7.8.2): + dependencies: + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/router': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + rfc4648: 1.5.4 + rxjs: 7.8.2 + tslib: 2.8.1 + angular-eslint@21.3.1(@angular/cli@21.2.7(@types/node@20.19.9)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.4.2))(typescript-eslint@8.62.1(eslint@9.39.4(jiti@2.4.2))(typescript@5.9.3))(typescript@5.9.3): dependencies: '@angular-devkit/core': 21.2.7(chokidar@5.0.0) @@ -13543,8 +13690,7 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: - optional: true + ansi-styles@5.2.0: {} ansi-styles@6.2.3: {} @@ -13562,6 +13708,10 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + aria-query@5.3.2: {} array-flatten@1.1.1: {} @@ -13602,6 +13752,8 @@ snapshots: postcss: 8.5.16 postcss-value-parser: 4.2.0 + axe-core@4.12.1: {} + axios@1.16.0: dependencies: follow-redirects: 1.16.0(debug@4.4.3) @@ -14329,6 +14481,8 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + destroy@1.2.0: {} detect-libc@2.1.2: {} @@ -14348,6 +14502,8 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 + dom-accessibility-api@0.5.16: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -15256,6 +15412,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -15960,6 +16118,8 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash-es@4.18.1: {} + lodash.debounce@4.0.8: {} lodash.memoize@4.1.2: {} @@ -15997,6 +16157,8 @@ snapshots: lunr@2.3.9: {} + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -16116,6 +16278,8 @@ snapshots: mimic-function@5.0.1: {} + min-indent@1.0.1: {} + mini-css-extract-plugin@2.10.0(webpack@5.105.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.6)): dependencies: schema-utils: 4.3.3 @@ -17035,6 +17199,12 @@ snapshots: prettier@3.9.4: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-format@30.4.1: dependencies: '@jest/schemas': 30.4.1 @@ -17110,6 +17280,8 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + react-is@17.0.2: {} + react-is@18.3.1: optional: true @@ -17144,6 +17316,11 @@ snapshots: readdirp@5.0.0: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reflect-metadata@0.2.2: {} regenerate-unicode-properties@10.2.2: @@ -17217,6 +17394,8 @@ snapshots: reusify@1.1.0: {} + rfc4648@1.5.4: {} + rfdc@1.4.1: {} rolldown@1.0.0-rc.16: @@ -17790,6 +17969,10 @@ snapshots: strip-final-newline@4.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} style-loader@3.3.4(webpack@5.105.2(@swc/core@1.15.8(@swc/helpers@0.5.18))(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.6)): @@ -18035,6 +18218,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.6.3: {} + tslib@2.8.1: {} tsyringe@4.10.0: @@ -18266,6 +18451,16 @@ snapshots: terser: 5.48.0 yaml: 2.9.0 + vitest-axe@0.1.0(vitest@4.1.9): + dependencies: + aria-query: 5.3.2 + axe-core: 4.12.1 + chalk: 5.6.2 + dom-accessibility-api: 0.5.16 + lodash-es: 4.18.1 + redent: 3.0.0 + vitest: 4.1.9(@types/node@20.19.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@22.1.0)(vite@8.0.9(@types/node@20.19.9)(esbuild@0.27.7)(jiti@2.4.2)(less@4.5.1)(sass-embedded@1.100.0)(sass@1.97.3)(terser@5.48.0)(yaml@2.9.0)) + vitest@4.1.9(@types/node@20.19.9)(@vitest/coverage-v8@4.1.9)(@vitest/ui@4.1.9)(jsdom@22.1.0)(vite@8.0.9(@types/node@20.19.9)(esbuild@0.27.7)(jiti@2.4.2)(less@4.5.1)(sass-embedded@1.100.0)(sass@1.97.3)(terser@5.48.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 diff --git a/tsconfig.base.json b/tsconfig.base.json index 32f79da..baae6c7 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -15,7 +15,9 @@ "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { - "api-client": ["./libs/api-client/src/index.ts"] + "api-client": ["./libs/api-client/src/index.ts"], + "ui": ["./libs/ui/src/index.ts"], + "auth": ["./libs/auth/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]