Skip to content

Commit 2de40c3

Browse files
docs: testing basics tutorial (#1789)
1 parent 366368b commit 2de40c3

18 files changed

+3247
-1194
lines changed

examples/basic/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
module.exports = {
22
preset: 'react-native',
33
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
4+
transformIgnorePatterns: [
5+
'node_modules/(?!(jest-)?react-native|@react-native(-community)?)',
6+
],
47
setupFilesAfterEnv: ['./jest-setup.ts'],
58
};

examples/basic/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@types/react": "~18.3.12",
2727
"eslint": "^8.57.0",
2828
"jest": "^29.7.0",
29-
"react-test-renderer": "18.2.0",
29+
"react-test-renderer": "18.3.1",
3030
"typescript": "^5.3.0"
3131
},
3232
"private": true,

examples/basic/yarn.lock

Lines changed: 1228 additions & 1190 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from 'react';
2+
import { html } from 'react-strict-dom';
3+
import { render, screen } from '@testing-library/react-native';
4+
5+
function Greeting({ name = 'World' }) {
6+
return (
7+
<html.div>
8+
<html.span>Hello, {name}!</html.span>
9+
</html.div>
10+
);
11+
}
12+
13+
describe('Greeting', () => {
14+
it('should render', () => {
15+
// Arrange
16+
render(<Greeting />);
17+
18+
// Assert
19+
expect(screen.getByText('Hello, World!')).toBeOnTheScreen();
20+
});
21+
22+
it('should render with the correct name', () => {
23+
// Arrange
24+
render(<Greeting name="John" />);
25+
26+
// Assert
27+
expect(screen.getByText('Hello, John!')).toBeOnTheScreen();
28+
});
29+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as React from 'react';
2+
import { html } from 'react-strict-dom';
3+
import { render, screen } from '@testing-library/react-native';
4+
5+
test('showcase query variants', () => {
6+
render(
7+
<html.div>
8+
<html.span>Item 1</html.span>
9+
<html.span>Item 2</html.span>
10+
</html.div>,
11+
);
12+
13+
// Use getBy* queries to find a single element matching given predicate
14+
expect(screen.getByText('Item 1')).toBeOnTheScreen();
15+
16+
// Use getAllBy* queries to find all elements matching given predicate (note the use of a regex)
17+
expect(screen.getAllByText(/Item/)).toHaveLength(2);
18+
19+
// Use queryBy* to look for an element that you expect does not exist
20+
expect(screen.queryByText('Item 3')).not.toBeOnTheScreen();
21+
});
22+
23+
function LazyText({ content }: { content: string }) {
24+
const [isLoaded, setIsLoaded] = React.useState(false);
25+
26+
// Simulate async loading operation
27+
React.useEffect(() => {
28+
sleep(100);
29+
setIsLoaded(true);
30+
}, []);
31+
32+
return <html.span>{isLoaded ? content : 'Loading...'}</html.span>;
33+
}
34+
35+
test('showcase async query variants', async () => {
36+
render(
37+
<html.div>
38+
<LazyText content="Lazy Item 1" />
39+
<LazyText content="Lazy Item 2" />
40+
</html.div>,
41+
);
42+
43+
// Use findBy* to wait for an element to appear
44+
expect(await screen.findByText('Lazy Item 1')).toBeOnTheScreen();
45+
});
46+
47+
// Simulate async operation
48+
function sleep(ms: number) {
49+
return new Promise((resolve) => setTimeout(resolve, ms));
50+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import * as React from 'react';
2+
import { html } from 'react-strict-dom';
3+
import { render, screen } from '@testing-library/react-native';
4+
5+
test('query by semantic role: *ByRole (highly recommended)', () => {
6+
render(
7+
<html.div>
8+
<html.h1>Heading Text</html.h1>
9+
10+
<html.button>Button 1</html.button>
11+
12+
<html.p role="alert">Alert Text</html.p>
13+
14+
<html.div role="menu">
15+
<html.span role="menuitem">Menu Item 1</html.span>
16+
<html.span role="menuitem">Menu Item 2</html.span>
17+
</html.div>
18+
</html.div>,
19+
);
20+
21+
expect(screen.getByRole('heading', { name: 'Heading Text' })).toBeOnTheScreen();
22+
23+
expect(screen.getByRole('button', { name: 'Button 1' })).toBeOnTheScreen();
24+
expect(screen.getByRole('alert', { name: 'Alert Text' })).toBeOnTheScreen();
25+
26+
// TODO: RSD does not set accessible for role elements.
27+
// expect(screen.getByRole('menu')).toBeOnTheScreen();
28+
// expect(screen.getAllByRole('menuitem')).toHaveLength(2);
29+
});
30+
31+
test('querying TextInput elements (recommended)', () => {
32+
render(
33+
<html.div>
34+
<html.input placeholder="Enter Text..." aria-label="Text Label" defaultValue="Hello" />
35+
</html.div>,
36+
);
37+
38+
// Option 1: Query by a11y label
39+
expect(screen.getByLabelText('Text Label')).toHaveDisplayValue('Hello');
40+
41+
// Option 2: Query by placeholder text
42+
expect(screen.getByPlaceholderText('Enter Text...')).toHaveDisplayValue('Hello');
43+
44+
// Option 3: Query by display value
45+
expect(screen.getByDisplayValue('Hello')).toBeOnTheScreen();
46+
});
47+
48+
test('other accessible queries (ok)', () => {
49+
render(
50+
<html.div>
51+
<html.span>Text content</html.span>
52+
<html.div aria-label="ARIA Label" />
53+
</html.div>,
54+
);
55+
56+
expect(screen.getByText('Text content')).toBeOnTheScreen();
57+
expect(screen.getByLabelText('ARIA Label')).toBeOnTheScreen();
58+
});
59+
60+
test('escape hatch: *ByTestId (use as a last resort)', () => {
61+
render(
62+
<html.div>
63+
<html.span data-testid="Text 1">Text 1</html.span>
64+
</html.div>,
65+
);
66+
67+
expect(screen.getByTestId('Text 1')).toBeOnTheScreen();
68+
});
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import * as React from 'react';
2+
import { render, screen } from '@testing-library/react-native';
3+
import { html, css } from 'react-strict-dom';
4+
5+
test('showcase: toBeOnTheScreen', () => {
6+
render(
7+
<html.div>
8+
<html.div data-testid="view" />
9+
</html.div>,
10+
);
11+
12+
expect(screen.getByTestId('view')).toBeOnTheScreen();
13+
expect(screen.queryByTestId('non-existent')).not.toBeOnTheScreen();
14+
});
15+
16+
test('showcase: toBeIntoHaveTextContentTheDocument', () => {
17+
render(
18+
<html.div>
19+
<html.p data-testid="text">Hello World</html.p>
20+
</html.div>,
21+
);
22+
23+
expect(screen.getByTestId('text')).toHaveTextContent('Hello World');
24+
expect(screen.getByTestId('text')).toHaveTextContent(/hello/i);
25+
expect(screen.getByTestId('text')).toHaveTextContent('Hello', { exact: false });
26+
});
27+
28+
test('showcase: toContainElement', () => {
29+
render(
30+
<html.div>
31+
<html.div data-testid="outer">
32+
<html.div data-testid="inner" />
33+
</html.div>
34+
<html.div data-testid="outer-2" />
35+
</html.div>,
36+
);
37+
38+
expect(screen.getByTestId('outer')).toContainElement(screen.getByTestId('inner'));
39+
expect(screen.getByTestId('outer')).not.toContainElement(screen.getByTestId('outer-2'));
40+
});
41+
42+
test('showcase: toBeEmptyElement', () => {
43+
render(
44+
<html.div>
45+
<html.div data-testid="empty" />
46+
<html.div data-testid="not-empty">
47+
<html.p data-testid="text">Hello World</html.p>
48+
</html.div>
49+
</html.div>,
50+
);
51+
52+
expect(screen.getByTestId('empty')).toBeEmptyElement();
53+
expect(screen.getByTestId('not-empty')).not.toBeEmptyElement();
54+
});
55+
56+
test('showcase: toHaveDisplayValue', () => {
57+
render(
58+
<html.div>
59+
<html.input data-testid="input" value="Hello" />
60+
</html.div>,
61+
);
62+
63+
expect(screen.getByTestId('input')).toHaveDisplayValue('Hello');
64+
});
65+
66+
test('showcase: toHaveAccessibilityValue', () => {
67+
render(
68+
<html.div>
69+
<html.div
70+
data-testid="view"
71+
aria-valuetext="33%"
72+
aria-valuenow={33}
73+
aria-valuemax={100}
74+
aria-valuemin={0}
75+
/>
76+
</html.div>,
77+
);
78+
79+
expect(screen.getByTestId('view')).toHaveAccessibilityValue({ text: '33%' });
80+
expect(screen.getByTestId('view')).toHaveAccessibilityValue({ now: 33 });
81+
expect(screen.getByTestId('view')).toHaveAccessibilityValue({ now: 33, min: 0, max: 100 });
82+
});
83+
84+
test('showcase: toBeEnabled/toBeDisabled', () => {
85+
render(
86+
<html.div>
87+
<html.div data-testid="enabled" aria-disabled={false} />
88+
<html.div data-testid="disabled" aria-disabled />
89+
</html.div>,
90+
);
91+
92+
expect(screen.getByTestId('enabled')).toBeEnabled();
93+
expect(screen.getByTestId('enabled')).not.toBeDisabled();
94+
expect(screen.getByTestId('disabled')).toBeDisabled();
95+
expect(screen.getByTestId('disabled')).not.toBeEnabled();
96+
});
97+
98+
test('showcase: toBeSelected', () => {
99+
render(
100+
<html.div>
101+
<html.div data-testid="selected" aria-selected />
102+
<html.div data-testid="not-selected" />
103+
</html.div>,
104+
);
105+
106+
expect(screen.getByTestId('selected')).toBeSelected();
107+
expect(screen.getByTestId('not-selected')).not.toBeSelected();
108+
});
109+
110+
test('showcase: toBeBusy', () => {
111+
render(
112+
<html.div>
113+
<html.div data-testid="busy" aria-busy />
114+
<html.div data-testid="not-busy" />
115+
</html.div>,
116+
);
117+
118+
expect(screen.getByTestId('busy')).toBeBusy();
119+
expect(screen.getByTestId('not-busy')).not.toBeBusy();
120+
});
121+
122+
test('showcase: toBeExpanded/toBeCollapsed', () => {
123+
render(
124+
<html.div>
125+
<html.div data-testid="expanded" aria-expanded />
126+
<html.div data-testid="collapsed" aria-expanded={false} />
127+
<html.div data-testid="default" />
128+
</html.div>,
129+
);
130+
131+
expect(screen.getByTestId('expanded')).toBeExpanded();
132+
expect(screen.getByTestId('expanded')).not.toBeCollapsed();
133+
134+
expect(screen.getByTestId('collapsed')).toBeCollapsed();
135+
expect(screen.getByTestId('collapsed')).not.toBeExpanded();
136+
137+
expect(screen.getByTestId('default')).not.toBeCollapsed();
138+
expect(screen.getByTestId('default')).not.toBeExpanded();
139+
});
140+
141+
test('showcase: toBeVisible', () => {
142+
render(
143+
<html.div>
144+
<html.div data-testid="visible" />
145+
<html.div data-testid="not-visible" style={styles.opacityZero} />
146+
</html.div>,
147+
);
148+
149+
expect(screen.getByTestId('visible')).toBeVisible();
150+
expect(screen.getByTestId('not-visible')).not.toBeVisible();
151+
});
152+
153+
test('showcase: toHaveStyle', () => {
154+
render(
155+
<html.div>
156+
<html.span data-testid="text" style={styles.fontSize16}>
157+
Hello World
158+
</html.span>
159+
</html.div>,
160+
);
161+
162+
expect(screen.getByTestId('text')).toHaveStyle({ fontSize: 16 });
163+
expect(screen.getByTestId('text')).not.toHaveStyle({ fontSize: 12 });
164+
});
165+
166+
test('showcase: toHaveProp', () => {
167+
render(
168+
<html.div>
169+
<html.p data-testid="text" aria-label="Hello World" />
170+
</html.div>,
171+
);
172+
173+
expect(screen.getByTestId('text')).toHaveProp('accessibilityLabel');
174+
expect(screen.getByTestId('text')).not.toHaveProp('aria-hidden');
175+
176+
expect(screen.getByTestId('text')).toHaveProp('accessibilityLabel', 'Hello World');
177+
expect(screen.getByTestId('text')).not.toHaveProp('accessibilityLabel', 'Goodbye World');
178+
});
179+
180+
const styles = css.create({
181+
opacityZero: {
182+
opacity: '0%',
183+
},
184+
fontSize16: {
185+
fontSize: '16px',
186+
},
187+
});

0 commit comments

Comments
 (0)