Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate to testing-library #306

Merged
merged 2 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading