Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .storybook/storybook.requires.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 68 additions & 0 deletions app/components/UI/GlobalAlert/GlobalAlert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import { StyleSheet, View } from 'react-native';

import { storybookStore } from '../../../../.storybook/storybook-store';
import GlobalAlert from './index';

const mockStore = configureMockStore();

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});

const createAlertStore = (data: { msg: string; width?: number }) =>
mockStore({
...storybookStore,
alert: {
isVisible: true,
autodismiss: null,
content: 'clipboard-alert',
data,
},
});

const ClipboardAlertStory = ({
msg,
width,
}: {
msg: string;
width?: number;
}) => (
<Provider store={createAlertStore({ msg, width })}>
<View style={styles.container}>
<GlobalAlert />
</View>
</Provider>
);

const GlobalAlertMeta = {
title: 'Components / GlobalAlert',
component: GlobalAlert,
argTypes: {
msg: {
control: { type: 'text' },
defaultValue: 'Address copied to clipboard',
},
width: {
control: { type: 'number' },
defaultValue: 280,
},
},
};

export default GlobalAlertMeta;

export const ClipboardAlert = {
args: {
msg: 'Address copied to clipboard',
width: 280,
},
render: (args: { msg: string; width: number }) => (
<ClipboardAlertStory msg={args.msg} width={args.width} />
),
};
84 changes: 84 additions & 0 deletions app/components/UI/GlobalAlert/GlobalAlert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import { Text } from 'react-native';
import { brandColor, darkTheme } from '@metamask/design-tokens';
import GlobalAlert from './index';
import renderWithProvider from '../../../util/test/renderWithProvider';
import { AppThemeKey } from '../../../util/theme/models';
import { getElevatedSurfaceColor } from '../../../util/theme/themeUtils';

jest.mock('react-native-modal', () => {
const ReactMock = require('react');
const { View } = require('react-native');

return ({ children, isVisible }) =>
isVisible
? ReactMock.createElement(
View,
{ testID: 'global-alert-modal' },
children,
)
: null;
});

jest.mock('react-native-elevated-view', () => {
const ReactMock = require('react');
const { View } = require('react-native');

return ({ children, style }) =>
ReactMock.createElement(
View,
{ testID: 'global-alert-elevated-view', style },
children,
);
});

jest.mock('react-native-vector-icons/FontAwesome', () => {
const ReactMock = require('react');
const { Text: TextMock } = require('react-native');

return ({ color }) =>
ReactMock.createElement(TextMock, { testID: 'global-alert-icon', color });
});

const darkThemeValue = {
colors: darkTheme.colors,
themeAppearance: AppThemeKey.dark,
typography: darkTheme.typography,
shadows: darkTheme.shadows,
brandColors: brandColor,
};

describe('GlobalAlert', () => {
it('uses elevated surface and semantic foreground tokens for clipboard alerts', () => {
const { getByTestId, getByText } = renderWithProvider(<GlobalAlert />, {
theme: darkThemeValue,
state: {
alert: {
isVisible: true,
autodismiss: null,
content: 'clipboard-alert',
data: {
msg: 'Address copied to clipboard',
width: 280,
},
},
},
});

const elevatedView = getByTestId('global-alert-elevated-view');
const icon = getByTestId('global-alert-icon');
const message = getByText('Address copied to clipboard');

expect(elevatedView.props.style).toEqual(
expect.objectContaining({
backgroundColor: getElevatedSurfaceColor(darkThemeValue),
}),
);
expect(icon.props.color).toBe(darkThemeValue.colors.icon.default);
expect(message.props.style).toEqual(
expect.objectContaining({
color: darkThemeValue.colors.text.default,
}),
);
});
});
20 changes: 10 additions & 10 deletions app/components/UI/GlobalAlert/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ import { fontStyles } from '../../../styles/common';
import Icon from 'react-native-vector-icons/FontAwesome';
import ElevatedView from 'react-native-elevated-view';
import { ThemeContext, mockTheme } from '../../../util/theme';
import { getElevatedSurfaceColor } from '../../../util/theme/themeUtils';

