ecff144449
- BRAND_OLIVE_MIX (54 %, #363636) und BRAND_OLIVE_FILL in brand.ts, Kalibrierung kommentiert - vier achsenparallele Kacheln in tessera-logo.tsx: Inline-Style BRAND_OLIVE_FILL, festes Oliv-Attribut als Rückfall - brand.test.ts (neu): rechnet Kalibrierung, Neutralität und Grenzfälle aus denselben Konstanten nach - tessera-logo.test.tsx: Kacheltest auf Token-Fuellung (1x) und abgeleitete Fuellung (4x) umgestellt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
140 lines
5.3 KiB
TypeScript
140 lines
5.3 KiB
TypeScript
import { cleanup, render, screen } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { BRAND_OLIVE, BRAND_OLIVE_FILL, BRAND_YELLOW } from './brand';
|
|
import { TesseraLogo } from './tessera-logo';
|
|
import de from '@/messages/de.json';
|
|
import en from '@/messages/en.json';
|
|
|
|
vi.mock('next-intl', () => ({
|
|
useTranslations: (ns: string) => (key: string) => {
|
|
const map: Record<string, Record<string, string>> = {
|
|
common: {
|
|
appName: 'Tessera',
|
|
'brand.logoAlt': 'Tessera Logo',
|
|
},
|
|
};
|
|
return map[ns]?.[key] ?? key;
|
|
},
|
|
}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
function flattenKeys(obj: unknown, prefix = ''): string[] {
|
|
if (obj === null || typeof obj !== 'object') {
|
|
return [prefix];
|
|
}
|
|
return Object.entries(obj as Record<string, unknown>).flatMap(([key, value]) =>
|
|
flattenKeys(value, prefix ? `${prefix}.${key}` : key),
|
|
);
|
|
}
|
|
|
|
describe('TesseraLogo', () => {
|
|
it('variant "mark" renders an img-role element with the translated accessible name and no visible wordmark', () => {
|
|
render(<TesseraLogo variant="mark" />);
|
|
|
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
|
expect(mark).toBeInTheDocument();
|
|
expect(screen.queryByText('Tessera')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('variant "horizontal" renders the img-role mark and the visible wordmark text', () => {
|
|
render(<TesseraLogo variant="horizontal" />);
|
|
|
|
expect(screen.getByRole('img', { name: 'Tessera Logo' })).toBeInTheDocument();
|
|
expect(screen.getByText('Tessera')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders no tagline unless one is passed', () => {
|
|
render(<TesseraLogo variant="horizontal" />);
|
|
|
|
// Hand-geschriebener Erwartungstext, bewusst NICHT aus derselben Quelle
|
|
// gebildet, aus der die Komponente ihn zoege.
|
|
expect(
|
|
screen.queryByText('Modulare Workflow-Plattform fuer Ihr Unternehmen'),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('places mark, wordmark and tagline in that document order when a tagline is passed', () => {
|
|
render(
|
|
<TesseraLogo
|
|
variant="horizontal"
|
|
tagline="Modulare Workflow-Plattform fuer Ihr Unternehmen"
|
|
/>,
|
|
);
|
|
|
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
|
const wordmark = screen.getByText('Tessera');
|
|
const tagline = screen.getByText(
|
|
'Modulare Workflow-Plattform fuer Ihr Unternehmen',
|
|
);
|
|
|
|
expect(mark).toBeInTheDocument();
|
|
// Node.compareDocumentPosition: DOCUMENT_POSITION_FOLLOWING === 4.
|
|
// Prueft die tatsaechliche Reihenfolge im Baum — Bildmarke vor Schriftzug,
|
|
// Schriftzug vor Untertitel — statt nur deren Vorhandensein.
|
|
expect(mark.compareDocumentPosition(wordmark) & 4).toBe(4);
|
|
expect(wordmark.compareDocumentPosition(tagline) & 4).toBe(4);
|
|
});
|
|
|
|
it('enables the dark-mode plate outline class by default', () => {
|
|
render(<TesseraLogo variant="mark" />);
|
|
|
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
|
const plate = mark.querySelector('rect');
|
|
expect(plate?.getAttribute('class')).toContain('dark:stroke-white/25');
|
|
});
|
|
|
|
it('omits the dark-mode plate outline class when plateOutline is false', () => {
|
|
render(<TesseraLogo variant="mark" plateOutline={false} />);
|
|
|
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
|
const plate = mark.querySelector('rect');
|
|
expect(plate?.getAttribute('class')).not.toContain('dark:stroke-white/25');
|
|
});
|
|
|
|
it('renders exactly five tiles; the rotated one takes the accent token, the other four the derived olive tone with the fixed olive as fallback', () => {
|
|
render(<TesseraLogo variant="mark" />);
|
|
|
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
|
const tiles = Array.from(mark.querySelectorAll('g rect')) as SVGRectElement[];
|
|
expect(tiles).toHaveLength(5);
|
|
|
|
const accentTiles = tiles.filter((tile) => tile.style.fill === `var(--primary, ${BRAND_YELLOW})`);
|
|
expect(accentTiles).toHaveLength(1);
|
|
expect(accentTiles[0].hasAttribute('transform')).toBe(true);
|
|
|
|
const rotatedTiles = tiles.filter((tile) => tile.hasAttribute('transform'));
|
|
expect(rotatedTiles).toHaveLength(1);
|
|
|
|
const presentationYellowTiles = tiles.filter((tile) => tile.getAttribute('fill') === BRAND_YELLOW);
|
|
expect(presentationYellowTiles).toHaveLength(0);
|
|
|
|
// BRAND_OLIVE_FILL haengt selbst am --primary-Token — Zusicherung, dass
|
|
// die vier Kacheln wirklich daran haengen und nicht an einer festen Farbe.
|
|
expect(BRAND_OLIVE_FILL).toContain('var(--primary');
|
|
|
|
const derivedOliveTiles = tiles.filter((tile) => tile.style.fill === BRAND_OLIVE_FILL);
|
|
expect(derivedOliveTiles).toHaveLength(4);
|
|
for (const tile of derivedOliveTiles) {
|
|
expect(tile.getAttribute('fill')).toBe(BRAND_OLIVE);
|
|
expect(tile.hasAttribute('transform')).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('common.brand i18n namespace — de/en key parity', () => {
|
|
it('both locales define common.brand.logoAlt', () => {
|
|
expect(de.common).toHaveProperty('brand.logoAlt');
|
|
expect(en.common).toHaveProperty('brand.logoAlt');
|
|
});
|
|
|
|
it('has an identical (recursively-flattened) key set under common.brand in de and en', () => {
|
|
const deKeys = flattenKeys((de.common as { brand: unknown }).brand).sort();
|
|
const enKeys = flattenKeys((en.common as { brand: unknown }).brand).sort();
|
|
|
|
expect(deKeys).toEqual(enKeys);
|
|
});
|
|
});
|