Skip to content

Commit

Permalink
fix: files formatted
Browse files Browse the repository at this point in the history
  • Loading branch information
karpolan committed Jul 27, 2023
1 parent 990ec48 commit 848b473
Show file tree
Hide file tree
Showing 27 changed files with 569 additions and 158 deletions.
7 changes: 4 additions & 3 deletions public/img/favicon/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions src/components/AppAlert/AppAlert.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { render, screen } from '@testing-library/react';
import AppAlert from './AppAlert';
import { capitalize, randomText } from '../../utils';
import { AlertProps } from '@mui/material';

const ComponentToTest = AppAlert;

/**
* Tests for <AppAlert/> component
*/
describe('<AppAlert/> component', () => {
it('renders itself', () => {
const testId = randomText(8);
render(<ComponentToTest data-testid={testId} />);
const alert = screen.getByTestId(testId);
expect(alert).toBeDefined();
expect(alert).toHaveAttribute('role', 'alert');
expect(alert).toHaveClass('MuiAlert-root');
});

it('supports .severity property', () => {
const SEVERITIES = ['error', 'info', 'success', 'warning'];
for (const severity of SEVERITIES) {
const testId = randomText(8);
const severity = 'success';
render(
<ComponentToTest
data-testid={testId}
severity={severity}
variant="filled" // Needed to verify exact MUI classes
/>
);
const alert = screen.getByTestId(testId);
expect(alert).toBeDefined();
expect(alert).toHaveClass(`MuiAlert-filled${capitalize(severity)}`);
}
});

it('supports .variant property', () => {
const VARIANTS = ['filled', 'outlined', 'standard'];
for (const variant of VARIANTS) {
const testId = randomText(8);
render(
<ComponentToTest
data-testid={testId}
variant={variant as AlertProps['variant']}
severity="warning" // Needed to verify exact MUI classes
/>
);
const alert = screen.getByTestId(testId);
expect(alert).toBeDefined();
expect(alert).toHaveClass(`MuiAlert-${variant}Warning`);
}
});
});
50 changes: 26 additions & 24 deletions src/components/AppButton/AppButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { FunctionComponent } from 'react';
import { render, screen, within } from '@testing-library/react';
import createCache from '@emotion/cache';
import { AppThemeProvider } from '../../theme';
import { AppThemeProvider, createEmotionCache } from '../../theme';
import AppButton, { AppButtonProps } from './AppButton';
import DefaultIcon from '@mui/icons-material/MoreHoriz';

function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

function createEmotionCache() {
return createCache({ key: 'css', prepend: true });
}
import { randomText, capitalize } from '../../utils';

