Skip to content

Commit 848b473

Browse files
committed
fix: files formatted
1 parent 990ec48 commit 848b473

27 files changed

+569
-158
lines changed

public/img/favicon/favicon.svg

Lines changed: 4 additions & 3 deletions
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { render, screen } from '@testing-library/react';
2+
import AppAlert from './AppAlert';
3+
import { capitalize, randomText } from '../../utils';
4+
import { AlertProps } from '@mui/material';
5+
6+
const ComponentToTest = AppAlert;
7+
8+
/**
9+
* Tests for <AppAlert/> component
10+
*/
11+
describe('<AppAlert/> component', () => {
12+
it('renders itself', () => {
13+
const testId = randomText(8);
14+
render(<ComponentToTest data-testid={testId} />);
15+
const alert = screen.getByTestId(testId);
16+
expect(alert).toBeDefined();
17+
expect(alert).toHaveAttribute('role', 'alert');
18+
expect(alert).toHaveClass('MuiAlert-root');
19+
});
20+
21+
it('supports .severity property', () => {
22+
const SEVERITIES = ['error', 'info', 'success', 'warning'];
23+
for (const severity of SEVERITIES) {
24+
const testId = randomText(8);
25+
const severity = 'success';
26+
render(
27+
<ComponentToTest
28+
data-testid={testId}
29+
severity={severity}
30+
variant="filled" // Needed to verify exact MUI classes
31+
/>
32+
);
33+
const alert = screen.getByTestId(testId);
34+
expect(alert).toBeDefined();
35+
expect(alert).toHaveClass(`MuiAlert-filled${capitalize(severity)}`);
36+
}
37+
});
38+
39+
it('supports .variant property', () => {
40+
const VARIANTS = ['filled', 'outlined', 'standard'];
41+
for (const variant of VARIANTS) {
42+
const testId = randomText(8);
43+
render(
44+
<ComponentToTest
45+
data-testid={testId}
46+
variant={variant as AlertProps['variant']}
47+
severity="warning" // Needed to verify exact MUI classes
48+
/>
49+
);
50+
const alert = screen.getByTestId(testId);
51+
expect(alert).toBeDefined();
52+
expect(alert).toHaveClass(`MuiAlert-${variant}Warning`);
53+
}
54+
});
55+
});

src/components/AppButton/AppButton.test.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
import { FunctionComponent } from 'react';
22
import { render, screen, within } from '@testing-library/react';
3-
import createCache from '@emotion/cache';
4-
import { AppThemeProvider } from '../../theme';
3+
import { AppThemeProvider, createEmotionCache } from '../../theme';
54
import AppButton, { AppButtonProps } from './AppButton';
65
import DefaultIcon from '@mui/icons-material/MoreHoriz';
7-
8-
function capitalize(str: string) {
9-
return str.charAt(0).toUpperCase() + str.slice(1);
10-
}
11-
12-
function createEmotionCache() {
13-
return createCache({ key: 'css', prepend: true });
14-
}
6+
import { randomText, capitalize } from '../../utils';
157

