diff --git a/.storybook/config.js b/.storybook/config.js index fc54496..20b8fce 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -1,4 +1,4 @@ -import { configure } from '@storybook/react' +const { configure } = require('@storybook/react') const req = require.context('../src', true, /story\.tsx?$/) diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index cca0459..9971b9b 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -10,7 +10,7 @@ module.exports = (config, env) => { exclude: /node_modules/, include: path.resolve(__dirname, '..', 'src'), }) - + myConfig.resolve.extensions.unshift('.tsx') myConfig.resolve.extensions.unshift('.ts') diff --git a/README.md b/README.md index d213ebd..692a7cb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ You have to bring your own awesome. But here's a picture* after `npm i` and `np * lean production bundles * integrated storybook support * unit tests with mocking -* 100% code coverage with examples on how to keep it there +* storybook snapshot testing * code linting & formatting ### Documentation & Samples 🖨 diff --git a/docs/stack.md b/docs/stack.md index 3b01aae..3523dfd 100644 --- a/docs/stack.md +++ b/docs/stack.md @@ -89,6 +89,8 @@ Start your app only when your gut says, "You'll fuck this up long before your st Provides a sandbox to work on your components with whatever props you can dream of wanting set to make sure your components are in tip top shape before and during use in your application. If you are writing a component you should be writing stories about it. +Also the storyshots addon for working with `jest` is a great way to add snapshot testing for your React components by using your stories. (Like we needed another reason to use storybook!!) + ## Animations > **react-transition-group** diff --git a/package.json b/package.json index 2a17bd0..99a0490 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,9 @@ "react-transition-group": "^2.2.1" }, "devDependencies": { + "@storybook/addon-actions": "^3.2.15", + "@storybook/addon-links": "^3.2.15", + "@storybook/addon-storyshots": "^3.2.15", "@storybook/react": "^3.2.15", "@types/electron-is-dev": "^0.3.0", "@types/electron-store": "^1.2.0", @@ -99,6 +102,7 @@ "lint-staged": "^5.0.0", "npm-run-all": "^4.1.2", "prettier": "^1.8.2", + "react-powerplug": "^0.1.2", "react-test-renderer": "^16.0.0", "ts-jest": "^21.2.1", "ts-loader": "^3.1.1", @@ -113,19 +117,25 @@ "transform": { ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js" }, + "moduleNameMapper": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/test/mock-file.ts", + "\\.(css|less)$": "/test/mock-style.ts" + }, "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", "moduleFileExtensions": [ "ts", "tsx", - "js" + "js", + "json" ], "coveragePathIgnorePatterns": [ - "/node_modules/", - "/test/", - "/out/", - "/build/", - "/dist/", - "/docs/" + "./node_modules", + "./out", + "./build", + "./dist", + "./test", + "./docs", + "\\.story.tsx$" ], "coverageThreshold": { "global": { diff --git a/src/renderer/features/example-using-tabs/welcome-screen/sample-tabs.tsx b/src/renderer/features/example-using-tabs/welcome-screen/sample-tabs.tsx index 682bc6c..af2ba8b 100644 --- a/src/renderer/features/example-using-tabs/welcome-screen/sample-tabs.tsx +++ b/src/renderer/features/example-using-tabs/welcome-screen/sample-tabs.tsx @@ -19,9 +19,9 @@ export interface SampleTabsProps { onChangeTab: (tab: SampleTabType) => void } -const KEY_1 = `${commandOrControlKey}+1` -const KEY_2 = `${commandOrControlKey}+2` -const KEY_3 = `${commandOrControlKey}+3` +const KEY_1 = `${commandOrControlKey()}+1` +const KEY_2 = `${commandOrControlKey()}+2` +const KEY_3 = `${commandOrControlKey()}+3` // an extra layer just for the drag style due to electron bug const ROOT = css(styles.windowDrag) diff --git a/src/renderer/platform/components/centered-content/centered-content.story.tsx b/src/renderer/platform/components/centered-content/centered-content.story.tsx new file mode 100644 index 0000000..6d20239 --- /dev/null +++ b/src/renderer/platform/components/centered-content/centered-content.story.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import { StorybookStory as Story, StorybookGroup as Group } from '../storybook' +import { storiesOf } from '@storybook/react' +import { CenteredContent } from './index' + +storiesOf('CenteredContent', module).add('default', () => ( + + + +

i am in the middle

+

i am also a really strange component and shouldn't exist.

