Skip to content

Commit

Permalink
Add visual regression tests (mui#1121)
Browse files Browse the repository at this point in the history
* Add visual regression tests

* Make docs run in backgorund on ci for tests

* Fix percy command not found error

* Add cypress verify on install

* Restory cypress binary cache before running

* Add additional yarn install

* Change order of builds

* Fix tsconfig.json error

* Add more visual regression scenarios

* Update config.yml

* Disable random icons in percy

* Make year to be 2019 in tests

* Add more scenarios

* Completely hide Ads in percy
  • Loading branch information
dmtrKovalenko authored Jun 16, 2019
1 parent 705cd17 commit b797147
Show file tree
Hide file tree
Showing 19 changed files with 1,304 additions and 114 deletions.
35 changes: 26 additions & 9 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,30 @@ jobs:
- run: cd lib && yarn test:<<parameters.lib>> -- -- --runInBand
- steps: << parameters.after-tests >>

cypress_tests:
description: Run cypress tests
executor: cypress/browsers-chrome69
steps:
- attach_workspace:
at: .
- restore_cache:
name: Restore Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Additional yarn install
command: yarn install --frozen-lockfile
- run:
name: Install, if no cypress binary found
command: yarn cypress install
- run: yarn workspace docs build
- run:
background: true
name: Run docs in background
command: yarn workspace docs start
- run: npx wait-on http://localhost:3000
- run: yarn percy exec -- cypress run --record

####################
# Workflow
####################
Expand All @@ -90,17 +114,10 @@ workflows:
requires:
- checkout_code

- cypress/run:
- cypress_tests:
name: 'Cypress tests'
requires:
- 'Install deps, lint and build'
executor: cypress/browsers-chrome69
build: 'yarn workspace docs build'
start: 'yarn workspace docs start'
wait-on: 'http://localhost:3000'
record: true
yarn: true
cache-key: yarn-packages-{{ checksum "yarn.lock" }}
- checkout_code

- jest_tests:
name: 'Date-fns jest tests'
Expand Down
1 change: 1 addition & 0 deletions cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"baseUrl": "http://localhost:3000",
"integrationFolder": "e2e/integration",
"supportFile": "e2e/support/index.js",
"pluginsFile": "e2e/plugins/index.js",
"chromeWebSecurity": false,
"projectId": "qow28y"
}
5 changes: 5 additions & 0 deletions docs/_shared/Ad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const useStyles = makeStyles(theme => ({
color: theme.palette.text.primary + '! important',
},
},
'@media only percy': {
'#codefund': {
display: 'none',
},
},
},
}));

Expand Down
5 changes: 3 additions & 2 deletions docs/_shared/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { withUtilsService, UtilsContext } from './UtilsServiceContext';
import { makeStyles, IconButton, Collapse, Tooltip } from '@material-ui/core';

interface Props extends InjectedNotistackProps {
testId: string;
source: { raw: string; relativePath: string; default: React.FC<any> };
}

Expand Down Expand Up @@ -60,7 +61,7 @@ const useStyles = makeStyles(theme => ({
},
}));

