-
-
Save bholtbholt/c8351665a861aee62e915d8b32e2c759 to your computer and use it in GitHub Desktop.
// 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 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.
Thanks. Will report back if I figure out how to do it.
thx for sharing! How do you load the JS in Rails? Do you use importmaps or an builder?
IIRC we used js-bundling
with web pack. I wrote the original docs for the setup here, but it's been a couple years so I'm not sure it's still the best way.
Personally, I'd use Vite-Ruby today.
IIRC we used
js-bundling
with web pack. I wrote the original docs for the setup here, but it's been a couple years so I'm not sure it's still the best way.Personally, I'd use Vite-Ruby today.
Thank you for your response + links!
I'm actually in progress migrating an webpacker project towards vite-ruby. Beeing a little unsure if this is a good decision (not leading to next migration in a year or two) your comment supporting it 😅
@t3k4y I've been using Vite in my JS projects since ~2021 or maybe even 2020. I'm familiar and comfortable with Webpack, but it's a lot of configuration. I think Webpacker did a good job abstracting Webpack for Rails, but it felt very complicated. In Rails projects, I often went the more explicit route and tried to get as close to raw web pack as possible. In the project I was on when I wrote this gist, we were pretty happy to move away from Webpacker and were actively moving as close to vanilla Rails 7 as possible, but still relied on Node for richer JS libraries. I can't recall if vite-ruby was around yet or maybe it was untested or the team was skeptical, but for whatever reason it didn't get much consideration.
I contracted on a Rails project recently where we used vite-ruby, TypeScript, Svelte, and InertiaJS and it was a really nice dev experience. The setup was pretty straightforward and any complexity was between the JS libraries themselves, not Rails or the bundler. (For example, Storybook didn't support Svelte very well.)
At this point in time, I think many JS projects are moving off of webpack when they can and adding deeper Vite support. Vite powers VueJS and Svelte by default, as well as several React Frameworks (Remix for example). I'd say it's a pretty safe bet.
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.