Created
August 6, 2021 08:29
-
-
Save remcorakers/3d4fef275b3261b00ba71afac98544f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
set -e | |
# Script to setup a Next.js application with Typescript, Storybook, SCSS and Jest | |
# Inspired by https://medium.com/swlh/2020-complete-setup-for-storybook-nextjs-typescript-scss-and-jest-1c9ce41e6481 | |
# After this script is executed, manually add the following lines to package.json scripts section: | |
# "test": "jest", | |
# "test:watch": "jest --watch", | |
# "test:coverage": "jest --coverage", | |
# "storybook": "start-storybook -p 6006 -c .storybook" | |
echo "What is your project named? " | |
read projectName | |
echo $projectName | yarn create next-app --typescript | |
# TODO: upgrade Storybook to webpack5 when it's stable | |
# See https://storybook.js.org/blog/storybook-for-webpack-5/, https://www.gatsbyjs.com/docs/how-to/testing/visual-testing-with-storybook/ | |
cd ./$projectName | |
# Fix ReferenceError: React is not defined | |
echo -e "import * as React from \"react\";\n$(cat pages/_app.tsx)" > pages/_app.tsx | |
cat <<EOT > pages/index.tsx | |
import * as React from "react"; | |
const Home = () => { | |
return <h1>Welcome to My Next App!</h1>; | |
}; | |
export default Home; | |
EOT | |
# | |
# Storybook | |
# | |
yarn add -D @storybook/react | |
mkdir .storybook | |
yarn add -D sass style-loader@^2.0.0 css-loader@^5.2.7 sass-loader@^10.2.0 @babel/core babel-loader babel-preset-react-app | |
cat <<EOT > .storybook/next-preset.js | |
const path = require('path'); | |
module.exports = { | |
webpackFinal: async (baseConfig, options) => { | |
const { module = {} } = baseConfig; | |
const newConfig = { | |
...baseConfig, | |
module: { | |
...module, | |
rules: [...(module.rules || [])], | |
}, | |
}; | |
// SCSS | |
newConfig.module.rules.push({ | |
test: /\.(s*)css$/, | |
loaders: ['style-loader', 'css-loader', 'sass-loader'], | |
include: path.resolve(__dirname, '../styles/global.scss'), | |
}); | |
// If you are using CSS Modules, check out the setup from Justin (justincy) | |
// Many thanks to Justin for the inspiration | |
// https://gist.github.com/justincy/b8805ae2b333ac98d5a3bd9f431e8f70#file-next-preset-js | |
return newConfig; | |
}, | |
}; | |
EOT | |
cat <<EOT > styles/global.scss | |
html { | |
background: #f1f1f1; | |
max-width: 100%; | |
} | |
body { | |
background: linear-gradient( | |
315deg, | |
var(#f1f1f1) 0%, | |
var(#e7e7e7) 100% | |
); | |
font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", | |
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", | |
"Helvetica Neue", system-ui, sans-serif !important; | |
} | |
EOT | |
cat <<EOT > .storybook/preview.js | |
import "../styles/global.scss"; | |
EOT | |
cat <<EOT > .storybook/main.js | |
const path = require("path"); | |
module.exports = { | |
stories: ["../components/**/*.stories.tsx"], | |
presets: [path.resolve(__dirname, "./next-preset.js")], | |
}; | |
EOT | |
mkdir components | |
cat <<EOT > components/Button.tsx | |
import * as React from "react"; | |
type Props = { | |
text: string; | |
}; | |
export default ({ text }: Props) => <button>{text}</button>; | |
EOT | |
cat <<EOT > components/Button.stories.tsx | |
import { storiesOf } from "@storybook/react"; | |
import Button from "./Button"; | |
storiesOf("Button", module).add("with text", () => { | |
return <Button text="Hello World" />; | |
}); | |
storiesOf("Button", module).add("with emoji", () => { | |
return <Button text="π π π π―" />; | |
}); | |
EOT | |
# | |
# Jest | |
# | |
yarn add -D jest @types/jest @types/node ts-jest babel-jest @types/enzyme enzyme @wojtekmaj/enzyme-adapter-react-17 | |
# TODO: update to official enzyme-adapter-react-17 package when it's released. | |
# See https://github.com/enzymejs/enzyme/issues/2429. | |
mkdir config | |
cat <<EOT > config/setup.js | |
const enzyme = require('enzyme'); | |
const Adapter = require('@wojtekmaj/enzyme-adapter-react-17'); | |
enzyme.configure({ adapter: new Adapter() }); | |
EOT | |
cat <<EOT > jest.config.js | |
module.exports = { | |
collectCoverageFrom: [ | |
'**/*.{ts,tsx}', | |
'!**/node_modules/**', | |
'!**/.storybook/**', | |
'!**/tests/**', | |
'!**/coverage/**', | |
'!jest.config.js', | |
], | |
coverageThreshold: { | |
global: { | |
branches: 100, | |
functions: 100, | |
lines: 100, | |
statements: 100, | |
}, | |
}, | |
setupFiles: ['<rootDir>/config/setup.js'], | |
preset: 'ts-jest', | |
testPathIgnorePatterns: ['/.next/', '/node_modules/', '/lib/', '/tests/', '/coverage/', '/.storybook/'], | |
testRegex: '(/__test__/.*|\\.(test|spec))\\.(ts|tsx|js)$', | |
testURL: 'http://localhost', | |
testEnvironment: 'jsdom', | |
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], | |
moduleNameMapper: { | |
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js', | |
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': | |
'<rootDir>/__mocks__/fileMock.js', | |
}, | |
transform: { | |
'.(ts|tsx)': 'babel-jest', | |
}, | |
transformIgnorePatterns: ['<rootDir>/node_modules/'], | |
}; | |
EOT | |
mkdir __mocks__ | |
cat <<EOT > __mocks__/fileMock.js | |
module.exports = "test-file-stub"; | |
EOT | |
cat <<EOT > __mocks__/styleMock.js | |
module.exports = {}; | |
EOT | |
yarn add -D @babel/preset-env @babel/preset-react @babel/preset-flow @babel/plugin-transform-runtime babel-plugin-transform-es2015-modules-commonjs | |
cat <<EOT > babel.config.json | |
{ | |
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"], | |
"plugins": [ | |
"@babel/plugin-transform-runtime", | |
"@babel/plugin-syntax-dynamic-import", | |
"@babel/plugin-transform-modules-commonjs", | |
"@babel/plugin-proposal-class-properties" | |
], | |
"env": { | |
"development": { | |
"plugins": ["transform-es2015-modules-commonjs"] | |
}, | |
"test": { | |
"plugins": [ | |
"transform-es2015-modules-commonjs", | |
"@babel/plugin-proposal-class-properties" | |
], | |
"presets": ["@babel/preset-react"] | |
} | |
} | |
} | |
EOT | |
mkdir components/__test__ | |
cat <<EOT > components/__test__/index.test.tsx | |
import React from "react"; | |
import { mount } from "enzyme"; | |
import Home from "../../pages/index"; | |
describe("Pages", () => { | |
describe("Home", () => { | |
it("should render without throwing an error", function () { | |
const wrap = mount(<Home />); | |
expect(wrap.find("h1").text()).toBe("Welcome to My Next App!"); | |
}); | |
}); | |
}); | |
EOT | |
yarn add -D react-test-renderer @types/react-test-renderer | |
cat <<EOT > components/__test__/Button.snapshot.test.tsx | |
import React from "react"; | |
import Button from "../Button"; | |
import renderer from "react-test-renderer"; | |
it("renders correctly", () => { | |
const tree = renderer.create(<Button text="Some Text" />).toJSON(); | |
expect(tree).toMatchSnapshot(); | |
}); | |
EOT |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment