Created
April 29, 2025 22:13
-
-
Save markgandolfo/a2f5cf66ee1c622282be1ec4a198fd13 to your computer and use it in GitHub Desktop.
Stimulus Controller testing in rails/jest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This helper file provides a consistent API for testing Stimulus Controllers | |
// From: https://gist.github.com/bholtbholt/c8351665a861aee62e915d8b32e2c759 | |
// put the file in /spec/javascript/support/_stimulus-helper.js | |
// | |
// 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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example test. | |
// it'll live in /spec/javascript/controllers/<filename>>controller.test.js | |
import { screen } from "@testing-library/dom"; | |
import TruncatableTextController from "controllers/truncatable_text_controller.js"; | |
import { setHTML, startStimulus } from "../support/_stimulus-helper.js"; | |
beforeEach(() => startStimulus("truncatable-text", TruncatableTextController)); | |
test("should toggle content visibility when buttons are clicked", async () => { | |
await setHTML(` | |
<div data-controller="truncatable-text"> | |
<div data-truncatable-text-target="truncatedContent"> | |
Truncated Content | |
<button data-action="click->truncatable-text#showFull">Show more</button> | |
</div> | |
<div data-truncatable-text-target="fullContent" class="hidden"> | |
Full Content | |
<button data-action="click->truncatable-text#showLess">Show less</button> | |
</div> | |
</div>`); | |
// Get elements | |
const truncatedContent = screen | |
.getByText(/Truncated Content/i) | |
.closest('[data-truncatable-text-target="truncatedContent"]'); | |
const fullContent = screen | |
.getByText(/Full Content/i) | |
.closest('[data-truncatable-text-target="fullContent"]'); | |
const showMoreButton = screen.getByText("Show more"); | |
// Initial state | |
expect(truncatedContent).not.toHaveClass("hidden"); | |
expect(fullContent).toHaveClass("hidden"); | |
// Click "Show more" | |
showMoreButton.click(showMoreButton); | |
// Verify state after clicking "Show more" | |
expect(truncatedContent).toHaveClass("hidden"); | |
expect(fullContent).not.toHaveClass("hidden"); | |
// Click "Show less" | |
const showLessButton = screen.getByText("Show less"); | |
showLessButton.click(showLessButton); | |
// Verify state is back to initial | |
expect(truncatedContent).not.toHaveClass("hidden"); | |
expect(fullContent).toHaveClass("hidden"); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jest.config.js | |
module.exports = { | |
// Where Jest should look for test files | |
roots: ["<rootDir>/spec/javascript"], | |
// The environment Jest should use to run tests | |
// 'jsdom' simulates a browser environment, 'node' uses Node.js environment | |
testEnvironment: "jsdom", | |
// File patterns Jest should consider as test files | |
testMatch: [ | |
"**/spec/javascript/**/*.test.js", | |
"**/spec/javascript/**/*.spec.js", | |
], | |
// Module Name Mapper: THIS IS KEY FOR IMPORTMAPS | |
// This tells Jest how to resolve module paths that you use in your import statements, | |
// mimicking how importmap-rails resolves them in the browser. | |
moduleNameMapper: { | |
"^application$": "<rootDir>/app/javascript/application.js", | |
"^helpers/(.*)$": "<rootDir>/app/javascript/helpers/$1", | |
"^controllers/(.*)$": "<rootDir>/app/javascript/controllers/$1", | |
}, | |
transform: { | |
"^.+\\.js$": "babel-jest", | |
}, | |
transformIgnorePatterns: ["node_modules/(?!(@hotwired)/)"], | |
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"], | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "@testing-library/jest-dom"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"devDependencies": { | |
"@babel/core": "^7.26.10", | |
"@babel/preset-env": "^7.26.9", | |
"@hotwired/stimulus": "^3.2.2", | |
"@testing-library/dom": "^10.4.0", | |
"@testing-library/jest-dom": "^6.6.3", | |
"babel-jest": "^29.7.0", | |
"jest": "^29.7.0", | |
"jest-environment-jsdom": "^29.7.0" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment