Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(happo): add Happo for screenshot testing #1030

Merged
merged 17 commits into from
Jan 26, 2021
Merged
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
77 changes: 45 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,54 @@ name: CI

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12'
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines
- name: Build packages
run: yarn run build
- name: Run jest
run: yarn run jest -w 4
env:
CI: true
COVERAGE: true
- name: Report code coverage
uses: coverallsapp/github-action@v1.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run lint
run: yarn run lint
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '12'

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn install --frozen-lockfile --ignore-engines

- name: Build packages
run: yarn build

- name: Run jest
run: yarn jest -w 4
env:
CI: true
COVERAGE: true

- name: Report code coverage
uses: coverallsapp/github-action@v1.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Run lint
run: yarn lint

- name: Run happo
run: yarn run happo-ci-github-actions
working-directory: './packages/visx-demo/'
env:
HAPPO_API_KEY: ${{ secrets.HAPPO_API_KEY }}
HAPPO_API_SECRET: ${{ secrets.HAPPO_API_SECRET }}
HAPPO_COMMAND: '../../node_modules/happo.io/build/cli.js'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noting that the happo-ci binary expects this path to be in the same directory, but because this is run in ./packages/visx-demo/ it needs to point to the monorepo root node_modules where yarn installs it.

3 changes: 3 additions & 0 deletions packages/visx-demo/.happo-variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
asyncTimeout: 500,
};
67 changes: 67 additions & 0 deletions packages/visx-demo/.happo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const { RemoteBrowserTarget } = require('happo.io');
const { findPagesDir } = require('next/dist/lib/find-pages-dir');
const getWebpackConfig = require('next/dist/build/webpack-config').default;
const nextConfig = require('./next.config');
const path = require('path');
const { asyncTimeout } = require('./.happo-variables');

const happoTmpDir = './.happo'; // should match .gitignore

module.exports = {
// these are provided via github actions in CI, locally you
// need to provide them yourself
apiKey: process.env.HAPPO_API_KEY,
apiSecret: process.env.HAPPO_API_SECRET,

// ms timeout for async examples (default=200)
asyncTimeout,

// don't use JSDOM for rendering. saves having to mock out
// things like getBoundingClientRect + canvas element methods
prerender: false,

stylesheets: [path.join(__dirname, '/public/static/doc_styles.css')],

// happo snapshots to include
include: 'happo/*.@(ts|tsx)',

targets: {
'chrome-desktop': new RemoteBrowserTarget('chrome', {
viewport: '800x552',
}),
},

// extend next's webpack config so examples can be used directly
// this is largely taken from the happo storybook plugin
customizeWebpackConfig: async config => {
const base = await getWebpackConfig(__dirname, {
config: {
devIndicators: {},
distDir: happoTmpDir,
experimental: { plugins: [] },
future: {},
env: {},
pageExtensions: ['pages.js'],
sassOptions: {}, // we don't have this loader
...nextConfig,
},
rewrites: [],
entrypoints: {},
pagesDir: findPagesDir(process.cwd()),
});
config.plugins = base.plugins;
config.resolve = base.resolve;
config.resolveLoader = base.resolveLoader;
Object.keys(config.resolve.alias).forEach(key => {
if (!config.resolve.alias[key]) {
delete config.resolve.alias[key];
}
});
config.module = base.module;
return config;
},

// happo is unable to resolve some imports if the tmpdir isn't located inside
// the project structure. The default is an OS provided folder, `os.tmpdir()`.
tmpdir: path.join(__dirname, happoTmpDir),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I've seen this happen myself. I'll make a note to change the default tmp folder to something like ./.happo-tmp.

};
54 changes: 54 additions & 0 deletions packages/visx-demo/happo/gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { tiles as examples } from '../src/components/Gallery';
import AxisTile from '../src/components/Gallery/AxisTile';
import XYChartTile from '../src/components/Gallery/XYChartTile';
import { asyncTimeout } from '../.happo-variables.js';

type HappoSnapshot = {
component: string;
variants: {
[key: string]: (
renderInDom: (component: React.ReactElement) => void,
) => React.ReactNode | Promise<unknown>;
};
};

const specialCases = new Set(['@visx/demo-axis', '@visx/demo-xychart']);

// renders an example with a timeout
const renderWithTimeout: (
Example: React.ReactElement,
) => HappoSnapshot['variants'][string] = Example => renderInDom => {
return new Promise(resolve => {
renderInDom(Example);
setTimeout(resolve, asyncTimeout);
});
};

const getComponentName = (Example: typeof examples[0]) =>
Example.packageJson.name || 'missing-name';

