feat(person-mention): PR-B2 — read-mode rendering + hover card (issue #362) #371
@@ -280,13 +280,14 @@ describe('TranscriptionReadView — person-mention rendering', () => {
|
|||||||
|
|
||||||
const link = document.querySelector('a.person-mention')!;
|
const link = document.querySelector('a.person-mention')!;
|
||||||
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
const personFetches = fetchMock.mock.calls.filter((c) =>
|
const personFetches = fetchMock.mock.calls.filter((c) =>
|
||||||
String(c[0]).includes(`/api/persons/${PERSON_ID}`)
|
String(c[0]).includes(`/api/persons/${PERSON_ID}`)
|
||||||
);
|
);
|
||||||
expect(personFetches.length).toBeGreaterThanOrEqual(1);
|
expect(personFetches.length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('deduplicates fetches for the same personId across multiple mouseenter events (B15.5)', async () => {
|
it('deduplicates fetches for the same personId across multiple mouseenter events (B15.5)', async () => {
|
||||||
const fetchMock = vi.fn().mockResolvedValue({
|
const fetchMock = vi.fn().mockResolvedValue({
|
||||||
@@ -308,13 +309,13 @@ describe('TranscriptionReadView — person-mention rendering', () => {
|
|||||||
// Plus a re-hover on the first
|
// Plus a re-hover on the first
|
||||||
links[0].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
links[0].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 10));
|
await vi.waitFor(() => {
|
||||||
|
|
||||||
const personFetches = fetchMock.mock.calls.filter(
|
const personFetches = fetchMock.mock.calls.filter(
|
||||||
(c) => String(c[0]) === `/api/persons/${PERSON_ID}`
|
(c) => String(c[0]) === `/api/persons/${PERSON_ID}`
|
||||||
);
|
);
|
||||||
expect(personFetches.length).toBe(1);
|
expect(personFetches.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('mounts the hover card on mouseenter when the fetch loads', async () => {
|
it('mounts the hover card on mouseenter when the fetch loads', async () => {
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
@@ -349,10 +350,11 @@ describe('TranscriptionReadView — person-mention rendering', () => {
|
|||||||
const link = document.querySelector('a.person-mention')!;
|
const link = document.querySelector('a.person-mention')!;
|
||||||
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 50));
|
await vi.waitFor(() => {
|
||||||
const card = document.querySelector('[data-testid="person-hover-card"]');
|
const card = document.querySelector('[data-testid="person-hover-card"]');
|
||||||
expect(card).not.toBeNull();
|
expect(card).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('unmounts the hover card on mouseleave', async () => {
|
it('unmounts the hover card on mouseleave', async () => {
|
||||||
render(TranscriptionReadView, {
|
render(TranscriptionReadView, {
|
||||||
@@ -362,13 +364,13 @@ describe('TranscriptionReadView — person-mention rendering', () => {
|
|||||||
|
|
||||||
const link = document.querySelector('a.person-mention')!;
|
const link = document.querySelector('a.person-mention')!;
|
||||||
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||||
await new Promise((r) => setTimeout(r, 5));
|
|
||||||
link.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
|
link.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true }));
|
||||||
await new Promise((r) => setTimeout(r, 5));
|
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
const card = document.querySelector('[data-testid="person-hover-card"]');
|
const card = document.querySelector('[data-testid="person-hover-card"]');
|
||||||
expect(card).toBeNull();
|
expect(card).toBeNull();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('mounts the hover card on focusin so keyboard users see the preview (WCAG 2.1.1)', async () => {
|
it('mounts the hover card on focusin so keyboard users see the preview (WCAG 2.1.1)', async () => {
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
@@ -468,13 +470,15 @@ describe('TranscriptionReadView — person-mention rendering', () => {
|
|||||||
|
|
||||||
const link = document.querySelector('a.person-mention')!;
|
const link = document.querySelector('a.person-mention')!;
|
||||||
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
link.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
||||||
await new Promise((r) => setTimeout(r, 50));
|
|
||||||
|
|
||||||
// 404 → no card mounted
|
await vi.waitFor(() => {
|
||||||
const card = document.querySelector('[data-testid="person-hover-card"]');
|
|
||||||
expect(card).toBeNull();
|
|
||||||
// Anchor is marked as deleted so subsequent hovers/clicks treat it as plain text
|
// Anchor is marked as deleted so subsequent hovers/clicks treat it as plain text
|
||||||
const stillLink = document.querySelector('a.person-mention')!;
|
const stillLink = document.querySelector('a.person-mention')!;
|
||||||
expect(stillLink.getAttribute('data-person-deleted')).toBe('true');
|
expect(stillLink.getAttribute('data-person-deleted')).toBe('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 404 → no card mounted
|
||||||
|
const card = document.querySelector('[data-testid="person-hover-card"]');
|
||||||
|
expect(card).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user