Skip to content

Sample app for how to use & test React-Query for data fetching and Zustand for state management in a React Native application.

Notifications You must be signed in to change notification settings

tarikfp/react-native-testing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

53 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ§ͺ React Native Testing πŸ› οΈ

This app is a simple example of how to use/test React-Query for data fetching and Zustand for state management in a React Native application.

It provides comprehensive and detailed examples to aid in the understanding of the testing process. The app uses following core packages:

  • react (18.2.0)
  • react-native (0.71.2, new arch.)
  • jest (29.4.1)
  • ts-jest (29.0.5)
  • @testing-library/react-hooks (8.0.1)
  • @testing-library/react-native (11.5.1)
  • msw (1.0.0)

❓ I don't get why this repo exists

To have better context about the repo, you can start by reading this compherensive article.

πŸ“š Table of contents

πŸ’» Running locally

Install dependencies

  yarn install

Start the emulator for ios.

 cd ios && pod install && yarn run ios

or for android

  yarn run android

πŸ§ͺ Running tests

  yarn test

πŸ‘€ Overview of product list screen

Code - Testing product list screen
import {NavigationContainer} from '@react-navigation/native';
import {renderHook} from '@testing-library/react-hooks';
import {fireEvent, render, screen} from '@testing-library/react-native';
import React from 'react';
import {setupGetAllProductsFailedHandler} from '../../../__mocks__/msw/handlers';
import {useGetAllProducts} from '../../api/product';
import ProductStack from '../../navigation/product-stack';
import {RouteNames} from '../../navigation/route-names';
import {useProductStore} from '../../store/product';
import {createReactQueryWrapper} from '../../utils/testing';
import ProductListScreen from '../product-list';

// We render the whole app stack instead of rendering just the screen
// because we need access to the react-navigation's header, which wouldn't
// be possible if we just rendered the screen.
const rootAppComponent = (
  <NavigationContainer>
    <ProductStack />
  </NavigationContainer>
);
const navigateMock = jest.fn();
const navigation = {navigate: navigateMock} as any;
const route = jest.fn() as any;

const component = <ProductListScreen navigation={navigation} route={route} />;

