diff --git a/frontend/src/lib/components/ProgressRing.svelte b/frontend/src/lib/components/ProgressRing.svelte new file mode 100644 index 00000000..4b4c5d89 --- /dev/null +++ b/frontend/src/lib/components/ProgressRing.svelte @@ -0,0 +1,26 @@ + + +
+ + + + + + {percentage}% + +
diff --git a/frontend/src/lib/components/ProgressRing.svelte.spec.ts b/frontend/src/lib/components/ProgressRing.svelte.spec.ts new file mode 100644 index 00000000..8efc36e5 --- /dev/null +++ b/frontend/src/lib/components/ProgressRing.svelte.spec.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import ProgressRing from './ProgressRing.svelte'; + +afterEach(cleanup); + +describe('ProgressRing', () => { + it('renders the correct stroke-dasharray for 75%', async () => { + render(ProgressRing, { percentage: 75 }); + const arc = document.querySelector('circle.fill-arc') as SVGCircleElement | null; + expect(arc).not.toBeNull(); + // circumference = 2 * π * 7 ≈ 43.98; 75% of that ≈ 32.99 + const dasharray = arc!.getAttribute('stroke-dasharray') ?? ''; + const filled = parseFloat(dasharray.split(' ')[0]); + expect(filled).toBeCloseTo(32.99, 1); + }); + + it('renders a gray label when percentage is 0', async () => { + render(ProgressRing, { percentage: 0 }); + const label = page.getByText('0%'); + await expect.element(label).toBeInTheDocument(); + // Label should carry the gray class, not the mint class + const el = (await label.element()) as HTMLElement; + expect(el.className).toContain('text-gray-400'); + }); + + it('renders a mint-colored label when percentage is > 0', async () => { + render(ProgressRing, { percentage: 75 }); + const label = page.getByText('75%'); + await expect.element(label).toBeInTheDocument(); + const el = (await label.element()) as HTMLElement; + expect(el.className).toContain('text-accent'); + }); + + it('renders a fully filled arc for 100%', async () => { + render(ProgressRing, { percentage: 100 }); + const arc = document.querySelector('circle.fill-arc') as SVGCircleElement | null; + expect(arc).not.toBeNull(); + const dasharray = arc!.getAttribute('stroke-dasharray') ?? ''; + const filled = parseFloat(dasharray.split(' ')[0]); + expect(filled).toBeCloseTo(43.98, 1); + }); +});