Skip to content

Commit

Permalink
feat: toBeExpanded matcher (#1497)
Browse files Browse the repository at this point in the history
* feat: toBeExpanded matcher

* feat: toBeCollapsed matcher

* chore: shorten syntax for isElementCollapsed check

* refactor: clean up

---------

Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com>
  • Loading branch information
siepra and mdjastrzebski committed Sep 19, 2023
1 parent d898a9d commit a9f32a6
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/helpers/accessiblity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,20 @@ export function isElementBusy(
return ariaBusy ?? accessibilityState?.busy ?? false;
}

export function isElementCollapsed(
element: ReactTestInstance
): NonNullable<AccessibilityState['expanded']> {
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
return (ariaExpanded ?? accessibilityState?.expanded) === false;
}

export function isElementExpanded(
element: ReactTestInstance
): NonNullable<AccessibilityState['expanded']> {
const { accessibilityState, 'aria-expanded': ariaExpanded } = element.props;
return ariaExpanded ?? accessibilityState?.expanded ?? false;
}

export function isElementSelected(
element: ReactTestInstance
): NonNullable<AccessibilityState['selected']> {
Expand Down
97 changes: 97 additions & 0 deletions src/matchers/__tests__/to-be-collapsed.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as React from 'react';
import { View } from 'react-native';
import { render, screen } from '../..';
import '../extend-expect';

test('toBeCollapsed() basic case', () => {
render(
<>
<View testID="expanded" accessibilityState={{ expanded: true }} />
<View testID="expanded-aria" aria-expanded />
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
<View testID="not-expanded-aria" aria-expanded={false} />
<View testID="default" />
</>
);

expect(screen.getByTestId('expanded')).not.toBeCollapsed();
expect(screen.getByTestId('expanded-aria')).not.toBeCollapsed();
expect(screen.getByTestId('not-expanded')).toBeCollapsed();
expect(screen.getByTestId('not-expanded-aria')).toBeCollapsed();
expect(screen.getByTestId('default')).not.toBeCollapsed();
});

test('toBeCollapsed() error messages', () => {
render(
<>
<View testID="expanded" accessibilityState={{ expanded: true }} />
<View testID="expanded-aria" aria-expanded />
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
<View testID="not-expanded-aria" aria-expanded={false} />
<View testID="default" />
</>
);

expect(() => expect(screen.getByTestId('expanded')).toBeCollapsed())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeCollapsed()
Received element is not collapsed:
<View
accessibilityState={
{
"expanded": true,
}
}
testID="expanded"
/>"
`);

expect(() => expect(screen.getByTestId('expanded-aria')).toBeCollapsed())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeCollapsed()
Received element is not collapsed:
<View
aria-expanded={true}
testID="expanded-aria"
/>"
`);

expect(() => expect(screen.getByTestId('not-expanded')).not.toBeCollapsed())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).not.toBeCollapsed()
Received element is collapsed:
<View
accessibilityState={
{
"expanded": false,
}
}
testID="not-expanded"
/>"
`);

expect(() =>
expect(screen.getByTestId('not-expanded-aria')).not.toBeCollapsed()
).toThrowErrorMatchingInlineSnapshot(`
"expect(element).not.toBeCollapsed()
Received element is collapsed:
<View
aria-expanded={false}
testID="not-expanded-aria"
/>"
`);

expect(() => expect(screen.getByTestId('default')).toBeCollapsed())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeCollapsed()
Received element is not collapsed:
<View
testID="default"
/>"
`);
});
96 changes: 96 additions & 0 deletions src/matchers/__tests__/to-be-expanded.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as React from 'react';
import { View } from 'react-native';
import { render, screen } from '../..';
import '../extend-expect';

test('toBeExpanded() basic case', () => {
render(
<>
<View testID="expanded" accessibilityState={{ expanded: true }} />
<View testID="expanded-aria" aria-expanded />
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
<View testID="not-expanded-aria" aria-expanded={false} />
<View testID="default" />
</>
);

expect(screen.getByTestId('expanded')).toBeExpanded();
expect(screen.getByTestId('expanded-aria')).toBeExpanded();
expect(screen.getByTestId('not-expanded')).not.toBeExpanded();
expect(screen.getByTestId('not-expanded-aria')).not.toBeExpanded();
expect(screen.getByTestId('default')).not.toBeExpanded();
});

test('toBeExpanded() error messages', () => {
render(
<>
<View testID="expanded" accessibilityState={{ expanded: true }} />
<View testID="expanded-aria" aria-expanded />
<View testID="not-expanded" accessibilityState={{ expanded: false }} />
<View testID="not-expanded-aria" aria-expanded={false} />
<View testID="default" />
</>
);

expect(() => expect(screen.getByTestId('expanded')).not.toBeExpanded())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).not.toBeExpanded()
Received element is expanded:
<View
accessibilityState={
{
"expanded": true,
}
}
testID="expanded"
/>"
`);

expect(() => expect(screen.getByTestId('expanded-aria')).not.toBeExpanded())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).not.toBeExpanded()
Received element is expanded:
<View
aria-expanded={true}
testID="expanded-aria"
/>"
`);

