import { render } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { IsotypeDiagonal } from './IsotypeDiagonal'; import { ISOTYPE_COLORS } from './patterns'; import React from 'react'; type Point = [number, number]; function shapeWithFill(container: HTMLElement, fill: string) { return container.querySelector(`[fill="${fill}"]`)!; } function rectCorners(rect: Element): Point[] { const x = Number(rect.getAttribute('x')); const y = Number(rect.getAttribute('y')); const width = Number(rect.getAttribute('width')); const height = Number(rect.getAttribute('height')); return [ [x, y], [x + width, y], [x + width, y + height], [x, y + height], ]; } function polygonPoints(polygon: Element): Point[] { return polygon .getAttribute('points')! .trim() .split(/\s+/) .map(pair => pair.split(',').map(Number) as Point); } function shapePoints(shape: Element): Point[] { return shape.tagName === 'rect' ? rectCorners(shape) : polygonPoints(shape); } function shapeEdges(shape: Element): [Point, Point][] { const points = shapePoints(shape); return points.map((point, i) => [point, points[(i + 1) % points.length]]); } /** True if `shape` has an edge between `a` and `b`, in either direction. */ function hasEdge(shape: Element, a: Point, b: Point): boolean { return shapeEdges(shape).some( ([p, q]) => (p[0] === a[0] && p[1] === a[1] && q[0] === b[0] && q[1] === b[1]) || (p[0] === b[0] && p[1] === b[1] && q[0] === a[0] && q[1] === a[1]) ); } describe('IsotypeDiagonal', () => { it('renders 4 rects (blue, red, purple, brown) and 4 polygons (green, magenta, yellow, blank)', () => { const { container } = render(); expect(container.querySelectorAll('rect')).toHaveLength(4); expect(container.querySelectorAll('polygon')).toHaveLength(4); }); it('uses every ISOTYPE_COLORS value exactly once as a fill', () => { const { container } = render(); Object.values(ISOTYPE_COLORS).forEach(color => { expect(container.querySelectorAll(`[fill="${color}"]`)).toHaveLength(1); }); }); it('renders exactly one shape with fill="none"', () => { const { container } = render(); expect(container.querySelectorAll('[fill="none"]')).toHaveLength(1); }); it('renders no shape with a stroke', () => { const { container } = render(); container.querySelectorAll('rect, polygon').forEach(shape => { expect(shape.getAttribute('stroke')).toBeNull(); }); }); it('passes className through to the root', () => { const { container } = render(); expect((container.firstChild as HTMLElement).className).toContain('w-64'); }); it('is decorative by default (aria-hidden, no role)', () => { const { container } = render(); const root = container.firstChild as HTMLElement; expect(root.getAttribute('aria-hidden')).toBe('true'); expect(root.getAttribute('role')).toBeNull(); }); it('renders as an image with a label when aria-label is set', () => { const { container } = render(); const root = container.firstChild as HTMLElement; expect(root.getAttribute('role')).toBe('img'); expect(root.getAttribute('aria-label')).toBe('Xertica isotype'); expect(root.getAttribute('aria-hidden')).toBeNull(); }); it('uses a 0 0 100 100 viewBox', () => { const { container } = render(); expect(container.querySelector('svg')!.getAttribute('viewBox')).toBe('0 0 100 100'); }); it('backs blue and red with full-width rectangles, wider than the triangular area they end up showing', () => { const { container } = render(); const blue = shapeWithFill(container, ISOTYPE_COLORS.blue); const red = shapeWithFill(container, ISOTYPE_COLORS.red); expect(blue.tagName).toBe('rect'); expect(red.tagName).toBe('rect'); expect(rectCorners(blue)).toEqual([ [0, 0], [100, 0], [100, 25], [0, 25], ]); expect(rectCorners(red)).toEqual([ [0, 75], [100, 75], [100, 100], [0, 100], ]); }); it('paints purple/green after blue, and magenta/brown after red, so they overlap their backing rectangle instead of abutting it', () => { const { container } = render(); const shapes = Array.from(container.querySelectorAll('rect, polygon')); const indexOf = (color: string) => shapes.indexOf(shapeWithFill(container, color)); expect(indexOf(ISOTYPE_COLORS.blue)).toBeLessThan(indexOf(ISOTYPE_COLORS.purple)); expect(indexOf(ISOTYPE_COLORS.blue)).toBeLessThan(indexOf(ISOTYPE_COLORS.green)); expect(indexOf(ISOTYPE_COLORS.red)).toBeLessThan(indexOf(ISOTYPE_COLORS.magenta)); expect(indexOf(ISOTYPE_COLORS.red)).toBeLessThan(indexOf(ISOTYPE_COLORS.brown)); }); it('keeps purple and brown fully within the blue/red rectangles they sit on top of', () => { const { container } = render(); const purple = shapeWithFill(container, ISOTYPE_COLORS.purple); const brown = shapeWithFill(container, ISOTYPE_COLORS.brown); expect(Number(purple.getAttribute('y'))).toBeGreaterThanOrEqual(0); expect(Number(purple.getAttribute('y')) + Number(purple.getAttribute('height'))).toBeLessThanOrEqual(25); expect(Number(brown.getAttribute('y'))).toBeGreaterThanOrEqual(75); expect(Number(brown.getAttribute('y')) + Number(brown.getAttribute('height'))).toBeLessThanOrEqual(100); }); it('keeps the untouched seams — purple/magenta, magenta/blank, yellow/green, and brown/green — matching exactly', () => { const { container } = render(); const purple = shapeWithFill(container, ISOTYPE_COLORS.purple); const magenta = shapeWithFill(container, ISOTYPE_COLORS.magenta); const blank = shapeWithFill(container, 'none'); const yellow = shapeWithFill(container, ISOTYPE_COLORS.yellow); const green = shapeWithFill(container, ISOTYPE_COLORS.green); const brown = shapeWithFill(container, ISOTYPE_COLORS.brown); expect(hasEdge(purple, [0, 25], [25, 25])).toBe(true); expect(hasEdge(magenta, [0, 25], [25, 25])).toBe(true); expect(hasEdge(magenta, [25, 25], [25, 75])).toBe(true); expect(hasEdge(blank, [25, 25], [25, 75])).toBe(true); expect(hasEdge(yellow, [75, 25], [75, 75])).toBe(true); expect(hasEdge(green, [75, 25], [75, 75])).toBe(true); expect(hasEdge(brown, [75, 75], [100, 75])).toBe(true); expect(hasEdge(green, [75, 75], [100, 75])).toBe(true); }); it('traces a single continuous diagonal through the blue/green tip, the magenta/red tip, and the yellow hypotenuse', () => { const diagonalVertices: Point[] = [ [100, 0], [0, 100], [75, 25], [25, 75], ]; diagonalVertices.forEach(([x, y]) => { expect(x + y).toBe(100); }); }); });