const createStyles = (colors) =>
const createStyles = (theme) =>
StyleSheet.create({
modal: {
margin: 0,
width: '100%',
},
copyAlert: (width) => ({
width: width || 180,
backgroundColor: colors.overlay.alternative,
backgroundColor: getElevatedSurfaceColor(theme),
padding: 20,
paddingTop: 30,
alignSelf: 'center',
Expand All @@ -30,7 +31,7 @@ const createStyles = (colors) =>
},
copyAlertText: {
textAlign: 'center',
color: colors.overlay.inverse,
color: theme.colors.text.default,
fontSize: 16,
...fontStyles.normal,
},
Expand Down Expand Up @@ -91,13 +92,13 @@ class GlobalAlert extends PureComponent {
}

getStyles = () => {
const colors = this.context.colors || mockTheme.colors;
return createStyles(colors);
const theme = this.context || mockTheme;
return createStyles(theme);
};

renderClipboardAlert = () => {
const colors = this.context.colors || mockTheme.colors;
const styles = this.getStyles(colors);
const theme = this.context || mockTheme;
const styles = this.getStyles();

return (
<ElevatedView
Expand All @@ -108,7 +109,7 @@ class GlobalAlert extends PureComponent {
<Icon
name={'check-circle'}
size={64}
color={colors.overlay.inverse}
color={theme.colors.icon.default}
/>
</View>
<Text style={styles.copyAlertText}>
Expand All @@ -120,8 +121,7 @@ class GlobalAlert extends PureComponent {

render = () => {
const { content, isVisible } = this.props;
const colors = this.context.colors || mockTheme.colors;
const styles = this.getStyles(colors);
const styles = this.getStyles();

return (
<Modal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useSharedValue } from 'react-native-reanimated';

import SimpleNotification from './index';
import { BaseNotificationStatus } from '../../../../component-library/components-temp/BaseNotification/BaseNotification.types';

const styles = StyleSheet.create({
container: {
flex: 1,
minHeight: 300,
},
});

interface SimpleNotificationStoryArgs {
status: BaseNotificationStatus;
title: string;
description: string;
isInBrowserView: boolean;
}

const SimpleNotificationStory = ({
status,
title,
description,
isInBrowserView,
}: SimpleNotificationStoryArgs) => {
const notificationAnimated = useSharedValue(0);

return (
<View style={styles.container}>
<SimpleNotification
isInBrowserView={isInBrowserView}
notificationAnimated={notificationAnimated}
hideCurrentNotification={() => undefined}
currentNotification={{
status,
title,
description,
}}
/>
</View>
);
};

const SimpleNotificationMeta = {
title: 'Components / SimpleNotification',
component: SimpleNotification,
argTypes: {
status: {
options: ['simple_notification', 'simple_notification_rejected'],
control: { type: 'select' },
defaultValue: 'simple_notification',
},
title: {
control: { type: 'text' },
defaultValue: 'Token added',
},
description: {
control: { type: 'text' },
defaultValue: 'USDC has been added to your wallet.',
},
isInBrowserView: {
control: { type: 'boolean' },
defaultValue: false,
},
},
};

export default SimpleNotificationMeta;

export const Success = {
args: {
status: 'simple_notification',
title: 'Token added',
description: 'USDC has been added to your wallet.',
isInBrowserView: false,
},
render: (args: SimpleNotificationStoryArgs) => (
<SimpleNotificationStory {...args} />
),
};

export const Rejected = {
args: {
status: 'simple_notification_rejected',
title: 'Request rejected',
description: 'The connection request was declined.',
isInBrowserView: false,
},
render: (args: SimpleNotificationStoryArgs) => (
<SimpleNotificationStory {...args} />
),
};

export const InBrowserView = {
args: {
status: 'simple_notification',
title: 'Token added',
description: 'USDC has been added to your wallet.',
isInBrowserView: true,
},
render: (args: SimpleNotificationStoryArgs) => (
<SimpleNotificationStory {...args} />
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { useSharedValue } from 'react-native-reanimated';
import { colors } from '../../../../styles/common';
import SimpleNotification from './index';
import renderWithProvider from '../../../../util/test/renderWithProvider';

jest.mock('react-native-elevated-view', () => {
const ReactMock = require('react');
const { View } = require('react-native');

return ({ children, style }) =>
ReactMock.createElement(
View,
{ testID: 'simple-notification-elevated-view', style },
children,
);
});

const SimpleNotificationHarness = (props) => {
const notificationAnimated = useSharedValue(0);

return (
<SimpleNotification
{...props}
notificationAnimated={notificationAnimated}
/>
);
};

describe('SimpleNotification', () => {
it('renders a simple notification without legacy importedColors', () => {
const { getByTestId, getByText } = renderWithProvider(
<SimpleNotificationHarness
isInBrowserView={false}
hideCurrentNotification={jest.fn()}
currentNotification={{
status: 'simple_notification',
title: 'Token added',
description: 'USDC has been added to your wallet.',
}}
/>,
);

const elevatedView = getByTestId('simple-notification-elevated-view');

expect(elevatedView.props.style).toEqual(
expect.objectContaining({
backgroundColor: colors.transparent,
}),
);
expect(getByText('Token added')).toBeTruthy();
expect(getByText('USDC has been added to your wallet.')).toBeTruthy();
});
});
Loading
Loading