From 03741c9575bd5747188ba9471fd7f6f971fd9f4c Mon Sep 17 00:00:00 2001 From: Ariella Vu <20778143+digiwand@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:30:30 -0400 Subject: [PATCH] feat: split ConfirmPage storybook pages by transaction type (#25283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - Separate ConfirmPage stories by signature type - Fix issue that caused 404 bug in storybook [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25283?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/25052 (continuation) Related: https://github.com/MetaMask/metamask-extension/pull/25054 ## **Manual testing steps** 1. `yarn storybook` 2. Go to http://localhost:6006/?path=/docs/pages-confirmation-confirmpage-signatures-permit--docs 3. View other stories in relevant signatures directory ## **Screenshots/Recordings** ### **Before** ### **After** ![CleanShot 2024-06-18 at 22 38 56@2x](https://github.com/MetaMask/metamask-extension/assets/20778143/8e0224cf-141a-4d39-a5fd-7a9146feeab4) ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- test/data/confirmations/personal_sign.ts | 8 +- test/data/confirmations/typed_sign.ts | 10 +- .../typed-sign-v1/typed-sign-v1.stories.tsx | 2 +- .../typedSignDataV1.stories.tsx | 2 +- .../typedSignDataV1.test.tsx | 4 +- .../typed-sign-data/typedSignData.stories.tsx | 2 +- .../typed-sign-data/typedSignData.test.tsx | 19 ++-- .../scroll-to-bottom.stories.tsx | 2 +- .../confirmations/confirm/confirm.stories.tsx | 96 ------------------- .../signatures/personal-sign.stories.tsx | 37 +++++++ .../sign-typed-data-v3-or-v4.stories.tsx | 52 ++++++++++ .../signatures/sign-typed-data.stories.tsx | 25 +++++ .../confirm/stories/signatures/utils.tsx | 47 +++++++++ .../confirmation-network-switch.stories.js | 2 +- 14 files changed, 188 insertions(+), 120 deletions(-) delete mode 100644 ui/pages/confirmations/confirm/confirm.stories.tsx create mode 100644 ui/pages/confirmations/confirm/stories/signatures/personal-sign.stories.tsx create mode 100644 ui/pages/confirmations/confirm/stories/signatures/sign-typed-data-v3-or-v4.stories.tsx create mode 100644 ui/pages/confirmations/confirm/stories/signatures/sign-typed-data.stories.tsx create mode 100644 ui/pages/confirmations/confirm/stories/signatures/utils.tsx diff --git a/test/data/confirmations/personal_sign.ts b/test/data/confirmations/personal_sign.ts index c6f7907eccc4..bac5b3f9838a 100644 --- a/test/data/confirmations/personal_sign.ts +++ b/test/data/confirmations/personal_sign.ts @@ -1,3 +1,5 @@ +import { SignatureRequestType } from '../../../ui/pages/confirmations/types/confirm'; + export const PERSONAL_SIGN_SENDER_ADDRESS = '0x8eeee1781fd885ff5ddef7789486676961873d12'; @@ -13,7 +15,7 @@ export const unapprovedPersonalSignMsg = { origin: 'https://metamask.github.io', siwe: { isSIWEMessage: false, parsedMessage: null }, }, -}; +} as SignatureRequestType; export const signatureRequestSIWE = { id: '210ca3b0-1ccb-11ef-b096-89c4d726ebb5', @@ -45,7 +47,7 @@ export const signatureRequestSIWE = { }, }, }, -}; +} as SignatureRequestType; export const SignatureRequestSIWEWithResources = { id: '210ca3b0-1ccb-11ef-b096-89c4d726ebb5', @@ -83,4 +85,4 @@ export const SignatureRequestSIWEWithResources = { }, }, }, -}; +} as SignatureRequestType; diff --git a/test/data/confirmations/typed_sign.ts b/test/data/confirmations/typed_sign.ts index e368d74234f1..61e6fa68713d 100644 --- a/test/data/confirmations/typed_sign.ts +++ b/test/data/confirmations/typed_sign.ts @@ -1,3 +1,5 @@ +import { SignatureRequestType } from '../../../ui/pages/confirmations/types/confirm'; + export const unapprovedTypedSignMsgV1 = { id: '82ab2400-e2c6-11ee-9627-73cc88f00492', securityAlertResponse: { @@ -19,7 +21,7 @@ export const unapprovedTypedSignMsgV1 = { version: 'V1', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; const rawMessageV3 = { types: { @@ -71,7 +73,7 @@ export const unapprovedTypedSignMsgV3 = { signatureMethod: 'eth_signTypedData_v3', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; export const rawMessageV4 = { domain: { @@ -132,7 +134,7 @@ export const unapprovedTypedSignMsgV4 = { data: JSON.stringify(rawMessageV4), origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; export const permitSignatureMsg = { id: '0b1787a0-1c44-11ef-b70d-e7064bd7b659', @@ -151,4 +153,4 @@ export const permitSignatureMsg = { signatureMethod: 'eth_signTypedData_v4', origin: 'https://metamask.github.io', }, -}; +} as SignatureRequestType; diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.stories.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.stories.tsx index b1eaa95424d2..6d8f83a02658 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.stories.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.stories.tsx @@ -18,7 +18,7 @@ const store = configureStore({ }); const Story = { - title: 'Components/App/Confirm/info/TypedSignInfoV1', + title: 'Components/App/Confirm/Info/TypedSignInfoV1', component: TypedSignInfoV1, decorators: [ (story: () => any) => {story()}, diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.stories.tsx b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.stories.tsx index 94f9487c510f..b6a7fb5183a1 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.stories.tsx +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.stories.tsx @@ -31,7 +31,7 @@ export const DefaultStory = ({ variant, data }) => ( DefaultStory.storyName = 'Default'; DefaultStory.args = { - data: unapprovedTypedSignMsgV1.msgParams.data, + data: unapprovedTypedSignMsgV1.msgParams?.data, }; export default ConfirmInfoRowTypedSignDataStory; diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.test.tsx b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.test.tsx index 293df91810bd..9563b5523f39 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.test.tsx +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data-v1/typedSignDataV1.test.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { render } from '@testing-library/react'; - import { unapprovedTypedSignMsgV1 } from '../../../../../../../test/data/confirmations/typed_sign'; +import { TypedSignDataV1Type } from '../../../../types/confirm'; import { ConfirmInfoRowTypedSignDataV1 } from './typedSignDataV1'; describe('ConfirmInfoRowTypedSignData', () => { it('should match snapshot', () => { const { container } = render( , ); expect(container).toMatchSnapshot(); diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.stories.tsx b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.stories.tsx index 4db4f6c38fbc..1e7977ee6ab9 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.stories.tsx +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.stories.tsx @@ -28,7 +28,7 @@ export const DefaultStory = ({ variant, data }) => ( DefaultStory.storyName = 'Default'; DefaultStory.args = { - data: unapprovedTypedSignMsgV4.msgParams.data, + data: unapprovedTypedSignMsgV4.msgParams?.data, }; export default ConfirmInfoRowTypedSignDataStory; diff --git a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.test.tsx b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.test.tsx index fbe31564d19e..436edbecd3e0 100644 --- a/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.test.tsx +++ b/ui/pages/confirmations/components/confirm/row/typed-sign-data/typedSignData.test.tsx @@ -10,7 +10,7 @@ import { ConfirmInfoRowTypedSignData } from './typedSignData'; describe('ConfirmInfoRowTypedSignData', () => { const renderWithComponentData = ( - data: string = unapprovedTypedSignMsgV4.msgParams.data, + data = unapprovedTypedSignMsgV4.msgParams?.data as string, ) => { const store = configureStore(mockState); @@ -22,7 +22,7 @@ describe('ConfirmInfoRowTypedSignData', () => { it('should match snapshot', () => { const { container } = renderWithComponentData( - unapprovedTypedSignMsgV4.msgParams.data, + unapprovedTypedSignMsgV4.msgParams?.data as string, ); expect(container).toMatchSnapshot(); }); @@ -35,16 +35,15 @@ describe('ConfirmInfoRowTypedSignData', () => { it('should not render data whose type is not defined', () => { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any - (rawMessageV4.message as any).do_not_display = 'one'; - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (rawMessageV4.message as any).do_not_display_2 = { + const mockRawMessageV4 = { ...rawMessageV4 } as any; + + mockRawMessageV4.message.do_not_display = 'one'; + mockRawMessageV4.message.do_not_display_2 = { do_not_display: 'two', }; - unapprovedTypedSignMsgV4.msgParams.data = JSON.stringify(rawMessageV4); - const { queryByText } = renderWithComponentData( - unapprovedTypedSignMsgV4.msgParams.data, - ); + + const mockV4MsgParamsData = JSON.stringify(mockRawMessageV4); + const { queryByText } = renderWithComponentData(mockV4MsgParamsData); expect(queryByText('do_not_display')).not.toBeInTheDocument(); expect(queryByText('one')).not.toBeInTheDocument(); diff --git a/ui/pages/confirmations/components/confirm/scroll-to-bottom/scroll-to-bottom.stories.tsx b/ui/pages/confirmations/components/confirm/scroll-to-bottom/scroll-to-bottom.stories.tsx index 86e4a3fcf917..44da5f9d5044 100644 --- a/ui/pages/confirmations/components/confirm/scroll-to-bottom/scroll-to-bottom.stories.tsx +++ b/ui/pages/confirmations/components/confirm/scroll-to-bottom/scroll-to-bottom.stories.tsx @@ -30,7 +30,7 @@ const Story = { export const DefaultStory = (args) => { return ( -
{args.children}
+
{args.children}
); }; diff --git a/ui/pages/confirmations/confirm/confirm.stories.tsx b/ui/pages/confirmations/confirm/confirm.stories.tsx deleted file mode 100644 index 3e428eba997f..000000000000 --- a/ui/pages/confirmations/confirm/confirm.stories.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import { cloneDeep } from 'lodash'; -import { unapprovedPersonalSignMsg, signatureRequestSIWE } from '../../../../test/data/confirmations/personal_sign'; -import { unapprovedTypedSignMsgV1, unapprovedTypedSignMsgV4, permitSignatureMsg } from '../../../../test/data/confirmations/typed_sign'; -import mockState from '../../../../test/data/mock-state.json'; -import configureStore from '../../../store/store'; -import ConfirmPage from './confirm'; - -/** - * @note When we extend this storybook page to support more confirmation types, - * consider creating a new storybook pages. - */ -const ConfirmPageStory = { - title: 'Pages/Confirm/ConfirmPage', - decorators: [(story) =>
{story()}
], -} - -const ARGS_SIGNATURE = { - msgParams: { ...unapprovedPersonalSignMsg.msgParams }, -} - -const ARG_TYPES_SIGNATURE = { - msgParams: { - control: 'object', - description: '(non-param) overrides currentConfirmation.msgParams', - }, -} - -function SignatureStoryTemplate(args, confirmation) { - const mockConfirmation = cloneDeep(confirmation); - mockConfirmation.msgParams = args.msgParams; - - const store = configureStore({ - confirm: { - currentConfirmation: mockConfirmation, - }, - metamask: { ...mockState.metamask }, - }); - - return ; -} - -export const PersonalSignStory = (args) => { - return SignatureStoryTemplate(args, unapprovedPersonalSignMsg); -}; - -PersonalSignStory.storyName = 'Personal Sign'; -PersonalSignStory.argTypes = ARG_TYPES_SIGNATURE; -PersonalSignStory.args = ARGS_SIGNATURE; - -export const SignInWithEthereumSIWEStory = (args) => { - return SignatureStoryTemplate(args, signatureRequestSIWE); -}; - -SignInWithEthereumSIWEStory.storyName = 'Sign-in With Ethereum (SIWE)'; -SignInWithEthereumSIWEStory.argTypes = ARG_TYPES_SIGNATURE; -SignInWithEthereumSIWEStory.args = { - ...ARGS_SIGNATURE, - msgParams: signatureRequestSIWE.msgParams, -}; - -export const SignTypedDataStory = (args) => { - return SignatureStoryTemplate(args, unapprovedTypedSignMsgV1); -}; - -SignTypedDataStory.storyName = 'SignTypedData'; -SignTypedDataStory.argTypes = ARG_TYPES_SIGNATURE; -SignTypedDataStory.args = { - ...ARGS_SIGNATURE, - msgParams: unapprovedTypedSignMsgV1.msgParams, -}; - -export const PermitStory = (args) => { - return SignatureStoryTemplate(args, permitSignatureMsg); -}; - -PermitStory.storyName = 'SignTypedData Permit'; -PermitStory.argTypes = ARG_TYPES_SIGNATURE; -PermitStory.args = { - ...ARGS_SIGNATURE, - msgParams: permitSignatureMsg.msgParams, -}; - -export const SignTypedDataV4Story = (args) => { - return SignatureStoryTemplate(args, unapprovedTypedSignMsgV4); -}; - -SignTypedDataV4Story.storyName = 'SignTypedData V4'; -SignTypedDataV4Story.argTypes = ARG_TYPES_SIGNATURE; -SignTypedDataV4Story.args = { - ...ARGS_SIGNATURE, - msgParams: unapprovedTypedSignMsgV4.msgParams, -}; - -export default ConfirmPageStory; diff --git a/ui/pages/confirmations/confirm/stories/signatures/personal-sign.stories.tsx b/ui/pages/confirmations/confirm/stories/signatures/personal-sign.stories.tsx new file mode 100644 index 000000000000..c43a54c5baee --- /dev/null +++ b/ui/pages/confirmations/confirm/stories/signatures/personal-sign.stories.tsx @@ -0,0 +1,37 @@ +import ConfirmPage from '../../confirm' +import { + ARG_TYPES_SIGNATURE, + CONFIRM_PAGE_DECORATOR, + SignatureStoryTemplate +} from './utils'; +import { signatureRequestSIWE, unapprovedPersonalSignMsg } from '../../../../../../test/data/confirmations/personal_sign'; + +/** + * The `` that's displayed when the current confirmation is a `personal_sign` signature. + */ +export default { + title: 'Pages/Confirmations/Confirm/Signatures/PersonalSign', + component: ConfirmPage, + decorators: CONFIRM_PAGE_DECORATOR, + argTypes: ARG_TYPES_SIGNATURE, +}; + +export const DefaultStory = (args) => { + return SignatureStoryTemplate(args, unapprovedPersonalSignMsg); +} +DefaultStory.storyName = 'Default'; +DefaultStory.args = { + msgParams: { ...unapprovedPersonalSignMsg.msgParams }, +}; + +/** + * The `` that's displayed when the current confirmation is a `personal_sign` signature + * that parses as a valid Sign-in-With-Ethereum (SIWE)(EIP-4361) signature. + */ +export const SignInWithEthereumStory = (args) => { + return SignatureStoryTemplate(args, signatureRequestSIWE); +} +SignInWithEthereumStory.storyName = 'Sign-in With Ethereum (SIWE)'; +SignInWithEthereumStory.args = { + msgParams: { ...signatureRequestSIWE.msgParams }, +}; \ No newline at end of file diff --git a/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data-v3-or-v4.stories.tsx b/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data-v3-or-v4.stories.tsx new file mode 100644 index 000000000000..1c8fe17d9c46 --- /dev/null +++ b/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data-v3-or-v4.stories.tsx @@ -0,0 +1,52 @@ +import ConfirmPage from '../../confirm' +import { + ARG_TYPES_SIGNATURE, + CONFIRM_PAGE_DECORATOR, + SignatureStoryTemplate +} from './utils'; +import { permitSignatureMsg, unapprovedTypedSignMsgV3, unapprovedTypedSignMsgV4 } from '../../../../../../test/data/confirmations/typed_sign'; + +/** + * The `` that's displayed when the current confirmation is either a version + * "V3" or "V4" `eth_signTypedData` signature. The default example is version "V4". + */ +export default { + title: 'Pages/Confirmations/Confirm/Signatures/SignedTypedDataV3orV4', + component: ConfirmPage, + decorators: CONFIRM_PAGE_DECORATOR, + argTypes: ARG_TYPES_SIGNATURE, +}; + +/** + * The `` that's displayed when the current confirmation is either a + * "V3" or "V4" `eth_signTypedData` signature that parses as a valid permit signature. + */ +export const PermitStory = (args) => { + return SignatureStoryTemplate(args, permitSignatureMsg); +}; +PermitStory.storyName = 'Permit'; +PermitStory.args = { + msgParams: { ...permitSignatureMsg.msgParams }, +}; + +/** + * The `` that's displayed when the current confirmation is a "V3" `eth_signTypedData` signature. + */ +export const V3Story = (args) => { + return SignatureStoryTemplate(args, unapprovedTypedSignMsgV4); +} +V3Story.storyName = 'V3'; +V3Story.args = { + msgParams: { ...unapprovedTypedSignMsgV3.msgParams }, +}; + +/** + * The `` that's displayed when the current confirmation is a "V4" `eth_signTypedData` signature. + */ +export const DefaultStory = (args) => { + return SignatureStoryTemplate(args, unapprovedTypedSignMsgV4); +} +DefaultStory.storyName = 'V4'; +DefaultStory.args = { + msgParams: { ...unapprovedTypedSignMsgV4.msgParams }, +}; diff --git a/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data.stories.tsx b/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data.stories.tsx new file mode 100644 index 000000000000..3268fcde5a8b --- /dev/null +++ b/ui/pages/confirmations/confirm/stories/signatures/sign-typed-data.stories.tsx @@ -0,0 +1,25 @@ +import ConfirmPage from '../../confirm' +import { + ARG_TYPES_SIGNATURE, + CONFIRM_PAGE_DECORATOR, + SignatureStoryTemplate +} from './utils'; +import { unapprovedTypedSignMsgV1 } from '../../../../../../test/data/confirmations/typed_sign'; + +/** + * The `` that's displayed when the current confirmation is a version "V1" `eth_signTypedData` signature. + */ +export default { + title: 'Pages/Confirmations/Confirm/Signatures/SignTypedDataV1', + component: ConfirmPage, + decorators: CONFIRM_PAGE_DECORATOR, + argTypes: ARG_TYPES_SIGNATURE, + args: { + msgParams: { ...unapprovedTypedSignMsgV1.msgParams }, + }, +}; + +export const DefaultStory = (args) => { + return SignatureStoryTemplate(args, unapprovedTypedSignMsgV1); +} +DefaultStory.storyName = 'Default'; \ No newline at end of file diff --git a/ui/pages/confirmations/confirm/stories/signatures/utils.tsx b/ui/pages/confirmations/confirm/stories/signatures/utils.tsx new file mode 100644 index 000000000000..918f30820abb --- /dev/null +++ b/ui/pages/confirmations/confirm/stories/signatures/utils.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route } from 'react-router-dom'; +import { cloneDeep } from 'lodash'; +import mockState from '../../../../../../test/data/mock-state.json'; +import configureStore from '../../../../../store/store'; +import ConfirmPage from '../../confirm'; +import { SignatureRequestType } from '../../../types/confirm'; + +export const ARG_TYPES_SIGNATURE = { + msgParams: { + control: 'object', + description: '(non-param) overrides currentConfirmation.msgParams', + }, +}; + +export const CONFIRM_PAGE_DECORATOR = [ + (story: () => React.ReactFragment) => { + return
{story()}
; + }, +]; + +export function SignatureStoryTemplate( + args: { msgParams: SignatureRequestType['msgParams'] }, + confirmation: SignatureRequestType, +): JSX.Element { + const mockConfirmation = cloneDeep(confirmation) as SignatureRequestType; + mockConfirmation.msgParams = args.msgParams; + + const store = configureStore({ + confirm: { + currentConfirmation: mockConfirmation, + }, + metamask: { ...mockState.metamask }, + }); + + return ( + + {/* Adding the MemoryRouter and Route is a workaround to bypass a 404 error in storybook that + is caused when the 'ui/pages/confirmations/hooks/syncConfirmPath.ts' hook calls + history.replace. To avoid history.replace, we can provide a param id. */} + + } /> + + + ); +} diff --git a/ui/pages/confirmations/confirmation/components/confirmation-network-switch/confirmation-network-switch.stories.js b/ui/pages/confirmations/confirmation/components/confirmation-network-switch/confirmation-network-switch.stories.js index a9975ad71f18..372b715f7fb5 100644 --- a/ui/pages/confirmations/confirmation/components/confirmation-network-switch/confirmation-network-switch.stories.js +++ b/ui/pages/confirmations/confirmation/components/confirmation-network-switch/confirmation-network-switch.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import ConfirmationNetworkSwitch from '.'; export default { - title: 'Pages/Confirmation/Components/ConfirmationNetworkSwitch', + title: 'Pages/Confirmations/Components/ConfirmationNetworkSwitch', argTypes: { toNetwork: { controls: 'object',