Compare commits
2 Commits
worktree-c
...
feat/issue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379bc84e11 | ||
|
|
110da9b8b0 |
@@ -40,6 +40,26 @@ export default defineConfig(
|
|||||||
parser: ts.parser,
|
parser: ts.parser,
|
||||||
svelteConfig
|
svelteConfig
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// text-accent resolves to #a1dcd8 in light mode (1.52:1 on white — WCAG fail).
|
||||||
|
// layout.css documents it as decorative-only (borders, icon tints, bg fills).
|
||||||
|
// For any text label use text-primary or text-ink instead. This rule catches
|
||||||
|
// the pattern where text-accent appears inside a JavaScript string literal
|
||||||
|
// (e.g. conditional ternary class expressions in Svelte templates).
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'Literal[value=/\\btext-accent\\b/]',
|
||||||
|
message:
|
||||||
|
'text-accent is decorative-only (#a1dcd8 in light mode = 1.52:1 contrast — WCAG fail). Use text-primary or text-ink-2 for text labels.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'TemplateLiteral > TemplateElement[value.raw=/\\btext-accent\\b/]',
|
||||||
|
message:
|
||||||
|
'text-accent is decorative-only (#a1dcd8 in light mode = 1.52:1 contrast — WCAG fail). Use text-primary or text-ink-2 for text labels.'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ let {
|
|||||||
aria-label={showAnnotations ? m.pdf_annotations_hide() : m.pdf_annotations_show()}
|
aria-label={showAnnotations ? m.pdf_annotations_hide() : m.pdf_annotations_show()}
|
||||||
class="flex items-center gap-1.5 rounded px-2 py-1 font-sans text-xs transition {showAnnotations
|
class="flex items-center gap-1.5 rounded px-2 py-1 font-sans text-xs transition {showAnnotations
|
||||||
? 'text-ink-2 hover:bg-surface/10'
|
? 'text-ink-2 hover:bg-surface/10'
|
||||||
: 'bg-surface/10 text-accent'}"
|
: 'bg-surface/10 text-primary'}"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
class="h-3.5 w-3.5 shrink-0"
|
class="h-3.5 w-3.5 shrink-0"
|
||||||
|
|||||||
67
frontend/src/lib/components/PdfControls.svelte.spec.ts
Normal file
67
frontend/src/lib/components/PdfControls.svelte.spec.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { vi, describe, it, expect, afterEach } from 'vitest';
|
||||||
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
|
import { page } from 'vitest/browser';
|
||||||
|
|
||||||
|
import PdfControls from './PdfControls.svelte';
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
currentPage: 1,
|
||||||
|
totalPages: 3,
|
||||||
|
isLoaded: true,
|
||||||
|
showAnnotations: false,
|
||||||
|
annotationCount: 0,
|
||||||
|
onPrev: vi.fn(),
|
||||||
|
onNext: vi.fn(),
|
||||||
|
onZoomIn: vi.fn(),
|
||||||
|
onZoomOut: vi.fn(),
|
||||||
|
onToggleAnnotations: vi.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('PdfControls — annotation toggle visibility', () => {
|
||||||
|
it('renders annotation toggle when annotationCount is greater than zero', async () => {
|
||||||
|
render(PdfControls, { ...defaultProps, annotationCount: 3 });
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /annotierungen anzeigen/i }))
|
||||||
|
.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render annotation toggle when annotationCount is zero', async () => {
|
||||||
|
render(PdfControls, { ...defaultProps, annotationCount: 0 });
|
||||||
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: /annotierungen/i }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PdfControls — annotation toggle label', () => {
|
||||||
|
it('shows "Annotierungen anzeigen" label when annotations are hidden', async () => {
|
||||||
|
render(PdfControls, { ...defaultProps, annotationCount: 2, showAnnotations: false });
|
||||||
|
const btn = page.getByRole('button', { name: /annotierungen anzeigen/i });
|
||||||
|
await expect.element(btn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "Annotierungen verbergen" label when annotations are visible', async () => {
|
||||||
|
render(PdfControls, { ...defaultProps, annotationCount: 2, showAnnotations: true });
|
||||||
|
const btn = page.getByRole('button', { name: /annotierungen verbergen/i });
|
||||||
|
await expect.element(btn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PdfControls — annotation toggle contrast (WCAG 2.1 AA)', () => {
|
||||||
|
it('uses text-primary class on annotation toggle button when annotations are hidden', async () => {
|
||||||
|
const { container } = render(PdfControls, {
|
||||||
|
...defaultProps,
|
||||||
|
annotationCount: 2,
|
||||||
|
showAnnotations: false
|
||||||
|
});
|
||||||
|
const allButtons = container.querySelectorAll('button');
|
||||||
|
const annotationBtn = Array.from(allButtons).find((b) =>
|
||||||
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
||||||
|
);
|
||||||
|
expect(annotationBtn).not.toBeNull();
|
||||||
|
expect(annotationBtn!.className).toContain('text-primary');
|
||||||
|
expect(annotationBtn!.className).not.toContain('text-accent');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,7 +19,7 @@ let { percentage }: { percentage: number } = $props();
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span
|
<span
|
||||||
class="block text-center font-sans text-xs font-bold {percentage > 0 ? 'text-accent' : 'text-gray-400'}"
|
class="block text-center font-sans text-xs font-bold {percentage > 0 ? 'text-primary' : 'text-gray-400'}"
|
||||||
>
|
>
|
||||||
{percentage}%
|
{percentage}%
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ describe('ProgressRing', () => {
|
|||||||
expect(el.className).toContain('text-gray-400');
|
expect(el.className).toContain('text-gray-400');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a mint-colored label when percentage is > 0', async () => {
|
it('renders a primary-colored label when percentage is > 0', async () => {
|
||||||
render(ProgressRing, { percentage: 75 });
|
render(ProgressRing, { percentage: 75 });
|
||||||
const label = page.getByText('75%');
|
const label = page.getByText('75%');
|
||||||
await expect.element(label).toBeInTheDocument();
|
await expect.element(label).toBeInTheDocument();
|
||||||
const el = (await label.element()) as HTMLElement;
|
const el = (await label.element()) as HTMLElement;
|
||||||
expect(el.className).toContain('text-accent');
|
expect(el.className).toContain('text-primary');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a fully filled arc for 100%', async () => {
|
it('renders a fully filled arc for 100%', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user