feat: byA11yState matcher (#260)

* feat: byA11yState matcher

* Kick CI

* Update src/helpers/a11yAPI.js

Co-Authored-By: Michał Pierzchała <thymikee@gmail.com>

* Correct types

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
This commit is contained in:
Kacper Wiszczuk
2020-03-18 10:06:31 +01:00
committed by GitHub
parent db087f7108
commit 62cda2205b
5 changed files with 203 additions and 70 deletions
+45 -2
View File
@@ -9,11 +9,12 @@ const BUTTON_ROLE = 'button';
const TEXT_LABEL = 'cool text';
const TEXT_HINT = 'static text';
const TEXT_ROLE = 'link';
const NO_MATCHES_TEXT = 'not-existent-element';
// Little hack to make all the methods happy with type
const NO_MATCHES_TEXT: any = 'not-existent-element';
const NO_INSTANCES_FOUND = 'No instances found';
const FOUND_TWO_INSTANCES = 'Expected 1 but found 2 instances';
const Typography = ({ children, ...rest }) => {
const Typography = ({ children, ...rest }: any) => {
return <Text {...rest}>{children}</Text>;
};
@@ -31,6 +32,7 @@ class Button extends React.Component<any> {
accessibilityLabel={TEXT_LABEL}
accessibilityRole={TEXT_ROLE}
accessibilityStates={['selected']}
accessibilityState={{ expanded: false, selected: true }}
>
{this.props.children}
</Typography>
@@ -47,6 +49,7 @@ function Section() {
accessibilityLabel={TEXT_LABEL}
accessibilityRole={TEXT_ROLE}
accessibilityStates={['selected', 'disabled']}
accessibilityState={{ expanded: false }}
>
Title
</Typography>
@@ -159,3 +162,43 @@ test('getAllByA11yStates, queryAllByA11yStates', () => {
expect(() => getAllByA11yStates([])).toThrow(NO_INSTANCES_FOUND);
expect(queryAllByA11yStates(NO_MATCHES_TEXT)).toEqual([]);
});
test('getByA11yState, queryByA11yState', () => {
const { getByA11yState, queryByA11yState } = render(<Section />);
expect(getByA11yState({ selected: true }).props.accessibilityState).toEqual({
selected: true,
expanded: false,
});
expect(
queryByA11yState({ selected: true })?.props.accessibilityState
).toEqual({
selected: true,
expanded: false,
});
expect(() => getByA11yState({ disabled: true })).toThrow(NO_INSTANCES_FOUND);
expect(queryByA11yState({ disabled: true })).toEqual(null);
expect(() => getByA11yState({ expanded: false })).toThrow(
FOUND_TWO_INSTANCES
);
expect(() => queryByA11yState({ expanded: false })).toThrow(
FOUND_TWO_INSTANCES
);
});
test('getAllByA11yState, queryAllByA11yState', () => {
const { getAllByA11yState, queryAllByA11yState } = render(<Section />);
expect(getAllByA11yState({ selected: true }).length).toEqual(1);
expect(queryAllByA11yState({ selected: true }).length).toEqual(1);
expect(() => getAllByA11yState({ disabled: true })).toThrow(
NO_INSTANCES_FOUND
);
expect(queryAllByA11yState({ disabled: true })).toEqual([]);
expect(getAllByA11yState({ expanded: false }).length).toEqual(2);
expect(queryAllByA11yState({ expanded: false }).length).toEqual(2);
});
+57 -28
View File
@@ -1,37 +1,48 @@
// @flow
import makeQuery from './makeQuery';
import type { A11yRole, A11yStates, A11yState } from '../types.flow';
type QueryFn = (string | RegExp) => ReactTestInstance | null;
type QueryAllFn = (string | RegExp) => Array<ReactTestInstance> | [];
type GetFn = (string | RegExp) => ReactTestInstance;
type GetAllFn = (string | RegExp) => Array<ReactTestInstance>;
type ArrayQueryFn = (string | Array<string>) => ReactTestInstance | null;
type ArrayQueryAllFn = (
string | Array<string>
) => Array<ReactTestInstance> | [];
type ArrayGetFn = (string | Array<string>) => ReactTestInstance;
type ArrayGetAllFn = (string | Array<string>) => Array<ReactTestInstance>;
type GetReturn = ReactTestInstance;
type GetAllReturn = Array<ReactTestInstance>;
type QueryReturn = ReactTestInstance | null;
type QueryAllReturn = Array<ReactTestInstance> | [];
type A11yAPI = {|
getByA11yLabel: GetFn,
getAllByA11yLabel: GetAllFn,
queryByA11yLabel: QueryFn,
queryAllByA11yLabel: QueryAllFn,
getByA11yHint: GetFn,
getAllByA11yHint: GetAllFn,
queryByA11yHint: QueryFn,
queryAllByA11yHint: QueryAllFn,
getByA11yRole: GetFn,
getAllByA11yRole: GetAllFn,
queryByA11yRole: QueryFn,
queryAllByA11yRole: QueryAllFn,
getByA11yStates: ArrayGetFn,
getAllByA11yStates: ArrayGetAllFn,
queryByA11yStates: ArrayQueryFn,
queryAllByA11yStates: ArrayQueryAllFn,
// Label
getByA11yLabel: (string | RegExp) => GetReturn,
getAllByA11yLabel: (string | RegExp) => GetAllReturn,
queryByA11yLabel: (string | RegExp) => QueryReturn,
queryAllByA11yLabel: (string | RegExp) => QueryAllReturn,
// Hint
getByA11yHint: (string | RegExp) => GetReturn,
getAllByA11yHint: (string | RegExp) => GetAllReturn,
queryByA11yHint: (string | RegExp) => QueryReturn,
queryAllByA11yHint: (string | RegExp) => QueryAllReturn,
// Role
getByA11yRole: (A11yRole | RegExp) => GetReturn,
getAllByA11yRole: (A11yRole | RegExp) => GetAllReturn,
queryByA11yRole: (A11yRole | RegExp) => QueryReturn,
queryAllByA11yRole: (A11yRole | RegExp) => QueryAllReturn,
// States
getByA11yStates: (A11yStates | Array<A11yStates>) => GetReturn,
getAllByA11yStates: (A11yStates | Array<A11yStates>) => GetAllReturn,
queryByA11yStates: (A11yStates | Array<A11yStates>) => QueryReturn,
queryAllByA11yStates: (A11yStates | Array<A11yStates>) => QueryAllReturn,
// State
getByA11yState: A11yState => GetReturn,
getAllByA11yState: A11yState => GetAllReturn,
queryByA11yState: A11yState => QueryReturn,
queryAllByA11yState: A11yState => QueryAllReturn,
|};
export function matchStringValue(prop?: string, matcher: string | RegExp) {
export function matchStringValue(
prop?: string,
matcher: string | RegExp
): boolean {
if (!prop) {
return false;
}
@@ -46,7 +57,7 @@ export function matchStringValue(prop?: string, matcher: string | RegExp) {
export function matchArrayValue(
prop?: Array<string>,
matcher: string | Array<string>
) {
): boolean {
if (!prop || matcher.length === 0) {
return false;
}
@@ -58,6 +69,14 @@ export function matchArrayValue(
return !matcher.some(e => !prop.includes(e));
}
export function matchObject<T: {}>(prop?: T, matcher: T): boolean {
return prop
? Object.keys(matcher).length !== 0 &&
Object.keys(prop).length !== 0 &&
!Object.keys(matcher).some(key => prop[key] !== matcher[key])
: false;
}
const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
({
...makeQuery(
@@ -100,6 +119,16 @@ const a11yAPI = (instance: ReactTestInstance): A11yAPI =>
},
matchArrayValue
)(instance),
...makeQuery(
'accessibilityState',
{
getBy: ['getByA11yState', 'getByAccessibilityState'],
getAllBy: ['getAllByA11yState', 'getAllByAccessibilityState'],
queryBy: ['queryByA11yState', 'queryByAccessibilityState'],
queryAllBy: ['queryAllByA11yState', 'queryAllByAccessibilityState'],
},
matchObject
)(instance),
}: any);
export default a11yAPI;
+48
View File
@@ -0,0 +1,48 @@
// @flow
export type A11yRole =
| 'none'
| 'button'
| 'link'
| 'search'
| 'image'
| 'keyboardkey'
| 'text'
| 'adjustable'
| 'imagebutton'
| 'header'
| 'summary'
| 'alert'
| 'checkbox'
| 'combobox'
| 'menu'
| 'menubar'
| 'menuitem'
| 'progressbar'
| 'radio'
| 'radiogroup'
| 'scrollbar'
| 'spinbutton'
| 'switch'
| 'tab'
| 'tablist'
| 'timer'
| 'toolbar';
export type A11yState = {|
disabled?: boolean,
selected?: boolean,
checked?: boolean | 'mixed',
busy?: boolean,
expanded?: boolean,
|};
export type A11yStates =
| 'disabled'
| 'selected'
| 'checked'
| 'unchecked'
| 'busy'
| 'expanded'
| 'collapsed'
| 'hasPopup';
+17 -12
View File
@@ -137,32 +137,37 @@ const queryByA11yHint: ReactTestInstance = tree.queryByA11yHint('label');
const queryAllByA11yHint: Array<ReactTestInstance> = tree.queryAllByA11yHint(
'label'
);
const getByA11yRole: ReactTestInstance = tree.getByA11yRole('label');
const getByA11yRole: ReactTestInstance = tree.getByA11yRole('button');
const getAllByA11yRole: Array<ReactTestInstance> = tree.getAllByA11yRole(
'label'
'button'
);
const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('label');
const queryByA11yRole: ReactTestInstance = tree.queryByA11yRole('button');
const queryAllByA11yRole: Array<ReactTestInstance> = tree.queryAllByA11yRole(
'label'
'button'
);
const getByA11yStates: ReactTestInstance = tree.getByA11yStates('label');
const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['label']);
const getByA11yStates: ReactTestInstance = tree.getByA11yStates('selected');
const getByA11yStatesArray: ReactTestInstance = tree.getByA11yStates(['selected']);
const getAllByA11yStates: Array<ReactTestInstance> = tree.getAllByA11yStates(
'label'
'selected'
);
const getAllByA11yStatesArray: Array<
ReactTestInstance
> = tree.getAllByA11yStates(['label']);
const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('label');
> = tree.getAllByA11yStates(['selected']);
const queryByA11yStates: ReactTestInstance = tree.queryByA11yStates('selected');
const queryByA11yStatesArray: ReactTestInstance = tree.queryByA11yStates([
'label',
'selected',
]);
const queryAllByA11yStates: Array<
ReactTestInstance
> = tree.queryAllByA11yStates('label');
> = tree.queryAllByA11yStates('selected');
const queryAllByA11yStatesArray: Array<
ReactTestInstance
> = tree.queryAllByA11yStates(['label']);
> = tree.queryAllByA11yStates(['selected']);
const getByA11yState: ReactTestInstance = tree.getByA11yState({ busy: true });
const getAllByA11yState: Array<ReactTestInstance> = tree.getAllByA11yState({ busy: true });
const queryByA11yState: ReactTestInstance = tree.queryByA11yState({ busy: true });
const queryAllByA11yState: Array<ReactTestInstance> = tree.queryAllByA11yState({ busy: true });
const debugFn = tree.debug();
const debugFnWithMessage = tree.debug('my message');
+36 -28
View File
@@ -1,4 +1,5 @@
import * as React from 'react';
import { AccessibilityState, AccessibilityStates, AccessibilityRole } from 'react-native';
import { ReactTestInstance, ReactTestRendererJSON } from 'react-test-renderer';
export interface GetByAPI {
@@ -49,35 +50,42 @@ export interface QueryByAPI {
) => Array<ReactTestInstance> | [];
}
type QueryFn = (text: string | RegExp) => ReactTestInstance | null;
type QueryAllFn = (text: string | RegExp) => Array<ReactTestInstance> | [];
type GetFn = (text: string | RegExp) => ReactTestInstance;
type GetAllFn = (text: string | RegExp) => Array<ReactTestInstance>;
type ArrayQueryFn = (text: string | Array<string>) => ReactTestInstance | null;
type ArrayQueryAllFn = (
text: string | Array<string>
) => Array<ReactTestInstance> | [];
type ArrayGetFn = (text: string | Array<string>) => ReactTestInstance;
type ArrayGetAllFn = (text: string | Array<string>) => Array<ReactTestInstance>;
type GetReturn = ReactTestInstance;
type GetAllReturn = Array<ReactTestInstance>;
type QueryReturn = ReactTestInstance | null;
type QueryAllReturn = Array<ReactTestInstance> | [];
export interface A11yAPI {
getByA11yLabel: GetFn;
getAllByA11yLabel: GetAllFn;
queryByA11yLabel: QueryFn;
queryAllByA11yLabel: QueryAllFn;
getByA11yHint: GetFn;
getAllByA11yHint: GetAllFn;
queryByA11yHint: QueryFn;
queryAllByA11yHint: QueryAllFn;
getByA11yRole: GetFn;
getAllByA11yRole: GetAllFn;
queryByA11yRole: QueryFn;
queryAllByA11yRole: QueryAllFn;
getByA11yStates: ArrayGetFn;
getAllByA11yStates: ArrayGetAllFn;
queryByA11yStates: ArrayQueryFn;
queryAllByA11yStates: ArrayQueryAllFn;
}
type A11yAPI = {
// Label
getByA11yLabel: (matcher: string | RegExp) => GetReturn,
getAllByA11yLabel: (matcher: string | RegExp) => GetAllReturn,
queryByA11yLabel: (matcher: string | RegExp) => QueryReturn,
queryAllByA11yLabel: (matcher: string | RegExp) => QueryAllReturn,
// Hint
getByA11yHint: (matcher: string | RegExp) => GetReturn,
getAllByA11yHint: (matcher: string | RegExp) => GetAllReturn,
queryByA11yHint: (matcher: string | RegExp) => QueryReturn,
queryAllByA11yHint: (matcher: string | RegExp) => QueryAllReturn,
// Role
getByA11yRole: (matcher: AccessibilityRole | RegExp) => GetReturn,
getAllByA11yRole: (matcher: AccessibilityRole | RegExp) => GetAllReturn,
queryByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryReturn,
queryAllByA11yRole: (matcher: AccessibilityRole | RegExp) => QueryAllReturn,
// States
getByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => GetReturn,
getAllByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => GetAllReturn,
queryByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => QueryReturn,
queryAllByA11yStates: (matcher: AccessibilityStates | Array<AccessibilityStates>) => QueryAllReturn,
// State
getByA11yState: (matcher: AccessibilityState) => GetReturn,
getAllByA11yState: (matcher: AccessibilityState) => GetAllReturn,
queryByA11yState: (matcher: AccessibilityState) => QueryReturn,
queryAllByA11yState: (matcher: AccessibilityState) => QueryAllReturn,
};
export interface Thenable {
then: (resolve: () => any, reject?: () => any) => any;