/**
* AppButton wrapped with Theme Provider
Expand All @@ -30,20 +22,19 @@ const ComponentToTest: FunctionComponent<AppButtonProps> = (props) => (
*/
function testButtonColor(colorName: string, ignoreClassName = false, expectedClassName = colorName) {
it(`supports "${colorName}" color`, () => {
const testId = randomText(8);
let text = `${colorName} button`;
render(
<ComponentToTest
color={colorName}
data-testid={testId}
variant="contained" // Required to get correct CSS class name
>
{text}
</ComponentToTest>
);

let span = screen.getByText(text); // <span> with specific text
expect(span).toBeDefined();

let button = span.closest('button'); // parent <button> element
let button = screen.getByTestId(testId);
expect(button).toBeDefined();
// console.log('button.className:', button?.className);
if (!ignoreClassName) {
Expand All @@ -54,27 +45,38 @@ function testButtonColor(colorName: string, ignoreClassName = false, expectedCla
});
}

describe('AppButton component', () => {
describe('<AppButton/> component', () => {
// beforeEach(() => {});

it('renders itself', () => {
let text = 'sample button';
render(<ComponentToTest>{text}</ComponentToTest>);
let span = screen.getByText(text);
expect(span).toBeDefined();
expect(span).toHaveTextContent(text);
let button = span.closest('button'); // parent <button> element
const testId = randomText(8);
render(<ComponentToTest data-testid={testId}>{text}</ComponentToTest>);
const button = screen.getByTestId(testId);
expect(button).toBeDefined();
expect(button).toHaveAttribute('role', 'button');
expect(button).toHaveAttribute('type', 'button'); // not "submit" or "input" by default
});

it('has .margin style by default', () => {
let text = 'button with default margin';
const testId = randomText(8);
render(<ComponentToTest data-testid={testId}>{text}</ComponentToTest>);
const button = screen.getByTestId(testId);
expect(button).toBeDefined();
expect(button).toHaveStyle('margin: 8px'); // Actually it is theme.spacing(1) value
});

it('supports .className property', () => {
let text = 'button with specific class';
let className = 'someClassName';
render(<ComponentToTest className={className}>{text}</ComponentToTest>);
let span = screen.getByText(text);
expect(span).toBeDefined();
let button = span.closest('button'); // parent <button> element
const testId = randomText(8);
render(
<ComponentToTest data-testid={testId} className={className}>
{text}
</ComponentToTest>
);
const button = screen.getByTestId(testId);
expect(button).toBeDefined();
expect(button).toHaveClass(className);
});
Expand Down
11 changes: 5 additions & 6 deletions src/components/AppButton/AppButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { APP_BUTTON_VARIANT } from '../config';

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

const DEFAULT_SX_VALUES = {
margin: 1, // By default the AppButton has theme.spacing(1) margin on all sides
};

export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' | 'startIcon'> {
color?: string; // Not only 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning',
endIcon?: string | ReactNode;
Expand All @@ -20,10 +24,6 @@ export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' |
underline?: 'none' | 'hover' | 'always'; // Link prop
}

const DEFAULT_SX_VALUES = {
margin: 1, // By default the AppButton has theme.spacing(1) margin on all sides
};

/**
* Application styled Material UI Button with Box around to specify margins using props
* @component AppButton
Expand All @@ -47,7 +47,7 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
endIcon,
label,
startIcon,
sx: propSx,
sx: propSx = DEFAULT_SX_VALUES,
text,
underline = 'none',
variant = APP_BUTTON_VARIANT,
Expand All @@ -70,7 +70,6 @@ const AppButton: FunctionComponent<AppButtonProps> = ({

const colorToRender = isMuiColor ? (propColor as ButtonProps['color']) : 'inherit';
const sxToRender = {
...DEFAULT_SX_VALUES,
...propSx,
...(isMuiColor ? {} : { color: propColor }),
};
Expand Down
63 changes: 63 additions & 0 deletions src/components/AppIcon/AppIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { render, screen } from '@testing-library/react';
import AppIcon, { ICONS } from './AppIcon';
import { APP_ICON_SIZE } from '../config';
import { randomColor, randomText } from '../../utils';

const ComponentToTest = AppIcon;

/**
* Tests for <AppIcon/> component
*/
describe('<AppIcon/> component', () => {
it('renders itself', () => {
const testId = randomText(8);
render(<ComponentToTest data-testid={testId} />);
const svg = screen.getByTestId(testId);
expect(svg).toBeDefined();
expect(svg).toHaveAttribute('data-icon', 'default');
expect(svg).toHaveAttribute('size', String(APP_ICON_SIZE)); // default size
expect(svg).toHaveAttribute('height', String(APP_ICON_SIZE)); // default size when .size is not set
expect(svg).toHaveAttribute('width', String(APP_ICON_SIZE)); // default size when .size is not se
});

it('supports .color property', () => {
const testId = randomText(8);
const color = randomColor(); // Note: 'rgb(255, 128, 0)' format is used by react-icons npm, so tests may fail
render(<ComponentToTest data-testid={testId} color={color} />);
const svg = screen.getByTestId(testId);
expect(svg).toHaveAttribute('data-icon', 'default');
// expect(svg).toHaveAttribute('color', color); // TODO: Looks like MUI Icons exclude .color property from <svg> rendering
expect(svg).toHaveStyle(`color: ${color}`);
expect(svg).toHaveAttribute('fill', 'currentColor'); // .fill must be 'currentColor' when .color property is set
});

it('supports .icon property', () => {
// Verify that all icons are supported
for (const icon of Object.keys(ICONS)) {
const testId = randomText(8);
render(<ComponentToTest data-testid={testId} icon={icon} />);
const svg = screen.getByTestId(testId);
expect(svg).toBeDefined();
expect(svg).toHaveAttribute('data-icon', icon.toLowerCase());
}
});

it('supports .size property', () => {
const testId = randomText(8);
const size = Math.floor(Math.random() * 128) + 1;
render(<ComponentToTest data-testid={testId} size={size} />);
const svg = screen.getByTestId(testId);
expect(svg).toHaveAttribute('size', String(size));
expect(svg).toHaveAttribute('height', String(size));
expect(svg).toHaveAttribute('width', String(size));
});

it('supports .title property', () => {
const testId = randomText(8);
const title = randomText(16);
render(<ComponentToTest data-testid={testId} title={title} />);
const svg = screen.getByTestId(testId);
expect(svg).toBeDefined();
expect(svg).toHaveAttribute('title', title);
});
});
62 changes: 42 additions & 20 deletions src/components/AppIcon/AppIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FunctionComponent } from 'react';
import { SvgIcon } from '@mui/material';
import { ComponentType, FunctionComponent, SVGAttributes } from 'react';
import { APP_ICON_SIZE } from '../config';
// SVG assets
import { ReactComponent as LogoIcon } from './logo.svg';
import LogoIcon from './icons/LogoIcon';
// Material Icons
import DefaultIcon from '@mui/icons-material/MoreHoriz';
import SettingsIcon from '@mui/icons-material/Settings';
Expand All @@ -25,17 +25,13 @@ import NotificationsIcon from '@mui/icons-material/NotificationsOutlined';
* How to use:
* 1. Import all required MUI or other SVG icons into this file.
* 2. Add icons with "unique lowercase names" into ICONS object.
* 3. Use icons everywhere in the App by their names in <AppIcon name="xxx" /> component
* 3. Use icons everywhere in the App by their names in <AppIcon icon="xxx" /> component
* Important: properties of ICONS object MUST be lowercase!
* Note: You can use camelCase or UPPERCASE in the <AppIcon name="someIconByName" /> component
* Note: You can use camelCase or UPPERCASE in the <AppIcon icon="someIconByName" /> component
*/
const ICONS: Record<string, React.ComponentType> = {
export const ICONS: Record<string, ComponentType> = {
default: DefaultIcon,
logo: () => (
<SvgIcon>
<LogoIcon />
</SvgIcon>
),
logo: LogoIcon,
close: CloseIcon,
menu: MenuIcon,
settings: SettingsIcon,
Expand All @@ -54,21 +50,47 @@ const ICONS: Record<string, React.ComponentType> = {
notifications: NotificationsIcon,
};

interface Props {
name?: string; // Icon's name
icon?: string; // Icon's name alternate prop
export interface AppIconProps extends SVGAttributes<SVGElement> {
color?: string;
icon?: string;
size?: string | number;
title?: string;
}

/**
* Renders SVG icon by given Icon name
* @component AppIcon
* @param {string} [props.name] - name of the Icon to render
* @param {string} [props.icon] - name of the Icon to render
* @param {string} [color] - color of the icon as a CSS color value
* @param {string} [icon] - name of the Icon to render
* @param {string} [title] - title/hint to show when the cursor hovers the icon
* @param {string | number} [size] - size of the icon, default is ICON_SIZE
*/
const AppIcon: FunctionComponent<Props> = ({ name, icon, ...restOfProps }) => {
const iconName = (name || icon || 'default').trim().toLowerCase();
const ComponentToRender = ICONS[iconName] || DefaultIcon;
return <ComponentToRender {...restOfProps} />;
const AppIcon: FunctionComponent<AppIconProps> = ({
color,
icon = 'default',
size = APP_ICON_SIZE,
style,
...restOfProps
}) => {
const iconName = (icon || 'default').trim().toLowerCase();

let ComponentToRender = ICONS[iconName];
if (!ComponentToRender) {
console.warn(`AppIcon: icon "${iconName}" is not found!`);
ComponentToRender = DefaultIcon;
}

const propsToRender = {
height: size,
color,
fill: color && 'currentColor',
size,
style: { ...style, color },
width: size,
...restOfProps,
};

return <ComponentToRender data-icon={iconName} {...propsToRender} />;
};

export default AppIcon;
Loading

0 comments on commit 848b473

Please sign in to comment.