Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import AccessRestrictedModal from './AccessRestrictedModal';
import { AccessRestrictedModalSelectorsIDs } from './AccessRestrictedModal.testIds';

jest.mock(
'../../../../component-library/components/BottomSheets/BottomSheet',
() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { View } = require('react-native');
return {
__esModule: true,
default: jest.fn(
({
children,
testID,
}: {
children: React.ReactNode;
testID?: string;
}) => <View testID={testID}>{children}</View>,
),
};
},
);

jest.mock(
'../../../../component-library/components/BottomSheets/BottomSheetHeader',
() => ({
__esModule: true,
default: jest.fn(({ children }) => <>{children}</>),
}),
);
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

jest.mock('@metamask/design-system-twrnc-preset', () => ({
useTailwind: () => ({ style: (...args: string[]) => args }),
}));

describe('AccessRestrictedModal', () => {
const defaultProps = {
isVisible: true,
onClose: jest.fn(),
onContactSupport: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('renders nothing when isVisible is false', () => {
const { queryByTestId } = render(
<AccessRestrictedModal {...defaultProps} isVisible={false} />,
);

expect(
queryByTestId(AccessRestrictedModalSelectorsIDs.BOTTOM_SHEET),
).toBeNull();
});

it('renders the modal with title and description when visible', () => {
const { getByTestId } = render(<AccessRestrictedModal {...defaultProps} />);

expect(getByTestId(AccessRestrictedModalSelectorsIDs.TITLE)).toBeTruthy();
expect(
getByTestId(AccessRestrictedModalSelectorsIDs.DESCRIPTION),
).toBeTruthy();
expect(
getByTestId(AccessRestrictedModalSelectorsIDs.CONTACT_SUPPORT_BUTTON),
).toBeTruthy();
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
});

it('calls onContactSupport when pressing the contact support button', () => {
const { getByTestId } = render(<AccessRestrictedModal {...defaultProps} />);

fireEvent.press(
getByTestId(AccessRestrictedModalSelectorsIDs.CONTACT_SUPPORT_BUTTON),
);

expect(defaultProps.onContactSupport).toHaveBeenCalledTimes(1);
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NON-BLOCKING] Missing test for the close (X) button calling onClose. The test covers onContactSupport but not onClose. The BottomSheet mock currently strips out onClose, so this interaction isn't exercised.

Consider adding a test case that exposes the onClose behavior, either by updating the mock to render the BottomSheetHeader close trigger, or by testing AccessRestrictedModal in integration with the real BottomSheet (using a wrapper test environment).

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const AccessRestrictedModalSelectorsIDs = {
BOTTOM_SHEET: 'access-restricted-modal',
TITLE: 'access-restricted-modal-title',
DESCRIPTION: 'access-restricted-modal-description',
CONTACT_SUPPORT_BUTTON: 'access-restricted-modal-contact-support',
} as const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MINOR] The close (X) button on BottomSheetHeader renders with the generic default testID "button-icon", making it non-specific for E2E targeting. BottomSheetHeader accepts closeButtonProps which can override testID:

closeButtonProps={{ testID: AccessRestrictedModalSelectorsIDs.CLOSE_BUTTON }}

Would require adding CLOSE_BUTTON: 'access-restricted-modal-close' here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useRef } from 'react';
import { Pressable } from 'react-native';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { Box } from '@metamask/design-system-react-native';
import BottomSheet, {
BottomSheetRef,
} from '../../../../component-library/components/BottomSheets/BottomSheet';
import BottomSheetHeader from '../../../../component-library/components/BottomSheets/BottomSheetHeader';
import Text, {
TextColor,
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import { strings } from '../../../../../locales/i18n';
import { AccessRestrictedModalProps } from './AccessRestrictedModal.types';
import { AccessRestrictedModalSelectorsIDs } from './AccessRestrictedModal.testIds';

const AccessRestrictedModal: React.FC<AccessRestrictedModalProps> = ({
isVisible,
onClose,
onContactSupport,
}) => {
const tw = useTailwind();
const bottomSheetRef = useRef<BottomSheetRef>(null);

if (!isVisible) return null;

return (
<BottomSheet
ref={bottomSheetRef}
shouldNavigateBack={false}
onClose={onClose}
testID={AccessRestrictedModalSelectorsIDs.BOTTOM_SHEET}
>
<BottomSheetHeader onClose={onClose}>

Check warning on line 34 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'BottomSheetHeader' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcA&open=AZ0Gofubu5_pCbq7ikcA&pullRequest=27694
<Text

Check warning on line 35 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcB&open=AZ0Gofubu5_pCbq7ikcB&pullRequest=27694
variant={TextVariant.HeadingSM}
testID={AccessRestrictedModalSelectorsIDs.TITLE}
>
{strings('access_restricted.title')}
</Text>

Check warning on line 40 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcC&open=AZ0Gofubu5_pCbq7ikcC&pullRequest=27694
</BottomSheetHeader>

Check warning on line 41 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'BottomSheetHeader' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcD&open=AZ0Gofubu5_pCbq7ikcD&pullRequest=27694

<Box twClassName="px-4 pb-6">
<Text

Check warning on line 44 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcE&open=AZ0Gofubu5_pCbq7ikcE&pullRequest=27694
variant={TextVariant.BodyMD}
color={TextColor.Alternative}
testID={AccessRestrictedModalSelectorsIDs.DESCRIPTION}
>
{strings('access_restricted.description_line1')}
{'\n\n'}
{strings('access_restricted.description_line2')}
</Text>

Check warning on line 52 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcF&open=AZ0Gofubu5_pCbq7ikcF&pullRequest=27694

<Pressable
onPress={onContactSupport}
testID={AccessRestrictedModalSelectorsIDs.CONTACT_SUPPORT_BUTTON}
style={({ pressed }) =>
tw.style(
'w-full h-12 items-center justify-center rounded-xl bg-muted mt-6',
pressed && 'opacity-70',
)
}
>
<Text variant={TextVariant.BodyMDMedium}>

Check warning on line 64 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcG&open=AZ0Gofubu5_pCbq7ikcG&pullRequest=27694
{strings('access_restricted.contact_support')}
</Text>

Check warning on line 66 in app/components/UI/Compliance/AccessRestrictedModal/AccessRestrictedModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'Text' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZ0Gofubu5_pCbq7ikcH&open=AZ0Gofubu5_pCbq7ikcH&pullRequest=27694
</Pressable>
</Box>
</BottomSheet>
);
};

export default AccessRestrictedModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface AccessRestrictedModalProps {
isVisible: boolean;
onClose: () => void;
onContactSupport: () => void;
}
3 changes: 3 additions & 0 deletions app/components/UI/Compliance/AccessRestrictedModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from './AccessRestrictedModal';
export type { AccessRestrictedModalProps } from './AccessRestrictedModal.types';
export { AccessRestrictedModalSelectorsIDs } from './AccessRestrictedModal.testIds';
148 changes: 148 additions & 0 deletions app/components/UI/Compliance/contexts/AccessRestrictedContext.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React from 'react';
import { render, fireEvent, act } from '@testing-library/react-native';
import { Text, Pressable } from 'react-native';
import {
AccessRestrictedProvider,
useAccessRestrictedModal,
} from './AccessRestrictedContext';

const mockNavigate = jest.fn();
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({ navigate: mockNavigate }),
}));