expect(() => expect(screen.getByTestId('not-expanded')).toBeExpanded())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeExpanded()
Received element is not expanded:
<View
accessibilityState={
{
"expanded": false,
}
}
testID="not-expanded"
/>"
`);

expect(() => expect(screen.getByTestId('not-expanded-aria')).toBeExpanded())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeExpanded()
Received element is not expanded:
<View
aria-expanded={false}
testID="not-expanded-aria"
/>"
`);

expect(() => expect(screen.getByTestId('default')).toBeExpanded())
.toThrowErrorMatchingInlineSnapshot(`
"expect(element).toBeExpanded()
Received element is not expanded:
<View
testID="default"
/>"
`);
});
2 changes: 2 additions & 0 deletions src/matchers/extend-expect.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import type { Style } from './to-have-style';
export interface JestNativeMatchers<R> {
toBeOnTheScreen(): R;
toBeChecked(): R;
toBeCollapsed(): R;
toBeDisabled(): R;
toBeBusy(): R;
toBeEmptyElement(): R;
toBeEnabled(): R;
toBeExpanded(): R;
toBePartiallyChecked(): R;
toBeSelected(): R;
toBeVisible(): R;
Expand Down
4 changes: 4 additions & 0 deletions src/matchers/extend-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import { toBeOnTheScreen } from './to-be-on-the-screen';
import { toBeChecked } from './to-be-checked';
import { toBeCollapsed } from './to-be-collapsed';
import { toBeDisabled, toBeEnabled } from './to-be-disabled';
import { toBeBusy } from './to-be-busy';
import { toBeEmptyElement } from './to-be-empty-element';
import { toBeExpanded } from './to-be-expanded';
import { toBePartiallyChecked } from './to-be-partially-checked';
import { toBeSelected } from './to-be-selected';
import { toBeVisible } from './to-be-visible';
Expand All @@ -17,10 +19,12 @@ import { toHaveTextContent } from './to-have-text-content';
expect.extend({
toBeOnTheScreen,
toBeChecked,
toBeCollapsed,
toBeDisabled,
toBeBusy,
toBeEmptyElement,
toBeEnabled,
toBeExpanded,
toBePartiallyChecked,
toBeSelected,
toBeVisible,
Expand Down
2 changes: 2 additions & 0 deletions src/matchers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export { toBeBusy } from './to-be-busy';
export { toBeChecked } from './to-be-checked';
export { toBeCollapsed } from './to-be-collapsed';
export { toBeDisabled, toBeEnabled } from './to-be-disabled';
export { toBeEmptyElement } from './to-be-empty-element';
export { toBeExpanded } from './to-be-expanded';
export { toBeOnTheScreen } from './to-be-on-the-screen';
export { toBePartiallyChecked } from './to-be-partially-checked';
export { toBeSelected } from './to-be-selected';
Expand Down
28 changes: 28 additions & 0 deletions src/matchers/to-be-collapsed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactTestInstance } from 'react-test-renderer';
import { matcherHint } from 'jest-matcher-utils';
import { isElementCollapsed } from '../helpers/accessiblity';
import { checkHostElement, formatElement } from './utils';

export function toBeCollapsed(
this: jest.MatcherContext,
element: ReactTestInstance
) {
checkHostElement(element, toBeCollapsed, this);

return {
pass: isElementCollapsed(element),
message: () => {
const matcher = matcherHint(
`${this.isNot ? '.not' : ''}.toBeCollapsed`,
'element',
''
);
return [
matcher,
'',
`Received element is ${this.isNot ? '' : 'not '}collapsed:`,
formatElement(element),
].join('\n');
},
};
}
28 changes: 28 additions & 0 deletions src/matchers/to-be-expanded.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactTestInstance } from 'react-test-renderer';
import { matcherHint } from 'jest-matcher-utils';
import { isElementExpanded } from '../helpers/accessiblity';
import { checkHostElement, formatElement } from './utils';

export function toBeExpanded(
this: jest.MatcherContext,
element: ReactTestInstance
) {
checkHostElement(element, toBeExpanded, this);

return {
pass: isElementExpanded(element),
message: () => {
const matcher = matcherHint(
`${this.isNot ? '.not' : ''}.toBeExpanded`,
'element',
''
);
return [
matcher,
'',
`Received element is ${this.isNot ? '' : 'not '}expanded:`,
formatElement(element),
].join('\n');
},
};
}

0 comments on commit a9f32a6

Please sign in to comment.