feat(card): Netzwerk- und Marken-Logos
18 Netzwerk-Logos, AirCards-Symbol/-Text und Apple als bereinigte SVGs (Wikimedia Commons, gemeinfrei; abgeleitete Varianten selbst gebaut), Register marks.ts mit Einfärben (currentColor) und Bild-Lader sowie festfarbiges Favicon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a4ed0d595
commit
eb6b4ac59d
24 files changed
+318
-4
No files matched your search
@@ -0,0 +1,165 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { NETWORK_IDS } from './config';
|
||||
import favicon from '../../public/favicon.svg?raw';
|
||||
import {
|
||||
BRAND_MARKS,
|
||||
NETWORK_MARKS,
|
||||
getBrandMark,
|
||||
getNetworkMark,
|
||||
loadMarkImage,
|
||||
svgAspect,
|
||||
tintSvg,
|
||||
} from './marks';
|
||||
|
||||
const NETWORK_LABELS = [
|
||||
'Automatisch', 'Blau', 'Marineblau', 'Metallisches Silber',
|
||||
'Farbe', 'Metallisches Silber', 'Wortmarke', 'Silber + Text', 'Klassisch',
|
||||
'Farbe', 'Metallisches Silber', 'Weißer Rahmen', 'Englisch',
|
||||
'Blau', 'Umriss', 'Select',
|
||||
'JCB', 'Discover',
|
||||
];
|
||||
|
||||
const ALL = [...NETWORK_MARKS, ...BRAND_MARKS];
|
||||
const TINT_IDS = ['visa-auto', 'amex-outline', 'aircards-symbol', 'aircards-text', 'apple'];
|
||||
|
||||
describe('Registers', () => {
|
||||
it('NETWORK_MARKS folgt NETWORK_IDS', () => {
|
||||
expect(NETWORK_MARKS.map((m) => m.id)).toEqual([...NETWORK_IDS]);
|
||||
});
|
||||
|
||||
it('Labels stehen im Wortlaut der Vorlage in Reihenfolge', () => {
|
||||
expect(NETWORK_MARKS.map((m) => m.label)).toEqual(NETWORK_LABELS);
|
||||
expect(BRAND_MARKS.map((m) => m.label)).toEqual(['AirCards · Symbol', 'AirCards · Text', 'Apple']);
|
||||
});
|
||||
|
||||
it('BRAND_MARKS: Logo-Gruppe, feste Reihenfolge', () => {
|
||||
expect(BRAND_MARKS.map((m) => m.id)).toEqual(['aircards-symbol', 'aircards-text', 'apple']);
|
||||
expect(BRAND_MARKS.every((m) => m.group === 'Logo')).toBe(true);
|
||||
});
|
||||
|
||||
it('Gruppen der Netzwerke', () => {
|
||||
const groups = NETWORK_MARKS.map((m) => m.group);
|
||||
expect(groups).toEqual([
|
||||
'Visa', 'Visa', 'Visa', 'Visa',
|
||||
'Mastercard', 'Mastercard', 'Mastercard', 'Mastercard', 'Mastercard',
|
||||
'UnionPay', 'UnionPay', 'UnionPay', 'UnionPay',
|
||||
'American Express', 'American Express', 'American Express',
|
||||
'JCB', 'Discover',
|
||||
]);
|
||||
});
|
||||
|
||||
it('getNetworkMark / getBrandMark finden den Eintrag', () => {
|
||||
for (const id of NETWORK_IDS) expect(getNetworkMark(id).id).toBe(id);
|
||||
expect(getBrandMark('apple').id).toBe('apple');
|
||||
expect(getBrandMark('aircards-text').label).toBe('AirCards · Text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SVG-Inhalt', () => {
|
||||
it.each(ALL.map((m) => [m.id, m] as const))('%s ist ein sauberes SVG', (_id, m) => {
|
||||
expect(m.svg.trimStart().startsWith('<svg')).toBe(true);
|
||||
expect(m.svg).toMatch(/viewBox="[^"]+"/);
|
||||
expect(m.svg).not.toContain('<image');
|
||||
expect(m.svg).not.toContain('<script');
|
||||
const a = svgAspect(m.svg);
|
||||
expect(Number.isFinite(a)).toBe(true);
|
||||
expect(a).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('Wurzelelement: xmlns für Blob-Bilder, keine feste Größe (die viewBox bestimmt die Größe)', () => {
|
||||
for (const m of ALL) {
|
||||
const root = /^<svg\b[^>]*>/.exec(m.svg.trimStart())![0];
|
||||
expect(root, m.id).toContain('xmlns="http://www.w3.org/2000/svg"');
|
||||
expect(root, m.id).not.toMatch(/\s(width|height)=/);
|
||||
}
|
||||
});
|
||||
|
||||
it('svgAspect: ohne brauchbare viewBox 1', () => {
|
||||
expect(svgAspect('<svg></svg>')).toBe(1);
|
||||
expect(svgAspect('<svg viewBox="0 0 0 10"></svg>')).toBe(1);
|
||||
});
|
||||
|
||||
it('svgAspect liest die viewBox', () => {
|
||||
expect(svgAspect('<svg viewBox="0 0 200 100"></svg>')).toBeCloseTo(2, 1);
|
||||
expect(svgAspect('<svg viewBox="10 20 300 100"></svg>')).toBeCloseTo(3, 1);
|
||||
});
|
||||
|
||||
it('svgAspect: Visa ist ein breites Wortlogo', () => {
|
||||
expect(svgAspect(getNetworkMark('visa-auto').svg)).toBeCloseTo(1000 / 324.68, 1);
|
||||
});
|
||||
|
||||
it('Einfärbbar genau bei den fünf Tint-Marken', () => {
|
||||
const tinted = ALL.filter((m) => m.tint).map((m) => m.id).sort();
|
||||
expect(tinted).toEqual([...TINT_IDS].sort());
|
||||
});
|
||||
|
||||
it('Tint-Marken enthalten currentColor, alle anderen nicht', () => {
|
||||
for (const m of ALL) {
|
||||
expect(m.svg.includes('currentColor'), m.id).toBe(m.tint);
|
||||
}
|
||||
});
|
||||
|
||||
it('tintSvg ersetzt jedes currentColor', () => {
|
||||
for (const id of TINT_IDS) {
|
||||
const m = [...NETWORK_MARKS, ...BRAND_MARKS].find((x) => x.id === id)!;
|
||||
const dark = tintSvg(m.svg, 'dark');
|
||||
expect(dark).toContain('#111111');
|
||||
expect(dark).not.toContain('currentColor');
|
||||
const light = tintSvg(m.svg, 'light');
|
||||
expect(light).toContain('#FFFFFF');
|
||||
expect(light).not.toContain('currentColor');
|
||||
}
|
||||
});
|
||||
|
||||
it('tintSvg lässt Marken ohne currentColor unverändert', () => {
|
||||
const m = getNetworkMark('jcb');
|
||||
expect(tintSvg(m.svg, 'dark')).toBe(m.svg);
|
||||
});
|
||||
|
||||
it('Verlauf-IDs sind je Datei eindeutig (Präfix = Marken-ID)', () => {
|
||||
const seen = new Map<string, string>();
|
||||
for (const m of ALL) {
|
||||
for (const hit of m.svg.matchAll(/\bid="([^"]+)"/g)) {
|
||||
const id = hit[1];
|
||||
expect(seen.has(id), `${id} doppelt in ${m.id} und ${seen.get(id)}`).toBe(false);
|
||||
seen.set(id, m.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxH', () => {
|
||||
const h = (id: string) => NETWORK_MARKS.find((m) => m.id === id)!.maxH;
|
||||
|
||||
it('109 für Visa, Mastercard-Wortmarken, UnionPay English, Discover', () => {
|
||||
for (const id of ['visa-auto', 'visa-blue', 'visa-navy', 'visa-silver', 'mc-wordmark', 'mc-silver-text', 'up-english', 'discover']) {
|
||||
expect(h(id), id).toBe(109);
|
||||
}
|
||||
});
|
||||
|
||||
it('125 für Symbol-Logos', () => {
|
||||
for (const id of ['mc-color', 'mc-silver', 'mc-classic', 'up-color', 'up-silver', 'up-white-frame', 'amex-blue', 'amex-outline', 'jcb']) {
|
||||
expect(h(id), id).toBe(125);
|
||||
}
|
||||
});
|
||||
|
||||
it('90 für Amex Select, 100 für alle Marken', () => {
|
||||
expect(h('amex-select')).toBe(90);
|
||||
expect(BRAND_MARKS.every((m) => m.maxH === 100)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Favicon', () => {
|
||||
it('hat feste Farben und kein currentColor', () => {
|
||||
expect(favicon).not.toContain('currentColor');
|
||||
expect(favicon).toContain('#E8EEF7');
|
||||
expect(favicon).toContain('#0F1828');
|
||||
expect(favicon).toMatch(/viewBox="0 0 64 64"/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadMarkImage', () => {
|
||||
it('ist eine Funktion', () => {
|
||||
expect(typeof loadMarkImage).toBe('function');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user