+
+
+
+)) diff --git a/src/renderer/platform/components/enter-animation/enter-animation.story.tsx b/src/renderer/platform/components/enter-animation/enter-animation.story.tsx new file mode 100644 index 0000000..ab75a20 --- /dev/null +++ b/src/renderer/platform/components/enter-animation/enter-animation.story.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { StorybookStory as Story, StorybookGroup as Group } from '../storybook' + +import { storiesOf } from '@storybook/react' +import { EnterAnimation } from './index' +// import { Value } from 'react-powerplug' + +storiesOf('EnterAnimation', module).add('default', () => ( + + + +

hi

+
+
+ + +

hi

+
+
+
+)) diff --git a/src/renderer/platform/components/enter-animation/enter-animation.tsx b/src/renderer/platform/components/enter-animation/enter-animation.tsx index 32ddff4..9ac343c 100644 --- a/src/renderer/platform/components/enter-animation/enter-animation.tsx +++ b/src/renderer/platform/components/enter-animation/enter-animation.tsx @@ -28,6 +28,7 @@ const FINISH = cssProps({ transform: `translate(0, 0) scale(1, 1)` }) export class EnterAnimation extends React.Component { state: EnterAnimationState = {} + // istanbul ignore next componentDidMount() { setTimeout(() => this.setState({ mounted: true }), 1) } @@ -55,6 +56,7 @@ export class EnterAnimation extends React.Component ( + + + +

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+

hi

+
+
+
+)) diff --git a/src/renderer/platform/components/spin-animation/spin-animation-state.test.ts b/src/renderer/platform/components/spin-animation/spin-animation-state.test.ts index 3d0d95f..2689f0c 100644 --- a/src/renderer/platform/components/spin-animation/spin-animation-state.test.ts +++ b/src/renderer/platform/components/spin-animation/spin-animation-state.test.ts @@ -10,8 +10,15 @@ test('next', () => { test('createSpinStates', () => { const states = createSpinStates({}) + expect(typeof states.forward).toBe('function') expect(typeof states.back).toBe('function') + const forwardResults = states.forward({ value: { get: () => 1 } }) + expect(forwardResults.current).toBe(1) + + const backResults = states.back({ value: { get: () => 1 } }) + expect(backResults.current).toBe(1) + const value: any = () => {} value.get = () => 1 value.previousState = 'back' diff --git a/src/renderer/platform/components/tab/tab.story.tsx b/src/renderer/platform/components/tab/tab.story.tsx new file mode 100644 index 0000000..f71ed0b --- /dev/null +++ b/src/renderer/platform/components/tab/tab.story.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import { StorybookStory as Story, StorybookGroup as Group } from '../storybook' + +import { storiesOf } from '@storybook/react' +import { Tab } from './index' +import { Value } from 'react-powerplug' + +storiesOf('Tab', module).add('Tab', () => ( + + + + {({ value, setValue }) => ( +
+ setValue('one')} /> + setValue('two')} /> + setValue('three')} /> + setValue('four')} /> +
+ )} +
+
+
+)) diff --git a/src/renderer/platform/components/text/text.story.tsx b/src/renderer/platform/components/text/text.story.tsx index 86dbf46..5b63657 100644 --- a/src/renderer/platform/components/text/text.story.tsx +++ b/src/renderer/platform/components/text/text.story.tsx @@ -6,13 +6,17 @@ import { storiesOf } from '@storybook/react' import { Text } from './index' storiesOf('Text', module) - .add('text styles', () => + .add('text styles', () => ( Hello World! $123.45 The quick brown fox jumped over the slow lazy dog. + + + + Wayfarers intelligentsia salvia sartorial keffiyeh locavore direct trade flexitarian @@ -45,9 +49,9 @@ storiesOf('Text', module) Hello World! - , - ) - .add('with nested children', () => + + )) + .add('with nested children', () => ( @@ -62,5 +66,5 @@ storiesOf('Text', module)

-
, - ) + + )) diff --git a/src/renderer/platform/utils/keyboard/keyboard.test.ts b/src/renderer/platform/utils/keyboard/keyboard.test.ts index e5e6bf2..3f0b6fd 100644 --- a/src/renderer/platform/utils/keyboard/keyboard.test.ts +++ b/src/renderer/platform/utils/keyboard/keyboard.test.ts @@ -11,7 +11,6 @@ jest.mock('../../../../shared', () => { }) import * as Mousetrap from 'mousetrap' -// import * as platform from '../../../../shared/utils/platform/platform' test('changes tabs', () => { const fn = () => true @@ -25,13 +24,13 @@ test('changes tabs', () => { test('mac is command key', () => { const { commandOrControlKey } = require('./keyboard') - expect(commandOrControlKey).toBe('command') + expect(commandOrControlKey()).toBe('command') }) test('non-mac is control', () => { - jest.resetModules() + // jest.resetModules() const shared = require('../../../../shared') shared.isMac = jest.fn().mockReturnValue(false) const { commandOrControlKey } = require('./keyboard') - expect(commandOrControlKey).toBe('ctrl') + expect(commandOrControlKey()).toBe('ctrl') }) diff --git a/src/renderer/platform/utils/keyboard/keyboard.ts b/src/renderer/platform/utils/keyboard/keyboard.ts index dbb3eea..4c30d1e 100644 --- a/src/renderer/platform/utils/keyboard/keyboard.ts +++ b/src/renderer/platform/utils/keyboard/keyboard.ts @@ -11,11 +11,11 @@ export type KeyboardAction = 'keypress' | 'keydown' | 'keyup' // Mousetrap.prototype.stopCallback = () => false // } -export const commandOrControlKey = isMac() ? 'command' : 'ctrl' +export const commandOrControlKey = () => (isMac() ? 'command' : 'ctrl') /** * Binds a keystroke to a function. - * + * * @param keys The keystroke. * @param callback The function to fire. * @param action Optional keyboard event to further constraint. @@ -30,7 +30,7 @@ export function bindKey( /** * Removes a keybind. - * + * * @param keys The keystroke. * @param action Optional keyboard event to further constraint. */ diff --git a/src/shared/utils/platform/platform.ts b/src/shared/utils/platform/platform.ts index 142a681..ea4fc79 100644 --- a/src/shared/utils/platform/platform.ts +++ b/src/shared/utils/platform/platform.ts @@ -1,11 +1,11 @@ -export function isLinux() { - return process.platform === 'linux' +export function isLinux(platform = process.platform) { + return platform === 'linux' } -export function isWindows() { - return process.platform === 'win32' +export function isWindows(platform = process.platform) { + return platform === 'win32' } -export function isMac() { - return process.platform === 'darwin' +export function isMac(platform = process.platform) { + return platform === 'darwin' } diff --git a/test/__snapshots__/storyshots.test.ts.snap b/test/__snapshots__/storyshots.test.ts.snap new file mode 100644 index 0000000..118ae5e --- /dev/null +++ b/test/__snapshots__/storyshots.test.ts.snap @@ -0,0 +1,542 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots CenteredContent default 1`] = ` +
+
+

+ default +

+
+

+ i am in the middle +

+

+ i am also a really strange component and shouldn't exist. +

+
+
+
+`; + +exports[`Storyshots EnterAnimation default 1`] = ` +
+
+

+ grow +

+
+

+ hi +

+
+
+
+

+ slide +

+
+

+ hi +

+
+
+
+`; + +exports[`Storyshots Fun Dog default 1`] = ` +
+
+

+ is a doggo +

+
+
+ +
+
+
+
+`; + +exports[`Storyshots ScrollableContent default 1`] = ` +
+
+

+ default +

+
+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+

+ hi +

+
+
+
+`; + +exports[`Storyshots Tab Tab 1`] = ` +
+
+

+ inactive +

+
+
+
+

+ One +

+
+
+
+
+

+ Two +

+
+
+
+
+

+ Three +

+
+
+
+
+

+ Four +

+
+
+
+
+
+`; + +exports[`Storyshots Text text styles 1`] = ` +
+
+

+ regular text +

+

+ Hello World! +

+

+ $123.45 +

+

+ The quick brown fox jumped over the slow lazy dog. +

+
+
+

+ with text property +

+

+ Passed in text property +

+
+
+

+ huge paragraph +

+

+ Wayfarers intelligentsia salvia sartorial keffiyeh locavore direct trade flexitarian vexillologist ugh single-origin coffee. Hexagon heirloom direct trade palo santo distillery, PBR&B bespoke fanny pack adaptogen affogato bitters kombucha sartorial taiyaki next level. Cliche artisan iPhone bushwick church-key. Artisan forage mustache, chartreuse vexillologist echo park cronut. 8-bit fanny pack 90's post-ironic venmo vegan. Humblebrag cliche pork belly, cronut twee wayfarers salvia meditation plaid cornhole. Tumeric literally yr XOXO, ennui single-origin coffee blog jianbing jean shorts plaid typewriter prism whatever pabst flannel. Tousled lomo next level pickled mixtape. Everyday carry ennui polaroid chartreuse, biodiesel trust fund hashtag umami cardigan hot chicken locavore gochujang quinoa coloring book williamsburg. Next level 8-bit cornhole brunch venmo. Enamel pin normcore DIY jianbing irony thundercats. Taxidermy quinoa kinfolk, hexagon godard hell of banjo forage ugh blog. Kale chips umami +1 shabby chic air plant keffiyeh authentic whatever sriracha wayfarers letterpress tofu brooklyn next level salvia. Selfies readymade vegan synth chillwave pug banjo dreamcatcher freegan. Artisan cliche subway tile mumblecore, whatever pok pok tote bag celiac hella poke tousled listicle. Deep v unicorn scenester cred direct trade kickstarter microdosing cardigan mustache ennui tumblr umami farm-to-table literally listicle. Post-ironic semiotics venmo gochujang cray green juice lo-fi cardigan tilde prism pop-up jean shorts shoreditch occupy readymade. Portland messenger bag art party, succulents cred lyft bespoke. Kinfolk plaid selfies pinterest iPhone pug narwhal, fashion axe coloring book meditation tousled. Lyft trust fund copper mug DIY la croix banh mi. Jianbing raclette man bun mustache tote bag. Vinyl taiyaki kombucha tattooed, try-hard blog freegan you probably haven't heard of them schlitz shaman listicle chambray. Swag slow-carb enamel pin affogato migas bespoke fashion axe flannel prism marfa activated charcoal pop-up shabby chic. Oh. You need a little dummy text for your mockup? How quaint. I bet you’re still using Bootstrap too… +

