Skip to content

Commit c048e58

Browse files
committed
up to date with first pr
1 parent f807286 commit c048e58

File tree

2 files changed

+254
-196
lines changed

2 files changed

+254
-196
lines changed
+151-138
Original file line numberDiff line numberDiff line change
@@ -1,211 +1,224 @@
1-
import { fireEvent, render, screen } from '@testing-library/react';
2-
import { beforeEach, describe, expect, it, vi } from 'vitest';
1+
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
34
import { Popover } from './Popover';
45

5-
vi.mock('react-dom', () => ({
6-
createPortal: (node: React.ReactNode) => node,
7-
}));
8-
96
describe('Popover', () => {
10-
const onClose = vi.fn();
117
let anchorEl: HTMLElement;
128

139
beforeEach(() => {
14-
vi.clearAllMocks();
10+
Object.defineProperty(window, 'matchMedia', {
11+
writable: true,
12+
value: vi.fn().mockImplementation((query) => ({
13+
matches: false,
14+
media: query,
15+
onchange: null,
16+
addListener: vi.fn(),
17+
removeListener: vi.fn(),
18+
addEventListener: vi.fn(),
19+
removeEventListener: vi.fn(),
20+
dispatchEvent: vi.fn(),
21+
})),
22+
});
23+
1524
anchorEl = document.createElement('button');
1625
anchorEl.setAttribute('data-testid', 'anchor');
1726
document.body.appendChild(anchorEl);
18-
vi.spyOn(anchorEl, 'getBoundingClientRect').mockReturnValue({
19-
x: 100,
20-
y: 100,
21-
top: 100,
22-
right: 200,
23-
bottom: 200,
24-
left: 100,
25-
width: 100,
26-
height: 100,
27-
toJSON: () => {},
28-
});
27+
});
28+
29+
afterEach(() => {
30+
cleanup();
31+
document.body.innerHTML = '';
32+
vi.clearAllMocks();
2933
});
3034

3135
describe('rendering', () => {
32-
it('renders nothing when isOpen is false', () => {
36+
it('should not render when isOpen is false', () => {
3337
render(
34-
<Popover isOpen={false} anchorEl={anchorEl} onClose={onClose}>
35-
<div>Content</div>
38+
<Popover anchorEl={anchorEl} isOpen={false}>
39+
Content
3640
</Popover>,
3741
);
42+
3843
expect(screen.queryByTestId('ockPopover')).not.toBeInTheDocument();
3944
});
4045

41-
it('renders content when isOpen is true', () => {
46+
it('should render when isOpen is true', () => {
4247
render(
43-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
44-
<div data-testid="content">Content</div>
48+
<Popover anchorEl={anchorEl} isOpen={true}>
49+
Content
4550
</Popover>,
4651
);
52+
4753
expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
48-
expect(screen.getByTestId('content')).toBeInTheDocument();
54+
expect(screen.getByText('Content')).toBeInTheDocument();
4955
});
50-
});
5156

52-
describe('accessibility', () => {
53-
it('sets all ARIA attributes correctly', () => {
57+
it('should handle null anchorEl gracefully', () => {
5458
render(
55-
<Popover
56-
isOpen={true}
57-
anchorEl={anchorEl}
58-
aria-label="Test Popover"
59-
aria-describedby="desc"
60-
aria-labelledby="title"
61-
onClose={onClose}
62-
>
63-
<div>Content</div>
59+
<Popover anchorEl={null} isOpen={true}>
60+
Content
6461
</Popover>,
6562
);
6663

67-
const popover = screen.getByTestId('ockPopover');
68-
expect(popover).toHaveAttribute('role', 'dialog');
69-
expect(popover).toHaveAttribute('aria-label', 'Test Popover');
70-
expect(popover).toHaveAttribute('aria-describedby', 'desc');
71-
expect(popover).toHaveAttribute('aria-labelledby', 'title');
64+
expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
7265
});
7366
});
7467

7568
describe('positioning', () => {
76-
const testCases = [
77-
{
78-
position: 'top',
79-
align: 'start',
80-
expectedTop: -8,
81-
expectedLeft: 100,
82-
},
83-
{
84-
position: 'bottom',
85-
align: 'center',
86-
expectedTop: 208,
87-
expectedLeft: 100,
88-
},
89-
{
90-
position: 'left',
91-
align: 'end',
92-
expectedTop: 200,
93-
expectedLeft: -8,
94-
},
95-
{
96-
position: 'right',
97-
align: 'center',
98-
expectedTop: 150,
99-
expectedLeft: 208,
100-
},
101-
] as const;
102-
103-
testCases.forEach(({ position, align, expectedTop, expectedLeft }) => {
104-
it(`positions correctly with position=${position} and align=${align}`, () => {
105-
render(
106-
<Popover
107-
isOpen={true}
108-
anchorEl={anchorEl}
109-
position={position}
110-
align={align}
111-
offset={8}
112-
onClose={onClose}
113-
>
114-
<div style={{ width: '100px', height: '100px' }}>Content</div>
115-
</Popover>,
116-
);
117-
118-
const popover = screen.getByTestId('ockPopover');
119-
vi.spyOn(popover, 'getBoundingClientRect').mockReturnValue({
120-
width: 100,
121-
height: 100,
122-
} as DOMRect);
123-
124-
fireEvent(window, new Event('resize'));
125-
126-
expect(popover.style.top).toBe(`${expectedTop}px`);
127-
expect(popover.style.left).toBe(`${expectedLeft}px`);
128-
});
69+
const positions = ['top', 'right', 'bottom', 'left'] as const;
70+
const alignments = ['start', 'center', 'end'] as const;
71+
72+
for (const position of positions) {
73+
for (const align of alignments) {
74+
it(`should position correctly with position=${position} and align=${align}`, () => {
75+
render(
76+
<Popover
77+
anchorEl={anchorEl}
78+
isOpen={true}
79+
position={position}
80+
align={align}
81+
offset={8}
82+
>
83+
Content
84+
</Popover>,
85+
);
86+
87+
const popover = screen.getByTestId('ockPopover');
88+
expect(popover).toBeInTheDocument();
89+
90+
expect(popover.style.top).toBeDefined();
91+
expect(popover.style.left).toBeDefined();
92+
});
93+
}
94+
}
95+
96+
it('should update position on window resize', async () => {
97+
render(
98+
<Popover anchorEl={anchorEl} isOpen={true}>
99+
Content
100+
</Popover>,
101+
);
102+
103+
fireEvent(window, new Event('resize'));
104+
105+
expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
129106
});
130107

131-
it('updates position on scroll', () => {
108+
it('should update position on scroll', async () => {
132109
render(
133-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
134-
<div>Content</div>
110+
<Popover anchorEl={anchorEl} isOpen={true}>
111+
Content
135112
</Popover>,
136113
);
137114

138115
fireEvent.scroll(window);
139-
expect(anchorEl.getBoundingClientRect).toHaveBeenCalled();
116+
117+
expect(screen.getByTestId('ockPopover')).toBeInTheDocument();
140118
});
141-
});
142119

143-
describe('dismissal behavior', () => {
144-
it('calls onClose when clicking outside', () => {
120+
it('should handle missing getBoundingClientRect gracefully', () => {
121+
const originalGetBoundingClientRect =
122+
Element.prototype.getBoundingClientRect;
123+
Element.prototype.getBoundingClientRect = vi
124+
.fn()
125+
.mockReturnValue(undefined);
126+
145127
render(
146-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
147-
<div>Content</div>
128+
<Popover anchorEl={anchorEl} isOpen={true}>
129+
Content
148130
</Popover>,
149131
);
150132

151-
fireEvent.pointerDown(document.body);
152-
expect(onClose).toHaveBeenCalledTimes(1);
133+
const popover = screen.getByTestId('ockPopover');
134+
expect(popover).toBeInTheDocument();
135+
136+
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
153137
});
138+
});
154139

155-
it('calls onClose when pressing Escape', () => {
140+
describe('interactions', () => {
141+
it('should not call onClose when clicking inside', async () => {
142+
const onClose = vi.fn();
156143
render(
157-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
158-
<div>Content</div>
144+
<Popover anchorEl={anchorEl} isOpen={true} onClose={onClose}>
145+
Content
159146
</Popover>,
160147
);
161148

162-
fireEvent.keyDown(document, { key: 'Escape' });
163-
expect(onClose).toHaveBeenCalledTimes(1);
149+
fireEvent.mouseDown(screen.getByText('Content'));
150+
expect(onClose).not.toHaveBeenCalled();
164151
});
165152

166-
it('handles undefined onClose prop gracefully', () => {
153+
it('should call onClose when pressing Escape', async () => {
154+
const onClose = vi.fn();
167155
render(
168-
<Popover isOpen={true} anchorEl={anchorEl}>
169-
<div>Content</div>
156+
<Popover anchorEl={anchorEl} isOpen={true} onClose={onClose}>
157+
Content
170158
</Popover>,
171159
);
172160

173-
fireEvent.pointerDown(document.body);
174-
fireEvent.keyDown(document, { key: 'Escape' });
161+
fireEvent.keyDown(document.body, { key: 'Escape' });
162+
expect(onClose).toHaveBeenCalled();
175163
});
176164
});
177165

178-
describe('cleanup', () => {
179-
it('removes event listeners on unmount', () => {
180-
const { unmount } = render(
181-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
182-
<div>Content</div>
166+
describe('accessibility', () => {
167+
it('should have correct ARIA attributes', () => {
168+
render(
169+
<Popover
170+
anchorEl={anchorEl}
171+
isOpen={true}
172+
aria-label="Test Label"
173+
aria-labelledby="labelId"
174+
aria-describedby="describeId"
175+
>
176+
Content
183177
</Popover>,
184178
);
185179

186-
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
187-
unmount();
180+
const popover = screen.getByTestId('ockPopover');
181+
expect(popover).toHaveAttribute('role', 'dialog');
182+
expect(popover).toHaveAttribute('aria-label', 'Test Label');
183+
expect(popover).toHaveAttribute('aria-labelledby', 'labelId');
184+
expect(popover).toHaveAttribute('aria-describedby', 'describeId');
185+
});
188186

189-
expect(removeEventListenerSpy).toHaveBeenCalledWith(
190-
'resize',
191-
expect.any(Function),
192-
);
193-
expect(removeEventListenerSpy).toHaveBeenCalledWith(
194-
'scroll',
195-
expect.any(Function),
187+
it('should trap focus when open', async () => {
188+
const user = userEvent.setup();
189+
render(
190+
<Popover anchorEl={anchorEl} isOpen={true}>
191+
<button type="button">First</button>
192+
<button type="button">Second</button>
193+
</Popover>,
196194
);
195+
196+
const firstButton = screen.getByText('First');
197+
const secondButton = screen.getByText('Second');
198+
199+
firstButton.focus();
200+
expect(document.activeElement).toBe(firstButton);
201+
202+
await user.tab();
203+
expect(document.activeElement).toBe(secondButton);
204+
205+
await user.tab();
206+
expect(document.activeElement).toBe(firstButton);
197207
});
198208
});
199209

200-
describe('portal rendering', () => {
201-
it('renders in portal', () => {
202-
const { baseElement } = render(
203-
<Popover isOpen={true} anchorEl={anchorEl} onClose={onClose}>
204-
<div>Content</div>
210+
describe('cleanup', () => {
211+
it('should remove event listeners on unmount', () => {
212+
const { unmount } = render(
213+
<Popover anchorEl={anchorEl} isOpen={true}>
214+
Content
205215
</Popover>,
206216
);
207217

208-
expect(baseElement.contains(screen.getByTestId('ockPopover'))).toBe(true);
218+
const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
219+
unmount();
220+
221+
expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
209222
});
210223
});
211-
});
224+
});

0 commit comments

Comments
 (0)