test(DateInput): add Vitest specs for DateInput component and date utils
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 4m3s
CI / Backend Unit Tests (pull_request) Failing after 2m24s
CI / E2E Tests (pull_request) Failing after 1h46m24s
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 4m3s
CI / Backend Unit Tests (pull_request) Failing after 2m24s
CI / E2E Tests (pull_request) Failing after 1h46m24s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
206
frontend/src/lib/components/DateInput.svelte.spec.ts
Normal file
206
frontend/src/lib/components/DateInput.svelte.spec.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { describe, expect, it, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import DateInput from './DateInput.svelte';
|
||||
|
||||
/** Wait one macrotask so Svelte can flush reactive DOM updates. */
|
||||
const domFlush = () => new Promise<void>((r) => setTimeout(r, 50));
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
// ─── Rendering ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – rendering', () => {
|
||||
it('renders a text input with inputmode=numeric and maxlength=10', async () => {
|
||||
render(DateInput, {});
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toBeInTheDocument();
|
||||
await expect.element(input).toHaveAttribute('inputmode', 'numeric');
|
||||
await expect.element(input).toHaveAttribute('maxlength', '10');
|
||||
});
|
||||
|
||||
it('has default placeholder from paraglide', async () => {
|
||||
render(DateInput, {});
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveAttribute('placeholder', 'TT.MM.JJJJ');
|
||||
});
|
||||
|
||||
it('accepts a custom placeholder', async () => {
|
||||
render(DateInput, { placeholder: 'Geburtsdatum' });
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveAttribute('placeholder', 'Geburtsdatum');
|
||||
});
|
||||
|
||||
it('passes id prop to the input', async () => {
|
||||
render(DateInput, { id: 'my-date' });
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveAttribute('id', 'my-date');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Init from value ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – init from value', () => {
|
||||
it('displays ISO value in German format on mount', async () => {
|
||||
render(DateInput, { value: '2024-12-20' });
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveValue('20.12.2024');
|
||||
});
|
||||
|
||||
it('starts empty and error-free when no value is given', async () => {
|
||||
let errorMessage: string | null = null;
|
||||
render(DateInput, {
|
||||
get errorMessage() {
|
||||
return errorMessage;
|
||||
},
|
||||
set errorMessage(v) {
|
||||
errorMessage = v;
|
||||
}
|
||||
});
|
||||
const input = page.getByRole('textbox');
|
||||
await expect.element(input).toHaveValue('');
|
||||
expect(errorMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Typing valid date ────────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – typing a valid date', () => {
|
||||
it('auto-formats to DD.MM.YYYY and updates value to ISO', async () => {
|
||||
let value = '';
|
||||
let errorMessage: string | null = null;
|
||||
render(DateInput, {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(v) {
|
||||
value = v;
|
||||
},
|
||||
get errorMessage() {
|
||||
return errorMessage;
|
||||
},
|
||||
set errorMessage(v) {
|
||||
errorMessage = v;
|
||||
}
|
||||
});
|
||||
const input = page.getByRole('textbox');
|
||||
await input.fill('20122024');
|
||||
await expect.element(input).toHaveValue('20.12.2024');
|
||||
expect(value).toBe('2024-12-20');
|
||||
expect(errorMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Typing invalid month ─────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – typing a date with invalid month', () => {
|
||||
it('sets errorMessage and clears value when month > 12', async () => {
|
||||
let value = '';
|
||||
let errorMessage: string | null = null;
|
||||
render(DateInput, {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(v) {
|
||||
value = v;
|
||||
},
|
||||
get errorMessage() {
|
||||
return errorMessage;
|
||||
},
|
||||
set errorMessage(v) {
|
||||
errorMessage = v;
|
||||
}
|
||||
});
|
||||
const input = page.getByRole('textbox');
|
||||
await input.fill('22222222');
|
||||
await expect.element(input).toHaveValue('22.22.2222');
|
||||
expect(value).toBe('');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Typing partial date ──────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – typing a partial date', () => {
|
||||
it('sets errorMessage and clears value when date is incomplete', async () => {
|
||||
let value = '';
|
||||
let errorMessage: string | null = null;
|
||||
render(DateInput, {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(v) {
|
||||
value = v;
|
||||
},
|
||||
get errorMessage() {
|
||||
return errorMessage;
|
||||
},
|
||||
set errorMessage(v) {
|
||||
errorMessage = v;
|
||||
}
|
||||
});
|
||||
const input = page.getByRole('textbox');
|
||||
await input.fill('2212');
|
||||
await expect.element(input).toHaveValue('22.12');
|
||||
expect(value).toBe('');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Clearing date ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – clearing the date', () => {
|
||||
it('resets value and errorMessage to null when cleared', async () => {
|
||||
let value = '';
|
||||
let errorMessage: string | null = null;
|
||||
render(DateInput, {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(v) {
|
||||
value = v;
|
||||
},
|
||||
get errorMessage() {
|
||||
return errorMessage;
|
||||
},
|
||||
set errorMessage(v) {
|
||||
errorMessage = v;
|
||||
}
|
||||
});
|
||||
const input = page.getByRole('textbox');
|
||||
// Type a valid date first
|
||||
await input.fill('20122024');
|
||||
expect(value).toBe('2024-12-20');
|
||||
// Now clear
|
||||
await input.fill('');
|
||||
expect(value).toBe('');
|
||||
expect(errorMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Hidden input ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('DateInput – hidden input for form submission', () => {
|
||||
it('renders a hidden input with the given name when name prop is set', async () => {
|
||||
render(DateInput, { name: 'documentDate' });
|
||||
const hidden = document.querySelector('input[type="hidden"][name="documentDate"]');
|
||||
expect(hidden).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not render a hidden input when name prop is absent', async () => {
|
||||
render(DateInput, {});
|
||||
const hidden = document.querySelector('input[type="hidden"]');
|
||||
expect(hidden).toBeNull();
|
||||
});
|
||||
|
||||
it('hidden input value reflects the ISO value', async () => {
|
||||
render(DateInput, { name: 'documentDate', value: '' });
|
||||
const input = page.getByRole('textbox');
|
||||
await input.fill('20122024');
|
||||
await domFlush();
|
||||
const hidden = document.querySelector<HTMLInputElement>(
|
||||
'input[type="hidden"][name="documentDate"]'
|
||||
);
|
||||
expect(hidden?.value).toBe('2024-12-20');
|
||||
});
|
||||
});
|
||||
109
frontend/src/lib/utils/date.spec.ts
Normal file
109
frontend/src/lib/utils/date.spec.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatGermanDateInput, isoToGerman, germanToIso } from './date';
|
||||
|
||||
// ─── isoToGerman ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('isoToGerman', () => {
|
||||
it('converts a valid ISO date to DD.MM.YYYY', () => {
|
||||
expect(isoToGerman('2024-12-20')).toBe('20.12.2024');
|
||||
});
|
||||
|
||||
it('returns empty string for empty input', () => {
|
||||
expect(isoToGerman('')).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty string for invalid format', () => {
|
||||
expect(isoToGerman('not-a-date')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── germanToIso ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('germanToIso', () => {
|
||||
it('converts DD.MM.YYYY to ISO', () => {
|
||||
expect(germanToIso('20.12.2024')).toBe('2024-12-20');
|
||||
});
|
||||
|
||||
it('returns empty string for partial input', () => {
|
||||
expect(germanToIso('20.12')).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty string for empty input', () => {
|
||||
expect(germanToIso('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── formatGermanDateInput ────────────────────────────────────────────────────
|
||||
|
||||
describe('formatGermanDateInput – digit stream (no dots typed)', () => {
|
||||
it('leaves 1–2 digits as-is', () => {
|
||||
expect(formatGermanDateInput('2')).toBe('2');
|
||||
expect(formatGermanDateInput('20')).toBe('20');
|
||||
});
|
||||
|
||||
it('auto-inserts dot after 2 digits for 3–4 digit input', () => {
|
||||
expect(formatGermanDateInput('201')).toBe('20.1');
|
||||
expect(formatGermanDateInput('2012')).toBe('20.12');
|
||||
});
|
||||
|
||||
it('auto-inserts two dots for 5–8 digit input', () => {
|
||||
expect(formatGermanDateInput('20121')).toBe('20.12.1');
|
||||
expect(formatGermanDateInput('20122024')).toBe('20.12.2024');
|
||||
});
|
||||
|
||||
it('ignores digits beyond 8', () => {
|
||||
expect(formatGermanDateInput('201220249')).toBe('20.12.2024');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatGermanDateInput – manual dot entry with padding', () => {
|
||||
it('pads single-digit day to 2 digits when dot is typed after it', () => {
|
||||
expect(formatGermanDateInput('3.')).toBe('03.');
|
||||
});
|
||||
|
||||
it('does not pad a 2-digit day', () => {
|
||||
expect(formatGermanDateInput('03.')).toBe('03.');
|
||||
expect(formatGermanDateInput('20.')).toBe('20.');
|
||||
});
|
||||
|
||||
it('pads single-digit month to 2 digits when dot is typed after it', () => {
|
||||
expect(formatGermanDateInput('03.3.')).toBe('03.03.');
|
||||
});
|
||||
|
||||
it('does not pad a 2-digit month', () => {
|
||||
expect(formatGermanDateInput('03.12.')).toBe('03.12.');
|
||||
});
|
||||
|
||||
it('pads both day and month in a fully typed date', () => {
|
||||
expect(formatGermanDateInput('3.3.2012')).toBe('03.03.2012');
|
||||
});
|
||||
|
||||
it('pads only day when month is already 2 digits', () => {
|
||||
expect(formatGermanDateInput('3.12.2024')).toBe('03.12.2024');
|
||||
});
|
||||
|
||||
it('pads only month when day is already 2 digits', () => {
|
||||
expect(formatGermanDateInput('20.3.2024')).toBe('20.03.2024');
|
||||
});
|
||||
|
||||
it('handles a complete date entered with manual dots and no padding needed', () => {
|
||||
expect(formatGermanDateInput('20.12.2024')).toBe('20.12.2024');
|
||||
});
|
||||
|
||||
it('overflows excess day digits into month when dot follows', () => {
|
||||
expect(formatGermanDateInput('123.')).toBe('12.3');
|
||||
});
|
||||
|
||||
it('caps year digits at 4', () => {
|
||||
expect(formatGermanDateInput('03.03.20249')).toBe('03.03.2024');
|
||||
});
|
||||
|
||||
it('overflows excess month digits into year (digit stream then continue typing)', () => {
|
||||
// User typed digits → auto-dot gave "20.12", then types "2" → raw becomes "20.122"
|
||||
expect(formatGermanDateInput('20.122')).toBe('20.12.2');
|
||||
});
|
||||
|
||||
it('continues building year after overflow', () => {
|
||||
expect(formatGermanDateInput('20.12.2024')).toBe('20.12.2024');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user