├── setup.js ├── .babelrc ├── .gitignore ├── .prettierrc.json ├── src ├── setup.js └── index.js ├── .github └── workflows │ └── test.yaml ├── package.json ├── test └── controller.test.js └── README.md /setup.js: -------------------------------------------------------------------------------- 1 | require('./src/setup.js'); 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | yarn-error.log 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "es5", 4 | "tabWidth": 4, 5 | "jsxBracketSameLine": true, 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /src/setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Symfony package. 3 | * 4 | * (c) Fabien Potencier 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 'use strict'; 11 | 12 | require('mutationobserver-shim'); 13 | require('regenerator-runtime/runtime.js'); 14 | require('@testing-library/jest-dom'); 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Symfony package. 3 | * 4 | * (c) Fabien Potencier 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 'use strict'; 11 | 12 | module.exports.mountDOM = (htmlString = '') => { 13 | const div = document.createElement('div'); 14 | div.innerHTML = htmlString; 15 | document.body.appendChild(div); 16 | 17 | return div; 18 | }; 19 | 20 | module.exports.clearDOM = () => { 21 | document.body.innerHTML = ''; 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Symfony UX 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | coding-style: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - name: Prettier 11 | run: | 12 | yarn add prettier@^2.2.0 13 | yarn run prettier --check {src,test}/**/*.js --config .prettierrc.json 14 | 15 | tests: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@master 19 | - run: | 20 | yarn 21 | yarn test 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@symfony/stimulus-testing", 3 | "description": "@testing-library integration for Symfony UX", 4 | "version": "2.1.0", 5 | "main": "src/index.js", 6 | "license": "MIT", 7 | "author": "Titouan Galopin ", 8 | "engines": { 9 | "node": "^10.13.0 || >=12.0.0" 10 | }, 11 | "scripts": { 12 | "test": "jest" 13 | }, 14 | "dependencies": { 15 | "@testing-library/dom": "^7.28.1", 16 | "@testing-library/jest-dom": "^5.11.6", 17 | "@testing-library/user-event": "^12.2.2", 18 | "babel-jest": "^26.6.3", 19 | "jest": "^26.6.3", 20 | "mutationobserver-shim": "^0.3.7", 21 | "regenerator-runtime": "^0.13.7" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.12.3", 25 | "@babel/preset-env": "^7.12.7", 26 | "@hotwired/stimulus": "^3.0.0" 27 | }, 28 | "jest": { 29 | "testRegex": "test/.*\\.test.js", 30 | "setupFilesAfterEnv": [ 31 | "./setup.js" 32 | ] 33 | }, 34 | "files": [ 35 | "src/", 36 | "setup.js" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/controller.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Symfony package. 3 | * 4 | * (c) Fabien Potencier 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 'use strict'; 11 | 12 | import { Application, Controller } from '@hotwired/stimulus'; 13 | import { getByTestId, waitFor } from '@testing-library/dom'; 14 | import { clearDOM, mountDOM } from '../src/index'; 15 | 16 | // Controller used to check the actual controller was properly booted 17 | class AppController extends Controller { 18 | connect() { 19 | this.element.classList.add('connected'); 20 | } 21 | } 22 | 23 | const startStimulus = () => { 24 | const application = Application.start(); 25 | application.register('app', AppController); 26 | }; 27 | 28 | describe('AppController', () => { 29 | let container; 30 | 31 | beforeEach(() => { 32 | container = mountDOM(` 33 |
34 | `); 35 | }); 36 | 37 | afterEach(() => { 38 | clearDOM(); 39 | }); 40 | 41 | it('connect', async () => { 42 | expect(getByTestId(container, 'app')).not.toHaveClass('connected'); 43 | 44 | startStimulus(); 45 | await waitFor(() => expect(getByTestId(container, 'app')).toHaveClass('connected')); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony UX Stimulus testing 2 | 3 | > [!WARNING] 4 | > **Deprecated**: This package is deprecated and will not receive any further updates. 5 | 6 | Because this package only provides very small helpers to help write tests for Stimulus controllers, and is tightly coupled with [Jest](https://jestjs.io/), [jsdom](https://github.com/jsdom/jsdom) and [Testing Library](https://testing-library.com/) dependencies, we can no longer recommend it. 7 | 8 | In 2025, we cannot force developers to install Jest (and [~270 sub-dependencies](https://npmgraph.js.org/?q=jest) including [Babel](https://babeljs.io/)) and the like, since [many test runners exist](https://npmtrends.com/ava-vs-japa-vs-jasmine-vs-jest-vs-karma-vs-mocha-vs-tap-vs-vitest), and many of them are more modern and much faster, like [Vitest](https://vitest.dev/). 9 | 10 | We want to give you the choice to use the best tools for your needs, and not force you to use what we suggested in the past. 11 | 12 | To migrate from `@symfony/stimulus-testing`, you can follow these steps: 13 | 14 | 1. Install the dev dependencies `@testing-library/jest-dom @testing-library/dom`; 15 | you may also want to install `mutationobserver-shim regenerator-runtime` if you still have 16 | legacy code or _architecture_. 17 | 2. In the file `assets/test/setup.js`, replace imports: 18 | ```diff 19 | -import '@symfony/stimulus-testing/setup'; 20 | +import '@testing-library/jest-dom'; 21 | ``` 22 | 3. Create the file `assets/test/stimulus-helpers.js` with the following content: 23 | ```js 24 | export function mountDOM(html = '') { 25 | const div = document.createElement('div'); 26 | div.innerHTML = html; 27 | document.body.appendChild(div); 28 | 29 | return div; 30 | } 31 | 32 | export function clearDOM() { 33 | document.body.innerHTML = ''; 34 | } 35 | ``` 36 | 4. In your tests files, replace imports for `mountDOM` and `clearDOM`: 37 | ```diff 38 | // assets/test/controllers/hello_controller.test.js 39 | -import { clearDOM, mountDOM } from '@symfony/stimulus-testing'; 40 | +import { clearDOM, mountDOM } from '../stimulus-helpers'; 41 | ``` 42 | 5. And finally, remove the `@symfony/stimulus-testing` dependency from your project. 43 | 44 | --- 45 | 46 | Symfony UX Stimulus testing is a low-level package to help write tests for Stimulus controllers 47 | in applications and reusable packages. 48 | 49 | Symfony UX Stimulus testing is currently considered **experimental**. 50 | 51 | ## Installation 52 | 53 | ```sh 54 | yarn add @symfony/stimulus-testing 55 | ``` 56 | 57 | ## Usage 58 | 59 | Symfony UX Stimulus testing ships several tools to help write tests for Stimulus controllers: 60 | 61 | * it uses [Jest](https://jestjs.io/) as test runner ; 62 | * it relies internally on [jsdom](https://github.com/jsdom/jsdom) and mutationobserver-shim to emulate a DOM 63 | implementation and allow to execute Stimulus controllers in the console ; 64 | * it provides an integration of [Testing Library](https://testing-library.com/) ; 65 | * it provides helper functions to ease Stimulus tests development in Symfony projects and bundles ; 66 | 67 | To start using Symfony UX Testing, you first need to configure a testing environment: 68 | 69 | 1. Create a `assets/test` directory ; 70 | 71 | 2. Create a `assets/test/setup.js` file to initialize Symfony UX Testing: 72 | 73 | ```js 74 | import '@symfony/stimulus-testing/setup'; 75 | ``` 76 | 77 | 3. Create a `assets/jest.config.js` file for Jest configuration: 78 | 79 | ```js 80 | module.exports = { 81 | 'testRegex': 'test/.*\\.test.js', 82 | 'setupFilesAfterEnv': ['./test/setup.js'] 83 | }; 84 | ``` 85 | 86 | 4. Create a `assets/.babelrc` file for Babel configuration (you may need to install Babel, 87 | `@babel/plugin-proposal-class-properties` and `@babel/preset-env` if you haven't already): 88 | 89 | ```json 90 | { 91 | "presets": ["@babel/preset-env"], 92 | "plugins": ["@babel/plugin-proposal-class-properties"] 93 | } 94 | ``` 95 | 96 | 5. Finally, create your first test, for instance `hello_controller.test.js`: 97 | 98 | ```js 99 | import { Application } from '@hotwired/stimulus'; 100 | import { clearDOM, mountDOM } from '@symfony/stimulus-testing'; 101 | import HelloController from '../controllers/hello_controller.js'; 102 | 103 | const startStimulus = () => { 104 | const application = Application.start(); 105 | application.register('hello', HelloController); 106 | }; 107 | 108 | describe('HelloController', () => { 109 | let container; 110 | 111 | beforeEach(() => { 112 | container = mountDOM('
'); 113 | }); 114 | 115 | afterEach(() => { 116 | clearDOM(); 117 | }); 118 | 119 | it('connect', async () => { 120 | startStimulus(); 121 | 122 | // Write a test here ... 123 | }); 124 | 125 | // You can create other tests here 126 | }); 127 | ``` 128 | --------------------------------------------------------------------------------