Skip to content

Commit

Permalink
feat: adding avatar favicon to design system react
Browse files Browse the repository at this point in the history
  • Loading branch information
georgewrmarshall committed Feb 4, 2025
1 parent a1d04ad commit cd8adf7
Show file tree
Hide file tree
Showing 7 changed files with 567 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';

import { AvatarFavicon } from './AvatarFavicon';
import { AvatarFaviconSize } from '.';
import README from './README.mdx';

const meta: Meta<typeof AvatarFavicon> = {
title: 'React Components/AvatarFavicon',
component: AvatarFavicon,
parameters: {
docs: {
page: README,
},
},
argTypes: {
name: {
control: 'text',
description:
'Required name of the dapp. Used as alt text for image and first letter is used as fallback if no fallbackText provided',
},
src: {
control: 'text',
description:
'Optional URL for the dapp favicon/logo. When provided, displays the image instead of fallback text',
},
imageProps: {
control: 'object',
description:
'Optional prop to pass to the underlying img element. Useful for overriding the default alt text',
},
size: {
control: 'select',
options: Object.keys(AvatarFaviconSize),
mapping: AvatarFaviconSize,
description:
'Optional prop to control the size of the avatar. Defaults to AvatarFaviconSize.Md',
},
fallbackText: {
control: 'text',
description:
'Optional text to display when no image is provided. If not provided, first letter of name will be used',
},
fallbackTextProps: {
control: 'object',
description:
'Optional props to be passed to the Text component when rendering fallback text. Only used when src is not provided',
},
className: {
control: 'text',
description:
'Optional additional CSS classes to be applied to the component',
},
},
};

export default meta;
type Story = StoryObj<typeof AvatarFavicon>;

export const Default: Story = {
args: {
src: 'https://opensea.io/static/images/favicon/favicon.ico',
name: 'OpenSea',
fallbackText: 'OS',
},
};

export const Src: Story = {
render: () => (
<div className="flex gap-2">
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
src="https://opensea.io/static/images/favicon/favicon.ico"
/>
<AvatarFavicon
name="Uniswap"
fallbackText="UNI"
src="https://app.uniswap.org/favicon.png"
/>
<AvatarFavicon
name="Aave"
fallbackText="AAVE"
src="https://app.aave.com/favicon.ico"
/>
</div>
),
};

export const Name: Story = {
render: () => (
<div className="flex gap-2">
<AvatarFavicon
name="OpenSea"
src="https://opensea.io/static/images/favicon/favicon.ico"
/>
<AvatarFavicon name="Uniswap" src="https://app.uniswap.org/favicon.png" />
<AvatarFavicon name="Aave" />
</div>
),
};

export const FallbackText: Story = {
render: () => (
<div className="flex gap-2">
<AvatarFavicon name="OpenSea" fallbackText="OS" />
<AvatarFavicon name="Uniswap" fallbackText="UNI" />
<AvatarFavicon name="Aave" fallbackText="AAVE" />
</div>
),
};

export const Size: Story = {
render: () => (
<div className="flex gap-2 items-center">
<AvatarFavicon
name="OpenSea"
fallbackText="O"
size={AvatarFaviconSize.Xs}
/>
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Sm}
/>
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Md}
/>
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Lg}
/>
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Xl}
/>
</div>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { render, screen } from '@testing-library/react';
import React from 'react';

import { TextColor } from '..';
import { AvatarFavicon } from './AvatarFavicon';
import { AvatarFaviconSize } from '.';

