├── src ├── test │ └── setup.ts ├── vite-env.d.ts ├── App.tsx ├── main.tsx └── components │ ├── List.tsx │ └── List.spec.tsx ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.json ├── package.json └── jest.config.ts /src/test/setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom' -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import List from "./components/List"; 2 | 3 | export default function App() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Unit Tests 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-unit-testing", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "test": "jest --watchAll" 10 | }, 11 | "dependencies": { 12 | "react": "^18.0.2", 13 | "react-dom": "^18.0.2" 14 | }, 15 | "devDependencies": { 16 | "@swc/core": "^1.2.241", 17 | "@swc/jest": "^0.2.17", 18 | "@testing-library/jest-dom": "^5.16.5", 19 | "@testing-library/react": "^13.3.0", 20 | "@testing-library/user-event": "^14.4.3", 21 | "@types/jest": "^28.1.7", 22 | "@types/react": "^18.0.17", 23 | "@types/react-dom": "^18.0.6", 24 | "@vitejs/plugin-react": "^2.0.1", 25 | "jest": "^28.1.3", 26 | "ts-node": "^10.9.1", 27 | "typescript": "^4.7.4", 28 | "vite": "^3.0.9" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/List.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | type ListProps = { 4 | initialItems: string[] 5 | } 6 | 7 | function List({ initialItems }: ListProps) { 8 | const [newItem, setNewItem] = useState(''); 9 | const [list, setList] = useState(initialItems); 10 | 11 | function addToList() { 12 | setTimeout(() => { 13 | setList(state => [...state, newItem]); 14 | }, 500) 15 | } 16 | 17 | function removeFromList(item: string) { 18 | setTimeout(() => { 19 | setList(state => state.filter(item => item !== item)); 20 | }, 500) 21 | } 22 | 23 | return ( 24 | <> 25 | setNewItem(e.target.value)} /> 26 | 27 | 35 | 36 | ) 37 | } 38 | 39 | export default List 40 | -------------------------------------------------------------------------------- /src/components/List.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, waitFor, screen } from "@testing-library/react"; 2 | import userEvent from "@testing-library/user-event"; 3 | import List from "./List"; 4 | 5 | describe("List Component", () => { 6 | it("should render list items", async () => { 7 | const { getByText, queryByText, rerender, unmount } = render( 8 | 9 | ); 10 | 11 | expect(getByText("Luiz")).toBeInTheDocument(); 12 | expect(getByText("Carlos")).toBeInTheDocument(); 13 | expect(getByText("Nicolas")).toBeInTheDocument(); 14 | 15 | unmount(); 16 | rerender(); 17 | 18 | expect(getByText("Yasmin")).toBeInTheDocument(); 19 | expect(queryByText("Nicolas")).not.toBeInTheDocument(); 20 | }); 21 | 22 | it("should be able to add new item to the list", async () => { 23 | const { getByText, getByPlaceholderText, findByText } = render( 24 | 25 | ); 26 | 27 | const inputElement = getByPlaceholderText("Novo item"); 28 | const addButton = getByText("Adicionar"); 29 | 30 | userEvent.type(inputElement, "Novo"); 31 | userEvent.click(addButton); 32 | 33 | await waitFor(() => { 34 | expect(getByText("Novo")).toBeInTheDocument(); 35 | }); 36 | }); 37 | 38 | it("should be able to add remove item from the list", async () => { 39 | const { getAllByText, queryByText } = render( 40 | 41 | ); 42 | 43 | const removeButtons = getAllByText("Remover"); 44 | 45 | userEvent.click(removeButtons[0]); 46 | 47 | await waitFor(() => { 48 | expect(queryByText("Luiz")).not.toBeInTheDocument(); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/ls/x2hs2rkx2r99frfkjtf4t9gh0000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls, instances and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | // collectCoverage: false, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | // coverageDirectory: undefined, 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: "v8", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | // globalSetup: undefined, 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "jsx", 77 | // "ts", 78 | // "tsx", 79 | // "json", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | // moduleNameMapper: {}, 85 | 86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 87 | // modulePathIgnorePatterns: [], 88 | 89 | // Activates notifications for test results 90 | // notify: false, 91 | 92 | // An enum that specifies notification mode. Requires { notify: true } 93 | // notifyMode: "failure-change", 94 | 95 | // A preset that is used as a base for Jest's configuration 96 | // preset: undefined, 97 | 98 | // Run tests from one or more projects 99 | // projects: undefined, 100 | 101 | // Use this configuration option to add custom reporters to Jest 102 | // reporters: undefined, 103 | 104 | // Automatically reset mock state before every test 105 | // resetMocks: false, 106 | 107 | // Reset the module registry before running each individual test 108 | // resetModules: false, 109 | 110 | // A path to a custom resolver 111 | // resolver: undefined, 112 | 113 | // Automatically restore mock state and implementation before every test 114 | // restoreMocks: false, 115 | 116 | // The root directory that Jest should scan for tests and modules within 117 | // rootDir: undefined, 118 | 119 | // A list of paths to directories that Jest should use to search for files in 120 | // roots: [ 121 | // "" 122 | // ], 123 | 124 | // Allows you to use a custom runner instead of Jest's default test runner 125 | // runner: "jest-runner", 126 | 127 | // The paths to modules that run some code to configure or set up the testing environment before each test 128 | // setupFiles: [], 129 | 130 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 131 | setupFilesAfterEnv: [ 132 | '/src/test/setup.ts' 133 | ], 134 | 135 | // The number of seconds after which a test is considered as slow and reported as such in the results. 136 | // slowTestThreshold: 5, 137 | 138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 139 | // snapshotSerializers: [], 140 | 141 | // The test environment that will be used for testing 142 | testEnvironment: "jsdom", 143 | 144 | // Options that will be passed to the testEnvironment 145 | // testEnvironmentOptions: {}, 146 | 147 | // Adds a location field to test results 148 | // testLocationInResults: false, 149 | 150 | // The glob patterns Jest uses to detect test files 151 | // testMatch: [ 152 | // "**/__tests__/**/*.[jt]s?(x)", 153 | // "**/?(*.)+(spec|test).[tj]s?(x)" 154 | // ], 155 | 156 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 157 | // testPathIgnorePatterns: [ 158 | // "/node_modules/" 159 | // ], 160 | 161 | // The regexp pattern or array of patterns that Jest uses to detect test files 162 | // testRegex: [], 163 | 164 | // This option allows the use of a custom results processor 165 | // testResultsProcessor: undefined, 166 | 167 | // This option allows use of a custom test runner 168 | // testRunner: "jest-circus/runner", 169 | 170 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 171 | // testURL: "http://localhost", 172 | 173 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 174 | // timers: "real", 175 | 176 | // A map from regular expressions to paths to transformers 177 | transform: { 178 | "^.+\\.(t|j)sx?$": [ 179 | "@swc/jest", 180 | { 181 | jsc: { 182 | parser: { 183 | syntax: 'typescript', 184 | tsx: true, 185 | decorators: true, 186 | }, 187 | keepClassNames: true, 188 | transform: { 189 | legacyDecorator: true, 190 | decoratorMetadata: true, 191 | react: { 192 | runtime: 'automatic' 193 | }, 194 | }, 195 | }, 196 | module: { 197 | type: 'es6', 198 | noInterop: false, 199 | }, 200 | }, 201 | ], 202 | }, 203 | 204 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 205 | // transformIgnorePatterns: [ 206 | // "/node_modules/", 207 | // "\\.pnp\\.[^\\/]+$" 208 | // ], 209 | 210 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 211 | // unmockedModulePathPatterns: undefined, 212 | 213 | // Indicates whether each individual test should be reported during the run 214 | // verbose: undefined, 215 | 216 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 217 | // watchPathIgnorePatterns: [], 218 | 219 | // Whether to use watchman for file crawling 220 | // watchman: true, 221 | }; 222 | --------------------------------------------------------------------------------