├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── preset.ts └── templates ├── .babelrc ├── index-dom.spec.js ├── index-dom.spec.ts ├── index.spec.js ├── index.spec.ts ├── jest.config.json └── tsconfig.spec.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Diagnostic reports (https://nodejs.org/api/report.html) 7 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Dependencies 16 | node_modules/ 17 | package-lock.json 18 | 19 | # TypeScript cache 20 | *.tsbuildinfo 21 | 22 | # Optional npm cache directory 23 | .npm 24 | 25 | # Optional eslint cache 26 | .eslintcache 27 | 28 | # Optional REPL history 29 | .node_repl_history 30 | 31 | # Output of 'npm pack' 32 | *.tgz 33 | 34 | # IDE Specific files 35 | .idea/ 36 | .vscode/ 37 | .history/ 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ross MacPhee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

🧪 Add Jest to Sveltekit

2 | 3 | ## ❓ What is this? 4 | 5 | This is an **experimental** command to run to add Jest to your SvelteKit project. 6 | 7 | ## 🛠 Usage 8 | 9 | You must start with a fresh copy of the official SvelteKit template, which is currently created by running this command: 10 | 11 | ```sh 12 | npm create svelte@latest 13 | ``` 14 | 15 | Once that is set up, run this command in your project directory to set up Jest: 16 | 17 | > ❗️ __When running with TypeScript support enabled, remove comments within `tsconfig.json` or the adder will fail. This is a known limitation of [Preset](https://usepreset.dev/), as it relies upon JSON.parse.__ 18 | 19 | ```sh 20 | npx apply rossyman/svelte-add-jest # --no-ssh 21 | ``` 22 | 23 | After the preset runs, 24 | 25 | - `npm install`, `pnpm i`, or `yarn` to update dependencies. 26 | 27 | - You can apply _another_ [Svelte Adder](https://github.com/svelte-add/svelte-adders) to your project for more functionality. 28 | 29 | ### ⚙️ Options 30 | 31 | | Description | Flag | Negated | Default | 32 | | ------------------------- | --------------- | ------------------ | ------- | 33 | | Interactive Mode | `--interaction` | `--no-interaction` | True | 34 | | Jest DOM Support | `--jest-dom` | `--no-jest-dom` | True | 35 | | Typescript Support | `--ts` | `--no-ts` | False | 36 | | JSDOM Jest Env by Default | `--jsdom` | `--jsdom` | True | 37 | | Generate Example | `--examples` | `--no-examples` | True | 38 | 39 | ### 📑 Relevant Documentation 40 | 41 | - [Svelte Testing Library Docs](https://testing-library.com/docs/svelte-testing-library/intro/) 42 | - [Jest DOM](https://github.com/testing-library/jest-dom#usage) 43 | - [Jest](https://jestjs.io) 44 | 45 | ## Routed Tests 46 | 47 | If you run into an issue when writing tests for routed svelte components or files (`Reference error: describe is not defined`), this is a known issue. To fix it, you must modify your `svelte.config.js` (Specifically the `routes` property) with the following modification: 48 | ```js 49 | kit: { 50 | // Prior svelte configuration goes here... 51 | routes: filepath => { 52 | return ![ 53 | // Exclude spec files 54 | /\.spec\.(ts|js)$/, 55 | // Original routes 56 | /(?:(?:^_|\/_)|(?:^\.|\/\.)(?!well-known))/, 57 | ].some(regex => regex.test(filepath)) 58 | }, 59 | } 60 | ``` 61 | 62 | The reason we cannot perform this change directly for you, is due to a limitation within [Preset](https://usepreset.dev/). 63 | 64 | ## 😵 Help! I have a question 65 | 66 | [Create an issue](https://github.com/svelte-add/jest/issues/new) and we'll try to help. 67 | 68 | ## 😡 Fix! There is something that needs improvement 69 | 70 | [Create an issue](https://github.com/rossyman/svelte-add-jest/issues/new) or [pull request](https://github.com/rossyman/svelte-add-jest/pulls) and we'll try to fix. 71 | 72 | These are new tools, so there are likely to be problems in this project. Thank you for bringing them to our attention or fixing them for us. 73 | 74 | # 📄 License 75 | 76 | MIT 77 | 78 | --- 79 | 80 | _Repository preview image generated with [GitHub Social Preview](https://social-preview.pqt.dev)_ 81 | 82 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@rossyman/svelte-add-jest", 4 | "version": "1.3.4", 5 | "description": "SvelteKit adder for Jest unit testing", 6 | "license": "MIT", 7 | "keywords": [ 8 | "svelte", 9 | "sveltekit", 10 | "svelte-kit", 11 | "jest", 12 | "unit-test", 13 | "test" 14 | ], 15 | "repository": "github:rossyman/svelte-add-jest", 16 | "bugs": "https://github.com/rossyman/svelte-add-jest/issues", 17 | "contributors": [ 18 | "Ross MacPhee (https://github.com/rossyman)", 19 | "Brady Wiggins (https://github.com/FractalHQ)" 20 | ], 21 | "preset": "preset.ts", 22 | "devDependencies": { 23 | "apply": "^0.2.13" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /preset.ts: -------------------------------------------------------------------------------- 1 | import { Preset, color } from 'apply'; 2 | 3 | type Dependencies = { 4 | [key: string]: { 5 | version: string; 6 | type?: 'DEV' | 'PEER'; 7 | reliesOn?: string | string[]; 8 | } 9 | } 10 | 11 | type Configuration = { 12 | [key: string]: { 13 | message: string; 14 | default: any; 15 | question?: true; 16 | } 17 | } 18 | 19 | /** 20 | * Svelte adder utility class. 21 | * 22 | * Used to simplify interaction with Preset and approach 23 | * file-structure modification in a configuration-based way. 24 | */ 25 | abstract class Adder { 26 | 27 | /** 28 | * The adder's name, which is displayed as the name 29 | * of the Preset. Specified on the implementation level. 30 | * 31 | * @protected 32 | */ 33 | protected abstract readonly ADDER_NAME: string; 34 | 35 | /** 36 | * A dictionary of configuration options. Each option 37 | * will either be determined interactively; when the 38 | * `--interaction` flag is present, or through CLI flags. 39 | * 40 | * @protected 41 | */ 42 | protected abstract readonly CONFIGURATION: Configuration; 43 | 44 | /** 45 | * A dictionary of required dependencies. Each dependency 46 | * has an associated version, and the type can be either 47 | * explicitly set as 'DEV' or 'PEER', or implicitly 48 | * inferred as a core dependency when left `undefined`. 49 | * 50 | * Each dependency can also specify whether or not they 51 | * should be installed based on the presence of a 52 | * configuration option, defined in `CONFIGURATION`. 53 | * 54 | * @protected 55 | */ 56 | protected abstract readonly REQUIRED_DEPENDENCIES: Dependencies; 57 | 58 | /** 59 | * Runs an adder. Initialises all configuration and 60 | * dependencies, followed by running the implementation 61 | * specific functionality. 62 | */ 63 | run(): void { 64 | this.initialiseAdder(); 65 | } 66 | 67 | /** 68 | * Safely extracts a file, ensuring the user acknowledges 69 | * that their previously defined data will be overwritten. 70 | * 71 | * @param title The title of the specific action being 72 | * performed. 73 | * @param filename The filename to move from the templates 74 | * folder. 75 | * @protected 76 | */ 77 | protected safeExtract(title: string, filename: string) { 78 | return Preset.extract(filename).whenConflict('ask').withTitle(title); 79 | } 80 | 81 | protected getConfiguration(key): T { 82 | return Preset.isInteractive() ? Preset.prompts[key] : Preset.options[key]; 83 | } 84 | 85 | private initialiseAdder(): void { 86 | Preset.setName(this.ADDER_NAME); 87 | this.setupConfiguration(); 88 | this.setupDependencies(); 89 | } 90 | 91 | private setupConfiguration(): void { 92 | 93 | Object.keys(this.CONFIGURATION).forEach(configurationKey => { 94 | 95 | const configuration = this.CONFIGURATION[configurationKey]; 96 | 97 | this.configure( 98 | configurationKey, 99 | configuration.message, 100 | configuration.default, 101 | configuration.question || false 102 | ); 103 | }); 104 | } 105 | 106 | private setupDependencies(): void { 107 | 108 | Preset.group(preset => { 109 | 110 | Object.keys(this.REQUIRED_DEPENDENCIES).forEach(dependencyName => { 111 | 112 | const dependencyConfig = this.REQUIRED_DEPENDENCIES[dependencyName]; 113 | 114 | let action; 115 | 116 | switch (dependencyConfig.type) { 117 | case 'DEV': 118 | action = preset.editNodePackages().addDev(dependencyName, dependencyConfig.version); 119 | break; 120 | case 'PEER': 121 | action = preset.editNodePackages().addPeer(dependencyName, dependencyConfig.version); 122 | break; 123 | case undefined: 124 | action = preset.editNodePackages().add(dependencyName, dependencyConfig.version); 125 | break; 126 | } 127 | 128 | action.if(() => dependencyConfig.reliesOn 129 | ? Array.isArray(dependencyConfig.reliesOn) 130 | ? dependencyConfig.reliesOn.every(Boolean) 131 | : this.getConfiguration(dependencyConfig.reliesOn) 132 | : true 133 | ); 134 | }); 135 | 136 | }).withTitle('Adding required dependencies'); 137 | } 138 | 139 | private configure(key: string, msg: string, init: any, confirm: boolean): void { 140 | 141 | Preset 142 | .group(preset => { 143 | preset.confirm(key, msg, init).if(() => confirm); 144 | preset.input(key, msg, init).if(() => !confirm); 145 | }) 146 | .withoutTitle() 147 | .ifInteractive(); 148 | 149 | Preset 150 | .group(preset => preset.option(key, init)) 151 | .withoutTitle() 152 | .if(() => !Preset.isInteractive()); 153 | } 154 | } 155 | 156 | class SvelteJestAdder extends Adder { 157 | 158 | protected readonly ADDER_NAME = 'svelte-add-jest'; 159 | 160 | protected readonly CONFIGURATION: Configuration = { 161 | 'jest-dom': {message: 'Enable Jest DOM support?', default: true, question: true}, 162 | 'ts': {message: 'Enable TypeScript support?', default: false, question: true}, 163 | 'examples': {message: 'Generate example test file?', default: true, question: true}, 164 | 'jsdom': {message: 'Enable JSDOM environment by default?', default: true, question: true} 165 | }; 166 | 167 | protected readonly REQUIRED_DEPENDENCIES: Dependencies = { 168 | '@babel/core': {version: '^7.14.0', type: 'DEV'}, 169 | '@babel/preset-env': {version: '^7.14.0', type: 'DEV'}, 170 | 'jest': {version: '^27.0.0', type: 'DEV'}, 171 | 'babel-jest': {version: '^27.0.0', type: 'DEV'}, 172 | 'svelte-jester': {version: '^2.0.1', type: 'DEV'}, 173 | '@testing-library/svelte': {version: '^3.0.0', type: 'DEV'}, 174 | 'cross-env': {version: '^7.0.3', type: 'DEV'}, 175 | '@testing-library/jest-dom': {version: '^5.14.0', type: 'DEV', reliesOn: 'jest-dom'}, 176 | 'ts-jest': {version: '^27.0.0', type: 'DEV', reliesOn: 'ts'}, 177 | '@types/jest': {version: '^27.0.0', type: 'DEV', reliesOn: 'ts'}, 178 | '@types/testing-library__jest-dom': {version: '^5.14.0', type: 'DEV', reliesOn: ['ts', 'jest-dom']} 179 | }; 180 | 181 | run(): void { 182 | 183 | super.run(); 184 | 185 | this.safeExtract('Initializing Jest config', 'jest.config.json'); 186 | this.safeExtract('Initializing Babel config', '.babelrc'); 187 | 188 | Preset 189 | .editJson('jest.config.json') 190 | .merge({setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect']}) 191 | .withTitle('Enabling Jest DOM Support') 192 | .if(() => this.getConfiguration('jest-dom')); 193 | 194 | Preset 195 | .editJson('tsconfig.json').merge({ 196 | exclude: ['src/**/*.spec.ts'] 197 | }) 198 | .withTitle('Modifying TypeScript config for project') 199 | .if(() => this.getConfiguration('ts')); 200 | 201 | Preset 202 | .editJson('jest.config.json').merge({ 203 | transform: { 204 | '^.+\\.svelte$': ['svelte-jester/dist/transformer.mjs', {preprocess: true}], 205 | '^.+\\.ts$': 'ts-jest' 206 | }, 207 | moduleFileExtensions: ['ts'], 208 | extensionsToTreatAsEsm: ['.ts'], 209 | globals: { 210 | 'ts-jest': { 211 | tsconfig: 'tsconfig.spec.json', 212 | 'useESM': true 213 | } 214 | } 215 | }) 216 | .withTitle('Modifying Jest config for TypeScript transformation') 217 | .if(() => this.getConfiguration('ts')); 218 | 219 | Preset 220 | .editJson('jest.config.json').merge({ 221 | testEnvironment: 'jsdom' 222 | }) 223 | .withTitle('Modifying Jest config to enable JSDOM environment') 224 | .if(() => this.getConfiguration('jsdom')); 225 | 226 | this.safeExtract('Initializing TypeScript config for tests', 'tsconfig.spec.json') 227 | .if(() => this.getConfiguration('ts')); 228 | 229 | this.safeExtract('Initializing example test file', 'index.spec.ts') 230 | .to('src/routes/') 231 | .if(() => this.getConfiguration('examples') && this.getConfiguration('ts') && !this.getConfiguration('jest-dom')); 232 | 233 | this.safeExtract('Initializing example test file', 'index.spec.js') 234 | .to('src/routes/') 235 | .if(() => this.getConfiguration('examples') && !this.getConfiguration('ts') && !this.getConfiguration('jest-dom')); 236 | 237 | this.safeExtract('Initializing example test file', 'index-dom.spec.ts') 238 | .to('src/routes/') 239 | .if(() => this.getConfiguration('examples') && this.getConfiguration('ts') && this.getConfiguration('jest-dom')); 240 | 241 | this.safeExtract('Initializing example test file', 'index-dom.spec.js') 242 | .to('src/routes/') 243 | .if(() => this.getConfiguration('examples') && !this.getConfiguration('ts') && this.getConfiguration('jest-dom')); 244 | 245 | Preset 246 | .editJson('package.json') 247 | .merge({scripts: {'test': 'cross-env NODE_OPTIONS=--experimental-vm-modules jest src --config jest.config.json', 'test:watch': 'npm run test -- --watch'}}) 248 | .withTitle('Adding test scripts to package.json'); 249 | 250 | Preset 251 | .instruct(`Run ${color.magenta("npm install")}, ${color.magenta("pnpm install")}, or ${color.magenta("yarn")} to install dependencies`) 252 | .withHeading("What's next?"); 253 | } 254 | } 255 | 256 | new SvelteJestAdder().run(); 257 | -------------------------------------------------------------------------------- /templates/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /templates/index-dom.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { render } from '@testing-library/svelte'; 6 | import Index from './index.svelte'; 7 | 8 | /** 9 | * An example test suite outlining the usage of 10 | * `describe()`, `beforeEach()`, `test()` and `expect()` 11 | * 12 | * @see https://jestjs.io/docs/getting-started 13 | * @see https://github.com/testing-library/jest-dom 14 | */ 15 | 16 | describe('Index', () => { 17 | 18 | let renderedComponent; 19 | 20 | beforeEach(() => { 21 | renderedComponent = render(Index); 22 | }); 23 | 24 | describe('once the component has been rendered', () => { 25 | 26 | test('should show the proper heading', () => { 27 | expect(renderedComponent.getByText(/SvelteKit/)).toBeInTheDocument(); 28 | }); 29 | 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /templates/index-dom.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import { render, RenderResult } from '@testing-library/svelte'; 6 | import Index from './index.svelte'; 7 | 8 | /** 9 | * An example test suite outlining the usage of 10 | * `describe()`, `beforeEach()`, `test()` and `expect()` 11 | * 12 | * @see https://jestjs.io/docs/getting-started 13 | * @see https://github.com/testing-library/jest-dom 14 | */ 15 | 16 | describe('Index', () => { 17 | 18 | let renderedComponent: RenderResult; 19 | 20 | beforeEach(() => { 21 | renderedComponent = render(Index); 22 | }); 23 | 24 | describe('once the component has been rendered', () => { 25 | 26 | test('should show the proper heading', () => { 27 | expect(renderedComponent.getByText(/SvelteKit/)).toBeInTheDocument(); 28 | }); 29 | 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /templates/index.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An example test suite outlining the usage of 3 | * `describe()`, `beforeEach()`, `test()` and `expect()` 4 | * 5 | * @see https://jestjs.io/docs/getting-started 6 | */ 7 | 8 | describe('Index', () => { 9 | 10 | let isTestSuitePassing = false; 11 | 12 | beforeEach(() => { 13 | isTestSuitePassing = true; 14 | }); 15 | 16 | describe('isTestSuitePassing', () => { 17 | 18 | test('should be true', () => { 19 | expect(isTestSuitePassing).toBe(true); 20 | }); 21 | 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /templates/index.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An example test suite outlining the usage of 3 | * `describe()`, `beforeEach()`, `test()` and `expect()` 4 | * 5 | * @see https://jestjs.io/docs/getting-started 6 | */ 7 | 8 | describe('Index', () => { 9 | 10 | let isTestSuitePassing = false; 11 | 12 | beforeEach(() => { 13 | isTestSuitePassing = true; 14 | }); 15 | 16 | describe('isTestSuitePassing', () => { 17 | 18 | test('should be true', () => { 19 | expect(isTestSuitePassing).toBe(true); 20 | }); 21 | 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /templates/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.js$": "babel-jest", 4 | "^.+\\.svelte$": "svelte-jester" 5 | }, 6 | "moduleNameMapper": { 7 | "^\\$lib(.*)$": "/src/lib$1", 8 | "^\\$app(.*)$": ["/.svelte-kit/dev/runtime/app$1", "/.svelte-kit/build/runtime/app$1"] 9 | }, 10 | "extensionsToTreatAsEsm": [".svelte"], 11 | "moduleFileExtensions": ["js", "svelte"] 12 | } 13 | -------------------------------------------------------------------------------- /templates/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "isolatedModules": false 5 | }, 6 | "exclude": [ 7 | "src/**" 8 | ], 9 | "include": [ 10 | "src/**/*.spec.ts" 11 | ] 12 | } 13 | --------------------------------------------------------------------------------