describe('AvatarFavicon', () => {
it('renders image when src is provided', () => {
render(
<AvatarFavicon src="test-image.jpg" name="OpenSea" fallbackText="OS" />,
);

const img = screen.getByRole('img');
expect(img).toBeInTheDocument();
expect(img).toHaveAttribute('src', 'test-image.jpg');
expect(img).toHaveAttribute('alt', 'OpenSea');
});

it('renders fallbackText when src is not provided', () => {
render(<AvatarFavicon name="OpenSea" fallbackText="OS" />);
expect(screen.getByText('OS')).toBeInTheDocument();
});

it('applies fallbackTextProps to Text component', () => {
render(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
fallbackTextProps={{
color: TextColor.TextAlternative,
className: 'test-class',
'data-testid': 'fallback-text',
}}
/>,
);

const text = screen.getByTestId('fallback-text');
expect(text).toHaveClass('text-alternative', 'test-class');
});

it('applies custom className to root element', () => {
render(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
className="custom-class"
data-testid="avatar"
/>,
);

const avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('custom-class');
});

it('passes through additional image props when src is provided', () => {
render(
<AvatarFavicon
src="test-image.jpg"
name="OpenSea"
fallbackText="OS"
imageProps={{
loading: 'lazy',
}}
/>,
);

screen.debug();

const img = screen.getByRole('img');
expect(img).toHaveAttribute('loading', 'lazy');
});

it('applies size classes correctly', () => {
const { rerender } = render(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Xs}
data-testid="avatar"
/>,
);

let avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-4 w-4');

rerender(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Sm}
data-testid="avatar"
/>,
);
avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-6 w-6');

rerender(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Md}
data-testid="avatar"
/>,
);
avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-8 w-8');

rerender(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Lg}
data-testid="avatar"
/>,
);
avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-10 w-10');

rerender(
<AvatarFavicon
name="OpenSea"
fallbackText="OS"
size={AvatarFaviconSize.Xl}
data-testid="avatar"
/>,
);
avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-12 w-12');
});

it('uses medium size by default', () => {
render(<AvatarFavicon name="OpenSea" data-testid="avatar" />);
const avatar = screen.getByTestId('avatar');
expect(avatar).toHaveClass('h-8 w-8');
});

it('uses name as alt text when fallbackText is not provided', () => {
render(<AvatarFavicon src="test-image.jpg" name="OpenSea" />);

const img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'OpenSea');
});

it('uses first letter of name as fallback text when fallbackText is not provided', () => {
render(<AvatarFavicon name="OpenSea" />);
expect(screen.getByText('O')).toBeInTheDocument();
});

it('prioritizes fallbackText over name for both alt text and fallback display', () => {
const { rerender } = render(
<AvatarFavicon
src="test-image.jpg"
name="OpenSea"
fallbackText="OS"
imageProps={{ alt: 'OS' }}
/>,
);

let img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'OS');

rerender(<AvatarFavicon name="OpenSea" fallbackText="OS" />);

expect(screen.getByText('OS')).toBeInTheDocument();
});
});

describe('text display and alt text logic', () => {
it('uses first letter of name when fallbackText is not provided', () => {
render(<AvatarFavicon name="OpenSea" />);
expect(screen.getByText('O')).toBeInTheDocument();
});

it('uses fallbackText for display when provided', () => {
render(<AvatarFavicon name="OpenSea" fallbackText="OS" />);
expect(screen.getByText('OS')).toBeInTheDocument();
});

it('uses name for alt text when src is provided', () => {
render(<AvatarFavicon name="OpenSea" src="test.jpg" />);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'OpenSea');
});

it('uses name for alt text even when fallbackText is provided', () => {
render(<AvatarFavicon name="OpenSea" fallbackText="OS" src="test.jpg" />);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'OpenSea');
});

it('allows alt text override through imageProps', () => {
render(
<AvatarFavicon
name="OpenSea"
src="test.jpg"
imageProps={{ alt: 'Custom Alt' }}
/>,
);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'Custom Alt');
});

it('uses empty string for display text when name is not provided', () => {
// @ts-expect-error testing invalid props
render(<AvatarFavicon data-testid="avatar" />);
const base = screen.getByTestId('avatar');
expect(base.querySelector('span')).toHaveTextContent('');
});

it('uses default "Token logo" for alt text when name is not provided', () => {
// @ts-expect-error testing invalid props
render(<AvatarFavicon src="test.jpg" />);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('alt', 'Dapp logo');
});
});
Loading

0 comments on commit cd8adf7

Please sign in to comment.