feat(stammbaum): pinned generation-label rail on all viewports (#692)
Generation labels are no longer drawn in-SVG (where they panned/zoomed off screen and were desktop-only). A new StammbaumGenerationRail overlays the canvas left edge, mapping each generation row's centre through the SVG's live getScreenCTM so chips stay pinned horizontally and track their row vertically at any pan/zoom — on phones too. The desktop stripe underlay stays (gated on the gutter breakpoint); the #689 label tests are rewritten against the rail. Verified live: labels stay at left=4px while the canvas pans. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -831,10 +831,13 @@ describe('StammbaumTree keyboard pan/zoom (#692)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('StammbaumTree generation gutter (#689)', () => {
|
||||
it('renders a G{n} label per occupied generation row when at least one node carries generation', async () => {
|
||||
// showGutter overrides the matchMedia detection so the test never
|
||||
// depends on the vitest-browser iframe viewport width.
|
||||
describe('StammbaumTree generation rail (#689, #692)', () => {
|
||||
const railLabels = () =>
|
||||
Array.from(document.querySelectorAll('[role="text"]')).map((el) =>
|
||||
el.getAttribute('aria-label')
|
||||
);
|
||||
|
||||
it('renders a G{n} label per occupied generation row on the pinned rail', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [
|
||||
{ id: ID_A, displayName: 'Walter', familyMember: true, generation: 2 },
|
||||
@@ -843,35 +846,37 @@ describe('StammbaumTree generation gutter (#689)', () => {
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {},
|
||||
showGutter: true
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
const labels = Array.from(document.querySelectorAll('g[role="text"]')).map((g) =>
|
||||
g.getAttribute('aria-label')
|
||||
);
|
||||
expect(labels).toContain('Generation 2');
|
||||
expect(labels).toContain('Generation 3');
|
||||
await vi.waitFor(() => {
|
||||
const labels = railLabels();
|
||||
expect(labels).toContain('Generation 2');
|
||||
expect(labels).toContain('Generation 3');
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps the visible G3 text inside an aria-labelled group so screen readers announce "Generation"', async () => {
|
||||
it('labels the chip so screen readers announce "Generation" and shows the G{n} glyph', async () => {
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
|
||||
edges: [],
|
||||
selectedId: null,
|
||||
panZoom: { x: 0, y: 0, z: 1 },
|
||||
onSelect: () => {},
|
||||
showGutter: true
|
||||
onSelect: () => {}
|
||||
});
|
||||
|
||||
const g3 = Array.from(document.querySelectorAll('g[role="text"]')).find(
|
||||
(g) => g.getAttribute('aria-label') === 'Generation 3'
|
||||
);
|
||||
expect(g3).toBeDefined();
|
||||
expect(g3!.querySelector('text')!.textContent).toMatch(/G\s*3/);
|
||||
await vi.waitFor(() => {
|
||||
const g3 = Array.from(document.querySelectorAll('[role="text"]')).find(
|
||||
(el) => el.getAttribute('aria-label') === 'Generation 3'
|
||||
);
|
||||
expect(g3).toBeDefined();
|
||||
expect(g3!.textContent).toMatch(/G\s*3/);
|
||||
});
|
||||
});
|
||||
|
||||
it('omits the gutter when showGutter is false (mobile breakpoint case)', async () => {
|
||||
it('keeps showing generation labels on the pinned rail even on mobile (showGutter false)', async () => {
|
||||
// The rail is viewport-independent (the #692 point); only the desktop
|
||||
// stripe underlay is gated on the gutter breakpoint.
|
||||
render(StammbaumTree, {
|
||||
nodes: [{ id: ID_A, displayName: 'Herbert', familyMember: true, generation: 3 }],
|
||||
edges: [],
|
||||
@@ -881,7 +886,6 @@ describe('StammbaumTree generation gutter (#689)', () => {
|
||||
showGutter: false
|
||||
});
|
||||
|
||||
const labelGroups = Array.from(document.querySelectorAll('g[role="text"]'));
|
||||
expect(labelGroups).toHaveLength(0);
|
||||
await vi.waitFor(() => expect(railLabels()).toContain('Generation 3'));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user