ammarahm-ed / react-native-actions-sheet

A Cross Platform(Android, iOS & Web) ActionSheet with a flexible api, native performance and zero dependency code for react native. Create anything you want inside ActionSheet.
https://rnas.vercel.app
MIT License
1.47k stars 121 forks source link

Cannot test this library with testing-library/react-native #221

Open dkulakov opened 1 year ago

dkulakov commented 1 year ago

First off all thanks for this great library!

After I've switched to this library some of my tests are failing, specifically test cases which interact with ActionSheet. I need to render ActionSheet content in my tests, after some research I found this peace of code, these changes were added not so far in the commit what I guess to cause a problem. I'm not sure if it's possible to mock this value outside the library.

see more details

johnny-mcfadden-dailypay commented 1 year ago

Hi @dkulakov, what functionality are you trying to test exactly? I would recommend not mocking this library and just asserting that the show and hide functions are called. If you really want to, you could also add a snapshot. Here is an example:

import React from 'react';
import { fireEvent, render } from 'utils/test-utils/custom-render';
import LoginRegisterInfoSheet from '../login-register-info-sheet';
import { hideActionSheet, SheetManagerSheets } from 'interfaces/sheet-manager';

describe('LoginRegisterInfoSheet', () => {
  test('should render correctly', () => {
    const tree = render({
      ui: <LoginRegisterInfoSheet />,
    });

    expect(tree).toMatchSnapshot();
  });

  test('should call hideActionSheet() when pressed', () => {
    const tree = render({
      ui: <LoginRegisterInfoSheet />,
    });

    const closeActionSheetButton = tree.getByTestId('loginRegisterInfoSheetCloseButton');

    fireEvent(closeActionSheetButton, 'press');

    expect(hideActionSheet).toBeCalledWith({ id: SheetManagerSheets.loginRegisterInfo });
  });
});

The custom render import aligns with docs: https://testing-library.com/docs/react-native-testing-library/setup

dkulakov commented 1 year ago

Hi @johnny-mcfadden-dailypay , thanks for your reply!

Exactly, I don't want to mock this library, but the problem is that the value dimensions.height will be always 0 in the test environment, so it means you can't test your custom content inside the ActionSheet component.

johnny-mcfadden-dailypay commented 1 year ago

@dkulakov if you trigger the sheet open and wait for it async, does it still have 0 dimensions in height? In the example above, the close action sheet button i'm getting:

const closeActionSheetButton = tree.getByTestId('loginRegisterInfoSheetCloseButton');

is within the action sheet, as "custom content" as you've referred to. But I could grab any content inside the action sheet by test id and interact with it.

dkulakov commented 1 year ago

@johnny-mcfadden-dailypay as I can see dimensions.height changes inside onLayout event, but test environment renders components in nodejs environment which doesn't have any dimensions, that's why dimensions.height always will be 0.

What version do you use? The bug appears after 0.8.3

johnny-mcfadden-dailypay commented 1 year ago

Not seeing this issue. Even my snapshots have all the child obj nodes. I'm using the latest: 0.8.7. Are you ensuring you open the action sheet first before trying to assert elements? (sorry maybe a silly question)

dkulakov commented 1 year ago

@johnny-mcfadden-dailypay I've created reproducible example. Can you check it? It fails with the latest version, but if you install version 0.8.3, test works well.

Thanks in advance!

johnny-mcfadden-dailypay commented 1 year ago

@dkulakov in your example it doesn't look like you've called the action sheet open before trying to assert the combo change option was called, could you try that?

dkulakov commented 1 year ago

@johnny-mcfadden-dailypay here is I call the action sheet open

Taloqq commented 1 year ago

Hey guys, I have a related problem and I thought this would be the best place to ask. I have a problem of finding the elements inside the ActionSheet in my test. Heres my component code:

const MyComponent = () => {

  const actionSheetRef = useRef<ActionSheetRef>(null);

  useEffect(() => {
    actionSheetRef.current?.show();
  }, []);

  return (
    <ActionSheet
      ref={actionSheetRef}
      ...
    >
      <View testID='viewTestId'>
      ...
      </View>
    </ActionSheet>
  )
}

If I render MyComponent in the test, it is unable to find 'viewTestId' with 'getById()' inside the ActionSheet.

It seems that 'findByText()' works though, but it does give me a reference error: ReferenceError: You are trying to import a file after the Jest environment has been torn down.

Heres the test:

describe('MyComponent', () => {

  it('Renders', () => {
    const component = render(
      <MyComponent onBack={jest.fn()} />
    );
      expect(component.findByText('Muokkaa tietoja')).toBeTruthy();
    })
});

The render() is a custom method:

const customRender = (ui : any) => {
  return render(ui, { wrapper: renderWithProviders });
};

Any suggestions? Thank you

dkulakov commented 1 year ago

@Taloqq try to add waitFor like in this example

your test should like like, but don't forget to add async await waitFor(() => { component.findByText('Muokkaa tietoja'); });

Taloqq commented 1 year ago

@dkulakov It still gives me the same referenceerror. Im able to fix the reference error with jest.useFakeTimers('legacy') but it still says A worker process has failed to exit gracefully and has been force exited. Anyhow, is it possible to use getByTestId() with this library?

dkulakov commented 1 year ago

@Taloqq I'm using version 0.8.3 and my tests work fine, but on the latest version it doesn't, so try to downgrade your version to 0.8.3 and check your tests again.

Taloqq commented 1 year ago

@dkulakov Hey I'm having a new issue. I have a pressable/button inside the sheet component, and when Im trying to press it, it fails with Cannot read properties of undefined (reading 'onPress') Have you had this issue? It doesnt matter if I find it by text or testid. await waitFor(() => fireEvent.press(component.findByTestId('taloButton'))); With getByTestId, it is unable to find it. Also if I just test expect(component.findByText('Talo')).toBeTruthy();, it is successful.

dkulakov commented 1 year ago

@Taloqq Look how I do it in my example

image
Taloqq commented 1 year ago

@dkulakov

await waitFor(() => component.getByText('Talo'));
await act(async () => {
  await fireEvent.press(component.getByText('Talo'));
});

Gives Unable to find an element with text: Talo And If i replace getByText with findByText, it gives Timed out in waitFor.

dkulakov commented 1 year ago

@Taloqq Can you share your whole test case?

Taloqq commented 1 year ago

@dkulakov

describe('NewAddressView', () => {

  it('Navigates to page 2 when address and location type is set', async () => {
    const component = render(
      <NewAddressView onBack={jest.fn()} />
    );
    fireEvent.press(component.getByTestId('locationConfirm'));
    await waitFor(() => fireEvent.press(component.getByTestId('nextPageButton')));
    await waitFor(() => component.getByText('Talo'));
    await act(async () => {
      await fireEvent.press(component.getByText('Talo'));
    });
  });
});

And heres how im calling it:

const askLocationType = async () => {
    const locationTypeResponse = await SheetManager.show('LocationTypeSheet');
    if (locationTypeResponse) {
     ...
    }
  };
dkulakov commented 1 year ago

@Taloqq Is it also inside ActionSheet? If so, does it work? await waitFor(() => fireEvent.press(component.getByTestId('nextPageButton')));

Taloqq commented 1 year ago

@dkulakov No, const askLocationType = async () => { } is not inside the actionSheet. Its in my AddressPage1 component. The action sheet fires when 'nextPageButton' is pressed

dkulakov commented 1 year ago

@Taloqq Try to remove waitFor for fireEvent.press(component.getByTestId('nextPageButton'))

Taloqq commented 1 year ago

@dkulakov It has no effect :/

dkulakov commented 1 year ago

@Taloqq Can you reproduce this issue using my demo project?

Kaung-Htet-Hein commented 1 year ago

jest.mock('react-native-actions-sheet', () => {
    const { View } = require('react-native')

    const ActionSheet = props => {
        return <View testID="actionSheet">{props.children}</View>
    }

    return {
        __esModule: true,
        default: ActionSheet,
        SheetManager: {
            hideAll: jest.fn(),
            hide: jest.fn(),
            show: jest.fn(),
        },
    }
})

I mocked the library, can you try this?

dkulakov commented 1 year ago

@Kaung-Htet-Hein thanks, I tried, but it doesn't help.

import { registerSheet } from 'react-native-actions-sheet';

this method renders sheets in a portal, so if we mock this lib, then a content never appears in the test.

dkulakov commented 1 year ago

@Kaung-Htet-Hein well, I made it work in this way

jest.mock('react-native-actions-sheet', () => {
  const funcs = jest.requireActual('react-native-actions-sheet');

  return {
    __esModule: true,
    ...funcs,
    SheetManager: {
      show: funcs.SheetManager.show,
      hide: jest.fn(),
      // hide: funcs.SheetManager.hide, // <-- It doesn't work if uncomment this line
    },
    default: ({ children }) => children,
  };
});