describe('Product list screen', () => {
  it('should display loading indicator initially', async () => {
    // We render the component and expect to see a loading indicator
    render(component, {wrapper: createReactQueryWrapper});
    expect(screen.queryByTestId(`screen-loader`)).toBeTruthy();

    // We render the product list using useGetAllProducts hook and wait until the products are loaded
    const {result, waitFor} = renderHook(() => useGetAllProducts(), {
      wrapper: createReactQueryWrapper,
    });
    await waitFor(() => result.current.isSuccess);

    // We expect the loading indicator to disappear after the products are loaded
    expect(screen.queryByTestId(`screen-loader`)).not.toBeTruthy();
  });

  it('should display product list data correctly', async () => {
    // Render the component and wait for it to load
    render(component, {wrapper: createReactQueryWrapper});

    // Check that no product card is rendered before fetching data
    expect(screen.queryByTestId(`product-list-card-1`)).not.toBeTruthy();

    // Fetch the product data
    const {result, waitFor} = renderHook(() => useGetAllProducts(), {
      wrapper: createReactQueryWrapper,
    });

    // Wait for the data to be fetched successfully
    await waitFor(() => result.current.isSuccess);

    // Check that each product card is rendered for each product in the data
    for (const {id} of result.current.data!) {
      expect(screen.queryByTestId(`product-list-card-${id}`)).toBeTruthy();
    }
  });

  it('should display error text in case get all products query fails', async () => {
    // Set up the mock handler for GET requests to the /products endpoint that returns an error response
    setupGetAllProductsFailedHandler();

    // Render the ProductListScreen component wrapped in the React Query wrapper
    render(component, {wrapper: createReactQueryWrapper});

    // Set up a mock React hook that calls the useGetAllProducts hook from the API module
    const {result, waitFor} = renderHook(() => useGetAllProducts(), {
      wrapper: createReactQueryWrapper,
    });

    // Wait for the useGetAllProducts hook to throw an error
    await waitFor(() => result.current.isError);

    // Assert that the "An error occurred" text is displayed on the screen
    expect(screen.getByText(`An error occurred`)).toBeTruthy();
  });

  it('should call navigation action on pressing the first product item', async () => {
    // Render the ProductListScreen component along with react-query wrapper
    render(component, {wrapper: createReactQueryWrapper});

    // Render the useGetAllProducts hook with react-query wrapper to fetch data
    const {result, waitFor} = renderHook(() => useGetAllProducts(), {
      wrapper: createReactQueryWrapper,
    });

    // Wait for the data to be fetched
    await waitFor(() => result.current.isSuccess);

    // Find the first product card in the list and simulate a press event on it
    const firstProductItem = screen.getByTestId(`product-list-card-1`);
    fireEvent.press(firstProductItem);

    // Expect that the navigation function is called with the correct route name and params
    expect(navigateMock).toHaveBeenCalledWith(RouteNames.productDetail, {
      id: 1,
    });
  });

  it('should add/remove product item correctly on pressing product items basket icon', async () => {
    // We render the entire app stack and use the createReactQueryWrapper as a
    // wrapper for the render function to set up the React Query Provider.
    render(rootAppComponent, {
      wrapper: createReactQueryWrapper,
    });

    // We use the renderHook function to invoke the useGetAllProducts hook which
    // fetches the data.
    const {result, waitFor} = renderHook(() => useGetAllProducts(), {
      wrapper: createReactQueryWrapper,
    });

    // We use the renderHook function to get access to the useProductStore hook
    // which we will use to check that the products in the basket were added/removed correctly.
    const {result: productStore} = renderHook(() => useProductStore(), {
      wrapper: createReactQueryWrapper,
    });

    // We wait for the useGetAllProducts hook to complete fetching the data before proceeding
    // with the test.
    await waitFor(() => result.current.isSuccess);

    // We get the basket button for the first product item using the getByTestId function.
    const firstProductItemBasketButton = screen.getByTestId(`basket-button-1`);

    // We click the basket button for the first product item.
    fireEvent.press(firstProductItemBasketButton);

    // We check that the basket icon quantity text is present using the getByTestId function.
    expect(screen.getByTestId('basket-icon-quantity-text-1')).toBeTruthy();

    // We check that the products in the basket have been added correctly using the productStore.
    expect(productStore.current.productsInBasket).toHaveLength(1);
    expect(productStore.current.productsInBasket[0].quantity).toBe(1);
    expect(productStore.current.productsInBasket[0].product).toMatchObject(
      result.current.data![0],
    );

    // We click the basket button for the first product item again to remove it from the basket.
    fireEvent.press(firstProductItemBasketButton);

    // We check that the basket icon quantity text is not present.
    expect(
      screen.queryByTestId('basket-icon-quantity-text-1'),
    ).not.toBeTruthy();

    // We check that the products in the basket have been removed correctly using the productStore.
    expect(productStore.current.productsInBasket).toHaveLength(0);
  });
});

πŸ‘€ Overview of product detail screen

Code - Testing product detail screen
import {renderHook} from '@testing-library/react-hooks';
import {fireEvent, render, screen} from '@testing-library/react-native';
import React from 'react';
import {ProductDetailScreen} from '..';
import {setupGetProductByIdFailedHandler} from '../../../__mocks__/msw/handlers';
import {useGetProductById} from '../../api/product';
import {RouteNames} from '../../navigation/route-names';
import {useProductInBasketQuantityById} from '../../store/product';
import {cutString} from '../../utils/cut-string';
import {getPriceText} from '../../utils/get-price-text';
import {createReactQueryWrapper} from '../../utils/testing';

const increaseProductQuantityInBasketMock = jest.fn();
const decreaseProductQuantityInBasketMock = jest.fn();
const addProductToBasketMock = jest.fn();
const removeProductFromBasketMock = jest.fn();

