├── .eslintrc.js ├── .github └── workflows │ └── bruno.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierrc.json ├── README.md ├── jest.config.js ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── vercel.svg ├── src ├── components │ ├── MultiStepForm.spec.tsx │ └── MultiStepForm.tsx ├── jest.setup.ts ├── pages │ ├── _app.tsx │ ├── _document.tsx │ ├── api │ │ └── hello.ts │ └── index.tsx └── theme.tsx ├── tsconfig.jest.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:react/recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 7 | 'plugin:jest/recommended', 8 | 'plugin:jest/style', 9 | 'plugin:testing-library/react', 10 | 'next', 11 | 'next/core-web-vitals', 12 | ], 13 | parser: '@typescript-eslint/parser', 14 | parserOptions: { 15 | project: './tsconfig.json', 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | ecmaVersion: 12, 20 | sourceType: 'module', 21 | }, 22 | rules: { 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | '@typescript-eslint/no-empty-function': 'off', 25 | '@next/next/no-document-import-in-page': 'off', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /.github/workflows/bruno.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: '*' 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{matrix.os}} 15 | 16 | strategy: 17 | matrix: 18 | node-version: [12.x, 14.x, 16.x] 19 | os: [ubuntu-latest, windows-latest, macos-11] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run lint 31 | - run: npm run test:ci 32 | - run: npm run build 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # eslint cache 37 | .eslintcache -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.tsx`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: './tsconfig.jest.json', 7 | }, 8 | }, 9 | setupFilesAfterEnv: ['./src/jest.setup.ts'], 10 | coverageThreshold: { 11 | global: { 12 | // I'll put it back to 100 after the video =) 13 | branches: 20, 14 | functions: 20, 15 | lines: 20, 16 | statements: 20, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // material-ui version 4, doesn't like strict mode 3 | // in material-ui version 5 this is already fixed :) 4 | reactStrictMode: false, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-react-testing-video8-forms-react-testing-library", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint --dir src", 10 | "test": "jest", 11 | "test:ci": "jest --coverage --silent --ci", 12 | "prepare": "husky install", 13 | "prettier": "prettier {*,src/*,src/**/*} --write --ignore-unknown", 14 | "lint-staged": "lint-staged" 15 | }, 16 | "dependencies": { 17 | "@material-ui/core": "4.12.3", 18 | "formik": "2.2.9", 19 | "formik-material-ui": "4.0.0-alpha.1", 20 | "next": "11.1.2", 21 | "react": "17.0.2", 22 | "react-dom": "17.0.2", 23 | "yup": "0.32.9" 24 | }, 25 | "devDependencies": { 26 | "@testing-library/dom": "8.5.0", 27 | "@testing-library/jest-dom": "5.14.1", 28 | "@testing-library/react": "12.1.0", 29 | "@testing-library/user-event": "13.2.1", 30 | "@types/jest": "27.0.1", 31 | "@types/react": "17.0.22", 32 | "@types/yup": "0.29.13", 33 | "@typescript-eslint/eslint-plugin": "4.31.1", 34 | "@typescript-eslint/parser": "4.31.1", 35 | "eslint": "7.32.0", 36 | "eslint-config-next": "11.1.2", 37 | "eslint-plugin-jest": "24.4.2", 38 | "eslint-plugin-react": "7.25.3", 39 | "eslint-plugin-testing-library": "4.12.3", 40 | "husky": "7.0.2", 41 | "jest": "27.2.0", 42 | "lint-staged": "11.1.2", 43 | "prettier": "2.4.1", 44 | "ts-jest": "27.0.5", 45 | "typescript": "4.4.3" 46 | }, 47 | "lint-staged": { 48 | "*.(tsx|ts)": "eslint --cache --fix", 49 | "*": "prettier --write --ignore-unknown" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmvantunes/youtube-react-testing-video8-forms-react-testing-library/40d6c4707ba8d72320b4bf557cd3f66a10beedfe/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/MultiStepForm.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen, waitFor, within } from '@testing-library/react'; 2 | import React from 'react'; 3 | import { MultiStepForm } from './MultiStepForm'; 4 | import user from '@testing-library/user-event'; 5 | import { check } from 'prettier'; 6 | 7 | describe('MultiStepForm', () => { 8 | const onSubmit = jest.fn(); 9 | 10 | beforeEach(() => { 11 | onSubmit.mockClear(); 12 | render(); 13 | }); 14 | 15 | it('onSubmit is called when all fields pass validation', async () => { 16 | user.type(getFirstName(), 'Bruno'); 17 | selectJobSituation('Full-Time'); 18 | user.type(getCity(), 'Vila Real'); 19 | user.click(getMillionaireCheckbox()); 20 | clickNextButton(); 21 | 22 | // 2nd step 23 | user.type(await findMoney(), '1000000'); 24 | clickNextButton(); 25 | 26 | // 3rd step 27 | user.type(await findDescription(), 'hello'); 28 | clickSubmitButton(); 29 | 30 | await waitFor(() => { 31 | expect(onSubmit).toHaveBeenCalledWith({ 32 | city: 'Vila Real', 33 | description: 'hello', 34 | firstName: 'Bruno', 35 | job: 'FULL', 36 | millionaire: true, 37 | money: 1000000, 38 | }); 39 | }); 40 | 41 | expect(onSubmit).toHaveBeenCalledTimes(1); 42 | }); 43 | 44 | it('has 3 required fields on first step', async () => { 45 | clickNextButton(); 46 | 47 | await waitFor(() => { 48 | expect(getFirstName()).toHaveErrorMessage('Your First Name is Required'); 49 | }); 50 | 51 | expect(getCity()).toHaveErrorMessage('city is a required field'); 52 | expect(getSelectJobSituation()).toHaveErrorMessage( 53 | 'You need to select your job situation' 54 | ); 55 | }); 56 | 57 | describe('city field', () => { 58 | it('shows error when city has less than 8 chars', async () => { 59 | user.type(getCity(), 'Vila'); 60 | user.tab(); 61 | 62 | await waitFor(() => { 63 | expect(getCity()).toHaveErrorMessage( 64 | 'city must be at least 8 characters' 65 | ); 66 | }); 67 | }); 68 | 69 | it('shows error when city has more than 11 chars', async () => { 70 | user.type(getCity(), 'Vila Real12312313123'); 71 | user.tab(); 72 | 73 | await waitFor(() => { 74 | expect(getCity()).toHaveErrorMessage( 75 | 'city must be at most 11 characters' 76 | ); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('first name field', () => { 82 | it('shows error when first name has more than 5 chars', async () => { 83 | user.type(getFirstName(), 'Carlos'); 84 | user.tab(); 85 | 86 | await waitFor(() => { 87 | expect(getFirstName()).toHaveErrorMessage( 88 | `Your name can't be longer than 5 chars` 89 | ); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('money field', () => { 95 | it('shows error when money is lower than 1000000 and millionaire selected', async () => { 96 | user.type(getFirstName(), 'Bruno'); 97 | selectJobSituation('Full-Time'); 98 | user.type(getCity(), 'Vila Real'); 99 | user.click(getMillionaireCheckbox()); 100 | clickNextButton(); 101 | 102 | // 2nd step 103 | user.type(await findMoney(), '100'); 104 | clickNextButton(); 105 | 106 | await waitFor(async () => { 107 | expect(await findMoney()).toHaveErrorMessage( 108 | 'Because you said you are a millionaire you need to have 1 million' 109 | ); 110 | }); 111 | }); 112 | }); 113 | 114 | // TODO: more tests during the video 115 | }); 116 | 117 | function clickSubmitButton() { 118 | user.click(screen.getByRole('button', { name: /Submit/i })); 119 | } 120 | 121 | function findDescription() { 122 | return screen.findByRole('textbox', { name: /Description/i }); 123 | } 124 | 125 | function findMoney() { 126 | return screen.findByRole('spinbutton', { name: /All the money I have/i }); 127 | } 128 | 129 | function clickNextButton() { 130 | user.click(screen.getByRole('button', { name: /Next/i })); 131 | } 132 | 133 | function getMillionaireCheckbox() { 134 | return screen.getByRole('checkbox', { name: /I am a millionaire/i }); 135 | } 136 | 137 | function getCity() { 138 | return screen.getByRole('textbox', { name: /city/i }); 139 | } 140 | 141 | function getFirstName() { 142 | return screen.getByRole('textbox', { name: /first name/i }); 143 | } 144 | 145 | function getSelectJobSituation() { 146 | return screen.getByRole('combobox', { name: /JOB situation/i }); 147 | } 148 | 149 | function selectJobSituation(jobSituation: string) { 150 | const dropdown = getSelectJobSituation(); 151 | user.selectOptions( 152 | dropdown, 153 | within(dropdown).getByRole('option', { name: jobSituation }) 154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/components/MultiStepForm.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Card, 5 | CardContent, 6 | CircularProgress, 7 | FormControl, 8 | Grid, 9 | InputBaseComponentProps, 10 | InputLabel, 11 | MenuItem, 12 | Step, 13 | StepLabel, 14 | Stepper, 15 | TextField as RealTextField, 16 | FormHelperText, 17 | NativeSelect, 18 | } from '@material-ui/core'; 19 | import { 20 | ErrorMessage, 21 | Field, 22 | FieldAttributes, 23 | Form, 24 | Formik, 25 | FormikConfig, 26 | FormikValues, 27 | useField, 28 | } from 'formik'; 29 | import { 30 | CheckboxWithLabel, 31 | TextField, 32 | Select, 33 | TextFieldProps, 34 | } from 'formik-material-ui'; 35 | import React, { useState } from 'react'; 36 | import { mixed, number, object, string } from 'yup'; 37 | 38 | const sleep = (time: number) => new Promise((acc) => setTimeout(acc, time)); 39 | 40 | export interface FormValues { 41 | firstName: string; 42 | job: string; 43 | millionaire: boolean; 44 | money: number; 45 | description: string; 46 | city: string; 47 | } 48 | 49 | export interface MultiStepFormProps { 50 | onSubmit: (formValue: FormValues) => void; 51 | } 52 | 53 | export function MultiStepForm({ onSubmit }: MultiStepFormProps) { 54 | return ( 55 | 56 | 57 | 58 | initialValues={{ 59 | firstName: '', 60 | job: 'EMPTY', 61 | city: '', 62 | millionaire: false, 63 | money: 0, 64 | description: '', 65 | }} 66 | onSubmit={async (values) => { 67 | await sleep(500); 68 | onSubmit(values); 69 | }} 70 | > 71 | 83 | 84 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 107 | 108 | 109 | 110 | 117 | 118 | 119 | 134 | 135 | 143 | 144 | 145 | 146 | 147 | 154 | 155 | 156 | 157 | 158 | 159 | ); 160 | } 161 | 162 | export interface FormikStepProps 163 | extends Pick, 'children' | 'validationSchema'> { 164 | label: string; 165 | } 166 | 167 | export function FormikStep({ children }: FormikStepProps) { 168 | return <>{children}; 169 | } 170 | 171 | export function FormikStepper({ 172 | children, 173 | ...props 174 | }: FormikConfig) { 175 | const childrenArray = React.Children.toArray( 176 | children 177 | ) as React.ReactElement[]; 178 | const [step, setStep] = useState(0); 179 | const currentChild = childrenArray[step]; 180 | const [completed, setCompleted] = useState(false); 181 | 182 | function isLastStep() { 183 | return step === childrenArray.length - 1; 184 | } 185 | 186 | return ( 187 | 188 | {...props} 189 | // eslint-disable-next-line 190 | validationSchema={currentChild.props.validationSchema} 191 | onSubmit={async (values, helpers) => { 192 | if (isLastStep()) { 193 | await props.onSubmit(values, helpers); 194 | setCompleted(true); 195 | } else { 196 | setStep((s) => s + 1); 197 | 198 | // the next line was not covered in the youtube video 199 | // 200 | // If you have multiple fields on the same step 201 | // we will see they show the validation error all at the same time after the first step! 202 | // 203 | // If you want to keep that behaviour, then, comment the next line :) 204 | // If you want the second/third/fourth/etc steps with the same behaviour 205 | // as the first step regarding validation errors, then the next line is for you! =) 206 | // 207 | // In the example of the video, it doesn't make any difference, because we only 208 | // have one field with validation in the second step :) 209 | helpers.setTouched({}); 210 | } 211 | }} 212 | > 213 | {({ isSubmitting }) => ( 214 |
215 | 216 | {childrenArray.map((child, index) => ( 217 | index || completed} 220 | > 221 | {child.props.label} 222 | 223 | ))} 224 | 225 | 226 | {currentChild} 227 | 228 | 229 | {step > 0 ? ( 230 | 231 | 239 | 240 | ) : null} 241 | 242 | 253 | 254 | 255 |
256 | )} 257 | 258 | ); 259 | } 260 | 261 | export function CustomDropdown({ name }: { name: string }) { 262 | const [field, props] = useField(name); 263 | 264 | return ( 265 | 266 | Job Situation 267 | 276 | {field.value !== 'EMPTY' ? null : ( 277 | 278 | )} 279 | 280 | 281 | 282 | 283 | 284 | {(message) => {message}} 285 | 286 | 287 | ); 288 | 289 | // return ( 290 | // 291 | // Job Situation 292 | // 300 | // {field.value !== 'EMPTY' ? null : ( 301 | // Select your job situation 302 | // )} 303 | // Full-Time 304 | // Part-Time 305 | // Unemployed 306 | // 307 | // 308 | // {(message) => {message}} 309 | // 310 | // 311 | // ); 312 | } 313 | 314 | export function CustomTextFieldWithErrorMessage(props: TextFieldProps) { 315 | const hasError = !!props.form.errors[props.field.name]; 316 | 317 | const inputProps = hasError 318 | ? { 319 | ...props.inputProps, 320 | 'aria-errormessage': `${props.field.name}-helper-text`, 321 | } 322 | : props.inputProps; 323 | 324 | return ; 325 | } 326 | -------------------------------------------------------------------------------- /src/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AppBar, 3 | Box, 4 | Container, 5 | CssBaseline, 6 | ThemeProvider, 7 | Toolbar, 8 | Typography, 9 | } from '@material-ui/core'; 10 | import { AppProps } from 'next/app'; 11 | import Head from 'next/head'; 12 | import React from 'react'; 13 | import { theme } from '../theme'; 14 | 15 | export default function MyApp({ Component, pageProps }: AppProps) { 16 | React.useEffect(() => { 17 | // Remove the server-side injected CSS. 18 | const jssStyles = document.querySelector('#jss-server-side'); 19 | if (jssStyles) { 20 | jssStyles.parentElement?.removeChild(jssStyles); 21 | } 22 | }, []); 23 | 24 | return ( 25 | 26 | 27 | Multi-Step Form 28 | 32 | 33 | 34 | 35 | 36 | Multi-Step Form 37 | 38 | 39 | 40 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { ServerStyleSheets } from '@material-ui/core/styles'; 2 | import Document, { Head, Html, Main, NextScript } from 'next/document'; 3 | import React from 'react'; 4 | import { theme } from '../theme'; 5 | 6 | export default class MyDocument extends Document { 7 | render() { 8 | return ( 9 | 10 | 11 | {/* PWA primary color */} 12 | 13 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | 27 | // `getInitialProps` belongs to `_document` (instead of `_app`), 28 | // it's compatible with server-side generation (SSG). 29 | MyDocument.getInitialProps = async (ctx) => { 30 | // Resolution order 31 | // 32 | // On the server: 33 | // 1. app.getInitialProps 34 | // 2. page.getInitialProps 35 | // 3. document.getInitialProps 36 | // 4. app.render 37 | // 5. page.render 38 | // 6. document.render 39 | // 40 | // On the server with error: 41 | // 1. document.getInitialProps 42 | // 2. app.render 43 | // 3. page.render 44 | // 4. document.render 45 | // 46 | // On the client 47 | // 1. app.getInitialProps 48 | // 2. page.getInitialProps 49 | // 3. app.render 50 | // 4. page.render 51 | 52 | // Render app and page and get the context of the page with collected side effects. 53 | const sheets = new ServerStyleSheets(); 54 | const { renderPage } = ctx; 55 | 56 | ctx.renderPage = () => 57 | renderPage({ 58 | enhanceApp: (App) => (props) => sheets.collect(), 59 | }); 60 | 61 | const initialProps = await Document.getInitialProps(ctx); 62 | 63 | return { 64 | ...initialProps, 65 | // Styles fragment is rendered after the app and page rendering finish. 66 | styles: [ 67 | ...React.Children.toArray(initialProps.styles), 68 | sheets.getStyleElement(), 69 | ], 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }); 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { MultiStepForm } from '../components/MultiStepForm'; 2 | 3 | export default function Home() { 4 | return ( 5 | { 7 | console.log('Form Submitted', values); 8 | }} 9 | /> 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/theme.tsx: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@material-ui/core/styles'; 2 | 3 | // Create a theme instance. 4 | export const theme = createTheme(); 5 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve" 16 | }, 17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 18 | "exclude": ["node_modules"] 19 | } 20 | --------------------------------------------------------------------------------