const snapshots: HappoSnapshot[] = examples
.filter(Example => !specialCases.has(getComponentName(Example)))
.map(Example => ({
// note: this (reasonably) asserts Examples have unique names
component: getComponentName(Example),
variants: { default: () => <Example.default /> },
}));

export default snapshots.concat([
// needs timeout for animated axes
{
component: '@visx/demo-axis',
variants: {
default: renderWithTimeout(<AxisTile />),
},
},
// needs timeout for animated axes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One option that you might want to consider is to not use an animation if prefers-reduced-motion is set to reduce, and then add prefersReducedMotion: true, to your RemoteBrowserTarget:

'chrome-desktop': new RemoteBrowserTarget('chrome', {
  viewport: '800x552',
  prefersReducedMotion: true,
}),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a cool idea. it'll be a bit of work to re-write those examples because the animations aren't css-based. looks like you can do the media query in JS to detect it, so I can swap out animated components for their static analogs. I may do this in a follow up PR depending on how big the change is.

{
component: '@visx/demo-xychart',
variants: {
default: renderWithTimeout(<XYChartTile />),
},
},
]);
10 changes: 7 additions & 3 deletions packages/visx-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"description": "visx demo",
"repository": "https://github.com/airbnb/visx",
"scripts": {
"dev": "next",
"build": "next build && next export",
"start": "next start",
"deploy": "rm -rf out && yarn build && cd out && touch .nojekyll && git init && git add . && git commit -m \"Deploy commit\" && git remote add origin git@github.com:airbnb/visx.git && git push -f origin master:gh-pages",
"dev": "next",
"happo": "happo",
"happo-ci-github-actions": "happo-ci-github-actions",
"preview": "yarn build && cd ./out && npx serve",
"deploy": "rm -rf out && yarn build && cd out && touch .nojekyll && git init && git add . && git commit -m \"Deploy commit\" && git remote add origin git@github.com:airbnb/visx.git && git push -f origin master:gh-pages"
"start": "next start"
},
"keywords": [
"visx",
Expand Down Expand Up @@ -66,6 +68,7 @@
"@visx/xychart": "1.4.0",
"@visx/zoom": "1.3.0",
"@zeit/next-css": "^1.0.1",
"babel-loader": "8.2.2",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed by happo.io

"classnames": "^2.2.5",
"d3-array": "^1.1.1",
"d3-collection": "^1.0.4",
Expand All @@ -77,6 +80,7 @@
"d3-scale-chromatic": "^1.3.3",
"d3-shape": "^1.0.6",
"d3-time-format": "^2.0.5",
"happo.io": "^6.4.0",
"markdown-loader": "^5.1.0",
"next": "9.5.4",
"nprogress": "^0.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import Annotation, { AnnotationProps, greens } from '../../sandboxes/visx-annotation/Example';
import GalleryTile from '../GalleryTile';

export { default as packageJson } from '../../sandboxes/visx-area/package.json';
export { default as packageJson } from '../../sandboxes/visx-annotation/package.json';

const tileStyles = { background: greens[0] };
const detailsStyles: React.CSSProperties = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SplitLinePath, {
} from '../../sandboxes/visx-shape-splitlinepath/Example';
import GalleryTile from '../GalleryTile';

export { default as packageJson } from '../../sandboxes/visx-area/package.json';
export { default as packageJson } from '../../sandboxes/visx-shape-splitlinepath/package.json';

const tileStyles = { background: backgroundLight };
const detailsStyles = { color: 'white' };
Expand Down
2 changes: 1 addition & 1 deletion packages/visx-demo/src/components/Gallery/TextTile.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import GalleryTile from '../GalleryTile';

export const packageJson = { name: 'visx-text', dependencies: { '@visx/text': 'current' } };
export const packageJson = { name: '@visx/demo-text', dependencies: { '@visx/text': 'current' } };

const tileStyles = { background: 'white', border: '1px solid lightgray', borderRadius: '14px' };
const detailsStyles = { color: '#232323', zIndex: 1 };
Expand Down
2 changes: 1 addition & 1 deletion packages/visx-demo/src/components/Gallery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import exampleToVisxDependencyLookup, {

const tiltOptions = { max: 8, scale: 1 };

const tiles = [
export const tiles = [
CurvesTile,
BarsTile,
DotsTile,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Label, Connector, CircleSubject, LineSubject } from '@visx/annotation';
import { LinePath } from '@visx/shape';

import ExampleControls from './ExampleControls';
import findNearestDatum from './findNearestDatum';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@visx/annotation-example",
"name": "@visx/demo-annotation",
"description": "Standalone visx annotation demo.",
"main": "index.tsx",
"private": true,
Expand Down
4 changes: 3 additions & 1 deletion packages/visx-demo/src/sandboxes/visx-axis/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { curveMonotoneX } from '@visx/curve';
import { scaleUtc, scaleLinear, scaleLog, scaleBand, ScaleInput, coerceNumber } from '@visx/scale';
import { Orientation, SharedAxisProps, AxisScale } from '@visx/axis';
import { AnimatedAxis, AnimatedGridRows, AnimatedGridColumns } from '@visx/react-spring';
import { getSeededRandom } from '@visx/mock-data';
import { LinearGradient } from '@visx/gradient';
import { timeFormat } from 'd3-time-format';

Expand All @@ -12,6 +13,7 @@ const axisColor = '#fff';
const tickLabelColor = '#fff';
export const labelColor = '#340098';
const gridColor = '#6e0fca';
const seededRandom = getSeededRandom(0.5);
const margin = {
top: 40,
right: 150,
Expand Down Expand Up @@ -167,7 +169,7 @@ export default function Example({
('bandwidth' in scale && typeof scale!.bandwidth !== 'undefined'
? scale.bandwidth!() / 2
: 0),
yScale(10 + Math.random() * 90),
yScale(10 + seededRandom() * 90),
])}
yScale={yScale}
curve={curveMonotoneX}
Expand Down
11 changes: 6 additions & 5 deletions packages/visx-demo/src/sandboxes/visx-curve/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ type CurveType = keyof typeof allCurves;

const curveTypes = Object.keys(allCurves);
const lineCount = 5;
const series = new Array(lineCount)
.fill(null)
.map(_ =>
generateDateValue(25).sort((a: DateValue, b: DateValue) => a.date.getTime() - b.date.getTime()),
);
const series = new Array(lineCount).fill(null).map((_, i) =>
// vary each series value deterministically
generateDateValue(25, /* seed= */ i / 72).sort(
(a: DateValue, b: DateValue) => a.date.getTime() - b.date.getTime(),
),
);
const allData = series.reduce((rec, d) => rec.concat(d), []);

// data accessors
Expand Down
2 changes: 1 addition & 1 deletion packages/visx-demo/src/sandboxes/visx-dots/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withToolti
import { voronoi, VoronoiPolygon } from '@visx/voronoi';
import { localPoint } from '@visx/event';

const points: PointsRange[] = genRandomNormalPoints(600).filter((d, i) => i < 600);
const points: PointsRange[] = genRandomNormalPoints(600, /* seed= */ 0.5).filter((_, i) => i < 600);

const x = (d: PointsRange) => d[0];
const y = (d: PointsRange) => d[1];
Expand Down
17 changes: 12 additions & 5 deletions packages/visx-demo/src/sandboxes/visx-drag-i/generateCircles.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { getSeededRandom } from '@visx/mock-data';

export interface Circle {
id: string;
radius: number;
x: number;
y: number;
}

const generateCircles = ({ width, height }: { width: number; height: number }) =>
new Array(width < 360 ? 40 : 185).fill(1).map((d, i) => {
const radius = 25 - Math.random() * 20;
const generateCircles = ({ width, height }: { width: number; height: number }) => {
const radiusRandom = getSeededRandom(0.2);
const xRandom = getSeededRandom(0.3);
const yRandom = getSeededRandom(0.4);

return new Array(width < 360 ? 40 : 185).fill(1).map((d, i) => {
const radius = 25 - radiusRandom() * 20;
return {
id: `${i}`,
radius,
x: Math.round(Math.random() * (width - radius * 2) + radius),
y: Math.round(Math.random() * (height - radius * 2) + radius),
x: Math.round(xRandom() * (width - radius * 2) + radius),
y: Math.round(yRandom() * (height - radius * 2) + radius),
};
});
};

export default generateCircles;
1 change: 1 addition & 0 deletions packages/visx-demo/src/sandboxes/visx-drag-i/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@types/react-dom": "^16",
"@visx/drag": "latest",
"@visx/gradient": "latest",
"@visx/mock-data": "latest",
"@visx/responsive": "latest",
"@visx/scale": "latest",
"react": "^16",
Expand Down
2 changes: 1 addition & 1 deletion packages/visx-demo/src/sandboxes/visx-glyph/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const Glyphs = [
),
];

const data: DateValue[] = genDateValue(Glyphs.length * 2);
const data: DateValue[] = genDateValue(Glyphs.length * 2, 0.91);

// accessors
const date = (d: DateValue) => d.date.valueOf();
Expand Down
Loading