jest.mock('../../store/product', () => ({
  useProductActions: () => ({
    increaseProductQuantityInBasket: increaseProductQuantityInBasketMock,
    decreaseProductQuantityInBasket: decreaseProductQuantityInBasketMock,
    addProductToBasket: addProductToBasketMock,
    removeProductFromBasket: removeProductFromBasketMock,
  }),
  useProductInBasketQuantityById: jest.fn(() => undefined),
}));

const navigateMock = jest.fn();
const setOptionsMock = jest.fn();
const navigation = {navigate: navigateMock, setOptions: setOptionsMock} as any;
const productId = 1;
const route = {params: {id: productId}} as any;

const component = <ProductDetailScreen navigation={navigation} route={route} />;

describe('Product detail screen', () => {
  it('should display loading indicator on mount', async () => {
    render(component, {wrapper: createReactQueryWrapper});

    expect(screen.queryByTestId(`screen-loader`)).toBeTruthy();

    const {result, waitFor} = renderHook(() => useGetProductById(productId), {
      wrapper: createReactQueryWrapper,
    });

    await waitFor(() => result.current.isSuccess);
  });

  it('should display product detail data correctly, and should set header title based on api data', async () => {
    // we need to render whole app stack in order to be able to get header title
    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    expect(setOptionsMock).toHaveBeenCalledWith({
      headerTitle: cutString(result.current.data!.title),
    });

    expect(screen.getByTestId(`product-detail-scroll-view`)).toBeTruthy();

    expect(screen.getByTestId('product-detail-image').props.source.uri).toBe(
      result.current.data!.image,
    );

    expect(screen.getByText(result.current.data!.title)).toBeTruthy();

    expect(screen.getByText(result.current.data!.description)).toBeTruthy();
  });

  it('should display error text in case get all products query fails', async () => {
    setupGetProductByIdFailedHandler();

    render(component, {wrapper: createReactQueryWrapper});

    const {result, waitFor} = renderHook(() => useGetProductById(productId), {
      wrapper: createReactQueryWrapper,
    });

    await waitFor(() => result.current.isError);

    expect(screen.getByText(`An error occurred`)).toBeTruthy();
  });

  it('should display price and quantity of the item correctly', async () => {
    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    expect(screen.getByTestId(`product-detail-price`).props.children).toBe(
      getPriceText(result.current.data!.price),
    );

    expect(
      screen.getByTestId(
        `quantity-toggler-value-${result.current.data?.id.toString()}`,
      ).props.children,
    ).toBe('0');
  });

  it('should have decrease quantity button disabled and should call addFavoriteProduct function in case product has not been added to basket yet', async () => {
    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    const increaseBtn = screen.getByTestId(
      `increase-quantity-btn-${result.current.data?.id.toString()}`,
    );
    const decreaseBtn = screen.getByTestId(
      `decrease-quantity-btn-${result.current.data?.id.toString()}`,
    );

    fireEvent.press(decreaseBtn);

    // decrease quantity button should be disabled if quantity equals to 0
    expect(decreaseProductQuantityInBasketMock).not.toHaveBeenCalled();

    fireEvent.press(increaseBtn);

    expect(addProductToBasketMock).toHaveBeenCalledWith(result.current.data!);
  });

  it('should increase quantity on pressing increase button in case product has already been added to basket', async () => {
    // product has been added to the basket
    (useProductInBasketQuantityById as jest.Mock).mockImplementation(() => 1);

    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    const increaseBtn = screen.getByTestId(
      `increase-quantity-btn-${result.current.data?.id.toString()}`,
    );

    fireEvent.press(increaseBtn);

    expect(increaseProductQuantityInBasketMock).toHaveBeenCalledWith(
      result.current.data!.id,
    );
  });

  it('should call remove favorited product on pressing decrease button in case product has already been added to basket and its quantity equals to 1', async () => {
    // product has been added to the basket
    (useProductInBasketQuantityById as jest.Mock).mockImplementation(() => 1);

    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    const decreaseBtn = screen.getByTestId(
      `decrease-quantity-btn-${result.current.data?.id.toString()}`,
    );

    fireEvent.press(decreaseBtn);

    expect(removeProductFromBasketMock).toHaveBeenCalledWith(
      result.current.data!.id,
    );
  });

  it('should call decrease favorited product on pressing decrease button in case product has already been added to basket and its quantity greater than 1', async () => {
    // product has been added to the basket and has quantity greater than 1
    (useProductInBasketQuantityById as jest.Mock).mockImplementation(() => 2);

    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    const decreaseBtn = screen.getByTestId(
      `decrease-quantity-btn-${result.current.data?.id.toString()}`,
    );

    fireEvent.press(decreaseBtn);

    expect(decreaseProductQuantityInBasketMock).toHaveBeenCalledWith(
      result.current.data!.id,
    );
  });

  it('should call navigation function with correct params on pressing Go To Basket button', async () => {
    render(component, {
      wrapper: createReactQueryWrapper,
    });

    const {result, waitFor: waitForHook} = renderHook(
      () => useGetProductById(productId),
      {
        wrapper: createReactQueryWrapper,
      },
    );

    await waitForHook(() => result.current.isSuccess);

    const goToBasketBtn = screen.getByText('Go to basket');

    fireEvent.press(goToBasketBtn);

    expect(navigateMock).toHaveBeenCalledWith(RouteNames.basket);
  });
});

