fix(stammbaum): raise cross-link opacity to 0.7 + add dash-render test (#724)
Review follow-ups: - Leonie/UX: 0.55 navy on the sand canvas was ~2.6:1, under the WCAG 1.4.11 3:1 non-text floor for senior readers; 0.7 clears it. - Sara/QA: add a browser test that actually renders a cross-level link and asserts the distinct 2 6 dash, and that a non-cross-link parent edge stays solid — the cadence was previously only validated via the structural crossLinks array, never where it renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,11 @@ let { edges, positions, crossLinks = [], isConnectorActive = () => true }: Props
|
|||||||
|
|
||||||
// Dash cadence + opacity for a cross-link, deliberately distinct from the
|
// Dash cadence + opacity for a cross-link, deliberately distinct from the
|
||||||
// ended-marriage spouse dash (`4 4`) so the two never read alike (WCAG 1.4.1:
|
// ended-marriage spouse dash (`4 4`) so the two never read alike (WCAG 1.4.1:
|
||||||
// geometry carries the meaning too, not stroke alone).
|
// geometry carries the meaning too, not stroke alone). Opacity stays at 0.7 so
|
||||||
|
// the dotted line clears the WCAG 1.4.11 3:1 non-text contrast floor for
|
||||||
|
// senior / low-vision readers (a lighter 0.55 fell just under).
|
||||||
const CROSS_LINK_DASH = '2 6';
|
const CROSS_LINK_DASH = '2 6';
|
||||||
const CROSS_LINK_OPACITY = 0.55;
|
const CROSS_LINK_OPACITY = 0.7;
|
||||||
|
|
||||||
const crossLinkSet = $derived(new SvelteSet(crossLinks.map((c) => `${c.parentId}->${c.childId}`)));
|
const crossLinkSet = $derived(new SvelteSet(crossLinks.map((c) => `${c.parentId}->${c.childId}`)));
|
||||||
function isCrossLink(parentId: string, childId: string): boolean {
|
function isCrossLink(parentId: string, childId: string): boolean {
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
import StammbaumConnectors from './StammbaumConnectors.svelte';
|
||||||
|
import { NODE_H } from './layout/buildLayout';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
|
type RelationshipDTO = components['schemas']['RelationshipDTO'];
|
||||||
|
|
||||||
|
const P = '00000000-0000-0000-0000-0000000000c1';
|
||||||
|
const C = '00000000-0000-0000-0000-0000000000c2';
|
||||||
|
const H = '00000000-0000-0000-0000-0000000000c3';
|
||||||
|
const W = '00000000-0000-0000-0000-0000000000c4';
|
||||||
|
|
||||||
|
function parentEdge(parentId: string, childId: string): RelationshipDTO {
|
||||||
|
return {
|
||||||
|
id: `${parentId}>${childId}`,
|
||||||
|
personId: parentId,
|
||||||
|
relatedPersonId: childId,
|
||||||
|
personDisplayName: '',
|
||||||
|
relatedPersonDisplayName: '',
|
||||||
|
relationType: 'PARENT_OF'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function endedSpouseEdge(a: string, b: string): RelationshipDTO {
|
||||||
|
return {
|
||||||
|
id: `${a}~${b}`,
|
||||||
|
personId: a,
|
||||||
|
relatedPersonId: b,
|
||||||
|
personDisplayName: '',
|
||||||
|
relatedPersonDisplayName: '',
|
||||||
|
relationType: 'SPOUSE_OF',
|
||||||
|
toYear: 1950
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const positions = new Map([
|
||||||
|
[P, { x: 0, y: 0 }],
|
||||||
|
[C, { x: 400, y: NODE_H + 80 }],
|
||||||
|
[H, { x: 0, y: 400 }],
|
||||||
|
[W, { x: 300, y: 400 }]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dashesOf = (selector: string) =>
|
||||||
|
Array.from(document.querySelectorAll(selector))
|
||||||
|
.map((el) => el.getAttribute('stroke-dasharray'))
|
||||||
|
.filter((d): d is string => d !== null);
|
||||||
|
|
||||||
|
describe('StammbaumConnectors — cross-link cadence (#724)', () => {
|
||||||
|
it('renders a cross-level link with the distinct 2 6 dash, never the 4 4 ended-marriage dash', async () => {
|
||||||
|
render(StammbaumConnectors, {
|
||||||
|
edges: [parentEdge(P, C), endedSpouseEdge(H, W)],
|
||||||
|
positions,
|
||||||
|
crossLinks: [{ parentId: P, childId: C }]
|
||||||
|
});
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
const dashes = dashesOf('line');
|
||||||
|
// The cross-link cadence is present …
|
||||||
|
expect(dashes).toContain('2 6');
|
||||||
|
// … the ended-marriage cadence is present …
|
||||||
|
expect(dashes).toContain('4 4');
|
||||||
|
// … and the two are genuinely different cadences (WCAG 1.4.1: not by
|
||||||
|
// stroke alone, but they must not collapse into the same pattern).
|
||||||
|
expect('2 6').not.toBe('4 4');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('draws a normal parent→child connector solid when it is NOT a cross-link', async () => {
|
||||||
|
render(StammbaumConnectors, {
|
||||||
|
edges: [parentEdge(P, C)],
|
||||||
|
positions,
|
||||||
|
crossLinks: []
|
||||||
|
});
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
// No dashed parent lines at all when nothing is a cross-link.
|
||||||
|
expect(dashesOf('line')).not.toContain('2 6');
|
||||||
|
expect(document.querySelectorAll('line').length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user