Skip to content

Commit de1687d

Browse files
authored
Increase test coverage (#41)
* test: add more tests * fix: remove deprecated files
1 parent 916153e commit de1687d

20 files changed

Lines changed: 1072 additions & 292 deletions

File tree

.github/workflows/frontend-CI.yml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,26 @@ jobs:
4747
# pnpm install playwright
4848
pnpm exec playwright install --with-deps
4949
pnpm test
50+
51+
- name: Run unit tests (Vitest)
52+
run: |
53+
cd src/frontend
54+
pnpm test:unit
55+
56+
- name: Generate unit test coverage
57+
run: |
58+
cd src/frontend
59+
pnpm coverage
60+
61+
- uses: actions/upload-artifact@v4
62+
if: ${{ !cancelled() }}
63+
with:
64+
name: vitest-coverage
65+
path: src/frontend/coverage/
66+
retention-days: 30
5067
- uses: actions/upload-artifact@v4
5168
if: ${{ !cancelled() }}
5269
with:
5370
name: playwright-report
54-
path: playwright-report/
55-
retention-days: 30
71+
path: src/frontend/playwright-report/
72+
retention-days: 30

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,6 @@ cython_debug/
166166
!/src/frontend/src/lib/
167167
/src/frontend/node_modules
168168
/src/frontend/.svelte-kit
169-
/src/frontend/build
169+
/src/frontend/build
170+
/src/frontend/coverage
171+
temp

src/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@sveltejs/adapter-node": "^5.4.0",
2929
"@sveltejs/kit": "^2.53.3",
3030
"@sveltejs/vite-plugin-svelte": "^6.1.0",
31+
"@vitest/coverage-v8": "^3.2.4",
3132
"autoprefixer": "^10.4.21",
3233
"dotenv": "^17.2.1",
3334
"drizzle-kit": "^0.31.8",

src/frontend/playwright.config.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import type { PlaywrightTestConfig } from '@playwright/test';
22

33
const config: PlaywrightTestConfig = {
44
webServer: {
5-
// command: 'pnpm run build && pnpm run preview',
6-
// running the tests in dev to avoid db connection issues
7-
command: 'pnpm run dev',
8-
port: 5173,
9-
reuseExistingServer:true
5+
// Tier 1: run the real production build, but skip DB calls via BUILD_MODE=true
6+
command: 'pnpm run build && node dist',
7+
url: 'http://localhost:3000',
8+
reuseExistingServer: false,
9+
env: {
10+
BUILD_MODE: 'true',
11+
PORT: '3000'
12+
}
1013
},
11-
testDir: './tests'
14+
testDir: './tests',
15+
use: {
16+
baseURL: 'http://localhost:3000'
17+
}
1218
};
1319

1420
export default config;

src/frontend/pnpm-lock.yaml

Lines changed: 839 additions & 206 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { _ } from './index';
3+
4+
describe('i18n translation helper', () => {
5+
it('returns the German value for a known nested key', () => {
6+
expect(_('homepage.emailCommunication.title', 'de')).toBe(
7+
'Kommunikation mittels Email'
8+
);
9+
});
10+
11+
it('returns the French value for a known nested key', () => {
12+
expect(_('homepage.emailCommunication.title', 'fr')).toBe(
13+
'Communication par mail'
14+
);
15+
});
16+
17+
it('returns a deterministic missing marker when the key does not exist', () => {
18+
// `_()` logs a warning when falling back fails; keep test output clean.
19+
const warnSpy = vi.spyOn(console, 'warn');
20+
warnSpy.mockImplementation(() => {});
21+
expect(_('homepage.does.not.exist.title', 'de')).toBe(
22+
'[Missing translation: homepage.does.not.exist.title]'
23+
);
24+
warnSpy.mockRestore();
25+
});
26+
});
27+
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
3+
let drizzleImpl: any;
4+
let drizzleMockFn: any;
5+
let createPoolFn: any;
6+
7+
vi.mock('drizzle-orm/mysql2', () => {
8+
drizzleImpl = vi.fn((pool: unknown) => ({ __realDb: true, pool }));
9+
drizzleMockFn = vi.fn(() => ({ __mockDb: true }));
10+
11+
// Important: keep `drizzle` as a normal function so we can attach a real `.mock()` method
12+
// (Vitest's `vi.fn` already has a `.mock` getter used for call metadata).
13+
const drizzle: any = (pool: unknown) => drizzleImpl(pool);
14+
drizzle.mock = drizzleMockFn;
15+
16+
return { drizzle };
17+
});
18+
19+
vi.mock('mysql2/promise', () => {
20+
createPoolFn = vi.fn(() => ({ __pool: true }));
21+
return { default: { createPool: createPoolFn } };
22+
});
23+
24+
describe('db module BUILD_MODE branching', () => {
25+
beforeEach(() => {
26+
vi.resetModules();
27+
delete process.env.BUILD_MODE;
28+
29+
if (typeof drizzleImpl?.mockClear === 'function') drizzleImpl.mockClear();
30+
if (typeof drizzleMockFn?.mockClear === 'function') drizzleMockFn.mockClear();
31+
if (typeof createPoolFn?.mockClear === 'function') createPoolFn.mockClear();
32+
});
33+
34+
it('uses drizzle.mock() and does not create a MySQL pool in BUILD_MODE=true', async () => {
35+
process.env.BUILD_MODE = 'true';
36+
37+
const mod = await import('./db');
38+
expect(mod.db).toEqual({ __mockDb: true });
39+
expect(drizzleMockFn).toHaveBeenCalledTimes(1);
40+
expect(createPoolFn).not.toHaveBeenCalled();
41+
});
42+
43+
it('creates a MySQL pool and uses drizzle(pool) in BUILD_MODE=false', async () => {
44+
process.env.BUILD_MODE = 'false';
45+
process.env.MYSQL_HOST = 'localhost';
46+
process.env.MYSQL_PORT = '3307';
47+
process.env.MYSQL_USER = 'user';
48+
process.env.MYSQL_PASSWORD = 'pass';
49+
process.env.MYSQL_DATABASE = 'db';
50+
51+
const mod = await import('./db');
52+
expect(mod.db).toEqual({ __realDb: true, pool: { __pool: true } });
53+
54+
expect(createPoolFn).toHaveBeenCalledTimes(1);
55+
const poolArgs = createPoolFn.mock.calls[0][0] as Record<string, unknown>;
56+
expect(poolArgs.host).toBe('localhost');
57+
expect(poolArgs.port).toBe(3307);
58+
expect(poolArgs.user).toBe('user');
59+
expect(poolArgs.password).toBe('pass');
60+
expect(poolArgs.database).toBe('db');
61+
62+
expect(drizzleImpl).toHaveBeenCalledTimes(1);
63+
});
64+
65+
it('uses default MySQL port 3306 when MYSQL_PORT is not set', async () => {
66+
process.env.BUILD_MODE = 'false';
67+
68+
process.env.MYSQL_HOST = 'localhost';
69+
// Intentionally unset MYSQL_PORT to cover the default branch.
70+
delete process.env.MYSQL_PORT;
71+
process.env.MYSQL_USER = 'user';
72+
process.env.MYSQL_PASSWORD = 'pass';
73+
process.env.MYSQL_DATABASE = 'db';
74+
75+
const mod = await import('./db');
76+
expect(mod.db).toEqual({ __realDb: true, pool: { __pool: true } });
77+
78+
expect(createPoolFn).toHaveBeenCalledTimes(1);
79+
const poolArgs = createPoolFn.mock.calls[0][0] as Record<string, unknown>;
80+
expect(poolArgs.port).toBe(3306);
81+
});
82+
});
83+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { donationsTable, newsletterTable } from './schema';
3+
4+
describe('database schema exports', () => {
5+
it('exports donationsTable with expected columns', () => {
6+
// We don't assert Drizzle internals deeply; we just ensure the module executes
7+
// and the expected column keys exist.
8+
expect(Object.keys(donationsTable).length).toBeGreaterThan(0);
9+
expect((donationsTable as any).donations).toBeUndefined();
10+
expect((donationsTable as any).gender).toBeDefined();
11+
expect((donationsTable as any).email).toBeDefined();
12+
});
13+
14+
it('exports newsletterTable with expected columns', () => {
15+
expect(Object.keys(newsletterTable).length).toBeGreaterThan(0);
16+
expect((newsletterTable as any).email).toBeDefined();
17+
});
18+
});
19+

src/frontend/src/routes/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { t } from '$lib/i18n';
44
</script>
55

6-
<div class="flex justify-center items-center flex-col p-8 mb-auto">
6+
<div data-testid="page-home" class="flex justify-center items-center flex-col p-8 mb-auto">
77

88
<div data-testid="card" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 w-full max-w-7xl">
99
<Card class="max-w-[500px] min-w-[300px] p-1 bg-custom-100 border-primary-900" href="/about" img="/images/pexels-suzyhazelwood-1768060.jpg">

src/frontend/src/routes/about/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { t } from '$lib/i18n';
44
</script>
55

6-
<main class='p-8 mb-auto'>
6+
<main class='p-8 mb-auto' data-testid="page-about">
77
<div class="flex justify-center flex-col p-8 mb-auto rounded-lg" style="background-color: rgba(254, 242, 242, 0.6);">
88

99
<h1 class="mb-4 font-extrabold text-center leading-none tracking-tight text-4xl">{$t.about.title}</h1>

0 commit comments

Comments
 (0)