Skip to content

Commit

Permalink
✨ TopAppBarMenu - add custom menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Castagliola committed Feb 26, 2024
1 parent cd51b8f commit 8a4461b
Show file tree
Hide file tree
Showing 15 changed files with 595 additions and 128 deletions.
57 changes: 52 additions & 5 deletions Storybook/components/TopAppBar/TopAppBar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import type { Meta, StoryObj } from '@storybook/react-native';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Headline, TopAppBar } from 'smartway-react-native-ui';
import { Pressable, StyleSheet, Text, View } from 'react-native';

Check failure on line 4 in Storybook/components/TopAppBar/TopAppBar.stories.tsx

View workflow job for this annotation

GitHub Actions / unittests / unittests

'Pressable' is defined but never used

Check failure on line 4 in Storybook/components/TopAppBar/TopAppBar.stories.tsx

View workflow job for this annotation

GitHub Actions / unittests / unittests

'Text' is defined but never used
import {
Headline,
TopAppBar,
} from '../../../src/components/topAppBar/TopAppBar';

import TopAppBarMenuItem from '../../../src/components/topAppBar/TopAppBarMenuItem';

import type { Title } from 'src/components/topAppBar/TopAppBar';

const asString = { value: 'menu' };
const asButton = { value: 'menu', onPress: () => {} };
const asComponent = { value: <Headline size="h1">Headline H1</Headline> };
const asComponent = { value: <Headline size='h1'>Headline H1</Headline> };

type ComponentProps = React.ComponentProps<typeof TopAppBar> & {
withBackButton?: boolean;
Expand All @@ -28,10 +34,14 @@ export default {
control: { type: 'radio' },
options: ['small', 'medium', 'large', 'center-aligned'],
},
withTitleAs: { control: { type: 'radio' }, options: ['string', 'button', 'component'] },
withTitleAs: {
control: { type: 'radio' },
options: ['string', 'button', 'component'],
},
withBackButton: { type: 'boolean' },
onBack: { action: 'onBack' },
onPressIcon: { action: 'onPressIcon' },
onMenuItemPress: { action: 'onMenuItemPress' },
},

decorators: [
Expand Down Expand Up @@ -60,9 +70,46 @@ export const Default: Story = {
size={args.size}
onBack={args.withBackButton ? args.onBack : undefined}
title={titleComponent}
icon={{ name: 'dots-vertical', onPress: args.onPressIcon }}
/>
);
},
};
export const WithMenu = (args) => {
let titleComponent: Title = asString;
if (args.withTitleAs === 'button') titleComponent = asButton;
if (args.withTitleAs === 'component') titleComponent = asComponent;

const handlePress = (menuItem, hideMenu) => () => {
hideMenu();
args.onMenuItemPress(menuItem);
};

const menu = {
items: [
{
title: 'Ne plus surveiller',
id: 'noLongerMonitor',
},
],
renderItem: (menuItem, { hideMenu }) => {
return (
<TopAppBarMenuItem
id={menuItem.id}
title={menuItem.title}
onPress={handlePress(menuItem, hideMenu)}
/>
);
},
};

return (
<TopAppBar
size={args.size}
onBack={args.withBackButton ? args.onBack : undefined}
title={titleComponent}
menu={menu}
/>
);
};

Default.parameters = { noSafeArea: false };
8 changes: 4 additions & 4 deletions Storybook/package-lock.json

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

2 changes: 1 addition & 1 deletion Storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@storybook/testing-library": "^0.0.13",
"@tsconfig/react-native": "^2.0.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.24",
"@types/react": "^18.2.0",
"@types/react-native": "^0.70.6",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.37.0",
Expand Down
2 changes: 1 addition & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const jestConfig: JestConfigWithTsJest = {
testMatch: ['**/?(*.)test.(ts|tsx)'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
transformIgnorePatterns: [
'node_modules/(?!(@react-native|react-native|react-native-drop-shadow|@gorhom/bottom-sheet|react-native-reanimated)/)',
'node_modules/(?!(@react-native|react-native|react-native-drop-shadow|@gorhom/bottom-sheet|react-native-reanimated|react-native-paper)/)',
],
moduleDirectories: ['node_modules', 'src'],
setupFilesAfterEnv: ['./jest.setup.ts'],
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
"devDependencies": {
"@testing-library/react-native": "^12.4.2",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.45",
"@types/react": "^18.2.0",
"@types/react-native": "^0.73.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"eslint-config-prettier": "^9.1.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "19.0.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.32.2",
Expand Down
157 changes: 157 additions & 0 deletions src/__tests__/components/TopAppBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React from 'react';
import { TopAppBar } from '../../components/topAppBar/TopAppBar';
import {
render,
screen,
setupFakeTimer,
userEvent,
waitForElementToBeRemoved,
} from '../../shared/testUtils';
import { Text } from 'react-native';
import TopAppBarMenuItem from '../../components/topAppBar/TopAppBarMenuItem';

const topBarTitle = 'Menu';

describe('TopAppBar mounting with a simple title', () => {
it('displays a title', () => {
const title = {
value: topBarTitle,
};

render(<TopAppBar title={title} />);

expect(screen.getByText(topBarTitle)).toBeOnTheScreen();
});

it.todo("testing menu title 'as string' on press");
});

describe('TopAppBar mounting with a title by passing a custom component', () => {
it('displays a title', () => {
const title = {
value: <Text>{topBarTitle}</Text>,
};

render(<TopAppBar title={title} />);

expect(screen.getByText(topBarTitle)).toBeOnTheScreen();
});

it.todo("testing menu title 'as component' on press");
});

describe('TopAppBar mounting with a go back button', () => {
it('triggers `goBack` event when user press the go back button', async () => {
setupFakeTimer();

const user = userEvent.setup();

const mockOnGoBack = jest.fn();

const title = {
value: topBarTitle,
};

render(<TopAppBar title={title} onBack={mockOnGoBack} />);

expect(mockOnGoBack).not.toHaveBeenCalled();

await user.press(screen.getByLabelText(/back/i));

expect(mockOnGoBack).toHaveBeenCalled();
});
});

describe('TopAppBar mounting with a menu', () => {
const noLongerMonitorMenuItem = {
id: 'noLongerMonitor' as const,
title: 'Ne plus surveiller',
};
const title = {
value: topBarTitle,
};
let mockOnMenuItemPress: jest.Mock;

beforeEach(() => {
mockOnMenuItemPress = jest.fn();

const handlePress =
(menuItem: typeof noLongerMonitorMenuItem, hideMenu: () => void) =>
() => {
hideMenu();
mockOnMenuItemPress(menuItem);
};

render(
<TopAppBar
title={title}
menu={{
items: [noLongerMonitorMenuItem],
renderItem: (menuItem, { hideMenu }) => {
return (
<TopAppBarMenuItem
id={menuItem.id}
title={menuItem.title}
onPress={handlePress(menuItem, hideMenu)}
/>
);
},
}}
/>,
);
});

it('displays action menu button', () => {
expect(screen.getByLabelText(/menu/i)).toBeOnTheScreen();
});

it('displays menu items when user press the action menu button', async () => {
setupFakeTimer();

const user = userEvent.setup();

expect(
screen.queryByText(noLongerMonitorMenuItem.title),
).not.toBeOnTheScreen();

await user.press(screen.getByLabelText(/menu/i));

expect(
screen.getByText(noLongerMonitorMenuItem.title),
).toBeOnTheScreen();
});

it('retreives menu item data when user press a menu item', async () => {
setupFakeTimer();

const user = userEvent.setup();

await user.press(screen.getByLabelText(/menu/i));

expect(mockOnMenuItemPress).not.toHaveBeenCalled();

await user.press(screen.getByText(noLongerMonitorMenuItem.title));

expect(mockOnMenuItemPress).toHaveBeenCalledWith(
noLongerMonitorMenuItem,
);
});

it('hides menu when user press a menu item', async () => {
setupFakeTimer();

const user = userEvent.setup();

await user.press(screen.getByLabelText(/menu/i));

expect(
screen.getByText(noLongerMonitorMenuItem.title),
).toBeOnTheScreen();

await user.press(screen.getByText(noLongerMonitorMenuItem.title));

await waitForElementToBeRemoved(() =>
screen.queryByText(noLongerMonitorMenuItem.title),
);
});
});
53 changes: 53 additions & 0 deletions src/__tests__/components/TopAppBarMenuItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { ComponentProps } from 'react';
import {
setupFakeTimer,
render,
screen,
userEvent,
} from '../../shared/testUtils';

import TopAppBarMenuItem from '../../components/topAppBar/TopAppBarMenuItem';

describe('TopAppBarMenuItem mounting', () => {
const title = 'Ne plus surveiller';
const id = 'noLongerMonitor';
const onPress = jest.fn();

it('displays a menu item', () => {
render(<TopAppBarMenuItem id={id} onPress={onPress} title={title} />);

expect(screen.getByText(title)).toBeOnTheScreen();
});

it('triggers onPress event when user press the menu item', async () => {
setupFakeTimer();

const user = userEvent.setup();

render(<TopAppBarMenuItem id={id} onPress={onPress} title={title} />);

await user.press(screen.getByText(title));

expect(onPress).toHaveBeenCalledTimes(1);
});
});

describe('TopAppBarMenuItem failing to mount', () => {
const title = 'Ne plus surveiller';
const notExpectedId = '__not_expected_id__' as ComponentProps<
typeof TopAppBarMenuItem
>['id'];
const onPress = jest.fn();

it('throw an error if menu item id is not handled', () => {
expect(() =>
render(
<TopAppBarMenuItem
id={notExpectedId}
onPress={onPress}
title={title}
/>,
),
).toThrowError();
});
});
Loading

0 comments on commit 8a4461b

Please sign in to comment.