Skip to content

Commit c5e6c54

Browse files
authored
Callout, Dialog, Modal: convert tests to use testing-library (#21664)
1 parent e07cc82 commit c5e6c54

File tree

8 files changed

+720
-1240
lines changed

8 files changed

+720
-1240
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Clear document.body after each test to avoid interference between tests",
4+
"packageName": "@fluentui/react-conformance",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/react-conformance/src/isConformant.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export function isConformant<TProps = {}>(...testInfo: Partial<IsConformantOptio
1111
const { componentPath, displayName, disabledTests = [], extraTests, tsconfigDir } = mergedOptions;
1212

1313
describe('isConformant', () => {
14+
afterEach(() => {
15+
// TODO: remove this once cleanup is properly implemented or after moving to testing-library
16+
document.body.innerHTML = '';
17+
});
18+
1419
if (!fs.existsSync(componentPath)) {
1520
throw new Error(`Path ${componentPath} does not exist`);
1621
}

packages/react/src/components/Callout/Callout.test.tsx

Lines changed: 82 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import * as React from 'react';
2-
import * as ReactDOM from 'react-dom';
3-
import * as ReactTestUtils from 'react-dom/test-utils';
4-
import { render } from '@testing-library/react';
5-
import { safeCreate } from '@fluentui/test-utilities';
2+
import { render, act } from '@testing-library/react';
63
import { isConformant } from '../../common/isConformant';
74
import { DirectionalHint } from '../../common/DirectionalHint';
85
import { resetIds } from '../../Utilities';
@@ -13,14 +10,10 @@ import { expectNoHiddenParents } from '../../common/testUtilities';
1310

1411
describe('Callout', () => {
1512
beforeEach(() => {
16-
realDom = document.createElement('div');
17-
document.body.appendChild(realDom);
1813
resetIds();
1914
});
2015

2116
afterEach(() => {
22-
ReactDOM.unmountComponentAtNode(realDom);
23-
document.body.removeChild(realDom);
2417
jest.useRealTimers();
2518
jest.resetAllMocks();
2619
});
@@ -29,8 +22,6 @@ describe('Callout', () => {
2922
resetIds();
3023
});
3124

32-
let realDom: HTMLDivElement;
33-
3425
isConformant({
3526
Component: Callout,
3627
displayName: 'Callout',
@@ -40,167 +31,116 @@ describe('Callout', () => {
4031
});
4132

4233
it('renders Callout correctly', () => {
43-
safeCreate(<CalloutContent>Content</CalloutContent>, component => {
44-
const tree = component.toJSON();
45-
expect(tree).toMatchSnapshot();
46-
});
34+
const { container } = render(<CalloutContent>Content</CalloutContent>);
35+
expect(container).toMatchSnapshot();
4736
});
4837

49-
it('target id strings does not throw exception', () => {
50-
let threwException = false;
51-
try {
52-
ReactTestUtils.renderIntoDocument<HTMLDivElement>(
38+
it('does not throw with target id string', () => {
39+
expect(() => {
40+
render(
5341
<div>
5442
<button id="target" style={{ top: '10px', left: '10px', height: '0', width: '0px' }}>
55-
{' '}
56-
target{' '}
43+
target
5744
</button>
5845
<Callout target="#target" directionalHint={DirectionalHint.topLeftEdge}>
5946
<div>Content</div>
6047
</Callout>
6148
</div>,
6249
);
63-
} catch (e) {
64-
threwException = true;
65-
}
66-
67-
expect(threwException).toEqual(false);
50+
}).not.toThrow();
6851
});
6952

70-
it('target MouseEvents does not throw exception', () => {
71-
const mouseEvent = document.createEvent('MouseEvent');
53+
it('does not throw with target MouseEvent', () => {
7254
const eventTarget = document.createElement('div');
73-
mouseEvent.initMouseEvent('click', false, false, window, 0, 0, 0, 0, 0, false, false, false, false, 1, eventTarget);
74-
let threwException = false;
75-
try {
76-
ReactTestUtils.renderIntoDocument<HTMLDivElement>(
55+
const mouseEvent = new MouseEvent('click', { relatedTarget: eventTarget });
56+
57+
expect(() => {
58+
render(
7759
<div>
78-
<Callout target={eventTarget} directionalHint={DirectionalHint.topLeftEdge}>
60+
<Callout target={mouseEvent} directionalHint={DirectionalHint.topLeftEdge}>
7961
<div>Content</div>
8062
</Callout>
8163
</div>,
8264
);
83-
} catch (e) {
84-
threwException = true;
85-
}
86-
87-
expect(threwException).toEqual(false);
65+
}).not.toThrow();
8866
});
8967

90-
it('target Elements does not throw exception', () => {
68+
it('does not throw with target Element', () => {
9169
const targetElement = document.createElement('div');
9270
document.body.appendChild(targetElement);
93-
let threwException = false;
94-
try {
95-
ReactTestUtils.renderIntoDocument<HTMLDivElement>(
71+
72+
expect(() => {
73+
render(
9674
<div>
9775
<Callout target={targetElement} directionalHint={DirectionalHint.topLeftEdge}>
9876
<div>Content</div>
9977
</Callout>
10078
</div>,
10179
);
102-
} catch (e) {
103-
threwException = true;
104-
}
105-
106-
expect(threwException).toEqual(false);
80+
}).not.toThrow();
10781
});
10882

109-
it('without target does not throw exception', () => {
110-
let threwException = false;
111-
try {
112-
ReactTestUtils.renderIntoDocument<HTMLDivElement>(
83+
it('does not throw without target', () => {
84+
expect(() => {
85+
render(
11386
<div>
11487
<Callout directionalHint={DirectionalHint.topLeftEdge}>
11588
<div>Content</div>
11689
</Callout>
11790
</div>,
11891
);
119-
} catch (e) {
120-
threwException = true;
121-
}
122-
expect(threwException).toEqual(false);
92+
}).not.toThrow();
12393
});
12494

12595
it('passes event to onDismiss prop', () => {
12696
jest.useFakeTimers();
127-
let threwException = false;
128-
let gotEvent = false;
129-
const onDismiss = (ev?: unknown) => {
130-
if (ev) {
131-
gotEvent = true;
132-
}
133-
};
134-
135-
// In order to have eventlisteners that have been added to the window to be called the JSX needs
136-
// to be rendered into the real dom rather than the testutil simulated dom.
137-
138-
try {
139-
ReactTestUtils.act(() => {
140-
ReactDOM.render<HTMLDivElement>(
141-
<div>
142-
<button id="focustarget"> button </button>
143-
<button id="target" style={{ top: '10px', left: '10px', height: '0', width: '0px' }}>
144-
{' '}
145-
target{' '}
146-
</button>
147-
<Callout target="#target" directionalHint={DirectionalHint.topLeftEdge} onDismiss={onDismiss}>
148-
<div>Content</div>
149-
</Callout>
150-
</div>,
151-
realDom,
152-
);
153-
});
154-
} catch (e) {
155-
threwException = true;
156-
}
157-
expect(threwException).toEqual(false);
97+
const onDismiss = jest.fn();
15898

159-
ReactTestUtils.act(() => {
160-
const focusTarget = document.querySelector('#focustarget') as HTMLButtonElement;
99+
const { getByText } = render(
100+
<div>
101+
<button>button</button>
102+
<button id="target" style={{ top: '10px', left: '10px', height: '0', width: '0px' }}>
103+
target
104+
</button>
105+
<Callout target="#target" directionalHint={DirectionalHint.topLeftEdge} onDismiss={onDismiss}>
106+
<div>Content</div>
107+
</Callout>
108+
</div>,
109+
);
161110

111+
act(() => {
162112
// Move focus
163113
jest.runAllTimers();
114+
});
164115

165-
focusTarget.focus();
116+
getByText('button').focus();
166117

167-
expect(gotEvent).toEqual(true);
168-
});
118+
// ensure event is passed to callback
119+
expect(onDismiss).toHaveBeenCalledWith(expect.objectContaining({ type: 'focus' }));
169120
});
170121

171122
it('prevents dismiss when preventDismissOnEvent is passed', () => {
172123
jest.useFakeTimers();
173-
let threwException = false;
174124
const onDismiss = jest.fn();
175125
const preventAllDismiss = () => true;
176126

177-
try {
178-
ReactTestUtils.act(() => {
179-
ReactDOM.render<HTMLDivElement>(
180-
<div>
181-
<button id="focustarget"> button </button>
182-
<Callout target="#target" preventDismissOnEvent={preventAllDismiss} onDismiss={onDismiss}>
183-
<div>Content</div>
184-
</Callout>
185-
</div>,
186-
realDom,
187-
);
188-
});
189-
} catch (e) {
190-
threwException = true;
191-
}
192-
expect(threwException).toEqual(false);
193-
194-
ReactTestUtils.act(() => {
195-
const focusTarget = document.querySelector('#focustarget') as HTMLButtonElement;
127+
const { getByText, queryByText } = render(
128+
<div>
129+
<button>button</button>
130+
<Callout target="#target" preventDismissOnEvent={preventAllDismiss} onDismiss={onDismiss}>
131+
<div>Content</div>
132+
</Callout>
133+
</div>,
134+
);
196135

136+
act(() => {
197137
// Move focus
198138
jest.runAllTimers();
199-
200-
focusTarget.focus();
201-
202-
expect(onDismiss.mock.calls.length).toEqual(0);
203139
});
140+
getByText('button').focus();
141+
142+
expect(queryByText('Content')).toBeTruthy();
143+
expect(onDismiss).not.toHaveBeenCalled();
204144
});
205145

206146
it('will correctly return focus to element that spawned it', () => {
@@ -211,59 +151,40 @@ describe('Callout', () => {
211151
// Callout/popup checks active element to get what currently has focus
212152
// to know what to return focus to. By mocking the return value we can be sure
213153
// that it will have something "focused" when mounted
214-
const b = jest.spyOn(window.document, 'activeElement', 'get');
215-
b.mockReturnValue(focusedElement as Element);
216-
217-
let threwException = false;
218-
let previousFocusElement;
219-
let isFocused;
220-
let restoreCalled = false;
221-
const onRestoreFocus = (options: IPopupRestoreFocusParams) => {
222-
previousFocusElement = options.originalElement;
223-
isFocused = options.containsFocus;
224-
restoreCalled = true;
225-
};
226-
// In order to have eventlisteners that have been added to the window to be called the JSX needs
227-
// to be rendered into the real dom rather than the testutil simulated dom.
228-
try {
229-
ReactTestUtils.act(() => {
230-
ReactDOM.render<HTMLDivElement>(
231-
<div>
232-
<button id="target" style={{ top: '10px', left: '10px', height: '0', width: '0px' }}>
233-
target
234-
</button>
235-
<Callout target="#target" directionalHint={DirectionalHint.topLeftEdge} onRestoreFocus={onRestoreFocus}>
236-
{/* must be a button to be focusable for the test*/}
237-
<button id={'inner'}>Content</button>
238-
</Callout>
239-
</div>,
240-
realDom,
241-
);
242-
});
243-
} catch (e) {
244-
threwException = true;
245-
}
246-
expect(threwException).toEqual(false);
247-
248-
ReactTestUtils.act(() => {
249-
const focusTarget = document.querySelector('#inner') as HTMLDivElement;
154+
jest.spyOn(window.document, 'activeElement', 'get').mockReturnValue(focusedElement as Element);
155+
156+
const onRestoreFocus = jest.fn();
157+
158+
const { getByText, unmount } = render(
159+
<div>
160+
<button id="target" style={{ top: '10px', left: '10px', height: '0', width: '0px' }}>
161+
target
162+
</button>
163+
<Callout target="#target" directionalHint={DirectionalHint.topLeftEdge} onRestoreFocus={onRestoreFocus}>
164+
{/* must be a button to be focusable for the test*/}
165+
<button id="inner">Content</button>
166+
</Callout>
167+
</div>,
168+
);
250169

170+
act(() => {
251171
jest.runAllTimers();
252-
// Make sure that focus is in the callout
253-
focusTarget.focus();
254172
});
255173

256-
// Unmounting everything is the same as dismissing the Callout. As
257-
// the tree is unmounted, popup will get unmounted first and the
258-
// onRestoreFocus method will get called
259-
ReactDOM.unmountComponentAtNode(realDom);
174+
// Make sure that focus is in the callout
175+
getByText('Content').focus();
260176

261-
expect(restoreCalled).toEqual(true);
262-
expect(isFocused).toEqual(true);
177+
// Unmounting everything is the same as dismissing the Callout.
178+
// As the tree is unmounted, popup will get unmounted first and onRestoreFocus will get called.
179+
unmount();
263180

264-
// Just to make sure that both elements are not undefined
265-
expect(previousFocusElement).not.toBeFalsy();
266-
expect(previousFocusElement).toEqual(focusedElement);
181+
expect(onRestoreFocus).toHaveBeenCalledTimes(1);
182+
expect(onRestoreFocus).toHaveBeenLastCalledWith(
183+
expect.objectContaining<Partial<IPopupRestoreFocusParams>>({
184+
originalElement: focusedElement,
185+
containsFocus: true,
186+
}),
187+
);
267188
});
268189

269190
// This behavior could be changed in the future

0 commit comments

Comments
 (0)