πŸ‘€ Overview of basket screen

Code - Testing basket screen
import {
  fireEvent,
  render,
  screen,
  waitFor,
} from '@testing-library/react-native';
import React from 'react';
import {GET_ALL_PRODUCTS_MOCK_RESPONSE} from '../../../__mocks__/msw/mock-data';
import {useProductsInBasket} from '../../store/product';
import {getBasketTotalPrice} from '../../utils/get-basket-total-price';
import {createReactQueryWrapper} from '../../utils/testing';
import BasketScreen from '../basket';

const increaseProductQuantityInBasketMock = jest.fn();
const decreaseProductQuantityInBasketMock = jest.fn();
const addProductToBasketMock = jest.fn();
const removeProductFromBasketMock = jest.fn();

const favoritedProducts = GET_ALL_PRODUCTS_MOCK_RESPONSE.map(product => ({
  product,
  quantity: Math.floor(Math.random() * 10) + 1,
}));

jest.mock('../../store/product', () => ({
  useProductActions: () => ({
    increaseProductQuantityInBasket: increaseProductQuantityInBasketMock,
    decreaseProductQuantityInBasket: decreaseProductQuantityInBasketMock,
    addProductToBasket: addProductToBasketMock,
    removeProductFromBasket: removeProductFromBasketMock,
  }),
  useProductsInBasket: jest.fn(),
}));

const navigateMock = jest.fn();
const setOptionsMock = jest.fn();
const navigation = {navigate: navigateMock, setOptions: setOptionsMock} as any;
const route = jest.fn() as any;

const component = <BasketScreen navigation={navigation} route={route} />;

