├── .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 |
--------------------------------------------------------------------------------