Skip to content

Instantly share code, notes, and snippets.

@bholtbholt
Last active June 20, 2024 08:34
Show Gist options
  • Save bholtbholt/c8351665a861aee62e915d8b32e2c759 to your computer and use it in GitHub Desktop.
Save bholtbholt/c8351665a861aee62e915d8b32e2c759 to your computer and use it in GitHub Desktop.
Testing Stimulus with Jest in a Rails App. Stimulus isn't mounted before the test runs, so these helpers wrap the calls in async functions to fix race conditions.
// This helper file provides a consistent API for testing Stimulus Controllers
//
// Use:
// import { getHTML, setHTML, startStimulus } from './_stimulus_helper';
// import MyController from '@javascripts/controllers/my_controller';
//
// beforeEach(() => startStimulus('my', MyController));
// test('should do something', async () => {
// await setHTML(`<button data-controller="my" data-action="my#action">click</button>`);
//
// const button = screen.getByText('click');
// await button.click();
//
// expect(getHTML()).toEqual('something');
// });
//
import { Application } from '@hotwired/stimulus';
// Initializes and registers the controller for the test file
// Use it in a before block:
// beforeEach(() => startStimulus('dom', DomController));
//
// @name = string of the controller
// @controller = controller class
//
// https://stimulus.hotwired.dev/handbook/installing#using-other-build-systems
export function startStimulus(name, controller) {
const application = Application.start();
application.register(name, controller);
}
// Helper function for setting HTML
// - It trims content to prevent false negatives
// - It's async so there's time for the Stimulus controller to load
//
// Use within tests:
// await setHTML(`<p>My HTML Content</p>`);
export async function setHTML(content = '') {
document.body.innerHTML = content.trim();
return document.body.innerHTML;
}
// Helper function for getting HTML content
// - Trims content to prevent false negatives
// - Is consistent with setHTML
//
// Use within tests:
// expect(getHTML()).toEqual('something');
export function getHTML() {
return document.body.innerHTML.trim();
}
import { screen } from '@testing-library/dom';
import { getHTML, setHTML, startStimulus } from './_stimulus_helper';
import DomController from '@javascripts/controllers/dom_controller';
beforeEach(() => startStimulus('dom', DomController));
test('should remove itself', async () => {
await setHTML(`<button data-controller="dom" data-action="dom#remove">remove</button>`);
const button = screen.getByText('remove');
await button.click();
expect(getHTML()).toEqual('');
});
test('should remove the parent element', async () => {
await setHTML(`
<div data-controller="dom">
<a data-action="dom#remove:once">remove</a>
</div>
`);
const button = screen.getByText('remove');
await button.click();
expect(getHTML()).toEqual('');
});
module.exports = {
setupFilesAfterEnv: ['./jest.setup.js'],
testMatch: ['**/test/**/*.test.js'],
cacheDirectory: './tmp/cache/jest',
moduleNameMapper: {
'@javascripts(.*)$': '<rootDir>/app/assets/javascripts$1',
},
testEnvironment: 'jsdom',
transformIgnorePatterns: ['node_modules']
};
import 'regenerator-runtime/runtime';
import '@testing-library/jest-dom';
@gwdox
Copy link

gwdox commented Mar 21, 2023

This is great! Did you ever bring in Slim templates for testing? I am writing controller tests that work on .html.slim files and I'm finding that I have to duplicate the HTML.

@bholtbholt
Copy link
Author

@gwdox I used Slim templates in this app, but found it easier to use setHTML (above) than import and parse slim. It means you miss an integration step in the tests -- the tested HTML markup may not be what's actually in use -- but I was more focused on unit testing. The Stimulus controller actually worked as documented and would work with identical setup.

You could probably use the NPM Slim package to parse slim, and have that pass through as a function. I'm not sure exactly which function they use though.

@gwdox
Copy link

gwdox commented Mar 25, 2023

Thanks. Will report back if I figure out how to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment