diff --git a/__tests__/App.integration.test.js b/__tests__/App.integration.test.js
index e34c801..2bb52dc 100644
--- a/__tests__/App.integration.test.js
+++ b/__tests__/App.integration.test.js
@@ -3,101 +3,214 @@ import App from '../App';
import renderInProvider from './utils/renderInProvider';
import { sampleResponse } from './mocks/handlers';
import { strings } from '../src/constants';
+import {
+ changeText,
+ fillPaymentInputs,
+ getXthOfItemsByText,
+ pressButton,
+ verifyCheckoutScreenContents,
+ verifyExistenceByText,
+ verifyItemCount,
+ verifyPaymentInputsFilled,
+} from './utils/testUtil';
+import { darkTheme, lightTheme } from '../src/constants/theme';
const anItem = sampleResponse[1]; // 2nd item in the sampleResponse
+// Helper functions
+const addItemToBasket = async () => {
+ const addToBasketButton = getXthOfItemsByText('Add to basket', 1);
+ fireEvent.press(addToBasketButton);
+
+ await waitFor(() => {
+ verifyExistenceByText('CHECKOUT (1)');
+ });
+
+ // Verify updated basket summary
+ verifyExistenceByText('Total: $22.30');
+};
+
+const navigateToCheckoutScreen = async () => {
+ pressButton('CHECKOUT (1)');
+
+ let orderButton;
+ await waitFor(() => {
+ orderButton = screen.getByText(/Order\s*\(\s*1\s*items\s*\)/);
+ });
+
+ // Verify checkout screen static and dynamic contents
+ await verifyCheckoutScreenContents({
+ totalPrice: '22.30',
+ totalItemCount: 1,
+ titleOfAnItem: anItem.title,
+ });
+
+ // Verify the item in the basket and in the checkout card
+ const checkoutCard = screen.getByTestId('checkout-card');
+ expect(within(checkoutCard).getByText(anItem.title)).toBeTruthy();
+
+ return orderButton;
+};
+
+const applyPromoCodeAndVerifyApplied = async (promoCode) => {
+ changeText('Promo Code', promoCode);
+ pressButton('Apply');
+
+ await waitFor(() => {
+ verifyExistenceByText('Total: $22.30');
+ });
+
+ // Verify the discount is applied
+ verifyExistenceByText('Total: $2.23');
+};
+
+const submitPayment = async (cardDetails) => {
+ fillPaymentInputs(cardDetails);
+ verifyPaymentInputsFilled(cardDetails);
+ pressButton(strings.buttons.payAndOrder);
+};
+
+const verifyPaymentScreenContents = (payAndOrderButton) => {
+ expect(payAndOrderButton).toBeTruthy();
+ verifyExistenceByText('Items in the basket: 1');
+ verifyExistenceByText('Total: $2.23');
+};
+
describe('', () => {
- renderInProvider();
- test('user expected journey', async () => {
- // Initial state in ProductListScreen
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test('user expected journey (success)', async () => {
+ renderInProvider();
+
+ // Verify initial state in ProductListScreen
await waitFor(() => {
- screen.getByText('CHECKOUT (0)');
+ verifyExistenceByText('CHECKOUT (0)');
});
- expect(screen.getAllByText('Add to basket').length).toBe(20); // 20 items in the list
- // add 2nd item in the sampleResponse
- const addToBasketButton = screen.getAllByText('Add to basket')[1];
- fireEvent.press(addToBasketButton);
+ verifyItemCount('Add to basket', 8); // 8 items in the list
- // Updated totalItemCount
+ // Add item to basket and navigate
+ await addItemToBasket();
+ const orderButton = await navigateToCheckoutScreen();
+
+ // Apply promo code
+ await applyPromoCodeAndVerifyApplied('A90');
+
+ // Navigate to PaymentScreen
+ fireEvent.press(orderButton);
+
+ let payAndOrderButton;
await waitFor(() => {
- expect(screen.getByText('CHECKOUT (1)')).toBeTruthy();
+ payAndOrderButton = screen.getByText(strings.buttons.payAndOrder);
});
- expect(screen.getByText('Total: $22.30')).toBeTruthy();
- const checkoutButton = await screen.findByText('CHECKOUT (1)');
- fireEvent.press(checkoutButton);
- // check navigates to CheckoutScreen
- let orderButton;
+ verifyPaymentScreenContents(payAndOrderButton);
+
+ // Submit payment
+ await submitPayment({
+ cardholderName: 'James Bond',
+ cardNumber: '5566561551349323', // Valid card for success
+ expirationDate: '12/28',
+ cvv: '156',
+ });
+
+ // Verify navigation to SuccessScreen
await waitFor(() => {
- orderButton = screen.getByText(/Order\s*\(\s*1\s*items\s*\)/);
+ verifyExistenceByText(strings.payment.success);
+ });
+ verifyExistenceByText('Redirecting to product list in 5 seconds...');
+
+ act(() => {
+ jest.advanceTimersByTime(5000);
});
- expect(orderButton).toBeTruthy();
- const promoInput = screen.getAllByText('Promo Code')[0];
- expect(promoInput).toBeTruthy();
- const applyButton = screen.getByText('Apply');
- expect(applyButton).toBeTruthy();
-
- // check item in the basket
- const checkoutCard = screen.getByTestId('checkout-card');
- expect(within(checkoutCard).getByText(anItem.title)).toBeTruthy();
-
- //apply promo code
- fireEvent.changeText(promoInput, 'A90');
- fireEvent.press(applyButton);
+
+ // Verify navigation back to ProductListScreen
await waitFor(() => {
- expect(screen.queryByText('Total: $22.30')).toBeFalsy();
+ verifyExistenceByText('CHECKOUT (0)');
});
- expect(screen.queryByText('Total: $2.23')).toBeTruthy();
+ verifyItemCount('Add to basket', 8); // 8 items in the list
+ }, 10000);
- // check navigates to PaymentScreen
+ test('user expected journey (error)', async () => {
+ renderInProvider();
+
+ // Verify initial state in ProductListScreen
+ await waitFor(() => {
+ verifyExistenceByText('CHECKOUT (0)');
+ });
+ verifyItemCount('Add to basket', 8); // 8 items in the list
+
+ // Add item to basket and navigate
+ await addItemToBasket();
+ const orderButton = await navigateToCheckoutScreen();
+
+ // Apply promo code
+ await applyPromoCodeAndVerifyApplied('A90');
+
+ // Navigate to PaymentScreen
fireEvent.press(orderButton);
let payAndOrderButton;
await waitFor(() => {
payAndOrderButton = screen.getByText(strings.buttons.payAndOrder);
});
- expect(payAndOrderButton).toBeTruthy();
-
- const itemCountText = screen.getByText('Items in the basket: 1');
- const totalText = screen.getByText('Total: $2.23');
- const cardholderNameInput = screen.getAllByText(strings.payment.cardholderName)[0];
- const cardNumberInput = screen.getAllByText(strings.payment.creditCardNumber)[0];
- const cvvInput = screen.getAllByText(strings.payment.cvv)[0];
- fireEvent.changeText(cvvInput, '12');
- const expirationInput = screen.getAllByText(strings.payment.expirationDate)[0];
- fireEvent.changeText(expirationInput, '07');
-
- expect(itemCountText).toBeTruthy();
- expect(totalText).toBeTruthy();
- expect(cardholderNameInput).toBeTruthy();
- expect(cvvInput).toBeTruthy();
- expect(expirationInput).toBeTruthy();
- expect(cardNumberInput).toBeTruthy();
-
- // Fill in valid inputs
- fireEvent.changeText(cardholderNameInput, 'James Bond');
- fireEvent.changeText(cardNumberInput, '5566561551349323'); // Successful card
- fireEvent.changeText(expirationInput, '12/28');
- fireEvent.changeText(cvvInput, '156');
- // Submit payment
- fireEvent.press(screen.getByText(strings.buttons.payAndOrder));
+ verifyPaymentScreenContents(payAndOrderButton);
- // Assert navigation to Success screen
- await waitFor(() => {
- expect(screen.getByText(strings.payment.success)).toBeTruthy();
+ // Submit payment with failing card
+ await submitPayment({
+ cardholderName: 'James Bond',
+ cardNumber: '5249045959484101', // Failing card
+ expirationDate: '12/28',
+ cvv: '156',
});
- expect(screen.getByText('Redirecting to product list in 5 seconds...')).toBeTruthy();
+ // Verify navigation to ErrorScreen
+ await waitFor(() => {
+ verifyExistenceByText('Card can not be processed');
+ });
+ verifyExistenceByText('Redirecting to product list in 10 seconds...');
act(() => {
- jest.advanceTimersByTime(5000);
+ jest.advanceTimersByTime(10000);
});
- // check navigates to ProductListScreen after 5 seconds
+ // Verify navigation back to ProductListScreen
await waitFor(() => {
- screen.getByText('CHECKOUT (0)');
+ verifyExistenceByText('CHECKOUT (1)'); // Basket still contains the item after failure
});
- expect(screen.getAllByText('Add to basket').length).toBe(20); // 20 items in the list
+ verifyItemCount('Add to basket', 8); // 8 items in the list
}, 10000);
+ test('toggle dark mode', async () => {
+ renderInProvider();
+
+ // Verify light mode is enabled by default
+ await waitFor(() => {
+ expect(screen.getByTestId('base-screen')).toHaveStyle({
+ backgroundColor: lightTheme.colors.background,
+ });
+ });
+
+ // Locate and toggle dark mode
+ const toggleButton = await waitFor(() => screen.getByTestId('toggle-dark-mode'));
+ fireEvent(toggleButton, 'valueChange', true);
+
+ // Verify dark mode is enabled
+ await waitFor(() => {
+ expect(screen.getByTestId('base-screen')).toHaveStyle({
+ backgroundColor: darkTheme.colors.background,
+ });
+ });
+
+ // Toggle back to light mode
+ fireEvent(toggleButton, 'valueChange', false);
+
+ // Verify light mode is enabled
+ await waitFor(() => {
+ expect(screen.getByTestId('base-screen')).toHaveStyle({
+ backgroundColor: lightTheme.colors.background,
+ });
+ });
+ });
});
diff --git a/__tests__/utils/testUtil.js b/__tests__/utils/testUtil.js
index 92910bf..7658a7e 100644
--- a/__tests__/utils/testUtil.js
+++ b/__tests__/utils/testUtil.js
@@ -1,23 +1,182 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
+import { fireEvent, screen, waitFor } from '@testing-library/react-native';
+import { sampleResponse, sampleBasket } from '@mocks/handlers';
+import { strings } from '../../src/constants';
-/* const initialState = {
- count: 0,
+// Utility for logging during tests
+const logHelper = (message, details = {}) => {
+ console.log(`Test Helper: ${message}`, details);
};
-const mockStore = configureStore({
- getState: () => {},
- reducer: {
- [apiSlice.reducerPath]: apiSlice.reducer,
- },
- middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiSlice.middleware), // Add RTK Query middleware
- preloadedState: {
- counter: { count: 0 }, // Initial state
- },
-}); */
-// const store = mockStore(initialState);
+const errorHelper = (message, details = {}) => {
+ console.error(`Test Helper Error: ${message}`, details);
+};
+
+// Getters
+
+export const getFirstOfItemsByText = (text) => {
+ const items = screen.getAllByText(text);
+ if (!items.length) {
+ errorHelper('No items found by text', { text });
+ throw new Error(`No items found for text: ${text}`);
+ }
+ return items[0];
+};
+
+export const getXthOfItemsByText = (text, index) => {
+ const items = screen.getAllByText(text);
+ if (!items.length) {
+ errorHelper('No items found by text', { text });
+ throw new Error(`No items found for text: ${text}`);
+ }
+ return items[index];
+};
+
+export const getFirstOfItemsByTestId = (testID) => {
+ const items = screen.getAllByTestId(testID);
+ if (!items.length) {
+ errorHelper('No items found by Test ID', { testID });
+ throw new Error(`No items found for Test ID: ${testID}`);
+ }
+ return items[0];
+};
+
+// Enhanced verification utils
+export const verifyItemCount = (text, count) => {
+ const items = screen.getAllByText(text);
+ if (items.length !== count) {
+ errorHelper('Item count mismatch', { text, expected: count, actual: items.length });
+ }
+ expect(items.length).toBe(count);
+};
+
+export const verifyInExistenceByText = (text) => {
+ const result = screen.queryByText(text);
+ if (result) {
+ errorHelper('Unexpected text found', { text });
+ }
+ expect(result).toBeFalsy();
+};
+
+export const verifyExistenceByText = (text) => {
+ const result = screen.getByText(text);
+ if (!result) {
+ errorHelper('Expected text not found', { text });
+ }
+ expect(result).toBeTruthy();
+};
+
+export const verifyExistenceByTestId = (testID) => {
+ const result = screen.getByTestId(testID);
+ if (!result) {
+ errorHelper('Expected element not found by Test ID', { testID });
+ }
+ expect(result).toBeTruthy();
+};
+
+export const verifyInExistenceByTestId = (testID) => {
+ const result = screen.queryByTestId(testID);
+ if (result) {
+ errorHelper('Unexpected item found by Test ID', { testID });
+ }
+ expect(result).toBeFalsy();
+};
+
+export const verifyInputExistenceByText = (text) => {
+ const inputs = screen.getAllByText(text);
+ if (inputs.length < 2) {
+ errorHelper('Expected multiple inputs but found less', { text, count: inputs.length });
+ }
+ expect(inputs[0]).toBeTruthy();
+ expect(inputs[1]).toBeTruthy();
+};
-// Custom global render function that wraps the component with Provider
+export const verifyCheckoutScreenContents = async ({ totalPrice, totalItemCount, titleOfAnItem }) => {
+ try {
+ verifyExistenceByText('Apply');
+ verifyInputExistenceByText('Promo Code');
+ verifyExistenceByText('Total: $' + totalPrice);
+ verifyExistenceByText(new RegExp(`Order\\s*\\(\\s*${totalItemCount}\\s*items\\s*\\)`));
+ verifyExistenceByText(titleOfAnItem);
+ } catch (error) {
+ errorHelper('Error verifying checkout screen contents', { error });
+ throw error;
+ }
+};
+
+export const verifyExistenceOfPaymentInputs = () => {
+ expect(screen.getAllByText(strings.payment.cardholderName).length).toBe(2);
+ expect(screen.getAllByText(strings.payment.creditCardNumber).length).toBe(2);
+ expect(screen.getAllByText(strings.payment.expirationDate).length).toBe(2);
+ expect(screen.getAllByText(strings.payment.cvv).length).toBe(2);
+};
+
+export const verifyPaymentInputsFilled = (cardDetails) => {
+ const { cardholderName, cardNumber, expirationDate, cvv } = cardDetails;
+
+ // Verify inputs are filled correctly
+ expect(screen.getByDisplayValue(cardholderName)).toBeTruthy();
+ expect(screen.getByDisplayValue(cardNumber)).toBeTruthy();
+ expect(screen.getByDisplayValue(expirationDate)).toBeTruthy();
+ expect(screen.getByDisplayValue(cvv)).toBeTruthy();
+};
+
+// Enhanced action utils
+export const pressButton = (buttonText) => {
+ const button = screen.getByText(buttonText);
+ if (!button) {
+ errorHelper('Button not found', { buttonText });
+ }
+ fireEvent.press(button);
+};
+
+export const pressButtonAsync = async (buttonText) => {
+ const button = await screen.findByText(buttonText);
+ if (!button) {
+ errorHelper('Button not found', { buttonText });
+ }
+ fireEvent.press(button);
+};
+
+export const changeText = (inputLabelText, text) => {
+ const input = screen.getAllByText(inputLabelText)[0];
+ if (!input) {
+ errorHelper('Input not found', { inputLabelText });
+ }
+ fireEvent.changeText(input, text);
+};
+
+export const applyPromoCodeAndVerifyApplied = async (code) => {
+ changeText('Promo Code', code);
+ pressButton('Apply');
+ await waitFor(() => {
+ try {
+ verifyInExistenceByText('Total: $2648.01');
+ } catch (error) {
+ errorHelper('Error applying promo code', { code, error });
+ throw error;
+ }
+ });
+};
+
+export const fillPaymentInputs = (cardDetails) => {
+ const { cardholderName, cardNumber, expirationDate, cvv } = cardDetails;
+ changeText(strings.payment.cardholderName, cardholderName);
+ changeText(strings.payment.creditCardNumber, cardNumber);
+ changeText(strings.payment.expirationDate, expirationDate);
+ changeText(strings.payment.cvv, cvv);
+};
+
+// Mock states
+export const mockBasketState = {
+ basket: { items: sampleBasket },
+};
+
+export const mockProductState = {
+ products: sampleResponse,
+};
+// Setup utils
export function setupApiStore(api, extraReducers = {}, initialState = {}) {
const getStore = (preloadedState) =>
configureStore({
diff --git a/src/Navigator/Navigator.js b/src/Navigator/Navigator.js
index b36c695..4ff316a 100644
--- a/src/Navigator/Navigator.js
+++ b/src/Navigator/Navigator.js
@@ -10,7 +10,9 @@ import { ThemeContext } from '@context/ThemeContext';
const Stack = createNativeStackNavigator();
const HeaderBackgroundFunc = () => ;
-const ToggleDark = (isDarkMode, toggleTheme) => ;
+const ToggleDark = (isDarkMode, toggleTheme) => (
+
+);
const HeaderBackground = () => {
const { colors } = useTheme();
diff --git a/src/components/atoms/Switch/index.js b/src/components/atoms/Switch/index.js
index f749560..77789ab 100644
--- a/src/components/atoms/Switch/index.js
+++ b/src/components/atoms/Switch/index.js
@@ -1,3 +1,3 @@
-import { Switch as PSwitch } from 'react-native';
+import { Switch as RNSwitch } from 'react-native';
-export default PSwitch;
+export default RNSwitch;
diff --git a/src/components/molecules/Toggle/index.js b/src/components/molecules/Toggle/index.js
index 35e001f..ad7385f 100644
--- a/src/components/molecules/Toggle/index.js
+++ b/src/components/molecules/Toggle/index.js
@@ -6,7 +6,7 @@ import { spacing } from '@constants/theme';
import styles from './Toggle.style';
import { Switch, Icon } from '../../atoms';
-const Toggle = ({ isDarkMode, toggleTheme }) => {
+const Toggle = ({ isDarkMode, toggleTheme, testID = 'toggle-switch' }) => {
const { colors } = useTheme();
return (
@@ -19,7 +19,7 @@ const Toggle = ({ isDarkMode, toggleTheme }) => {
ios_backgroundColor={isDarkMode ? colors.textPrimary : colors.textSecondary}
value={isDarkMode}
onValueChange={toggleTheme}
- testID="toggle-switch"
+ testID={testID}
/>
);
@@ -28,6 +28,7 @@ const Toggle = ({ isDarkMode, toggleTheme }) => {
Toggle.propTypes = {
isDarkMode: PropTypes.bool.isRequired,
toggleTheme: PropTypes.func.isRequired,
+ testID: PropTypes.string,
};
export default Toggle;
diff --git a/src/screens/CheckoutScreen/CheckoutScreen.test.js b/src/screens/CheckoutScreen/CheckoutScreen.test.js
index af32ecc..763ae35 100644
--- a/src/screens/CheckoutScreen/CheckoutScreen.test.js
+++ b/src/screens/CheckoutScreen/CheckoutScreen.test.js
@@ -1,13 +1,19 @@
import React from 'react';
-import { fireEvent, screen, waitFor, within } from '@testing-library/react-native';
import { useNavigation } from '@react-navigation/native';
+import { screen, waitFor, fireEvent, within } from '@testing-library/react-native';
import renderWithProvidersAndNavigation from '@testUtils/renderInProvidersAndNavigation';
+import {
+ mockBasketState,
+ verifyCheckoutScreenContents,
+ verifyInExistenceByText,
+ verifyExistenceByText,
+ pressButton,
+ changeText,
+ getFirstOfItemsByTestId,
+} from '@testUtils/testUtil';
import { sampleBasket } from '@mocks/handlers';
-import mockNavigation from '@mocks/navigation';
+import { strings } from '@constants';
import CheckoutScreen from '.';
-import { strings } from '../../constants';
-
-const initialState = { basket: { items: sampleBasket } };
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
@@ -20,70 +26,79 @@ useNavigation.mockReturnValue({
navigate: navigateMock,
});
+// Apply promo code is in the integration test
describe('CheckoutScreen', () => {
- it('should render CheckoutScreen correctly', () => {
- renderWithProvidersAndNavigation(, { initialState });
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
- expect(screen.getByText('Mens Casual Premium Slim Fit T-Shirts')).toBeTruthy();
+ const initialState = mockBasketState;
- expect(screen.getByText('Total: $2648.01')).toBeTruthy();
- expect(screen.getByText(/Order\s*\(\s*14\s*items\s*\)/)).toBeTruthy();
+ it('renders CheckoutScreen correctly', () => {
+ renderWithProvidersAndNavigation(, { initialState });
- expect(screen.getAllByText('Promo Code')[0]).toBeTruthy();
- expect(screen.getByText('Apply')).toBeTruthy();
+ verifyCheckoutScreenContents({
+ totalPrice: '2648.01',
+ totalItemCount: 14,
+ titleOfAnItem: sampleBasket[0].title,
+ });
});
+
it('should remove an item from the basket when "Remove Item" is pressed', async () => {
- renderWithProvidersAndNavigation(, { initialState });
+ renderWithProvidersAndNavigation(, { initialState });
// Get the checkoutcard and its 'Remove Item' button
- const firstCheckoutCard = screen.getAllByTestId('checkout-card')[0];
+ const firstCheckoutCard = getFirstOfItemsByTestId('checkout-card');
const removeItemButton = within(firstCheckoutCard).getByText('Remove Item');
// Initial assertions
- expect(screen.getByText(sampleBasket[0].title)).toBeTruthy();
- expect(screen.getByText(/Order\s*\(\s*14\s*items\s*\)/)).toBeTruthy(); // Basket starts with 14 item
- expect(screen.getByText('Total: $2648.01')).toBeTruthy();
+ verifyCheckoutScreenContents({
+ totalPrice: '2648.01',
+ totalItemCount: 14, // Basket starts with 14 item
+ titleOfAnItem: sampleBasket[0].title,
+ });
fireEvent.press(removeItemButton); // there were 3 items of removed product
// Check total item count and total price updated
- expect(screen.queryByText(sampleBasket[0].title)).toBeFalsy();
- expect(screen.getByText(/Order\s*\(\s*11\s*items\s*\)/)).toBeTruthy();
- expect(screen.getByText('Total: $2318.16')).toBeTruthy();
+ verifyInExistenceByText(sampleBasket[0].title);
+ verifyExistenceByText(/Order\s*\(\s*11\s*items\s*\)/);
+ verifyExistenceByText('Total: $2318.16');
});
- it('should navigate to Payment screen when the ORDER button is pressed', () => {
- renderWithProvidersAndNavigation(, { initialState });
+
+ it('should navigate to Payment screen when ORDER button is pressed', async () => {
+ renderWithProvidersAndNavigation(, { initialState });
// Press the "ORDER" button
- const orderButton = screen.getByText(/Order\s*\(\s*14\s*items\s*\)/);
- fireEvent.press(orderButton);
+ pressButton(/Order\s*\(\s*14\s*items\s*\)/);
- // Assert that navigation is called
- expect(navigateMock).toHaveBeenCalledTimes(1);
- expect(navigateMock).toHaveBeenCalledWith('Payment'); // Replace with the actual route name
+ await waitFor(() => {
+ expect(navigateMock).toHaveBeenCalledWith('Payment');
+ });
});
- it('should show an error message when an invalid promo code is applied', async () => {
- renderWithProvidersAndNavigation(, { initialState });
- const applyButton = screen.getByText('Apply');
- fireEvent.press(applyButton);
+
+ it('should show an error message when an invalid promo code or empty code is applied ', async () => {
+ renderWithProvidersAndNavigation(, { initialState });
+
+ pressButton('Apply');
+
await waitFor(() => {
- expect(screen.getByText(strings.checkout.promoCodeRequired)).toBeTruthy();
+ verifyExistenceByText(strings.checkout.promoCodeRequired);
});
- const promoInput = screen.getAllByText('Promo Code')[0];
- fireEvent.changeText(promoInput, 'INVALIDCODE');
+ changeText('Promo Code', 'INVALIDCODE');
- fireEvent.press(applyButton);
+ pressButton('Apply');
await waitFor(() => {
- expect(screen.getByText(strings.checkout.promoCodeNotValid)).toBeTruthy();
+ verifyExistenceByText(strings.checkout.promoCodeNotValid);
});
});
it('should disable order and promo buttons if the basket is empty', () => {
- renderWithProvidersAndNavigation(, {
+ renderWithProvidersAndNavigation(, {
initialState: { basket: { items: [] } },
});
- expect(screen.getByText(strings.checkout.emptyBasket)).toBeTruthy();
+ verifyExistenceByText(strings.checkout.emptyBasket);
const orderButton = screen.getByTestId('order-button');
const applyPromoButton = screen.getByText('Apply');
@@ -91,60 +106,69 @@ describe('CheckoutScreen', () => {
expect(applyPromoButton).toBeDisabled();
});
it('should increase unit quantity, total count, and total price on "+" button press', async () => {
- renderWithProvidersAndNavigation(, { initialState });
+ renderWithProvidersAndNavigation(, { initialState });
- const firstCheckoutCard = screen.getAllByTestId('checkout-card')[0];
+ const firstCheckoutCard = getFirstOfItemsByTestId('checkout-card');
const increaseQuantityButton = within(firstCheckoutCard).getByTestId('increase-button');
// Check initial state
- expect(screen.getByText(/Order\s*\(\s*14\s*items\s*\)/)).toBeTruthy();
- expect(screen.getByText('Total: $2648.01')).toBeTruthy();
+ verifyCheckoutScreenContents({
+ totalPrice: '2648.01',
+ totalItemCount: 14,
+ titleOfAnItem: sampleBasket[0].title,
+ });
// Press "+" button
fireEvent.press(increaseQuantityButton);
// Assertions after increment
await waitFor(() => {
- expect(screen.getByText(/Order\s*\(\s*15\s*items\s*\)/)).toBeTruthy();
+ verifyExistenceByText(/Order\s*\(\s*15\s*items\s*\)/);
});
- expect(screen.getByText('Total: $2757.96')).toBeTruthy(); // Adjusted for increased item
+ verifyExistenceByText('Total: $2757.96'); // Updated for increased item
});
it('should decrease unit quantity, total count, and total price on "-" button press', async () => {
- renderWithProvidersAndNavigation(, { initialState });
+ renderWithProvidersAndNavigation(, { initialState });
- const firstCheckoutCard = screen.getAllByTestId('checkout-card')[0];
+ const firstCheckoutCard = getFirstOfItemsByTestId('checkout-card');
const decreaseQuantityButton = within(firstCheckoutCard).getByTestId('decrease-button');
// Check initial state
- expect(screen.getByText(/Order\s*\(\s*14\s*items\s*\)/)).toBeTruthy();
- expect(screen.getByText('Total: $2648.01')).toBeTruthy();
+ verifyCheckoutScreenContents({
+ totalPrice: '2648.01',
+ totalItemCount: 14,
+ titleOfAnItem: sampleBasket[0].title,
+ });
// Press "-" button
fireEvent.press(decreaseQuantityButton);
// Assertions after decrement
await waitFor(() => {
- expect(screen.getByText(/Order\s*\(\s*13\s*items\s*\)/)).toBeTruthy();
+ verifyExistenceByText(/Order\s*\(\s*13\s*items\s*\)/);
});
- expect(screen.getByText('Total: $2538.06')).toBeTruthy(); // Adjusted for decreased item
+ verifyExistenceByText('Total: $2538.06'); // Adjusted for decreased item
});
it('should remove an item from the basket if quantity is 1 on delete icon press', async () => {
- renderWithProvidersAndNavigation(, { initialState });
+ renderWithProvidersAndNavigation(, { initialState });
- const firstCheckoutCard = screen.getAllByTestId('checkout-card')[0]; // first card in the basket
+ const firstCheckoutCard = getFirstOfItemsByTestId('checkout-card');
const decreaseQuantityButton = within(firstCheckoutCard).getByTestId('decrease-button');
const quantityText = within(firstCheckoutCard).getByTestId('product-quantity');
// Check initial state
- expect(screen.getByText(/Order\s*\(\s*14\s*items\s*\)/)).toBeTruthy();
- expect(screen.getByText('Total: $2648.01')).toBeTruthy();
- expect(screen.getByText(sampleBasket[0].title)).toBeTruthy(); // title of first item in the basket
+ verifyCheckoutScreenContents({
+ totalPrice: '2648.01',
+ totalItemCount: 14,
+ titleOfAnItem: sampleBasket[0].title,
+ });
+
expect(within(quantityText).getByText('3')).toBeTruthy(); // count of first item in the basket is 3
expect(within(firstCheckoutCard).queryByTestId('delete-button')).toBeFalsy(); // delete button is not present
- fireEvent.press(decreaseQuantityButton); // product count descrased to 2
+ fireEvent.press(decreaseQuantityButton); // product count decreased to 2
// await waitFor(() => {});
- fireEvent.press(decreaseQuantityButton); // product count descrased to 1
+ fireEvent.press(decreaseQuantityButton); // product count decreased to 1
expect(within(quantityText).getByText('1')).toBeTruthy(); // check product count is 1
expect(within(firstCheckoutCard).queryByTestId('decrease-button')).toBeFalsy(); // decrease button is removed namely changed to delete button
@@ -154,9 +178,9 @@ describe('CheckoutScreen', () => {
// Check item removed
await waitFor(() => {
- expect(screen.queryByText(sampleBasket[0].title)).toBeFalsy(); // first item in the basket removed
+ verifyInExistenceByText(sampleBasket[0].title); // first item in the basket removed
});
- expect(screen.getByText(/Order\s*\(\s*11\s*items\s*\)/)).toBeTruthy();
- expect(screen.getByText('Total: $2318.16')).toBeTruthy();
+ verifyExistenceByText(/Order\s*\(\s*11\s*items\s*\)/);
+ verifyExistenceByText('Total: $2318.16');
});
});
diff --git a/src/screens/PaymentScreen/PaymentForm/PaymentForm.test.js b/src/screens/PaymentScreen/PaymentForm/PaymentForm.test.js
index e5d8abd..198198f 100644
--- a/src/screens/PaymentScreen/PaymentForm/PaymentForm.test.js
+++ b/src/screens/PaymentScreen/PaymentForm/PaymentForm.test.js
@@ -1,8 +1,7 @@
import React from 'react';
-import { screen } from '@testing-library/react-native';
-import { strings } from '@constants';
import renderInFormProvider from '@testUtils/renderInFormProvider';
import PaymentForm from './index';
+import { verifyExistenceOfPaymentInputs } from '../../../../__tests__/utils/testUtil';
jest.mock('@utils', () => ({
paymentUtils: {
@@ -23,9 +22,6 @@ describe('', () => {
it('renders all inputs with correct labels', () => {
renderInFormProvider();
- expect(screen.getAllByText(strings.payment.cardholderName).length).toBe(2);
- expect(screen.getAllByText(strings.payment.creditCardNumber).length).toBe(2);
- expect(screen.getAllByText(strings.payment.expirationDate).length).toBe(2);
- expect(screen.getAllByText(strings.payment.cvv).length).toBe(2);
+ verifyExistenceOfPaymentInputs();
});
});
diff --git a/src/screens/PaymentScreen/PaymentScreen.test.js b/src/screens/PaymentScreen/PaymentScreen.test.js
index 1ed7b58..0f769fd 100644
--- a/src/screens/PaymentScreen/PaymentScreen.test.js
+++ b/src/screens/PaymentScreen/PaymentScreen.test.js
@@ -1,9 +1,17 @@
import React from 'react';
-import { fireEvent, screen, waitFor } from '@testing-library/react-native';
+import { waitFor } from '@testing-library/react-native';
import { strings } from '@constants';
import { useNavigation } from '@react-navigation/native';
import renderWithProvidersAndNavigation from '@testUtils/renderInProvidersAndNavigation';
import { sampleBasket } from '@mocks/handlers';
+import {
+ fillPaymentInputs,
+ verifyPaymentInputsFilled,
+ verifyExistenceOfPaymentInputs,
+ changeText,
+ pressButton,
+ verifyExistenceByText,
+} from '@testUtils/testUtil';
import PaymentScreen from '.';
jest.mock('@react-navigation/native', () => ({
@@ -34,13 +42,8 @@ describe('', () => {
renderWithProvidersAndNavigation(, {
initialState,
});
- const itemCountText = screen.getByText('Items in the basket: 14');
- const totalText = screen.getByText('Total: $2648.01');
- // const itemCountText = screen.getByText(/Items in the basket:/i);
- // const totalText = screen.getByText(/Total:/i);
-
- expect(itemCountText).toBeTruthy();
- expect(totalText).toBeTruthy();
+ verifyExistenceByText('Items in the basket: 14');
+ verifyExistenceByText('Total: $2648.01');
});
it('renders PaymentForm', () => {
@@ -48,10 +51,7 @@ describe('', () => {
initialState,
});
- expect(screen.getAllByText(strings.payment.cardholderName)[0]).toBeTruthy();
- expect(screen.getAllByText(strings.payment.creditCardNumber)[0]).toBeTruthy();
- expect(screen.getAllByText(strings.payment.expirationDate)[0]).toBeTruthy();
- expect(screen.getAllByText(strings.payment.cvv)[0]).toBeTruthy();
+ verifyExistenceOfPaymentInputs();
});
it('disables the order button when basket is empty', () => {
@@ -60,61 +60,61 @@ describe('', () => {
initialState,
});
- const orderButton = screen.getByText(strings.buttons.payAndOrder);
- fireEvent.press(orderButton);
+ pressButton(strings.buttons.payAndOrder);
expect(mockOnPress).not.toHaveBeenCalled();
});
+
it('validates credit card input', async () => {
renderWithProvidersAndNavigation(, {
initialState,
});
- const cardNumberInput = screen.getAllByText(strings.payment.creditCardNumber)[0];
- fireEvent.changeText(cardNumberInput, '1234');
-
- const orderButton = screen.getByText(strings.buttons.payAndOrder);
- fireEvent.press(orderButton);
+ changeText('Credit Card Number', '1234');
+ pressButton(strings.buttons.payAndOrder);
await waitFor(() => {
- expect(screen.getByText(strings.payment.invalidCard)).toBeTruthy();
+ verifyExistenceByText(strings.payment.invalidCard);
});
});
+
it('show feedback messages about required fields when pressed pay and order button with filling invalid inputs', async () => {
renderWithProvidersAndNavigation(, {
initialState,
});
- const cardholderNameInput = screen.getAllByText(strings.payment.cardholderName)[0];
- fireEvent.changeText(cardholderNameInput, 'AB');
- const cvvInput = screen.getAllByText(strings.payment.cvv)[0];
- fireEvent.changeText(cvvInput, '12');
- const expirationInput = screen.getAllByText(strings.payment.expirationDate)[0];
- fireEvent.changeText(expirationInput, '07');
+ const cardDetails = {
+ cardholderName: 'AB',
+ cardNumber: '5566561551349323', // Valid card
+ expirationDate: '07',
+ cvv: '12',
+ };
+
+ fillPaymentInputs(cardDetails);
+ verifyPaymentInputsFilled(cardDetails);
- const orderButton = screen.getByText(strings.buttons.payAndOrder);
- fireEvent.press(orderButton);
+ pressButton(strings.buttons.payAndOrder);
await waitFor(() => {
- expect(screen.getAllByText(strings.payment.cardHolderMinLength)[0]).toBeTruthy();
+ verifyExistenceByText(strings.payment.cardHolderMinLength);
});
- expect(screen.getAllByText(strings.payment.cvvLength)[0]).toBeTruthy();
- expect(screen.getAllByText(strings.payment.invalidExpirationDate)[0]).toBeTruthy();
+
+ verifyExistenceByText(strings.payment.invalidExpirationDate);
+ verifyExistenceByText(strings.payment.invalidExpirationDate);
+ verifyExistenceByText(strings.payment.cvvLength);
});
+
it('show feedback messages about required fields when pressed pay and order button without filling inputs', async () => {
renderWithProvidersAndNavigation(, {
initialState,
});
- const orderButton = screen.getByText(strings.buttons.payAndOrder);
- fireEvent.press(orderButton);
+ pressButton(strings.buttons.payAndOrder);
await waitFor(() => {
- expect(screen.getAllByText(strings.payment.cardHolderRequired)[0]).toBeTruthy();
- });
- expect(screen.getAllByText(strings.payment.cvvRequired)[0]).toBeTruthy();
- expect(screen.getAllByText(strings.payment.expirationDateRequired)[0]).toBeTruthy();
- await waitFor(() => {
- expect(screen.getAllByText(strings.payment.creditCardRequired)[0]).toBeTruthy();
+ verifyExistenceByText(strings.payment.cardHolderRequired);
});
+ verifyExistenceByText(strings.payment.cvvRequired);
+ verifyExistenceByText(strings.payment.expirationDateRequired);
+ verifyExistenceByText(strings.payment.creditCardRequired);
});
});
diff --git a/src/screens/ProductListScreen/ProductListScreen.test.js b/src/screens/ProductListScreen/ProductListScreen.test.js
index 791ae6e..582e4f5 100644
--- a/src/screens/ProductListScreen/ProductListScreen.test.js
+++ b/src/screens/ProductListScreen/ProductListScreen.test.js
@@ -1,11 +1,21 @@
import React from 'react';
-import { fireEvent, screen, waitFor, within } from '@testing-library/react-native';
+import { fireEvent, waitFor, within } from '@testing-library/react-native';
import { useNavigation } from '@react-navigation/native';
import renderWithProvidersAndNavigation from '@testUtils/renderInProvidersAndNavigation';
import { sampleBasket, sampleResponse } from '@mocks/handlers';
import { strings } from '@constants';
import { useGetProductsQuery } from '@redux/api/apiSlice';
import ProductListScreen from '.';
+import {
+ verifyExistenceByTestId,
+ verifyInExistenceByText,
+ verifyInExistenceByTestId,
+ pressButton,
+ verifyExistenceByText,
+ getFirstOfItemsByText,
+ getFirstOfItemsByTestId,
+ pressButtonAsync,
+} from '../../../__tests__/utils/testUtil';
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
@@ -38,8 +48,8 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
// Ensure the loading indicator is visible and error message is not
- expect(screen.getByTestId('loading-state')).toBeTruthy();
- expect(screen.queryByText(strings.productList.errorLoading)).toBeFalsy();
+ verifyExistenceByTestId('loading-state');
+ verifyInExistenceByText(strings.productList.errorLoading);
});
it('should render the error state when there is an error', async () => {
@@ -55,8 +65,8 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
// Ensure the error message is visible and Loading state is not
- expect(screen.getByText(strings.productList.errorLoading)).toBeTruthy();
- expect(screen.queryByText('loading-state')).toBeFalsy();
+ verifyExistenceByText(strings.productList.errorLoading);
+ verifyInExistenceByTestId('loading-state');
});
it('should call refetch when Retry pressed in error state', async () => {
@@ -71,9 +81,7 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
- const retryButton = screen.getByText('Retry');
- fireEvent.press(retryButton);
-
+ pressButton('Retry');
expect(refetchMock).toHaveBeenCalledTimes(1);
});
@@ -86,9 +94,9 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
await waitFor(() => {
- expect(screen.getByText(anItem.title)).toBeTruthy();
+ verifyExistenceByText(anItem.title);
});
- expect(screen.getByText('Mens Casual Premium Slim Fit T-Shirts ')).toBeTruthy();
+ verifyExistenceByText('Mens Casual Premium Slim Fit T-Shirts ');
});
it('navigates to Checkout screen when checkout button is pressed', async () => {
@@ -99,13 +107,11 @@ describe('ProductListScreen', () => {
});
renderWithProvidersAndNavigation(, { initialState: { basket: { items: sampleBasket } } });
- const checkoutButton = await screen.findByText('CHECKOUT (14)');
- fireEvent.press(checkoutButton);
+ await pressButtonAsync('CHECKOUT (14)');
await waitFor(() => {
- expect(navigateMock).toHaveBeenCalledTimes(1);
+ expect(navigateMock).toHaveBeenCalledWith('Checkout');
});
- expect(navigateMock).toHaveBeenCalledWith('Checkout'); // Replace with the actual route name if necessary
});
it('should disable the checkout button when the basket is invalid', () => {
@@ -116,8 +122,7 @@ describe('ProductListScreen', () => {
});
renderWithProvidersAndNavigation();
- const checkoutButton = screen.getByText('CHECKOUT (0)');
- fireEvent.press(checkoutButton);
+ pressButton('CHECKOUT (0)');
expect(navigateMock).toHaveBeenCalledTimes(0);
});
@@ -131,17 +136,17 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
// Initial totalItemCount
- expect(screen.getByText('CHECKOUT (0)')).toBeTruthy();
+ verifyExistenceByText('CHECKOUT (0)');
// Simulate adding the first product to the basket
- const addToBasketButton = screen.getAllByText('Add to basket')[0];
+ const addToBasketButton = getFirstOfItemsByText('Add to basket');
fireEvent.press(addToBasketButton);
// Updated totalItemCount
await waitFor(() => {
- expect(screen.getByText('CHECKOUT (1)')).toBeTruthy();
+ verifyExistenceByText('CHECKOUT (1)');
});
- expect(screen.getByText('Total: $109.95')).toBeTruthy();
+ verifyExistenceByText('Total: $109.95');
});
it('should not allow adding the same product to the basket more than five times', async () => {
useGetProductsQuery.mockReturnValue({
@@ -153,12 +158,13 @@ describe('ProductListScreen', () => {
renderWithProvidersAndNavigation();
// Get the first product's Card and its "Add to basket" button
- const firstProductCard = screen.getAllByTestId('product-card')[0];
+ const firstProductCard = getFirstOfItemsByTestId('product-card');
const addToBasketButton = within(firstProductCard).getByText('Add to basket');
// Initial assertions
- expect(screen.getByText('CHECKOUT (0)')).toBeTruthy(); // Basket starts empty
- expect(screen.queryByText(strings.productList.limitReached)).toBeFalsy();
+ verifyExistenceByText('CHECKOUT (0)'); // Basket starts empty
+ verifyExistenceByText('Total: $0.00'); // Total price starts at 0
+ verifyInExistenceByText(strings.productList.limitReached);
// Add the same product to the basket five times
for (let i = 0; i < 5; i += 1) {
@@ -167,17 +173,18 @@ describe('ProductListScreen', () => {
// Check total item count and total price updated
await waitFor(() => {
- expect(screen.getByText('CHECKOUT (5)')).toBeTruthy();
+ verifyExistenceByText('CHECKOUT (5)');
});
- expect(screen.getByText('Total: $549.75')).toBeTruthy(); // Ensure total price is updated correctly
+ // Ensure total price is updated correctly
+ verifyExistenceByText('Total: $549.75');
// Ensure the "Add to basket" button is removed after reaching the limit
expect(within(firstProductCard).queryByText('Add to basket')).toBeFalsy();
// Ensure the limit message is displayed
- expect(screen.getByText(strings.productList.limitReached)).toBeTruthy();
+ verifyExistenceByText(strings.productList.limitReached);
// TotalItemCount should still be 5, not updated further
- expect(screen.getByText('CHECKOUT (5)')).toBeTruthy();
+ verifyExistenceByText('CHECKOUT (5)');
});
});