+
+
+

+ style={{ color: "red" }} +

+

+ Hello World! +

+
+
+`; + +exports[`Storyshots Text with nested children 1`] = ` +
+
+

+ with nested children +

+

+

+ + I am STRONG + +

+

+ + I am ITALIC + +

+

+

+ I am another nested Text component. +

+

+

+
+
+`; diff --git a/test/mock-file.ts b/test/mock-file.ts new file mode 100644 index 0000000..0e56c5b --- /dev/null +++ b/test/mock-file.ts @@ -0,0 +1 @@ +module.exports = 'test-file-stub' diff --git a/test/mock-request-animation-frame.ts b/test/mock-request-animation-frame.ts new file mode 100644 index 0000000..ac8d840 --- /dev/null +++ b/test/mock-request-animation-frame.ts @@ -0,0 +1,8 @@ +/** + * Poor man's polyfill for raf. + */ + +// @ts-ignore +global.window = global +window.addEventListener = () => {} +window.requestAnimationFrame = () => 1 diff --git a/test/mock-style.ts b/test/mock-style.ts new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/test/mock-style.ts @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/setup.ts b/test/setup.ts index 6abfee5..ea692b9 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,3 +1,5 @@ +import './mock-request-animation-frame' + jest.mock('electron-window-state-manager', () => { return jest.fn().mockImplementation(() => ({ saveState: jest.fn(), diff --git a/test/storyshots.test.ts b/test/storyshots.test.ts new file mode 100644 index 0000000..a21dea4 --- /dev/null +++ b/test/storyshots.test.ts @@ -0,0 +1,3 @@ +import initStoryshots from '@storybook/addon-storyshots' + +initStoryshots() diff --git a/yarn.lock b/yarn.lock index e644da6..108dad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,6 +43,15 @@ dependencies: "@storybook/addons" "^3.2.15" +"@storybook/addon-storyshots@^3.2.15": + version "3.2.15" + resolved "https://registry.yarnpkg.com/@storybook/addon-storyshots/-/addon-storyshots-3.2.15.tgz#98c26e0b429a4da03b0a5eeca12949af09c235b2" + dependencies: + babel-runtime "^6.26.0" + global "^4.3.2" + prop-types "^15.6.0" + read-pkg-up "^3.0.0" + "@storybook/addons@^3.2.15": version "3.2.15" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.2.15.tgz#120fc3e34454b9e6c779f25b945d354511168abc" @@ -4818,7 +4827,7 @@ memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" -meow@^3.1.0, meow@^3.7.0: +meow@^3.1.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" dependencies: @@ -5130,14 +5139,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-scripts-info@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/npm-scripts-info/-/npm-scripts-info-0.3.6.tgz#14e7c78c1288bd6d82078769c7aa9583fc413c03" - dependencies: - chalk "^1.1.1" - meow "^3.7.0" - unquote "^1.1.0" - npm-which@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" @@ -6149,6 +6150,10 @@ react-modal@^3.1.2: exenv "^1.2.0" prop-types "^15.5.10" +react-powerplug@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/react-powerplug/-/react-powerplug-0.1.2.tgz#070e97f7f064cf7a5e2f2db5d0003aae84adf1df" + react-simple-di@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/react-simple-di/-/react-simple-di-1.2.0.tgz#dde0e5bf689f391ef2ab02c9043b213fe239c6d0" @@ -6255,6 +6260,13 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -7373,10 +7385,6 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unquote@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.0.tgz#98e1fc608b6b854c75afb1b95afc099ba69d942f" - unzip-response@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"