168
/**
179
* AppButton wrapped with Theme Provider
@@ -30,20 +22,19 @@ const ComponentToTest: FunctionComponent<AppButtonProps> = (props) => (
3022
*/
3123
function testButtonColor(colorName: string, ignoreClassName = false, expectedClassName = colorName) {
3224
it(`supports "${colorName}" color`, () => {
25+
const testId = randomText(8);
3326
let text = `${colorName} button`;
3427
render(
3528
<ComponentToTest
3629
color={colorName}
30+
data-testid={testId}
3731
variant="contained" // Required to get correct CSS class name
3832
>
3933
{text}
4034
</ComponentToTest>
4135
);
4236

43-
let span = screen.getByText(text); // <span> with specific text
44-
expect(span).toBeDefined();
45-
46-
let button = span.closest('button'); // parent <button> element
37+
let button = screen.getByTestId(testId);
4738
expect(button).toBeDefined();
4839
// console.log('button.className:', button?.className);
4940
if (!ignoreClassName) {
@@ -54,27 +45,38 @@ function testButtonColor(colorName: string, ignoreClassName = false, expectedCla
5445
});
5546
}
5647

57-
describe('AppButton component', () => {
48+
describe('<AppButton/> component', () => {
5849
// beforeEach(() => {});
5950

6051
it('renders itself', () => {
6152
let text = 'sample button';
62-
render(<ComponentToTest>{text}</ComponentToTest>);
63-
let span = screen.getByText(text);
64-
expect(span).toBeDefined();
65-
expect(span).toHaveTextContent(text);
66-
let button = span.closest('button'); // parent <button> element
53+
const testId = randomText(8);
54+
render(<ComponentToTest data-testid={testId}>{text}</ComponentToTest>);
55+
const button = screen.getByTestId(testId);
6756
expect(button).toBeDefined();
57+
expect(button).toHaveAttribute('role', 'button');
6858
expect(button).toHaveAttribute('type', 'button'); // not "submit" or "input" by default
6959
});
7060

61+
it('has .margin style by default', () => {
62+
let text = 'button with default margin';
63+
const testId = randomText(8);
64+
render(<ComponentToTest data-testid={testId}>{text}</ComponentToTest>);
65+
const button = screen.getByTestId(testId);
66+
expect(button).toBeDefined();
67+
expect(button).toHaveStyle('margin: 8px'); // Actually it is theme.spacing(1) value
68+
});
69+
7170
it('supports .className property', () => {
7271
let text = 'button with specific class';
7372
let className = 'someClassName';
74-
render(<ComponentToTest className={className}>{text}</ComponentToTest>);
75-
let span = screen.getByText(text);
76-
expect(span).toBeDefined();
77-
let button = span.closest('button'); // parent <button> element
73+
const testId = randomText(8);
74+
render(
75+
<ComponentToTest data-testid={testId} className={className}>
76+
{text}
77+
</ComponentToTest>
78+
);
79+
const button = screen.getByTestId(testId);
7880
expect(button).toBeDefined();
7981
expect(button).toHaveClass(className);
8082
});

src/components/AppButton/AppButton.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { APP_BUTTON_VARIANT } from '../config';
66

77
const MUI_BUTTON_COLORS = ['inherit', 'primary', 'secondary', 'success', 'error', 'info', 'warning'];
88

9+
const DEFAULT_SX_VALUES = {
10+
margin: 1, // By default the AppButton has theme.spacing(1) margin on all sides
11+
};
12+
913
export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' | 'startIcon'> {
1014
color?: string; // Not only 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning',
1115
endIcon?: string | ReactNode;
@@ -20,10 +24,6 @@ export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' |
2024
underline?: 'none' | 'hover' | 'always'; // Link prop
2125
}
2226

23-
const DEFAULT_SX_VALUES = {
24-
margin: 1, // By default the AppButton has theme.spacing(1) margin on all sides
25-
};
26-
2727
/**
2828
* Application styled Material UI Button with Box around to specify margins using props
2929
* @component AppButton
@@ -47,7 +47,7 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
4747
endIcon,
4848
label,
4949
startIcon,
50-
sx: propSx,
50+
sx: propSx = DEFAULT_SX_VALUES,
5151
text,
5252
underline = 'none',
5353
variant = APP_BUTTON_VARIANT,
@@ -70,7 +70,6 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
7070

7171
const colorToRender = isMuiColor ? (propColor as ButtonProps['color']) : 'inherit';
7272
const sxToRender = {
73-
...DEFAULT_SX_VALUES,
7473
...propSx,
7574
...(isMuiColor ? {} : { color: propColor }),
7675
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { render, screen } from '@testing-library/react';
2+
import AppIcon, { ICONS } from './AppIcon';
3+
import { APP_ICON_SIZE } from '../config';
4+
import { randomColor, randomText } from '../../utils';
5+
6+
const ComponentToTest = AppIcon;
7+
8+
/**
9+
* Tests for <AppIcon/> component
10+
*/
11+
describe('<AppIcon/> component', () => {
12+
it('renders itself', () => {
13+
const testId = randomText(8);
14+
render(<ComponentToTest data-testid={testId} />);
15+
const svg = screen.getByTestId(testId);
16+
expect(svg).toBeDefined();
17+
expect(svg).toHaveAttribute('data-icon', 'default');
18+
expect(svg).toHaveAttribute('size', String(APP_ICON_SIZE)); // default size
19+
expect(svg).toHaveAttribute('height', String(APP_ICON_SIZE)); // default size when .size is not set
20+
expect(svg).toHaveAttribute('width', String(APP_ICON_SIZE)); // default size when .size is not se
21+
});
22+
23+
it('supports .color property', () => {
24+
const testId = randomText(8);
25+
const color = randomColor(); // Note: 'rgb(255, 128, 0)' format is used by react-icons npm, so tests may fail
26+
render(<ComponentToTest data-testid={testId} color={color} />);
27+
const svg = screen.getByTestId(testId);
28+
expect(svg).toHaveAttribute('data-icon', 'default');
29+
// expect(svg).toHaveAttribute('color', color); // TODO: Looks like MUI Icons exclude .color property from <svg> rendering
30+
expect(svg).toHaveStyle(`color: ${color}`);
31+
expect(svg).toHaveAttribute('fill', 'currentColor'); // .fill must be 'currentColor' when .color property is set
32+
});
33+
34+
it('supports .icon property', () => {
35+
// Verify that all icons are supported
36+
for (const icon of Object.keys(ICONS)) {
37+
const testId = randomText(8);
38+
render(<ComponentToTest data-testid={testId} icon={icon} />);
39+
const svg = screen.getByTestId(testId);
40+
expect(svg).toBeDefined();
41+
expect(svg).toHaveAttribute('data-icon', icon.toLowerCase());
42+
}
43+
});
44+
45+
it('supports .size property', () => {
46+
const testId = randomText(8);
47+
const size = Math.floor(Math.random() * 128) + 1;
48+
render(<ComponentToTest data-testid={testId} size={size} />);
49+
const svg = screen.getByTestId(testId);
50+
expect(svg).toHaveAttribute('size', String(size));
51+
expect(svg).toHaveAttribute('height', String(size));
52+
expect(svg).toHaveAttribute('width', String(size));
53+
});
54+
55+
it('supports .title property', () => {
56+
const testId = randomText(8);
57+
const title = randomText(16);
58+
render(<ComponentToTest data-testid={testId} title={title} />);
59+
const svg = screen.getByTestId(testId);
60+
expect(svg).toBeDefined();
61+
expect(svg).toHaveAttribute('title', title);
62+
});
63+
});

src/components/AppIcon/AppIcon.tsx

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { FunctionComponent } from 'react';
2-
import { SvgIcon } from '@mui/material';
1+
import { ComponentType, FunctionComponent, SVGAttributes } from 'react';
2+
import { APP_ICON_SIZE } from '../config';
33
// SVG assets
4-
import { ReactComponent as LogoIcon } from './logo.svg';
4+
import LogoIcon from './icons/LogoIcon';
55
// Material Icons
66
import DefaultIcon from '@mui/icons-material/MoreHoriz';
77
import SettingsIcon from '@mui/icons-material/Settings';
@@ -25,17 +25,13 @@ import NotificationsIcon from '@mui/icons-material/NotificationsOutlined';
2525
* How to use:
2626
* 1. Import all required MUI or other SVG icons into this file.
2727
* 2. Add icons with "unique lowercase names" into ICONS object.
28-
* 3. Use icons everywhere in the App by their names in <AppIcon name="xxx" /> component
28+
* 3. Use icons everywhere in the App by their names in <AppIcon icon="xxx" /> component
2929
* Important: properties of ICONS object MUST be lowercase!
30-
* Note: You can use camelCase or UPPERCASE in the <AppIcon name="someIconByName" /> component
30+
* Note: You can use camelCase or UPPERCASE in the <AppIcon icon="someIconByName" /> component
3131
*/
32-
const ICONS: Record<string, React.ComponentType> = {
32+
export const ICONS: Record<string, ComponentType> = {
3333
default: DefaultIcon,
34-
logo: () => (
35-
<SvgIcon>
36-
<LogoIcon />
37-
</SvgIcon>
38-
),
34+
logo: LogoIcon,
3935
close: CloseIcon,
4036
menu: MenuIcon,
4137
settings: SettingsIcon,
@@ -54,21 +50,47 @@ const ICONS: Record<string, React.ComponentType> = {
5450
notifications: NotificationsIcon,
5551
};
5652

57-
interface Props {
58-
name?: string; // Icon's name
59-
icon?: string; // Icon's name alternate prop
53+
export interface AppIconProps extends SVGAttributes<SVGElement> {
54+
color?: string;
55+
icon?: string;
56+
size?: string | number;
57+
title?: string;
6058
}
6159

6260
/**
6361
* Renders SVG icon by given Icon name
6462
* @component AppIcon
65-
* @param {string} [props.name] - name of the Icon to render
66-
* @param {string} [props.icon] - name of the Icon to render
63+
* @param {string} [color] - color of the icon as a CSS color value
64+
* @param {string} [icon] - name of the Icon to render
65+
* @param {string} [title] - title/hint to show when the cursor hovers the icon
66+
* @param {string | number} [size] - size of the icon, default is ICON_SIZE
6767
*/
68-
const AppIcon: FunctionComponent<Props> = ({ name, icon, ...restOfProps }) => {
69-
const iconName = (name || icon || 'default').trim().toLowerCase();
70-
const ComponentToRender = ICONS[iconName] || DefaultIcon;
71-
return <ComponentToRender {...restOfProps} />;
68+
const AppIcon: FunctionComponent<AppIconProps> = ({
69+
color,
70+
icon = 'default',
71+
size = APP_ICON_SIZE,
72+
style,
73+
...restOfProps
74+
}) => {
75+
const iconName = (icon || 'default').trim().toLowerCase();
76+
77+
let ComponentToRender = ICONS[iconName];
78+
if (!ComponentToRender) {
79+
console.warn(`AppIcon: icon "${iconName}" is not found!`);
80+
ComponentToRender = DefaultIcon;
81+
}
82+
83+
const propsToRender = {
84+
height: size,
85+
color,
86+
fill: color && 'currentColor',
87+
size,
88+
style: { ...style, color },
89+
width: size,
90+
...restOfProps,
91+
};
92+
93+
return <ComponentToRender data-icon={iconName} {...propsToRender} />;
7294
};
7395

7496
export default AppIcon;

0 commit comments

Comments
 (0)