Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -49,6 +49,7 @@ const getStories = () => {
return {
"./../../packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarAccount/AvatarAccount.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarFavicon/AvatarFavicon.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarGroup/AvatarGroup.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarGroup/AvatarGroup.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarIcon/AvatarIcon.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarNetwork/AvatarNetwork.stories.tsx"),
"./../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx": require("../../../packages/design-system-react-native/src/components/AvatarToken/AvatarToken.stories.tsx"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,19 @@ export const DEFAULT_AVATARACCOUNT_PROPS: Required<
shape: AvatarBaseShape.Circle,
variant: AvatarAccountVariant.Jazzicon,
};

// Sample account addresses
export const SAMPLE_AVATARACCOUNT_ADDRESSES = [
'0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8',
'0xb9b81f6bd23B953c5257C3b5E2F0c03B07E944eB',
'0x360507dfEC4Bf0c03495f91154A78C672599F308',
'0x50cA820Ff810F7687E7d0aDb23A830e3ac6032C3',
'0x840C9Eb73729E626673714D6E4dA8afc8Ccc90d3',
'0xCA0361BE89B7d47a6233d1875F0727ddeAB23377',
'0xD78CBcA88eCd65c6128511e46a518CDc6c66fC74',
'0xCFc8b1d1031ef2ecce3a98d5D79ce4D75Edb06bA',
'0xDe53fa2E659b6134991bB56F64B691990e5C44E7',
'0x8AceA3A9748294d1B5C25a08EFE37b756AafDFdd',
'0xEC5CE72f2e18B0017C88F7B12d3308119C5Cf129',
'0xeC56Da21c90Af6b50E4Ba5ec252bD97e735290fc',
];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added these so they can be reused for both AvatarAccount, AvatarGroup, and any other components built from AvatarAccount

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type { Meta, StoryObj } from '@storybook/react-native';
import { View } from 'react-native';

import AvatarAccount from './AvatarAccount';
import { DEFAULT_AVATARACCOUNT_PROPS } from './AvatarAccount.constants';
import {
DEFAULT_AVATARACCOUNT_PROPS,
SAMPLE_AVATARACCOUNT_ADDRESSES,
} from './AvatarAccount.constants';
import type { AvatarAccountProps } from './AvatarAccount.types';
import { AvatarSize } from '../../shared/enums';
import { IconName } from '../Icon';
import { AvatarAccountVariant } from './AvatarAccount.types';

const meta: Meta<AvatarAccountProps> = {
Expand All @@ -29,28 +31,16 @@ const meta: Meta<AvatarAccountProps> = {
export default meta;

type Story = StoryObj<AvatarAccountProps>;
const sampleAccountAddresses = [
'0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8',
'0xb9b81f6bd23B953c5257C3b5E2F0c03B07E944eB',
'0x360507dfEC4Bf0c03495f91154A78C672599F308',
'0x50cA820Ff810F7687E7d0aDb23A830e3ac6032C3',
'0x840C9Eb73729E626673714D6E4dA8afc8Ccc90d3',
'0xCA0361BE89B7d47a6233d1875F0727ddeAB23377',
'0xD78CBcA88eCd65c6128511e46a518CDc6c66fC74',
'0xCFc8b1d1031ef2ecce3a98d5D79ce4D75Edb06bA',
'0xDe53fa2E659b6134991bB56F64B691990e5C44E7',
'0x8AceA3A9748294d1B5C25a08EFE37b756AafDFdd',
'0xEC5CE72f2e18B0017C88F7B12d3308119C5Cf129',
'0xeC56Da21c90Af6b50E4Ba5ec252bD97e735290fc',
];
export const Default: Story = {
args: {
size: DEFAULT_AVATARACCOUNT_PROPS.size,
variant: DEFAULT_AVATARACCOUNT_PROPS.variant,
twClassName: '',
},
render: (args) => {
return <AvatarAccount {...args} address={sampleAccountAddresses[0]} />;
return (
<AvatarAccount {...args} address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]} />
);
Comment on lines +41 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

I see the benefits of storing sample data in constants, but we should also be aware of the tradeoffs. No action items—just noting that there are some downsides.

Pros:

  • Reusability across stories, tests, and documentation
  • Single source of truth for sample data
  • Easier maintenance if addresses need to change
  • Can be used for testing

Cons:

  • Adds indirection—readers need to jump between files
  • May obscure the immediate understanding of what's being demonstrated
  • Could lead to overengineering with too many sample datasets

Let's keep these in mind—maybe a mixed approach would help mitigate this:

// Keep minimal examples direct in stories
export const Default: Story = {
  render: () => (
    <AvatarAccount 
      address="0x9Cbf7c41B7787F6c621115010D3B044029FE2Ce8" 
    />
  )
};

// Use constants for complex scenarios or when reuse is valuable
export const MultipleAddresses: Story = {
  render: () => (
    <View style={{ gap: 16 }}>
      {SAMPLE_AVATARACCOUNT_ADDRESSES.slice(0, 3).map((address) => (
        <AvatarAccount key={address} address={address} />
      ))}
    </View>
  )
};

Copy link
Contributor

@georgewrmarshall georgewrmarshall Mar 7, 2025

Choose a reason for hiding this comment

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

You can also do things like this without any type errors where the variant is token but the sample props are for network

export const Default: Story = {
  render: () => (
    <AvatarGroup
      variant={AvatarGroupVariant.Token}
      avatarPropsArr={SAMPLE_AVATARGROUP_AVATARNETWORKPROPSARR}
    />
  ),
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The same thing can be said for our tokens

},
};

Expand All @@ -62,12 +52,12 @@ export const Sizes: Story = {
<AvatarAccount
size={AvatarSize[sizeKey as keyof typeof AvatarSize]}
variant={AvatarAccountVariant.Blockies}
address={sampleAccountAddresses[0]}
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
/>
<AvatarAccount
size={AvatarSize[sizeKey as keyof typeof AvatarSize]}
variant={AvatarAccountVariant.Jazzicon}
address={sampleAccountAddresses[0]}
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
/>
</View>
))}
Expand All @@ -86,7 +76,7 @@ export const Variants: Story = {
variantKey as keyof typeof AvatarAccountVariant
]
}
address={sampleAccountAddresses[0]}
address={SAMPLE_AVATARACCOUNT_ADDRESSES[0]}
/>
))}
</View>
Expand All @@ -96,7 +86,7 @@ export const Variants: Story = {
export const SampleAddresses: Story = {
render: () => (
<View style={{ gap: 16 }}>
{sampleAccountAddresses.map((address) => (
{SAMPLE_AVATARACCOUNT_ADDRESSES.map((address) => (
<View key={address} style={{ flexDirection: 'row', gap: 8 }}>
<AvatarAccount
variant={AvatarAccountVariant.Blockies}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import { render } from '@testing-library/react-native';
import { AvatarAccountSize } from '../../shared/enums';
import AvatarAccount from './AvatarAccount';
import { AvatarAccountVariant } from './AvatarAccount.types';
import { DEFAULT_AVATARACCOUNT_PROPS } from './AvatarAccount.constants';
import {
DEFAULT_AVATARACCOUNT_PROPS,
SAMPLE_AVATARACCOUNT_ADDRESSES,
} from './AvatarAccount.constants';

describe('AvatarAccount', () => {
it('renders Jazzicon by default when no variant is provided', () => {
const address = '0x12345';
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];

const { getByTestId, queryByTestId } = render(
<AvatarAccount address={address} />,
Expand All @@ -20,7 +23,7 @@ describe('AvatarAccount', () => {
});

it('renders Blockies when variant is blockies', () => {
const address = '0xabcdef';
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];

const { getByTestId, queryByTestId } = render(
<AvatarAccount
Expand All @@ -34,7 +37,7 @@ describe('AvatarAccount', () => {
});

it('respects the default size and shape', () => {
const address = '0xlmnop';
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
const { getByTestId } = render(
<AvatarAccount address={address} testID="avatar-account" />,
);
Expand All @@ -49,7 +52,7 @@ describe('AvatarAccount', () => {
});

it('overrides the size if provided', () => {
const address = '0x999999';
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
const { getByTestId } = render(
<AvatarAccount
address={address}
Expand All @@ -71,7 +74,7 @@ describe('AvatarAccount', () => {
});

it('passes additional props to AvatarBase', () => {
const address = '0x67890';
const address = SAMPLE_AVATARACCOUNT_ADDRESSES[0];
const customStyle = { margin: 10 };
const { getByTestId } = render(
<AvatarAccount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,14 @@ export const DEFAULT_AVATARFAVICON_PROPS: Required<
width: '100%',
height: '100%',
};

// Sample Favicon URIs
export const SAMPLE_AVATARFAVICON_URIS = [
'https://metamask.github.io/test-dapp/metamask-fox.svg',
'https://www.coinbase.com/favicon.ico',
'https://www.myetherwallet.com/favicon.ico',
'https://www.blockchain.com/static/favicon.ico',
'https://trezor.io/favicon.ico',
'https://electrum.org/favicon.ico',
'https://cryptologos.cc/logos/ethereum-eth-logo.svg',
];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added these so they can be reused for both AvatarFavicon, AvatarGroup, and any other components built from AvatarFavicon

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Meta, StoryObj } from '@storybook/react-native';
import { ImageSourcePropType, View } from 'react-native';

import AvatarFavicon from './AvatarFavicon';
import { DEFAULT_AVATARFAVICON_PROPS } from './AvatarFavicon.constants';
import {
DEFAULT_AVATARFAVICON_PROPS,
SAMPLE_AVATARFAVICON_URIS,
} from './AvatarFavicon.constants';
import type { AvatarFaviconProps } from './AvatarFavicon.types';
import { AvatarSize } from '../../shared/enums';

Expand All @@ -24,7 +27,7 @@ export default meta;

type Story = StoryObj<AvatarFaviconProps>;
const storyImageSource: ImageSourcePropType = {
uri: 'https://metamask.github.io/test-dapp/metamask-fox.svg',
uri: SAMPLE_AVATARFAVICON_URIS[0],
Copy link
Contributor

Choose a reason for hiding this comment

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

non-blocking: same comment here about storing sample data in constants. I think using a string here is helpful to instantly show what the value should be.

Suggested change
uri: SAMPLE_AVATARFAVICON_URIS[0],
uri: 'https://metamask.github.io/test-dapp/metamask-fox.svg',

};

export const Default: Story = {
Expand All @@ -48,3 +51,18 @@ export const Sizes: Story = {
</View>
),
};

export const SampleFavicons: Story = {
render: () => (
<View style={{ gap: 16 }}>
{SAMPLE_AVATARFAVICON_URIS.map((faviconUri) => (
<AvatarFavicon
src={{
uri: faviconUri,
}}
key={faviconUri}
/>
))}
</View>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('AvatarFavicon Component', () => {

const avatarBase = getByTestId('avatar-base');

expect(avatarBase.props.children.props.children).toStrictEqual(fallback);
expect(avatarBase.props.children[1].props.children).toStrictEqual(fallback);
});

it('updates fallback text on svg error when fallbackText is provided', () => {
Expand All @@ -76,7 +76,7 @@ describe('AvatarFavicon Component', () => {
expect(onSvgErrorMock).toHaveBeenCalledTimes(1);
expect(onSvgErrorMock).toHaveBeenCalledWith(errorEvent);
const avatarBase = getByTestId('avatar-base');
expect(avatarBase.props.children.props.children).toStrictEqual(fallback);
expect(avatarBase.props.children[1].props.children).toStrictEqual(fallback);
});

it('computes backupFallbackText from name when fallbackText is not provided', () => {
Expand All @@ -96,7 +96,7 @@ describe('AvatarFavicon Component', () => {
fireEvent(imageOrSvg, 'onImageError', errorEvent);

const avatarBase = getByTestId('avatar-base');
expect(avatarBase.props.children.props.children).toStrictEqual('E');
expect(avatarBase.props.children[1].props.children).toStrictEqual('E');
});

it('passes additional AvatarBase props correctly', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const AvatarFavicon = ({
shape = DEFAULT_AVATARFAVICON_PROPS.shape,
fallbackText,
fallbackTextProps,
hasBorder,
hasSolidBackgroundColor,
twClassName,
testID,
style,
Expand Down Expand Up @@ -45,6 +47,8 @@ const AvatarFavicon = ({
fallbackText={finalFallbackText}
fallbackTextProps={fallbackTextProps}
twClassName={twClassName}
hasBorder={hasBorder}
hasSolidBackgroundColor={hasSolidBackgroundColor}
testID={testID}
style={style}
>
Expand Down
Loading
Loading