diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index eb91c87495..cedb13e5ab 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { - "branches": 93.51, + "branches": 93.34, "functions": 97.36, "lines": 98.33, - "statements": 98.06 + "statements": 98.07 } diff --git a/packages/snaps-controllers/src/interface/utils.test.tsx b/packages/snaps-controllers/src/interface/utils.test.tsx index 8265cb8e59..c6cdfdf72a 100644 --- a/packages/snaps-controllers/src/interface/utils.test.tsx +++ b/packages/snaps-controllers/src/interface/utils.test.tsx @@ -25,6 +25,7 @@ import { getAssetSelectorStateValue, getDefaultAsset, getJsxInterface, + isStatefulComponent, } from './utils'; import { MOCK_ACCOUNT_ID } from '../test-utils'; @@ -1222,3 +1223,45 @@ describe('getDefaultAsset', () => { ); }); }); + +describe('isStatefulComponent', () => { + it.each([ + , + + + , + + Option 1 + , + , + + + Option 1 + + , + , + , + , + ])('returns true for "%p"', () => { + expect(isStatefulComponent()).toBe(true); + }); + + it('returns false for stateless components', () => { + expect(isStatefulComponent(foo)).toBe(false); + }); + + it('returns false for nested stateful components', () => { + expect( + isStatefulComponent( + + + , + ), + ).toBe(false); + }); +}); diff --git a/packages/snaps-controllers/src/interface/utils.ts b/packages/snaps-controllers/src/interface/utils.ts index 2e5a321ccc..02e0fb7b6a 100644 --- a/packages/snaps-controllers/src/interface/utils.ts +++ b/packages/snaps-controllers/src/interface/utils.ts @@ -40,6 +40,41 @@ import { parseCaipChainId, } from '@metamask/utils'; +/** + * A list of stateful component types. + */ +const STATEFUL_COMPONENT_TYPES = [ + 'Input', + 'Dropdown', + 'RadioGroup', + 'FileInput', + 'Checkbox', + 'Selector', + 'AssetSelector', + 'AddressInput', +] as const; + +/** + * Type for stateful component types. + */ +type StatefulComponentType = (typeof STATEFUL_COMPONENT_TYPES)[number]; + +/** + * Check if a component is a stateful component. + * + * @param component - The component to check. + * @param component.type - The type of the component. + * + * @returns Whether the component is a stateful component. + */ +export function isStatefulComponent(component: { type: string }): component is { + type: StatefulComponentType; +} { + return STATEFUL_COMPONENT_TYPES.includes( + component.type as StatefulComponentType, + ); +} + /** * A function to get the MultichainAssetController state. * @@ -366,18 +401,7 @@ export function constructState( } // Stateful components inside a form - // TODO: This is becoming a bit of a mess, we should consider refactoring this. - if ( - currentForm && - (component.type === 'Input' || - component.type === 'Dropdown' || - component.type === 'RadioGroup' || - component.type === 'FileInput' || - component.type === 'Checkbox' || - component.type === 'Selector' || - component.type === 'AssetSelector' || - component.type === 'AddressInput') - ) { + if (currentForm && isStatefulComponent(component)) { const formState = newState[currentForm.name] as FormState; assertNameIsUnique(formState, component.props.name); formState[component.props.name] = constructInputState( @@ -390,17 +414,7 @@ export function constructState( } // Stateful components outside a form - // TODO: This is becoming a bit of a mess, we should consider refactoring this. - if ( - component.type === 'Input' || - component.type === 'Dropdown' || - component.type === 'RadioGroup' || - component.type === 'FileInput' || - component.type === 'Checkbox' || - component.type === 'Selector' || - component.type === 'AssetSelector' || - component.type === 'AddressInput' - ) { + if (isStatefulComponent(component)) { assertNameIsUnique(newState, component.props.name); newState[component.props.name] = constructInputState( oldState,