jest.mock(
'../../../../component-library/components/BottomSheets/BottomSheet',
() => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { View } = require('react-native');
return {
__esModule: true,
default: jest.fn(
({
children,
testID,
}: {
children: React.ReactNode;
testID?: string;
}) => <View testID={testID}>{children}</View>,
),
};
},
);

jest.mock(
'../../../../component-library/components/BottomSheets/BottomSheetHeader',
() => ({
__esModule: true,
default: jest.fn(({ children }) => <>{children}</>),
}),
);

jest.mock('@metamask/design-system-twrnc-preset', () => ({
useTailwind: () => ({ style: (...args: string[]) => args }),
}));

const TestConsumer = () => {
const {
showAccessRestrictedModal,
hideAccessRestrictedModal,
isAccessRestricted,
} = useAccessRestrictedModal();
return (
<>
<Text testID="is-restricted">{String(isAccessRestricted)}</Text>
<Pressable testID="show-btn" onPress={showAccessRestrictedModal} />
<Pressable testID="hide-btn" onPress={hideAccessRestrictedModal} />
</>
);
};

describe('AccessRestrictedContext', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('provides isAccessRestricted as false by default', () => {
const { getByTestId } = render(
<AccessRestrictedProvider>
<TestConsumer />
</AccessRestrictedProvider>,
);

expect(getByTestId('is-restricted').props.children).toBe('false');
});

it('sets isAccessRestricted to true when showAccessRestrictedModal is called', () => {
const { getByTestId } = render(
<AccessRestrictedProvider>
<TestConsumer />
</AccessRestrictedProvider>,
);

act(() => {
fireEvent.press(getByTestId('show-btn'));
});

expect(getByTestId('is-restricted').props.children).toBe('true');
});

it('sets isAccessRestricted back to false when hideAccessRestrictedModal is called', () => {
const { getByTestId } = render(
<AccessRestrictedProvider>
<TestConsumer />
</AccessRestrictedProvider>,
);

act(() => {
fireEvent.press(getByTestId('show-btn'));
});
expect(getByTestId('is-restricted').props.children).toBe('true');

act(() => {
fireEvent.press(getByTestId('hide-btn'));
});
expect(getByTestId('is-restricted').props.children).toBe('false');
});

it('throws when useAccessRestrictedModal is used outside provider', () => {
const spy = jest
.spyOn(console, 'error')
.mockImplementation(() => undefined);

expect(() => render(<TestConsumer />)).toThrow(
'useAccessRestrictedModal must be used within an AccessRestrictedProvider',
);

spy.mockRestore();
});

it('navigates to support webview when contact support is tapped', () => {
const { getByTestId } = render(
<AccessRestrictedProvider>
<TestConsumer />
</AccessRestrictedProvider>,
);

act(() => {
fireEvent.press(getByTestId('show-btn'));
});

const contactSupportBtn = getByTestId(
'access-restricted-modal-contact-support',
);
Comment thread
cursor[bot] marked this conversation as resolved.
act(() => {
fireEvent.press(contactSupportBtn);
});

expect(mockNavigate).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
params: expect.objectContaining({
url: 'https://support.metamask.io',
}),
}),
);
expect(getByTestId('is-restricted').props.children).toBe('false');
});
});
Loading
Loading