From 5436e6ae91e6d658d2a9cf461caf1e6933e5b656 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 17 Apr 2026 14:11:10 +0200 Subject: [PATCH 1/2] [No QA] Add tests for useReceiptPreviewsSizes --- .../components/ReceiptPreviews/index.tsx | 32 +--- .../hooks/useReceiptPreviewsSizes.ts | 34 ++++ .../hooks/useReceiptPreviewsSizes.test.ts | 147 ++++++++++++++++++ 3 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes.ts create mode 100644 tests/unit/hooks/useReceiptPreviewsSizes.test.ts diff --git a/src/pages/iou/request/step/IOURequestStepScan/components/ReceiptPreviews/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/components/ReceiptPreviews/index.tsx index cde51492ba09..eedbd172ff17 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/components/ReceiptPreviews/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/components/ReceiptPreviews/index.tsx @@ -12,9 +12,8 @@ import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionDraftReceipts from '@hooks/useTransactionDraftReceipts'; -import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; -import variables from '@styles/variables'; +import useReceiptPreviewsSizes from '@pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {Receipt} from '@src/types/onyx/Transaction'; @@ -36,35 +35,6 @@ type ReceiptPreviewsProps = { isInLandscapeMode?: boolean; }; -function useReceiptPreviewsSizes(isInLandscapeMode: boolean) { - const styles = useThemeStyles(); - const {windowWidth, windowHeight} = useWindowDimensions(); - - if (isInLandscapeMode) { - const previewItemSize = styles.receiptPlaceholderLandscape.height + styles.receiptPlaceholderLandscape.marginBottom; - - const submitButtonHeight = styles.singleAvatarMedium.height; - const tabSelectorButtonHeight = variables.tabSelectorButtonHeight + styles.pb4.paddingBottom; - const contentHeaderHeight = variables.contentHeaderHeight; - const initialReceiptsAmount = (windowHeight - submitButtonHeight - tabSelectorButtonHeight - contentHeaderHeight) / previewItemSize; - - return { - previewsSize: styles.receiptPlaceholderLandscape.width + styles.ph6.paddingHorizontal * 2, - previewItemSize, - initialReceiptsAmount, - }; - } - - const previewItemSize = styles.receiptPlaceholder.width + styles.receiptPlaceholder.marginRight; - const initialReceiptsAmount = (windowWidth - styles.ph4.paddingHorizontal * 2 - styles.singleAvatarMedium.width) / previewItemSize; - - return { - previewsSize: styles.receiptPlaceholder.height + styles.pv2.paddingVertical * 2, - previewItemSize, - initialReceiptsAmount, - }; -} - function ReceiptPreviews({submit, isMultiScanEnabled, isCapturingPhoto = false, isInLandscapeMode = false}: ReceiptPreviewsProps) { const icons = useMemoizedLazyExpensifyIcons(['ArrowRight']); const styles = useThemeStyles(); diff --git a/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes.ts b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes.ts new file mode 100644 index 000000000000..2132cf11e6f2 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes.ts @@ -0,0 +1,34 @@ +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import variables from '@styles/variables'; + +function useReceiptPreviewsSizes(isInLandscapeMode: boolean) { + const styles = useThemeStyles(); + const {windowWidth, windowHeight} = useWindowDimensions(); + + if (isInLandscapeMode) { + const previewItemSize = styles.receiptPlaceholderLandscape.height + styles.receiptPlaceholderLandscape.marginBottom; + + const submitButtonHeight = styles.singleAvatarMedium.height; + const tabSelectorButtonHeight = variables.tabSelectorButtonHeight + styles.pb4.paddingBottom; + const contentHeaderHeight = variables.contentHeaderHeight; + const initialReceiptsAmount = (windowHeight - submitButtonHeight - tabSelectorButtonHeight - contentHeaderHeight) / previewItemSize; + + return { + previewsSize: styles.receiptPlaceholderLandscape.width + styles.ph6.paddingHorizontal * 2, + previewItemSize, + initialReceiptsAmount, + }; + } + + const previewItemSize = styles.receiptPlaceholder.width + styles.receiptPlaceholder.marginRight; + const initialReceiptsAmount = (windowWidth - styles.ph4.paddingHorizontal * 2 - styles.singleAvatarMedium.width) / previewItemSize; + + return { + previewsSize: styles.receiptPlaceholder.height + styles.pv2.paddingVertical * 2, + previewItemSize, + initialReceiptsAmount, + }; +} + +export default useReceiptPreviewsSizes; diff --git a/tests/unit/hooks/useReceiptPreviewsSizes.test.ts b/tests/unit/hooks/useReceiptPreviewsSizes.test.ts new file mode 100644 index 000000000000..5caebdd5593e --- /dev/null +++ b/tests/unit/hooks/useReceiptPreviewsSizes.test.ts @@ -0,0 +1,147 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {renderHook} from '@testing-library/react-native'; +import useReceiptPreviewsSizes from '@pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes'; + +const MOCK_STYLES = { + receiptPlaceholder: {width: 44, marginRight: 8, height: 52}, + receiptPlaceholderLandscape: {height: 52, marginBottom: 8, width: 44}, + singleAvatarMedium: {height: 52, width: 52}, + ph4: {paddingHorizontal: 16}, + ph6: {paddingHorizontal: 24}, + pv2: {paddingVertical: 8}, + pb4: {paddingBottom: 16}, +}; + +jest.mock('@hooks/useThemeStyles', () => ({ + __esModule: true, + default: jest.fn(() => MOCK_STYLES), +})); + +const mockUseWindowDimensions = jest.fn(() => ({windowWidth: 400, windowHeight: 800})); + +jest.mock('@hooks/useWindowDimensions', () => ({ + __esModule: true, + default: () => mockUseWindowDimensions(), +})); + +describe('useReceiptPreviewsSizes', () => { + beforeEach(() => { + mockUseWindowDimensions.mockReturnValue({windowWidth: 400, windowHeight: 800}); + }); + + describe('portrait mode (isInLandscapeMode = false)', () => { + it('returns the correct previewItemSize', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(false)); + + // previewItemSize = receiptPlaceholder.width + receiptPlaceholder.marginRight = 44 + 8 = 52 + expect(result.current.previewItemSize).toBe(52); + }); + + it('returns the correct previewsSize', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(false)); + + // previewsSize = receiptPlaceholder.height + pv2.paddingVertical * 2 = 52 + 8 * 2 = 68 + expect(result.current.previewsSize).toBe(68); + }); + + it('returns the correct initialReceiptsAmount', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(false)); + + // initialReceiptsAmount = (windowWidth - ph4.paddingHorizontal * 2 - singleAvatarMedium.width) / previewItemSize + // = (400 - 16 * 2 - 52) / 52 = 316 / 52 + expect(result.current.initialReceiptsAmount).toBe(316 / 52); + }); + + it('initialReceiptsAmount scales with windowWidth', () => { + mockUseWindowDimensions.mockReturnValue({windowWidth: 520, windowHeight: 800}); + + const {result} = renderHook(() => useReceiptPreviewsSizes(false)); + + // (520 - 16 * 2 - 52) / 52 = 436 / 52 + expect(result.current.initialReceiptsAmount).toBe(436 / 52); + }); + + it('previewsSize and previewItemSize are unaffected by window dimensions', () => { + mockUseWindowDimensions.mockReturnValue({windowWidth: 1200, windowHeight: 900}); + + const {result} = renderHook(() => useReceiptPreviewsSizes(false)); + + expect(result.current.previewsSize).toBe(68); + expect(result.current.previewItemSize).toBe(52); + }); + }); + + describe('landscape mode (isInLandscapeMode = true)', () => { + it('returns the correct previewItemSize', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(true)); + + // previewItemSize = receiptPlaceholderLandscape.height + receiptPlaceholderLandscape.marginBottom = 52 + 8 = 60 + expect(result.current.previewItemSize).toBe(60); + }); + + it('returns the correct previewsSize', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(true)); + + // previewsSize = receiptPlaceholderLandscape.width + ph6.paddingHorizontal * 2 = 44 + 24 * 2 = 92 + expect(result.current.previewsSize).toBe(92); + }); + + it('returns the correct initialReceiptsAmount', () => { + const {result} = renderHook(() => useReceiptPreviewsSizes(true)); + + // initialReceiptsAmount = (windowHeight - submitButtonHeight - tabSelectorButtonHeight - contentHeaderHeight) / previewItemSize + // submitButtonHeight = singleAvatarMedium.height = 52 + // tabSelectorButtonHeight = tabSelectorButtonHeight (40) + pb4.paddingBottom (16) = 56 + // contentHeaderHeight = 100 (variables.contentHeaderHeight capped at maxValue in test environment) + // = (800 - 52 - 56 - 100) / 60 = 592 / 60 + expect(result.current.initialReceiptsAmount).toBe(592 / 60); + }); + + it('initialReceiptsAmount scales with windowHeight', () => { + mockUseWindowDimensions.mockReturnValue({windowWidth: 400, windowHeight: 600}); + + const {result} = renderHook(() => useReceiptPreviewsSizes(true)); + + // (600 - 52 - 56 - 100) / 60 = 392 / 60 + expect(result.current.initialReceiptsAmount).toBe(392 / 60); + }); + + it('previewsSize and previewItemSize are unaffected by window dimensions', () => { + mockUseWindowDimensions.mockReturnValue({windowWidth: 300, windowHeight: 1200}); + + const {result} = renderHook(() => useReceiptPreviewsSizes(true)); + + expect(result.current.previewsSize).toBe(92); + expect(result.current.previewItemSize).toBe(60); + }); + }); + + describe('switching between portrait and landscape', () => { + it('returns different previewItemSize values for portrait vs landscape', () => { + const {result: portraitResult} = renderHook(() => useReceiptPreviewsSizes(false)); + const {result: landscapeResult} = renderHook(() => useReceiptPreviewsSizes(true)); + + expect(portraitResult.current.previewItemSize).not.toBe(landscapeResult.current.previewItemSize); + }); + + it('returns different previewsSize values for portrait vs landscape', () => { + const {result: portraitResult} = renderHook(() => useReceiptPreviewsSizes(false)); + const {result: landscapeResult} = renderHook(() => useReceiptPreviewsSizes(true)); + + expect(portraitResult.current.previewsSize).not.toBe(landscapeResult.current.previewsSize); + }); + + it('updates when isInLandscapeMode changes', () => { + const {result, rerender} = renderHook(({isInLandscapeMode}: {isInLandscapeMode: boolean}) => useReceiptPreviewsSizes(isInLandscapeMode), { + initialProps: {isInLandscapeMode: false}, + }); + + const portraitPreviewItemSize = result.current.previewItemSize; + + rerender({isInLandscapeMode: true}); + + expect(result.current.previewItemSize).not.toBe(portraitPreviewItemSize); + expect(result.current.previewItemSize).toBe(60); + }); + }); +}); From 6b2bc7f00110851b6b1515dea05057de0dd9723a Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 17 Apr 2026 14:35:30 +0200 Subject: [PATCH 2/2] add comment --- tests/unit/hooks/useReceiptPreviewsSizes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/hooks/useReceiptPreviewsSizes.test.ts b/tests/unit/hooks/useReceiptPreviewsSizes.test.ts index 5caebdd5593e..67521e46f4f7 100644 --- a/tests/unit/hooks/useReceiptPreviewsSizes.test.ts +++ b/tests/unit/hooks/useReceiptPreviewsSizes.test.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/naming-convention -- Mock module paths use non-standard naming conventions required by jest.mock */ import {renderHook} from '@testing-library/react-native'; import useReceiptPreviewsSizes from '@pages/iou/request/step/IOURequestStepScan/hooks/useReceiptPreviewsSizes';