├── .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 |
--------------------------------------------------------------------------------
/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 |
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 | //
302 | // )}
303 | //
304 | //
305 | //
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 |
--------------------------------------------------------------------------------