describe('Basket screen', () => {
  it('should display all basket list data correctly', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(
      () => favoritedProducts,
    );

    render(component, {wrapper: createReactQueryWrapper});

    const eventData = {
      nativeEvent: {
        contentOffset: {
          y: 500,
        },
        contentSize: {
          height: 500,
          width: 100,
        },
        layoutMeasurement: {
          height: 100,
          width: 100,
        },
      },
    };

    // first 10 item will be visible on initial render
    for (const {
      product: {id},
    } of favoritedProducts.slice(0, favoritedProducts.length / 2)) {
      expect(await screen.getByTestId(`basket-card-${id}`)).toBeTruthy();
    }

    // scroll down to render remaining items
    fireEvent.scroll(screen.getByTestId('basket-screen-flat-list'), eventData);

    await waitFor(async () => {
      for (const {
        product: {id},
      } of favoritedProducts.slice(10, favoritedProducts.length)) {
        expect(await screen.getByTestId(`basket-card-${id}`)).toBeTruthy();
      }
    });
  });

  it('should set navigation header right component if there is at least one item in the basket', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(
      () => favoritedProducts,
    );

    render(component, {wrapper: createReactQueryWrapper});

    expect(setOptionsMock).toHaveBeenCalled();
  });

  it('should display basket total price correctly', async () => {
    render(component, {wrapper: createReactQueryWrapper});

    expect(
      screen.findByText(`$ ${getBasketTotalPrice(favoritedProducts)}`),
    ).toBeTruthy();
  });

  it('should not display basket total price when there is no items in the basket', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(() => []);

    render(component, {wrapper: createReactQueryWrapper});

    expect(
      screen.queryByText(`$ ${getBasketTotalPrice(favoritedProducts)}`),
    ).not.toBeTruthy();
  });

  it('should display empty basket when there is no item in the basket', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(() => []);

    render(component, {wrapper: createReactQueryWrapper});

    expect(screen.findByText(`Your basket is empty`)).toBeTruthy();
  });

  it('should increase quantity on pressing increase button', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(
      () => favoritedProducts,
    );

    render(component, {wrapper: createReactQueryWrapper});

    fireEvent.press(screen.getByTestId(`increase-quantity-btn-1`));

    expect(increaseProductQuantityInBasketMock).toHaveBeenCalledWith(
      favoritedProducts[0].product.id,
    );
  });

  it('should remove the product from the basket if quantity of the product equals to 1', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(() =>
      favoritedProducts.map(favoritedProduct => {
        // set first product quantity to 1
        if (favoritedProduct.product.id === 1) {
          return {
            ...favoritedProduct,
            product: favoritedProduct.product,
            quantity: 1,
          };
        }
        return favoritedProduct;
      }),
    );

    render(component, {wrapper: createReactQueryWrapper});

    fireEvent.press(screen.getByTestId(`decrease-quantity-btn-1`));

    expect(removeProductFromBasketMock).toHaveBeenCalledWith(
      favoritedProducts[0].product.id,
    );
  });

  it('should decrease the quantity of the product if its greater than 1', async () => {
    (useProductsInBasket as jest.Mock).mockImplementation(() =>
      favoritedProducts.map(favoritedProduct => {
        // set first product quantity to 2
        if (favoritedProduct.product.id === 1) {
          return {
            ...favoritedProduct,
            product: favoritedProduct.product,
            quantity: 2,
          };
        }
        return favoritedProduct;
      }),
    );

    render(component, {wrapper: createReactQueryWrapper});

    fireEvent.press(screen.getByTestId(`decrease-quantity-btn-1`));

    expect(decreaseProductQuantityInBasketMock).toHaveBeenCalledWith(
      favoritedProducts[0].product.id,
    );
  });
});

πŸ§ͺ Coverage

πŸ€– E2E Testing with Detox

iOS

e2e-iOS.mp4

Android

