Skip to content

Commit ce969d4

Browse files
committed
refactor: tweaks and improvements
1 parent 515b447 commit ce969d4

File tree

9 files changed

+170
-103
lines changed

9 files changed

+170
-103
lines changed

src/helpers/__tests__/format-default.test.tsx

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
import { ReactTestRendererJSON } from 'react-test-renderer';
21
import { defaultMapProps } from '../format-default';
32

4-
const node: ReactTestRendererJSON = {
5-
type: 'View',
6-
props: {},
7-
children: null,
8-
};
9-
103
describe('mapPropsForQueryError', () => {
114
test('preserves props that are helpful for debugging', () => {
125
const props = {
@@ -32,89 +25,79 @@ describe('mapPropsForQueryError', () => {
3225
defaultValue: 'DEFAULT_VALUE',
3326
};
3427

35-
const result = defaultMapProps(props, node);
28+
const result = defaultMapProps(props);
3629
expect(result).toStrictEqual(props);
3730
});
3831

3932
test('does not preserve less helpful props', () => {
40-
const result = defaultMapProps(
41-
{
42-
style: [{ flex: 1 }, { display: 'flex' }],
43-
onPress: () => null,
44-
key: 'foo',
45-
},
46-
node
47-
);
33+
const result = defaultMapProps({
34+
style: [{ flex: 1 }, { flexDirection: 'row' }],
35+
onPress: () => null,
36+
key: 'foo',
37+
});
4838

4939
expect(result).toStrictEqual({});
5040
});
5141

52-
test('preserves "display: none" style but no other style', () => {
53-
const result = defaultMapProps(
54-
{ style: [{ flex: 1 }, { display: 'none', flex: 2 }] },
55-
node
56-
);
42+
test('preserves "display" and "opacity" styles but no other style', () => {
43+
const result = defaultMapProps({
44+
style: [{ flex: 1 }, { display: 'none', flex: 2 }, { opacity: 0.5 }],
45+
});
5746

5847
expect(result).toStrictEqual({
59-
style: { display: 'none' },
48+
style: { display: 'none', opacity: 0.5 },
6049
});
6150
});
6251

6352
test('removes undefined keys from accessibilityState', () => {
64-
const result = defaultMapProps(
65-
{ accessibilityState: { checked: undefined, selected: false } },
66-
node
67-
);
53+
const result = defaultMapProps({
54+
accessibilityState: { checked: undefined, selected: false },
55+
});
6856

6957
expect(result).toStrictEqual({
7058
accessibilityState: { selected: false },
7159
});
7260
});
7361

7462
test('removes accessibilityState if all keys are undefined', () => {
75-
const result = defaultMapProps(
76-
{ accessibilityState: { checked: undefined, selected: undefined } },
77-
node
78-
);
63+
const result = defaultMapProps({
64+
accessibilityState: { checked: undefined, selected: undefined },
65+
});
7966

8067
expect(result).toStrictEqual({});
8168
});
8269

8370
test('does not fail if accessibilityState is a string, passes through', () => {
84-
const result = defaultMapProps({ accessibilityState: 'foo' }, node);
71+
const result = defaultMapProps({ accessibilityState: 'foo' });
8572
expect(result).toStrictEqual({ accessibilityState: 'foo' });
8673
});
8774

8875
test('does not fail if accessibilityState is an array, passes through', () => {
89-
const result = defaultMapProps({ accessibilityState: [1] }, node);
76+
const result = defaultMapProps({ accessibilityState: [1] });
9077
expect(result).toStrictEqual({ accessibilityState: [1] });
9178
});
9279

9380
test('does not fail if accessibilityState is null, passes through', () => {
94-
const result = defaultMapProps({ accessibilityState: null }, node);
81+
const result = defaultMapProps({ accessibilityState: null });
9582
expect(result).toStrictEqual({ accessibilityState: null });
9683
});
9784

9885
test('does not fail if accessibilityState is nested object, passes through', () => {
9986
const accessibilityState = { 1: { 2: 3 }, 2: undefined };
100-
const result = defaultMapProps({ accessibilityState }, node);
87+
const result = defaultMapProps({ accessibilityState });
10188
expect(result).toStrictEqual({ accessibilityState: { 1: { 2: 3 } } });
10289
});
10390

10491
test('removes undefined keys from accessibilityValue', () => {
105-
const result = defaultMapProps(
106-
{ accessibilityValue: { min: 1, max: undefined } },
107-
node
108-
);
92+
const result = defaultMapProps({
93+
accessibilityValue: { min: 1, max: undefined },
94+
});
10995

11096
expect(result).toStrictEqual({ accessibilityValue: { min: 1 } });
11197
});
11298

11399
test('removes accessibilityValue if all keys are undefined', () => {
114-
const result = defaultMapProps(
115-
{ accessibilityValue: { min: undefined } },
116-
node
117-
);
100+
const result = defaultMapProps({ accessibilityValue: { min: undefined } });
118101

119102
expect(result).toStrictEqual({});
120103
});

src/helpers/format-default.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { StyleSheet, ViewStyle } from 'react-native';
2-
import { MapPropsFunction } from './format';
32

43
const propsToDisplay = [
54
'accessibilityElementsHidden',
@@ -26,15 +25,20 @@ const propsToDisplay = [
2625
'value',
2726
];
2827

28+
const stylePropsToDisplay = ['display', 'opacity'] as const;
29+
2930
/**
3031
* Preserve props that are helpful in diagnosing test failures, while stripping rest
3132
*/
32-
export const defaultMapProps: MapPropsFunction = (props) => {
33+
export function defaultMapProps(
34+
props: Record<string, unknown>
35+
): Record<string, unknown> {
3336
const result: Record<string, unknown> = {};
3437

3538
const styles = StyleSheet.flatten(props.style as ViewStyle);
36-
if (styles?.display === 'none') {
37-
result.style = { display: 'none' };
39+
const styleToDisplay = extractStyle(styles);
40+
if (styleToDisplay !== undefined) {
41+
result.style = styleToDisplay;
3842
}
3943

4044
const accessibilityState = removeUndefinedKeys(props.accessibilityState);
@@ -54,7 +58,7 @@ export const defaultMapProps: MapPropsFunction = (props) => {
5458
});
5559

5660
return result;
57-
};
61+
}
5862

5963
function isObject(value: unknown): value is Record<string, unknown> {
6064
return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -65,17 +69,32 @@ function removeUndefinedKeys(prop: unknown) {
6569
return prop;
6670
}
6771

72+
let hasKeys = false;
6873
const result: Record<string, unknown> = {};
6974
Object.entries(prop).forEach(([key, value]) => {
7075
if (value !== undefined) {
7176
result[key] = value;
77+
hasKeys = true;
7278
}
7379
});
7480

75-
// If object does not have any props we will ignore it.
76-
if (Object.keys(result).length === 0) {
81+
return hasKeys ? result : undefined;
82+
}
83+
84+
function extractStyle(style: ViewStyle | undefined) {
85+
if (style == null) {
7786
return undefined;
7887
}
7988

80-
return result;
89+
const result: Record<string, unknown> = {};
90+
let hasAnyStyle = false;
91+
92+
stylePropsToDisplay.forEach((styleProp) => {
93+
if (styleProp in style) {
94+
result[styleProp] = style[styleProp];
95+
hasAnyStyle = true;
96+
}
97+
});
98+
99+
return hasAnyStyle ? result : undefined;
81100
}

src/matchers/__tests__/to-be-disabled.test.tsx

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TouchableNativeFeedback,
88
TouchableOpacity,
99
TouchableWithoutFeedback,
10+
Text,
1011
View,
1112
} from 'react-native';
1213
import { render } from '../..';
@@ -30,7 +31,72 @@ const ALL_COMPONENTS = {
3031
...ARIA_DISABLED_PROP_COMPONENTS,
3132
};
3233

33-
describe('.toBeDisabled', () => {
34+
test('toBeDisabled/toBeEnabled works with disabled Pressable', () => {
35+
const screen = render(
36+
<Pressable disabled testID="subject">
37+
<Text>Button</Text>
38+
</Pressable>
39+
);
40+
41+
const pressable = screen.getByTestId('subject');
42+
expect(pressable).toBeDisabled();
43+
expect(pressable).not.toBeEnabled();
44+
45+
const title = screen.getByText('Button');
46+
expect(title).toBeDisabled();
47+
expect(title).not.toBeEnabled();
48+
49+
expect(() => expect(pressable).toBeEnabled())
50+
.toThrowErrorMatchingInlineSnapshot(`
51+
"expect(element).toBeEnabled()
52+
53+
Received element is not enabled:
54+
<View
55+
accessibilityState={
56+
{
57+
"disabled": true,
58+
}
59+
}
60+
testID="subject"
61+
/>"
62+
`);
63+
64+
expect(() => expect(pressable).not.toBeDisabled())
65+
.toThrowErrorMatchingInlineSnapshot(`
66+
"expect(element).not.toBeDisabled()
67+
68+
Received element is disabled:
69+
<View
70+
accessibilityState={
71+
{
72+
"disabled": true,
73+
}
74+
}
75+
testID="subject"
76+
/>"
77+
`);
78+
79+
expect(() => expect(title).toBeEnabled()).toThrowErrorMatchingInlineSnapshot(`
80+
"expect(element).toBeEnabled()
81+
82+
Received element is not enabled:
83+
<Text>
84+
Button
85+
</Text>"
86+
`);
87+
88+
expect(() => expect(title).not.toBeDisabled())
89+
.toThrowErrorMatchingInlineSnapshot(`
90+
"expect(element).not.toBeDisabled()
91+
92+
Received element is disabled:
93+
<Text>
94+
Button
95+
</Text>"
96+
`);
97+
});
98+
99+
describe('toBeDisabled()', () => {
34100
Object.entries(DISABLED_PROP_COMPONENTS).forEach(([name, Component]) => {
35101
test(`handle disabled prop for element ${name}`, () => {
36102
const { queryByTestId } = render(

src/matchers/__tests__/to-be-visible.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ test('toBeVisible() on empty Modal', () => {
133133
134134
Received element is visible:
135135
<Modal
136-
hardwareAccelerated={false}
137136
testID="modal"
138-
visible={true}
139137
/>"
140138
`);
141139
});

src/matchers/to-be-disabled.tsx

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
import type { ReactTestInstance } from 'react-test-renderer';
22
import { matcherHint } from 'jest-matcher-utils';
33
import { isHostTextInput } from '../helpers/host-component-names';
4-
import { checkHostElement, formatMessage } from './utils';
5-
6-
function isElementDisabled(element: ReactTestInstance) {
7-
if (isHostTextInput(element) && element?.props?.editable === false) {
8-
return true;
9-
}
10-
11-
return (
12-
!!element?.props?.['aria-disabled'] ||
13-
!!element?.props?.accessibilityState?.disabled
14-
);
15-
}
16-
17-
function isAncestorDisabled(element: ReactTestInstance): boolean {
18-
const parent = element.parent;
19-
return (
20-
parent != null && (isElementDisabled(element) || isAncestorDisabled(parent))
21-
);
22-
}
4+
import { isTextInputEditable } from '../helpers/text-input';
5+
import { getHostParent } from '../helpers/component-tree';
6+
import { checkHostElement, formatElement } from './utils';
237

248
export function toBeDisabled(
259
this: jest.MatcherContext,
@@ -34,17 +18,10 @@ export function toBeDisabled(
3418
message: () => {
3519
const is = isDisabled ? 'is' : 'is not';
3620
return [
37-
formatMessage(
38-
matcherHint(
39-
`${this.isNot ? '.not' : ''}.toBeDisabled`,
40-
'element',
41-
''
42-
),
43-
'',
44-
'',
45-
`Received element ${is} disabled:`,
46-
null
47-
),
21+
matcherHint(`${this.isNot ? '.not' : ''}.toBeDisabled`, 'element', ''),
22+
'',
23+
`Received element ${is} disabled:`,
24+
formatElement(element),
4825
].join('\n');
4926
},
5027
};
@@ -63,14 +40,29 @@ export function toBeEnabled(
6340
message: () => {
6441
const is = isEnabled ? 'is' : 'is not';
6542
return [
66-
formatMessage(
67-
matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''),
68-
'',
69-
'',
70-
`Received element ${is} enabled:`,
71-
null
72-
),
43+
matcherHint(`${this.isNot ? '.not' : ''}.toBeEnabled`, 'element', ''),
44+
'',
45+
`Received element ${is} enabled:`,
46+
formatElement(element),
7347
].join('\n');
7448
},
7549
};
7650
}
51+
52+
function isElementDisabled(element: ReactTestInstance) {
53+
if (isHostTextInput(element) && !isTextInputEditable(element)) {
54+
return true;
55+
}
56+
57+
const { accessibilityState, 'aria-disabled': ariaDisabled } = element.props;
58+
return ariaDisabled ?? accessibilityState?.disabled ?? false;
59+
}
60+
61+
function isAncestorDisabled(element: ReactTestInstance): boolean {
62+
const parent = getHostParent(element);
63+
if (parent == null) {
64+
return false;
65+
}
66+
67+
return isElementDisabled(parent) || isAncestorDisabled(parent);
68+
}

0 commit comments

Comments
 (0)