diff --git a/api/jest.config.cts b/api/jest.config.cts index b892916..b0402c5 100644 --- a/api/jest.config.cts +++ b/api/jest.config.cts @@ -3,7 +3,10 @@ module.exports = { preset: '../jest.preset.js', testEnvironment: 'node', transform: { - '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + '^.+\\.[tj]s$': ['ts-jest', { + tsconfig: '/tsconfig.spec.json', + isolatedModules: true, + }], }, moduleFileExtensions: ['ts', 'js', 'html'], coverageDirectory: '../coverage/api', diff --git a/api/src/app/app.ts b/api/src/app/app.ts index b248ef1..bdaee04 100644 --- a/api/src/app/app.ts +++ b/api/src/app/app.ts @@ -57,5 +57,13 @@ export async function app(fastify: FastifyInstance, opts: AppOptions) { fastify.register(AutoLoad, { dir: path.join(__dirname, 'routes'), options: { ...opts, prefix: '/api' }, + ignorePattern: /root\.ts$/, + }); + + // Register root route without prefix + fastify.register(AutoLoad, { + dir: path.join(__dirname, 'routes'), + options: { ...opts }, + matchFilter: /root\.ts$/, }); } diff --git a/api/src/app/routes/root.ts b/api/src/app/routes/root.ts index 923ea65..1a2aeb7 100644 --- a/api/src/app/routes/root.ts +++ b/api/src/app/routes/root.ts @@ -1,7 +1,30 @@ import { FastifyInstance } from 'fastify'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +interface PackageJson { + version: string; +} + +let cachedVersion: string | null = null; + +function getVersion(): string { + if (cachedVersion) { + return cachedVersion; + } + + try { + const packageJsonPath = join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as PackageJson; + cachedVersion = packageJson.version; + return cachedVersion; + } catch { + return 'unknown'; + } +} export default async function (fastify: FastifyInstance) { fastify.get('/', async function () { - return { message: 'Hello API' }; + return { version: getVersion() }; }); } diff --git a/api/src/test-setup.ts b/api/src/test-setup.ts index 944fa54..a394aba 100644 --- a/api/src/test-setup.ts +++ b/api/src/test-setup.ts @@ -12,4 +12,36 @@ process.env.DOMAIN = 'http://localhost:3000'; process.env.API_URL = 'http://localhost:3000/api'; process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test'; process.env.BASE_URL = 'http://localhost:4200'; -process.env.NODE_ENV = 'test'; \ No newline at end of file +process.env.NODE_ENV = 'test'; + +// Mock ESM packages to avoid import issues in Jest +jest.mock('jsdom', () => ({ + JSDOM: class { + window = { + document: { + createElement: jest.fn(() => ({})), + }, + }; + }, +})); + +jest.mock('marked', () => ({ + marked: jest.fn((input: string) => `

${input}

`), +})); + +jest.mock('dompurify', () => { + const mockDOMPurify = { + sanitize: jest.fn((input: string) => input), + addHook: jest.fn(), + }; + const createDOMPurify = jest.fn(() => mockDOMPurify); + return createDOMPurify; +}); + +jest.mock('@nhcarrigan/logger', () => ({ + Logger: class { + log = jest.fn().mockResolvedValue(undefined); + error = jest.fn().mockResolvedValue(undefined); + metric = jest.fn().mockResolvedValue(undefined); + }, +})); \ No newline at end of file diff --git a/api/tsconfig.app.json b/api/tsconfig.app.json index 43114b8..2d3bdf4 100644 --- a/api/tsconfig.app.json +++ b/api/tsconfig.app.json @@ -10,6 +10,7 @@ "jest.config.ts", "jest.config.cts", "src/**/*.spec.ts", - "src/**/*.test.ts" + "src/**/*.test.ts", + "src/test-setup.ts" ] } diff --git a/apps/frontend/src/app/components/header/header.component.ts b/apps/frontend/src/app/components/header/header.component.ts index 0741dd8..6801e87 100644 --- a/apps/frontend/src/app/components/header/header.component.ts +++ b/apps/frontend/src/app/components/header/header.component.ts @@ -42,6 +42,10 @@ import { ApiService } from '../../services/api.service'; [alt]="user.username" class="user-avatar" (click)="toggleDropdown()" + (keyup.enter)="toggleDropdown()" + (keyup.space)="toggleDropdown()" + tabindex="0" + role="button" /> } @if (showDropdown()) { diff --git a/apps/frontend/src/app/components/toast/toast.component.html b/apps/frontend/src/app/components/toast/toast.component.html index 8ed978b..299026e 100644 --- a/apps/frontend/src/app/components/toast/toast.component.html +++ b/apps/frontend/src/app/components/toast/toast.component.html @@ -6,7 +6,14 @@
@for (toast of toastService.toastList(); track toast.id) { -
+
@switch (toast.type) { @case ('error') { ❌ } diff --git a/shared-types/test/book.types.spec.ts b/shared-types/test/book.types.spec.ts index c626932..7d8c813 100644 --- a/shared-types/test/book.types.spec.ts +++ b/shared-types/test/book.types.spec.ts @@ -17,10 +17,11 @@ describe("book Types", () => { it("should have all expected enum values", () => { const values = Object.values(BookStatus); - expect(values).toHaveLength(3); + expect(values).toHaveLength(4); expect(values).toContain("READING"); expect(values).toContain("FINISHED"); expect(values).toContain("TO_READ"); + expect(values).toContain("RETIRED"); }); }); diff --git a/shared-types/test/game.types.spec.ts b/shared-types/test/game.types.spec.ts index a8c89f2..3597a65 100644 --- a/shared-types/test/game.types.spec.ts +++ b/shared-types/test/game.types.spec.ts @@ -17,10 +17,11 @@ describe("game Types", () => { it("should have all expected enum values", () => { const values = Object.values(GameStatus); - expect(values).toHaveLength(3); + expect(values).toHaveLength(4); expect(values).toContain("PLAYING"); expect(values).toContain("COMPLETED"); expect(values).toContain("BACKLOG"); + expect(values).toContain("RETIRED"); }); }); diff --git a/shared-types/test/manga.types.spec.ts b/shared-types/test/manga.types.spec.ts index 0753302..c9180b5 100644 --- a/shared-types/test/manga.types.spec.ts +++ b/shared-types/test/manga.types.spec.ts @@ -17,10 +17,11 @@ describe("manga Types", () => { it("should have all expected enum values", () => { const values = Object.values(MangaStatus); - expect(values).toHaveLength(3); + expect(values).toHaveLength(4); expect(values).toContain("READING"); expect(values).toContain("COMPLETED"); expect(values).toContain("WANT_TO_READ"); + expect(values).toContain("RETIRED"); }); }); diff --git a/shared-types/test/music.types.spec.ts b/shared-types/test/music.types.spec.ts index 913b11f..f381edc 100644 --- a/shared-types/test/music.types.spec.ts +++ b/shared-types/test/music.types.spec.ts @@ -33,10 +33,11 @@ describe("music Types", () => { it("should have all expected enum values", () => { const values = Object.values(MusicStatus); - expect(values).toHaveLength(3); + expect(values).toHaveLength(4); expect(values).toContain("LISTENING"); expect(values).toContain("COMPLETED"); expect(values).toContain("WANT_TO_LISTEN"); + expect(values).toContain("RETIRED"); }); }); diff --git a/shared-types/test/show.types.spec.ts b/shared-types/test/show.types.spec.ts index a1732d1..39f3eea 100644 --- a/shared-types/test/show.types.spec.ts +++ b/shared-types/test/show.types.spec.ts @@ -35,10 +35,11 @@ describe("show Types", () => { it("should have all expected enum values", () => { const values = Object.values(ShowStatus); - expect(values).toHaveLength(3); + expect(values).toHaveLength(4); expect(values).toContain("WATCHING"); expect(values).toContain("COMPLETED"); expect(values).toContain("WANT_TO_WATCH"); + expect(values).toContain("RETIRED"); }); });