function Example({ source, enqueueSnackbar }: Props) {
function Example({ source, testId, enqueueSnackbar }: Props) {
if (!source.default || !source.raw || !source.relativePath) {
throw new Error(
'Missing component or raw component code, you likely forgot to .example to your example extension'
Expand Down Expand Up @@ -116,7 +117,7 @@ function Example({ source, enqueueSnackbar }: Props) {
</div>
</Collapse>

<div className={classes.pickers}>
<div data-test-id={testId} className={classes.pickers}>
<Tooltip title="Show/Hide the source">
<IconButton className={classes.sourceBtn} onClick={() => setExpanded(!expanded)}>
<CodeIcon />
Expand Down
13 changes: 9 additions & 4 deletions docs/_shared/svgIcons/KawaiiIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ const icons = {
};

function getRandomIcon() {
const icon = getRandomItem(Object.keys(icons));
// @ts-ignore
return icons[icon];
if (process.browser && window.Cypress) {
return icons.ghost;
}

const icon = getRandomItem(Object.keys(icons));
return icons[icon as keyof typeof icons];
}

interface KawaiiIconProps extends KawaiiProps {
Expand All @@ -27,8 +31,9 @@ interface KawaiiIconProps extends KawaiiProps {

const KawaiiIcon: React.FunctionComponent<KawaiiIconProps> = ({ icon, size, ...other }) => {
const theme = useTheme();
const dimensionXs = useMediaQuery(theme.breakpoints.down('xs'));
const calculatedSize = size || dimensionXs ? 230 : 320;
const isXs = useMediaQuery(theme.breakpoints.down('xs'));
const calculatedSize = size || isXs ? 230 : 320;

const Component = React.useMemo(() => (icon ? icons[icon] : getRandomIcon()), [icon]);

return (
Expand Down
2 changes: 1 addition & 1 deletion docs/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class Layout extends Component<LayoutProps> {
</Menu>

<Tooltip title="Toggle light/dark theme" enterDelay={300}>
<IconButton color="inherit" onClick={toggleThemeType}>
<IconButton data-testid="toggle-theme-btn" color="inherit" onClick={toggleThemeType}>
<LightbulbOutlineIcon />
</IconButton>
</Tooltip>
Expand Down
72 changes: 15 additions & 57 deletions docs/layout/components/NavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
import React from 'react';
import NavItem from './NavItem';
import PropTypesDoc from '../../prop-types.json';
import { withRouter } from 'next/router';
import { List } from '@material-ui/core';

const navItems = [
{
title: 'Getting Started',
children: [
{ title: 'Installation', href: '/getting-started/installation' },
{ title: 'Usage', href: '/getting-started/usage' },
{ title: 'Parsing dates', href: '/getting-started/parsing' },
],
},
{
title: 'Localization',
children: [
{ title: 'Using date-fns', href: '/localization/date-fns' },
{ title: 'Using moment', href: '/localization/moment' },
{ title: 'Persian Calendar System', href: '/localization/persian' },
],
},
{
title: 'Components Demo',
children: [
{ title: 'Date Picker', href: '/demo/datepicker' },
{ title: 'Time Picker', href: '/demo/timepicker' },
{ title: 'Date & Time Picker', href: '/demo/datetime-picker' },
],
},
{
title: 'Components API',
children: Object.keys(PropTypesDoc)
.filter(component => !['ModalWrapper'].includes(component))
.map(component => ({
title: component,
as: `/api/${component}`,
href: `/api/props?component=${component}`,
})),
},
{
title: 'Guides',
children: [
{ title: 'Form integration', href: '/guides/form-integration' },
{ title: 'CSS overrides', href: '/guides/css-overrides' },
{ title: 'Global format customization', href: '/guides/formats' },
{
title: 'Open pickers programmatically',
href: '/guides/controlling-programmatically',
},
{ title: 'Static picker`s components', href: '/guides/static-components' },
{ title: 'Updating to v3', href: '/guides/upgrading-to-v3' },
],
},
];
import { navItems } from './navigationMap';
import { stringToTestId } from 'utils/helpers';

class NavigationMenu extends React.Component<any> {
mapNavigation(depth: number) {
return ({ title, children, href, as }: any) => {
const { asPath } = this.props.router;
const open =
children && children.length > 0
? children.some((item: any) => item.href === asPath || item.as === asPath)
: false;
const hasChildren = children && children.length > 0;
const open = hasChildren
? children.some((item: any) => item.href === asPath || item.as === asPath)
: false;

return (
<NavItem key={href || title} as={as} title={title} depth={depth} href={href} open={open}>
<NavItem
as={as}
href={href}
open={open}
depth={depth}
title={title}
key={href || title}
data-nav={stringToTestId(title)}
>
{children && children.length > 0 && children.map(this.mapNavigation(depth + 1))}
</NavItem>
);
Expand Down
52 changes: 52 additions & 0 deletions docs/layout/components/navigationMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import PropTypesDoc from '../../prop-types.json';

export const navItems = [
{
title: 'Getting Started',
children: [
{ title: 'Installation', href: '/getting-started/installation' },
{ title: 'Usage', href: '/getting-started/usage' },
{ title: 'Parsing dates', href: '/getting-started/parsing' },
],
},
{
title: 'Localization',
children: [
{ title: 'Using date-fns', href: '/localization/date-fns' },
{ title: 'Using moment', href: '/localization/moment' },
{ title: 'Persian Calendar System', href: '/localization/persian' },
],
},
{
title: 'Components Demo',
children: [
{ title: 'Date Picker', href: '/demo/datepicker' },
{ title: 'Time Picker', href: '/demo/timepicker' },
{ title: 'Date & Time Picker', href: '/demo/datetime-picker' },
],
},
{
title: 'Components API',
children: Object.keys(PropTypesDoc)
.filter(component => !['ModalWrapper'].includes(component))
.map(component => ({
title: component,
as: `/api/${component}`,
href: `/api/props?component=${component}`,
})),
},
{
title: 'Guides',
children: [
{ title: 'Form integration', href: '/guides/form-integration' },
{ title: 'CSS overrides', href: '/guides/css-overrides' },
{ title: 'Global format customization', href: '/guides/formats' },
{
title: 'Open pickers programmatically',
href: '/guides/controlling-programmatically',
},
{ title: 'Static inner components', href: '/guides/static-components' },
{ title: 'Updating to v3', href: '/guides/upgrading-to-v3' },
],
},
] as const;
2 changes: 1 addition & 1 deletion docs/pages/demo/datepicker/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ color and type weight.

#### Basic usage

<Example source={BasicDatePicker} />
<Example testId="datepicker-example" source={BasicDatePicker} />

#### Keyboard Input

Expand Down
2 changes: 1 addition & 1 deletion docs/pages/guides/css-overrides.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ colors/fonts/theme settings as any other material-ui component.

You can customize the material-ui theme, that is passed to `ThemeProvider` and the pickers will leverage your settings.

<Example source={CssTheme} />
<Example testId="css-override" source={CssTheme} />

#### Override example

Expand Down
4 changes: 4 additions & 0 deletions docs/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import dayjs, { Dayjs } from 'dayjs';
import moment, { Moment } from 'moment';
import { DateTime } from 'luxon';

export function stringToTestId(string: string) {
return string.replace(/[&\/\\#,+()$~%.'":*?<>{}\s]/g, '');
}

export function makeJSDateObject(date: Date | Moment | DateTime | Dayjs) {
if (date instanceof dayjs) {
return (date as Dayjs).clone().toDate();
Expand Down
28 changes: 28 additions & 0 deletions e2e/integration/App.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { stringToTestId } from '../../docs/utils/helpers';
import { navItems } from '../../docs/layout/components/navigationMap';

describe('App navigation', () => {
before(() => {
cy.visit('/getting-started/installation');
});

navItems.forEach(navItem => {
context(navItem.title, () => {
beforeEach(() => {
cy.get(`[data-nav=${stringToTestId(navItem.title)}]`).as('nav-group');
cy.get('@nav-group').click();
});

navItem.children &&
navItem.children.forEach((leafItem: any) => {
it(`Opens ${leafItem.title} page`, () => {
cy.get('@nav-group')
.find(`[data-nav=${stringToTestId(leafItem.title)}]`)
.click();

cy.url().should('contain', leafItem.as || leafItem.href);
});
});
});
});
});
Loading

0 comments on commit b797147

Please sign in to comment.