|
1 | 1 | import React from 'react'; |
2 | | -import { fireEvent } from '@testing-library/react-native'; |
| 2 | +import { act, fireEvent } from '@testing-library/react-native'; |
| 3 | +import type { ReactTestInstance } from 'react-test-renderer'; |
3 | 4 | import renderWithProvider from '../../../../../util/test/renderWithProvider'; |
4 | 5 | import MoneyHomeView from './MoneyHomeView'; |
5 | 6 | import { MoneyHomeViewTestIds } from './MoneyHomeView.testIds'; |
@@ -335,6 +336,47 @@ describe('MoneyHomeView', () => { |
335 | 336 | }); |
336 | 337 | }); |
337 | 338 |
|
| 339 | + it('navigates to Card root when Get now row is pressed', () => { |
| 340 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 341 | + |
| 342 | + fireEvent.press(getByTestId(MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW)); |
| 343 | + |
| 344 | + expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.ROOT); |
| 345 | + }); |
| 346 | + |
| 347 | + it('navigates to potential earnings screen when View potential earnings is pressed', () => { |
| 348 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 349 | + |
| 350 | + fireEvent.press( |
| 351 | + getByTestId( |
| 352 | + MoneyPotentialEarningsTestIds.VIEW_POTENTIAL_EARNINGS_BUTTON, |
| 353 | + ), |
| 354 | + ); |
| 355 | + |
| 356 | + expect(mockNavigate).toHaveBeenCalledWith(Routes.MONEY.POTENTIAL_EARNINGS); |
| 357 | + }); |
| 358 | + |
| 359 | + it('opens the MUSD learn more URL when learn more is pressed in empty state', () => { |
| 360 | + const mockOpenURL = jest |
| 361 | + .spyOn(require('react-native').Linking, 'openURL') |
| 362 | + .mockResolvedValue(undefined); |
| 363 | + |
| 364 | + mockUseMoneyAccountTransactions.mockReturnValue({ |
| 365 | + allTransactions: [], |
| 366 | + deposits: [], |
| 367 | + transfers: [], |
| 368 | + submittedTransactions: [], |
| 369 | + moneyAddress: '0x0000000000000000000000000000000000000001', |
| 370 | + }); |
| 371 | + |
| 372 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 373 | + |
| 374 | + fireEvent.press(getByTestId(MoneyWhatYouGetTestIds.LEARN_MORE_BUTTON)); |
| 375 | + |
| 376 | + expect(mockOpenURL).toHaveBeenCalledTimes(1); |
| 377 | + mockOpenURL.mockRestore(); |
| 378 | + }); |
| 379 | + |
338 | 380 | describe('projected earnings', () => { |
339 | 381 | it('passes the formatted projected earnings to MoneyEarnings', () => { |
340 | 382 | mockMoneyFormatFiat.mockReturnValue('$0.12'); |
@@ -597,8 +639,270 @@ describe('MoneyHomeView', () => { |
597 | 639 | it('mounts the footer in its hidden initial position', () => { |
598 | 640 | const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
599 | 641 | expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
600 | | - // Animation behavior is verified by computeStepperVisibility's unit |
601 | | - // tests and the Detox/E2E pass; we don't simulate scroll/layout here. |
| 642 | + }); |
| 643 | + |
| 644 | + it('handleScrollViewLayout updates scroll view height and calls updateStepperVisibility', () => { |
| 645 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 646 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 647 | + |
| 648 | + act(() => { |
| 649 | + fireEvent(scrollView, 'layout', { |
| 650 | + nativeEvent: { layout: { height: 700, width: 390, x: 0, y: 0 } }, |
| 651 | + }); |
| 652 | + }); |
| 653 | + |
| 654 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 655 | + }); |
| 656 | + |
| 657 | + it('handleScrollViewLayout is a no-op when height is unchanged', () => { |
| 658 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 659 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 660 | + |
| 661 | + act(() => { |
| 662 | + fireEvent(scrollView, 'layout', { |
| 663 | + nativeEvent: { layout: { height: 600, width: 390, x: 0, y: 0 } }, |
| 664 | + }); |
| 665 | + }); |
| 666 | + |
| 667 | + act(() => { |
| 668 | + fireEvent(scrollView, 'layout', { |
| 669 | + nativeEvent: { layout: { height: 600, width: 390, x: 0, y: 0 } }, |
| 670 | + }); |
| 671 | + }); |
| 672 | + |
| 673 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 674 | + }); |
| 675 | + |
| 676 | + it('handleScroll records the current scroll offset and calls updateStepperVisibility', () => { |
| 677 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 678 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 679 | + |
| 680 | + act(() => { |
| 681 | + fireEvent.scroll(scrollView, { |
| 682 | + nativeEvent: { |
| 683 | + contentOffset: { y: 300, x: 0 }, |
| 684 | + contentSize: { height: 1200, width: 390 }, |
| 685 | + layoutMeasurement: { height: 700, width: 390 }, |
| 686 | + }, |
| 687 | + }); |
| 688 | + }); |
| 689 | + |
| 690 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 691 | + }); |
| 692 | + |
| 693 | + it('handleStepperLayout stores new layout and triggers visibility update', () => { |
| 694 | + const { UNSAFE_getAllByType, getByTestId } = renderWithProvider( |
| 695 | + <MoneyHomeView />, |
| 696 | + ); |
| 697 | + |
| 698 | + const Box = jest.requireActual( |
| 699 | + '@metamask/design-system-react-native', |
| 700 | + ).Box; |
| 701 | + const stepperBox = UNSAFE_getAllByType(Box).find( |
| 702 | + (b: { props: { onLayout?: unknown } }) => b.props.onLayout, |
| 703 | + ); |
| 704 | + |
| 705 | + act(() => { |
| 706 | + stepperBox?.props.onLayout({ |
| 707 | + nativeEvent: { layout: { y: 200, height: 120, x: 0, width: 390 } }, |
| 708 | + }); |
| 709 | + }); |
| 710 | + |
| 711 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 712 | + }); |
| 713 | + |
| 714 | + it('handleStepperLayout is a no-op when layout dimensions are unchanged', () => { |
| 715 | + const { UNSAFE_getAllByType, getByTestId } = renderWithProvider( |
| 716 | + <MoneyHomeView />, |
| 717 | + ); |
| 718 | + |
| 719 | + const Box = jest.requireActual( |
| 720 | + '@metamask/design-system-react-native', |
| 721 | + ).Box; |
| 722 | + const stepperBox = UNSAFE_getAllByType(Box).find( |
| 723 | + (b: { props: { onLayout?: unknown } }) => b.props.onLayout, |
| 724 | + ); |
| 725 | + |
| 726 | + act(() => { |
| 727 | + stepperBox?.props.onLayout({ |
| 728 | + nativeEvent: { layout: { y: 200, height: 120, x: 0, width: 390 } }, |
| 729 | + }); |
| 730 | + }); |
| 731 | + |
| 732 | + act(() => { |
| 733 | + stepperBox?.props.onLayout({ |
| 734 | + nativeEvent: { layout: { y: 200, height: 120, x: 0, width: 390 } }, |
| 735 | + }); |
| 736 | + }); |
| 737 | + |
| 738 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 739 | + }); |
| 740 | + |
| 741 | + it('footer peek-in: scrolling past stepper bottom triggers animateFooter(true)', () => { |
| 742 | + const { UNSAFE_getAllByType, getByTestId } = renderWithProvider( |
| 743 | + <MoneyHomeView />, |
| 744 | + ); |
| 745 | + |
| 746 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 747 | + |
| 748 | + act(() => { |
| 749 | + fireEvent(scrollView, 'layout', { |
| 750 | + nativeEvent: { layout: { height: 700, width: 390, x: 0, y: 0 } }, |
| 751 | + }); |
| 752 | + }); |
| 753 | + |
| 754 | + const Box = jest.requireActual( |
| 755 | + '@metamask/design-system-react-native', |
| 756 | + ).Box; |
| 757 | + const stepperBox = UNSAFE_getAllByType(Box).find( |
| 758 | + (b: { props: { onLayout?: unknown } }) => b.props.onLayout, |
| 759 | + ); |
| 760 | + |
| 761 | + act(() => { |
| 762 | + stepperBox?.props.onLayout({ |
| 763 | + nativeEvent: { layout: { y: 100, height: 200, x: 0, width: 390 } }, |
| 764 | + }); |
| 765 | + }); |
| 766 | + |
| 767 | + act(() => { |
| 768 | + fireEvent.scroll(scrollView, { |
| 769 | + nativeEvent: { |
| 770 | + contentOffset: { y: 500, x: 0 }, |
| 771 | + contentSize: { height: 2000, width: 390 }, |
| 772 | + layoutMeasurement: { height: 700, width: 390 }, |
| 773 | + }, |
| 774 | + }); |
| 775 | + }); |
| 776 | + |
| 777 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 778 | + }); |
| 779 | + |
| 780 | + it('footer hide: scrolling back above stepper bottom triggers animateFooter(false)', () => { |
| 781 | + const { UNSAFE_getAllByType, getByTestId } = renderWithProvider( |
| 782 | + <MoneyHomeView />, |
| 783 | + ); |
| 784 | + |
| 785 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 786 | + |
| 787 | + act(() => { |
| 788 | + fireEvent(scrollView, 'layout', { |
| 789 | + nativeEvent: { layout: { height: 700, width: 390, x: 0, y: 0 } }, |
| 790 | + }); |
| 791 | + }); |
| 792 | + |
| 793 | + const Box = jest.requireActual( |
| 794 | + '@metamask/design-system-react-native', |
| 795 | + ).Box; |
| 796 | + const stepperBox = UNSAFE_getAllByType(Box).find( |
| 797 | + (b: { props: { onLayout?: unknown } }) => b.props.onLayout, |
| 798 | + ); |
| 799 | + |
| 800 | + act(() => { |
| 801 | + stepperBox?.props.onLayout({ |
| 802 | + nativeEvent: { layout: { y: 100, height: 200, x: 0, width: 390 } }, |
| 803 | + }); |
| 804 | + }); |
| 805 | + |
| 806 | + act(() => { |
| 807 | + fireEvent.scroll(scrollView, { |
| 808 | + nativeEvent: { |
| 809 | + contentOffset: { y: 500, x: 0 }, |
| 810 | + contentSize: { height: 2000, width: 390 }, |
| 811 | + layoutMeasurement: { height: 700, width: 390 }, |
| 812 | + }, |
| 813 | + }); |
| 814 | + }); |
| 815 | + |
| 816 | + act(() => { |
| 817 | + fireEvent.scroll(scrollView, { |
| 818 | + nativeEvent: { |
| 819 | + contentOffset: { y: 50, x: 0 }, |
| 820 | + contentSize: { height: 2000, width: 390 }, |
| 821 | + layoutMeasurement: { height: 700, width: 390 }, |
| 822 | + }, |
| 823 | + }); |
| 824 | + }); |
| 825 | + |
| 826 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 827 | + }); |
| 828 | + |
| 829 | + it('updateStepperVisibility does not animate when visibility is unchanged', () => { |
| 830 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 831 | + const scrollView = getByTestId(MoneyHomeViewTestIds.SCROLL_VIEW); |
| 832 | + |
| 833 | + act(() => { |
| 834 | + fireEvent.scroll(scrollView, { |
| 835 | + nativeEvent: { |
| 836 | + contentOffset: { y: 0, x: 0 }, |
| 837 | + contentSize: { height: 2000, width: 390 }, |
| 838 | + layoutMeasurement: { height: 700, width: 390 }, |
| 839 | + }, |
| 840 | + }); |
| 841 | + }); |
| 842 | + |
| 843 | + act(() => { |
| 844 | + fireEvent.scroll(scrollView, { |
| 845 | + nativeEvent: { |
| 846 | + contentOffset: { y: 10, x: 0 }, |
| 847 | + contentSize: { height: 2000, width: 390 }, |
| 848 | + layoutMeasurement: { height: 700, width: 390 }, |
| 849 | + }, |
| 850 | + }); |
| 851 | + }); |
| 852 | + |
| 853 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 854 | + }); |
| 855 | + |
| 856 | + it('handleFooterLayout updates footer height on first measurement', () => { |
| 857 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 858 | + |
| 859 | + const footerEl = getByTestId(MoneyFooterTestIds.CONTAINER); |
| 860 | + let footerAnimatedView: ReactTestInstance | null = null; |
| 861 | + let cursor: ReactTestInstance | null = footerEl.parent ?? null; |
| 862 | + while (cursor) { |
| 863 | + if (typeof cursor.props?.onLayout === 'function') { |
| 864 | + footerAnimatedView = cursor; |
| 865 | + break; |
| 866 | + } |
| 867 | + cursor = cursor.parent ?? null; |
| 868 | + } |
| 869 | + |
| 870 | + act(() => { |
| 871 | + footerAnimatedView?.props.onLayout?.({ |
| 872 | + nativeEvent: { layout: { height: 80, width: 390, x: 0, y: 0 } }, |
| 873 | + }); |
| 874 | + }); |
| 875 | + |
| 876 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
| 877 | + }); |
| 878 | + |
| 879 | + it('handleFooterLayout is a no-op when footer height is unchanged', () => { |
| 880 | + const { getByTestId } = renderWithProvider(<MoneyHomeView />); |
| 881 | + |
| 882 | + const footerEl = getByTestId(MoneyFooterTestIds.CONTAINER); |
| 883 | + let footerAnimatedView: ReactTestInstance | null = null; |
| 884 | + let cursor: ReactTestInstance | null = footerEl.parent ?? null; |
| 885 | + while (cursor) { |
| 886 | + if (typeof cursor.props?.onLayout === 'function') { |
| 887 | + footerAnimatedView = cursor; |
| 888 | + break; |
| 889 | + } |
| 890 | + cursor = cursor.parent ?? null; |
| 891 | + } |
| 892 | + |
| 893 | + act(() => { |
| 894 | + footerAnimatedView?.props.onLayout?.({ |
| 895 | + nativeEvent: { layout: { height: 80, width: 390, x: 0, y: 0 } }, |
| 896 | + }); |
| 897 | + }); |
| 898 | + |
| 899 | + act(() => { |
| 900 | + footerAnimatedView?.props.onLayout?.({ |
| 901 | + nativeEvent: { layout: { height: 80, width: 390, x: 0, y: 0 } }, |
| 902 | + }); |
| 903 | + }); |
| 904 | + |
| 905 | + expect(getByTestId(MoneyFooterTestIds.CONTAINER)).toBeOnTheScreen(); |
602 | 906 | }); |
603 | 907 | }); |
604 | 908 | }); |
0 commit comments