e2e-android.mp4
Code - Testing complete app flow
describe('Example', () => {
  beforeAll(async () => {
    await device.launchApp({
      newInstance: true,
    });
  });

  beforeEach(async () => {
    await device.reloadReactNative();
  });

  test('complete app flow', async () => {
    // wait for product flat list to be visible with timeout of 6000ms
    // 6000ms can vary depending on the network, since we fetch the data from https://fakestoreapi.com
    // and server might not be stable all the time
    await waitFor(element(by.id('product-list-flat-list')))
      .toBeVisible()
      .withTimeout(6000);

    // it expects first product list card to be visible
    await expect(element(by.id('product-list-card-1'))).toBeVisible();

    // scroll to the end of the product list
    await element(by.id('product-list-flat-list')).scrollTo('bottom');

    // it expects last item of the product list to be visible after scrolling to the bottom
    await expect(element(by.id('product-list-card-20'))).toBeVisible();

    // scroll to top of the product list
    await element(by.id('product-list-flat-list')).scrollTo('top');

    // tap heart button of the first product list item in order to add product to the basket
    await element(by.id('basket-button-1')).tap();

    // tap heart button of the third product list item in order to add product to the basket
    await element(by.id('basket-button-3')).tap();

    // tap basket icon in order to navigate to basket screen
    await element(by.id('basket-icon')).tap();

    // it expects basket card of first product to be visible
    await waitFor(element(by.id('basket-card-1')))
      .toBeVisible()
      .withTimeout(1000);

    // it expects basket card of third product to be visible
    await expect(element(by.id('basket-card-3'))).toBeVisible();

    // increase quantity of the first product item by tapping + button in basket screen
    await element(by.id('increase-quantity-btn-1')).tap();

    // increase quantity of the first product item by tapping + button in basket screen
    await element(by.id('increase-quantity-btn-1')).tap();

    // it expects first product's quantity toggler component to have text of 3
    // since it has been added in product list with count of 1, and its quantity is increased by 2 in the basket screen/above
    await expect(element(by.id('quantity-toggler-value-1'))).toHaveText('3');

    // decrease first product quantity
    await element(by.id('decrease-quantity-btn-1')).tap();

    // it expects first product's quantity toggler component to have text of 2 since it is decreased by 1 above
    await expect(element(by.id('quantity-toggler-value-1'))).toHaveText('2');

    // increase quantity of the third product item by tapping + button in basket screen
    await element(by.id('increase-quantity-btn-3')).tap();

    // increase quantity of the third product item by tapping + button in basket screen
    await element(by.id('increase-quantity-btn-3')).tap();

    // it expects third product's quantity toggler component to have text of 3
    await expect(element(by.id('quantity-toggler-value-3'))).toHaveText('3');

    // decrease quantity of the third product item by tapping - button in basket screen
    await element(by.id('decrease-quantity-btn-3')).tap();

    // decrease quantity of the third product item by tapping - button in basket screen
    await element(by.id('decrease-quantity-btn-3')).tap();

    // decrease quantity of the third product item by tapping - button in basket screen
    await element(by.id('decrease-quantity-btn-3')).tap();

    // third product item is removed from the basket since its quantity its decreased by 3 times above
    await waitFor(element(by.id('basket-card-3')))
      .not.toBeVisible()
      .withTimeout(1000);

    // tap basket screen delete icon in the header right
    await element(by.id('basket-delete-icon')).tap();

    // it expects your basket is empty text to be visible after tapping basket delete icon
    await waitFor(element(by.text('Your basket is empty')))
      .toBeVisible()
      .withTimeout(1000);

    // tap basket screen header left to navigate back
    await element(by.id('basket-screen-header-left-btn')).tap();

    // it expects fourth product list item to be visible
    await waitFor(element(by.id('product-list-card-4')))
      .toBeVisible()
      .withTimeout(700);

    // tap fourth product list item
    await element(by.id('product-list-card-4')).tap();

    // it expects product detail image to be visible after tapping to the product item
    await waitFor(element(by.id('product-detail-image')))
      .toBeVisible()
      .withTimeout(700);

    // tap twice to increase quantity of fourth product item in product detail screen
    await element(by.id('increase-quantity-btn-4')).tap();
    await element(by.id('increase-quantity-btn-4')).tap();

    // it expects fourth product's quantity toggler component to have text of 1
    await expect(element(by.id('quantity-toggler-value-4'))).toHaveText('2');

    // tap to decrease quantity of fourth product item in product detail screen
    await element(by.id('decrease-quantity-btn-4')).tap();

    // it expects fourth product's quantity toggler component to have text of 1
    await expect(element(by.id('quantity-toggler-value-4'))).toHaveText('1');

    // tap go to basket button in order to navigate to basket screen from product detail screen
    await element(by.id('product-detail-go-to-basket-btn')).tap();

    // it expects fourth product item basket card to be visible since it has been added to the basket in above
    await waitFor(element(by.id('basket-card-4')))
      .toBeVisible()
      .withTimeout(1000);

    // it expects fourth product's quantity toggler component to have text of 1 in the basket screen
    await expect(element(by.id('quantity-toggler-value-4'))).toHaveText('1');
  });
});

🐞 Known issues

About

Sample app for how to use & test React-Query for data fetching and Zustand for state management in a React Native application.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published