diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 000000000..773e1ad92 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,34 @@ +name: Chromatic + +on: push + +jobs: + chromatic-deployment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v2 + with: + node-version: 16.14.2 + + - name: Cache pnpm modules + uses: actions/cache@v2 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + + - uses: pnpm/action-setup@v2 + with: + version: 6 + run_install: | + - args: [--frozen-lockfile] + + - name: Publish to Chromatic + uses: chromaui/action@v1 + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.gitignore b/.gitignore index f3932ff6f..21cd9bd74 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ cypress/screenshots/ # storybook .storybook.out .cache/ +build-storybook.log # logs yarn-error.log diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 8cbd973d4..113dbc616 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,12 +2,18 @@ import '@atlaskit/css-reset'; import React from 'react'; import { withPerformance } from 'storybook-addon-performance'; +import { resetData } from '../stories/src/data'; import GlobalStyles from './custom-decorators/global-styles'; import welcomeMessage from './welcome-message'; welcomeMessage(); export const decorators = [ + (Story: React.ElementType, { id }: { id: string }) => { + resetData(id); + + return ; + }, (Story: React.ElementType): React.ReactElement => ( diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json index a32dd4f23..06bc9649f 100644 --- a/.storybook/tsconfig.json +++ b/.storybook/tsconfig.json @@ -1,4 +1,10 @@ { "extends": "../tsconfig.json", - "include": ["**/*"] + "include": ["**/*"], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@react-forked/dnd": ["../src/index.ts"] + } + }, } diff --git a/.storybook/typings.d.ts b/.storybook/typings.d.ts index 3623b38e0..14461e25e 100644 --- a/.storybook/typings.d.ts +++ b/.storybook/typings.d.ts @@ -1,3 +1,8 @@ +declare module '*.png' { + const src: string; + export default src; +} + declare module '*.svg' { const src: string; export default src; diff --git a/cypress/integration/reorder.spec.ts b/cypress/integration/reorder.spec.ts index 900c16b74..10a2d1b0d 100644 --- a/cypress/integration/reorder.spec.ts +++ b/cypress/integration/reorder.spec.ts @@ -9,8 +9,8 @@ describe('reorder', () => { it('should reorder within a list', () => { // order: 1, 2 - cy.get(getHandleSelector()).eq(0).as('first').should('contain', 'id:1'); - cy.get(getHandleSelector()).eq(1).should('contain', 'id:2'); + cy.get(getHandleSelector()).eq(0).as('first').should('contain', 'id:G1'); + cy.get(getHandleSelector()).eq(1).should('contain', 'id:G2'); // reorder operation cy.get('@first') @@ -25,11 +25,11 @@ describe('reorder', () => { // order now 2, 1 // note: not using get aliases as they where returning incorrect results - cy.get(getHandleSelector()).eq(0).should('contain', 'id:2'); + cy.get(getHandleSelector()).eq(0).should('contain', 'id:G2'); - cy.get(getHandleSelector()).eq(1).should('contain', 'id:1'); + cy.get(getHandleSelector()).eq(1).should('contain', 'id:G1'); // element should maintain focus post drag - cy.focused().should('contain', 'id:1'); + cy.focused().should('contain', 'id:G1'); }); }); diff --git a/package.json b/package.json index 20a40c14c..d1d4866e9 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "prettier_target": "*.{js,jsx,ts,tsx,md,json} src/**/*.{js,jsx,ts,tsx,md,json} test/**/*.{js,jsx,ts,tsx,md,json} docs/**/*.{js,jsx,ts,tsx,md,json} stories/**/*.{js,jsx,ts,tsx,md,json} cypress/**/*.{js,jsx,ts,tsx,md,json} csp-server/**/*.{js,jsx,ts,tsx,md,json}" }, "scripts": { + "chromatic": "chromatic --project-token=f92123f238de", "prepare": "husky install", "release": "cross-env SKIP_PREPARE_COMMIT_MSG=true release-it", "test:accessibility": "lighthouse http://localhost:9002/iframe.html?id=single-vertical-list--basic --no-enable-error-reporting --config-path=lighthouse.config.js --chrome-flags='--headless' --output=json --output=html --output-path=./test-reports/lighthouse/a11y.json && node a11y-audit-parse.js", @@ -123,6 +124,7 @@ "@types/react-redux": "7.1.23", "@types/react-virtualized": "9.21.20", "@types/react-window": "1.8.5", + "@types/seedrandom": "3.0.2", "@typescript-eslint/eslint-plugin": "5.15.0", "@typescript-eslint/parser": "5.15.0", "@wojtekmaj/enzyme-adapter-react-17": "0.6.6", @@ -181,6 +183,7 @@ "rollup-plugin-dts": "4.2.0", "rollup-plugin-size-snapshot": "0.12.0", "rollup-plugin-terser": "7.0.2", + "seedrandom": "3.0.5", "storybook-addon-performance": "0.16.1", "styled-components": "5.3.3", "stylelint": "14.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2f4d6e19..da75975c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,7 @@ specifiers: '@types/react-redux': 7.1.23 '@types/react-virtualized': 9.21.20 '@types/react-window': 1.8.5 + '@types/seedrandom': 3.0.2 '@typescript-eslint/eslint-plugin': 5.15.0 '@typescript-eslint/parser': 5.15.0 '@wojtekmaj/enzyme-adapter-react-17': 0.6.6 @@ -114,6 +115,7 @@ specifiers: rollup-plugin-dts: 4.2.0 rollup-plugin-size-snapshot: 0.12.0 rollup-plugin-terser: 7.0.2 + seedrandom: 3.0.5 storybook-addon-performance: 0.16.1 styled-components: 5.3.3 stylelint: 14.6.0 @@ -187,6 +189,7 @@ devDependencies: '@types/react-redux': 7.1.23 '@types/react-virtualized': 9.21.20 '@types/react-window': 1.8.5 + '@types/seedrandom': 3.0.2 '@typescript-eslint/eslint-plugin': 5.15.0_f2c49ce7d0e93ebcfdb4b7d25b131b28 '@typescript-eslint/parser': 5.15.0_eslint@8.11.0+typescript@4.6.2 '@wojtekmaj/enzyme-adapter-react-17': 0.6.6_fae758709a8810ba97b4c03852dde4d0 @@ -245,6 +248,7 @@ devDependencies: rollup-plugin-dts: 4.2.0_rollup@2.70.1+typescript@4.6.2 rollup-plugin-size-snapshot: 0.12.0_rollup@2.70.1 rollup-plugin-terser: 7.0.2_rollup@2.70.1 + seedrandom: 3.0.5 storybook-addon-performance: 0.16.1_f456dfc349ace84b0ea11d9e6e4ef3ab styled-components: 5.3.3_react-dom@17.0.2+react@17.0.2 stylelint: 14.6.0 @@ -4514,6 +4518,10 @@ packages: /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/seedrandom/3.0.2: + resolution: {integrity: sha512-YPLqEOo0/X8JU3rdiq+RgUKtQhQtrppE766y7vMTu8dGML7TVtZNiiiaC/hhU9Zqw9UYopXxhuWWENclMVBwKQ==} + dev: true + /@types/serve-static/1.13.10: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: @@ -14849,6 +14857,10 @@ packages: ajv-keywords: 3.5.2_ajv@6.12.6 dev: true + /seedrandom/3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + dev: true + /semver-diff/3.1.1: resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} engines: {node: '>=8'} diff --git a/stories/1-single-vertical-list.stories.tsx b/stories/1-single-vertical-list.stories.tsx index 3322c1241..3dd542c6b 100644 --- a/stories/1-single-vertical-list.stories.tsx +++ b/stories/1-single-vertical-list.stories.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import styled from '@emotion/styled'; import QuoteApp from './src/vertical/quote-app'; -import { quotes, getQuotes } from './src/data'; +import { getQuotes } from './src/data'; import { grid } from './src/constants'; -const data = { - small: quotes, - medium: getQuotes(40), - large: getQuotes(500), +const generateData = { + small: () => getQuotes(), + medium: () => getQuotes(40), + large: () => getQuotes(500), }; const ScrollContainer = styled.div` @@ -27,11 +27,17 @@ const Title = styled.h4` `; storiesOf('single vertical list', module) - .add('basic', () => ) - .add('large data set', () => ) + .add('basic', () => ) + .add('large data set', () => , { + chromatic: { + // This is to make sure we do not reach + // the 25,000,000px limit of the snapshot. + viewports: [320], + }, + }) .add('Droppable is a scroll container', () => ( ( ( List is within a larger scroll container - + )) .add('with combine enabled', () => ( - + )); diff --git a/stories/10-table.stories.tsx b/stories/10-table.stories.tsx index 8885b048d..d035685e4 100644 --- a/stories/10-table.stories.tsx +++ b/stories/10-table.stories.tsx @@ -4,12 +4,14 @@ import WithDimensionLocking from './src/table/with-dimension-locking'; import WithFixedColumns from './src/table/with-fixed-columns'; import WithPortal from './src/table/with-portal'; import WithClone from './src/table/with-clone'; -import { quotes } from './src/data'; +import { getQuotes } from './src/data'; storiesOf('Tables', module) - .add('with fixed width columns', () => ) + .add('with fixed width columns', () => ( + + )) .add('with dimension locking', () => ( - + )) - .add('with clone', () => ) - .add('with custom portal', () => ); + .add('with clone', () => ) + .add('with custom portal', () => ); diff --git a/stories/11-portal.stories.tsx b/stories/11-portal.stories.tsx index ff937a4f2..ed3718715 100644 --- a/stories/11-portal.stories.tsx +++ b/stories/11-portal.stories.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import PortalApp from './src/portal/portal-app'; -import { quotes } from './src/data'; +import { getQuotes } from './src/data'; storiesOf('Portals', module).add('Using your own portal', () => ( - + )); diff --git a/stories/2-single-horizontal.stories.tsx b/stories/2-single-horizontal.stories.tsx index ec24c180f..ee013a559 100644 --- a/stories/2-single-horizontal.stories.tsx +++ b/stories/2-single-horizontal.stories.tsx @@ -2,25 +2,24 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import styled from '@emotion/styled'; import AuthorApp from './src/horizontal/author-app'; -import { quotes, getQuotes } from './src/data'; -import type { Quote } from './src/types'; +import { getQuotes } from './src/data'; -const bigData: Quote[] = getQuotes(30); +const generateBigData = () => getQuotes(30); const WideWindow = styled.div` width: 120vw; `; storiesOf('single horizontal list', module) - .add('simple', () => ) + .add('simple', () => ) .add('with combine enabled', () => ( - + )) .add('with overflow scroll', () => ( - + )) .add('with window scroll and overflow scroll', () => ( - + )); diff --git a/stories/3-board.stories.stories.tsx b/stories/3-board.stories.stories.tsx index ea091cb01..c062f5ff6 100644 --- a/stories/3-board.stories.stories.tsx +++ b/stories/3-board.stories.stories.tsx @@ -4,17 +4,17 @@ import Board from './src/board/board'; import { authorQuoteMap, generateQuoteMap } from './src/data'; const data = { - medium: generateQuoteMap(100), - large: generateQuoteMap(500), + medium: () => generateQuoteMap(100), + large: () => generateQuoteMap(500), }; storiesOf('board', module) .add('simple', () => ) .add('dragging a clone', () => ) - .add('medium data set', () => ) - .add('large data set', () => ) + .add('medium data set', () => ) + .add('large data set', () => ) .add('long lists in a short container', () => ( - + )) .add('scrollable columns', () => ( diff --git a/stories/40-programmatic.stories.tsx b/stories/40-programmatic.stories.tsx index 6e1242369..c3579875a 100644 --- a/stories/40-programmatic.stories.tsx +++ b/stories/40-programmatic.stories.tsx @@ -2,8 +2,10 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import WithControls from './src/programmatic/with-controls'; import Runsheet from './src/programmatic/runsheet'; -import { quotes } from './src/data'; +import { getQuotes } from './src/data'; storiesOf('Programmatic dragging', module) - .add('with controls', () => ) - .add('with runsheet', () => ); + .add('with controls', () => ( + + )) + .add('with runsheet', () => ); diff --git a/stories/45-virtual.stories.tsx b/stories/45-virtual.stories.tsx index fe341f19a..2a2e3430a 100644 --- a/stories/45-virtual.stories.tsx +++ b/stories/45-virtual.stories.tsx @@ -14,6 +14,14 @@ storiesOf('Virtual: react-window', module) storiesOf('Virtual: react-virtualized', module) .add('list', () => ) .add('board', () => ) - .add('window list', () => ( - - )); + .add( + 'window list', + () => , + { + chromatic: { + // This is to make sure we do not reach + // the 25,000,000px limit of the snapshot. + viewports: [320], + }, + }, + ); diff --git a/stories/5-multiple-vertical-lists.stories.tsx b/stories/5-multiple-vertical-lists.stories.tsx index fd5a8658b..96f7d2328 100644 --- a/stories/5-multiple-vertical-lists.stories.tsx +++ b/stories/5-multiple-vertical-lists.stories.tsx @@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react'; import QuoteApp from './src/multiple-vertical/quote-app'; import { getQuotes } from './src/data'; -const quoteMap = { +const generateQuoteMap = () => ({ alpha: getQuotes(7), beta: getQuotes(3), gamma: getQuotes(7), @@ -14,8 +14,8 @@ const quoteMap = { theta: getQuotes(5), iota: getQuotes(20), kappa: getQuotes(5), -}; +}); storiesOf('multiple vertical lists', module).add('stress test', () => ( - + )); diff --git a/stories/6-multiple-horizontal-lists.stories.tsx b/stories/6-multiple-horizontal-lists.stories.tsx index e8654c078..f317b3d0d 100644 --- a/stories/6-multiple-horizontal-lists.stories.tsx +++ b/stories/6-multiple-horizontal-lists.stories.tsx @@ -3,12 +3,12 @@ import { storiesOf } from '@storybook/react'; import QuoteApp from './src/multiple-horizontal/quote-app'; import { getQuotes } from './src/data'; -const quoteMap = { +const generateQuoteMap = () => ({ alpha: getQuotes(20), beta: getQuotes(18), gamma: getQuotes(22), -}; +}); storiesOf('multiple horizontal lists', module).add('stress test', () => ( - + )); diff --git a/stories/src/data.ts b/stories/src/data.ts index 3c31d23f8..f33b9332c 100644 --- a/stories/src/data.ts +++ b/stories/src/data.ts @@ -1,4 +1,5 @@ import { colors } from '@atlaskit/theme'; +import seedrandom from 'seedrandom'; import type { Author, Quote, QuoteMap } from './types'; import finnImg from '../static/media/finn-min.png'; import bmoImg from '../static/media/bmo-min.png'; @@ -117,29 +118,25 @@ export const quotes: Quote[] = [ ]; // So we do not have any clashes with our hardcoded ones -let idCount: number = quotes.length + 1; +let idCount: number; +let predictableMathRandom: seedrandom.PRNG; -export const getQuotes = (count: number): Quote[] => - // eslint-disable-next-line no-restricted-syntax - Array.from({ length: count }, (v, k) => k).map(() => { - const random: Quote = quotes[Math.floor(Math.random() * quotes.length)]; - - const custom: Quote = { - ...random, - id: `G${idCount++}`, - }; +export const resetData = (seed: string) => { + idCount = 1; + predictableMathRandom = seedrandom(seed); +}; - return custom; - }); +resetData('base'); -export const getAuthors = (count: number): Author[] => +export const getQuotes = (count: number = quotes.length): Quote[] => // eslint-disable-next-line no-restricted-syntax Array.from({ length: count }, (v, k) => k).map(() => { - const random: Author = authors[Math.floor(Math.random() * authors.length)]; + const random: Quote = + quotes[Math.floor(predictableMathRandom() * quotes.length)]; - const custom: Author = { + const custom: Quote = { ...random, - id: `author-${idCount++}`, + id: `G${idCount++}`, }; return custom;