Skip to content

Commit

Permalink
feat(test): migrate to testing-library (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlaessig authored Nov 16, 2024
1 parent a7c3837 commit 61c9a99
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 250 deletions.
139 changes: 57 additions & 82 deletions __tests__/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { FlatList, Text, TextInput, View } from 'react-native';
import React, { type ReactElement } from 'react';
import { render, screen, within } from '@testing-library/react-native';
import { FlatList, TextInput, type FlatListProps } from 'react-native';

import Autocomplete from '../index';

const ITEMS = [
Expand All @@ -12,111 +13,85 @@ const ITEMS = [
'Revenge of the Sith',
] as const;

const suggestionListTestId = 'suggestionListTestId';
function TestSuggestionList<T>(props: FlatListProps<T>): ReactElement {
return <FlatList {...props} testID={suggestionListTestId} />;
}

describe('<AutocompleteInput />', () => {
it('should hide suggestion list on initial render', () => {
const r = renderer.create(<Autocomplete data={[]} />);
const autocomplete = r.root;

expect(autocomplete.findAllByType(FlatList)).toHaveLength(0);
render(<Autocomplete data={[]} renderResultList={TestSuggestionList} />);
const suggestionList = screen.queryByTestId(suggestionListTestId);
expect(suggestionList).not.toBeOnTheScreen();
});

it('should show suggestion list when data gets updated with length > 0', () => {
const testRenderer = renderer.create(<Autocomplete data={[]} />);
const autocomplete = testRenderer.root;
it('should show suggestion list with suggestions when data gets updated with length > 0', () => {
const { rerender } = render(<Autocomplete data={[]} renderResultList={TestSuggestionList} />);

expect(autocomplete.findAllByType(FlatList)).toHaveLength(0);
const hiddenSuggestionList = screen.queryByTestId(suggestionListTestId);
expect(hiddenSuggestionList).not.toBeOnTheScreen();

testRenderer.update(<Autocomplete data={ITEMS} />);
rerender(<Autocomplete data={ITEMS} renderResultList={TestSuggestionList} />);

const list = autocomplete.findByType(FlatList);
expect(list.props.data).toEqual(ITEMS);
const suggestionList = screen.getByTestId(suggestionListTestId);
expect(suggestionList).toBeOnTheScreen();

const texts = list.findAllByType(Text);
expect(texts).toHaveLength(ITEMS.length);
const suggestions = within(suggestionList).getAllByRole('text');
suggestions.forEach((suggestion, index) => {
expect(suggestion).toHaveTextContent(ITEMS[index]);
});
});

it('should hide suggestion list when data gets updates with length < 1', () => {
const props = { data: ITEMS };
const testRenderer = renderer.create(<Autocomplete {...props} />);
const autocomplete = testRenderer.root;
it('should apply default render list function', () => {
render(<Autocomplete data={ITEMS} renderResultList={undefined} />);
const suggestions = screen.getAllByRole('text');
suggestions.forEach((suggestion, index) => {
expect(suggestion).toHaveTextContent(ITEMS[index]);
});
});

it('should hide suggestion list when data gets updated with length < 1', () => {
const { rerender } = render(
<Autocomplete data={ITEMS} renderResultList={TestSuggestionList} />,
);

expect(autocomplete.findAllByType(FlatList)).toHaveLength(1);
testRenderer.update(<Autocomplete data={[]} />);
const suggestionList = screen.getByTestId(suggestionListTestId);
expect(suggestionList).toBeOnTheScreen();

expect(autocomplete.findAllByType(FlatList)).toHaveLength(0);
rerender(<Autocomplete data={[]} renderResultList={TestSuggestionList} />);

const hiddenSuggestionList = screen.queryByTestId(suggestionListTestId);
expect(hiddenSuggestionList).not.toBeOnTheScreen();
});

it('should render custom text input', () => {
const text = 'Custom Text Input';
const testRenderer = renderer.create(
const customTextInputTestId = 'customTextInput';
render(
<Autocomplete
data={[]}
foo="bar"
renderTextInput={(props) => <Text {...props}>{text}</Text>}
renderTextInput={(props) => <TextInput {...props} testID={customTextInputTestId} />}
/>,
);

const autocomplete = testRenderer.root;
const customTextInput = autocomplete.findByType(Text);

expect((customTextInput.children[0] as { children: unknown[] }).children).toEqual([text]);
expect(autocomplete.findAllByType(TextInput)).toHaveLength(0);
const textInput = screen.getByTestId(customTextInputTestId);
expect(textInput).toBeOnTheScreen();
});

it('should render default <TextInput /> if no custom one is supplied', () => {
const props = { foo: 'bar' };
const testRenderer = renderer.create(<Autocomplete data={[]} {...props} />);
const autocomplete = testRenderer.root;
const textInput = autocomplete.findByType(TextInput);

expect(textInput.props).toEqual(expect.objectContaining(props));
});

it('should render default <FlatList /> if no custom one is supplied', () => {
const testRenderer = renderer.create(<Autocomplete data={ITEMS} />);
const autocomplete = testRenderer.root;
const list = autocomplete.findByType(FlatList);

expect(list.props.data).toEqual(ITEMS);
});

it('should only pass props in flatListProps to <FlatList />', () => {
// Using keyExtractor isn't important for the test, but prevents a warning
const keyExtractor = (_, index) => `key-${index}`;
const flatListProps = { foo: 'bar', keyExtractor };
const otherProps = { baz: 'qux' };
const testRenderer = renderer.create(
<Autocomplete data={ITEMS} flatListProps={flatListProps} {...otherProps} />,
);
const autocomplete = testRenderer.root;
const list = autocomplete.findByType(FlatList);

expect(list.props).toEqual(expect.objectContaining(flatListProps));
expect(list.props).toEqual(expect.not.objectContaining(otherProps));
});

it('should render a custom result list', () => {
const testRenderer = renderer.create(
<Autocomplete
data={ITEMS}
renderResultList={({ data, style }) => (
<View style={style}>{data?.map((item, index) => <Text key={index}>{item}</Text>)}</View>
)}
/>,
);

const autocomplete = testRenderer.root;
expect(autocomplete.findAllByType(FlatList)).toHaveLength(0);
render(<Autocomplete data={[]} placeholder="Enter search" />);

const texts = autocomplete.findAllByType(Text);
expect(texts).toHaveLength(ITEMS.length);
const input = screen.getByPlaceholderText('Enter search');
expect(input).toBeOnTheScreen();
});

it('should forward the ref to the input', () => {
const inputRef = React.createRef();
it('should forward the ref to the text input', async () => {
let ref: React.RefObject<TextInput>;
function TestForwardRefComponent() {
ref = React.useRef<TextInput>(null);
return <Autocomplete data={ITEMS} placeholder="TestText" ref={ref} />;
}

renderer.create(<Autocomplete data={ITEMS} ref={inputRef} />);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((inputRef.current as any)._reactInternals.elementType.displayName).toBe('TextInput');
render(<TestForwardRefComponent />);
expect(ref!.current?.constructor.name).toBe('TextInput');
});
});
12 changes: 4 additions & 8 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function DefaultTextInput(props: TextInputProps): React.ReactElement {
return <TextInput {...props} />;
}

function AutocompleteInputComponent<Item, Ref>(
export const AutocompleteInput = React.forwardRef(function AutocompleteInputComponent<Item, Ref>(
props: AutocompleteInputProps<Item>,
ref: React.ForwardedRef<Ref>,
): React.ReactElement {
Expand All @@ -61,7 +61,7 @@ function AutocompleteInputComponent<Item, Ref>(
ref,
};

return renderFunction?.(textProps);
return renderFunction(textProps);
}

const {
Expand Down Expand Up @@ -90,7 +90,7 @@ function AutocompleteInputComponent<Item, Ref>(
)}
</View>
);
}
});

const border = {
borderColor: '#b9b9b9',
Expand Down Expand Up @@ -150,13 +150,9 @@ const styles = StyleSheet.create({
}),
});

export const AutocompleteInput = React.forwardRef(
AutocompleteInputComponent,
) as typeof AutocompleteInputComponent;

export default AutocompleteInput;

(AutocompleteInput as React.FC).propTypes = {
AutocompleteInput.propTypes = {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
...TextInput.propTypes,
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/react-native/extend-expect'],
verbose: true,
};
Loading

0 comments on commit 61c9a99

Please sign in to comment.