Skip to content

Instantly share code, notes, and snippets.

@remcorakers
Created August 6, 2021 08:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save remcorakers/3d4fef275b3261b00ba71afac98544f0 to your computer and use it in GitHub Desktop.
Save remcorakers/3d4fef275b3261b00ba71afac98544f0 to your computer and use it in GitHub Desktop.
#!/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