| name | write-block-tests |
| description | Write Jest unit tests for Edge Delivery Services (EDS) blocks using jsdom HTML fixtures and decorate assertions. Use when adding or updating block tests, co-locating *.test.js files, mocking /scripts/ imports, or documenting the project's block testing strategy. |
EDS Block Testing
Stack
- Jest with
jsdom (npm test)
@testing-library/jest-dom matchers (via tests/setupTests.js)
@testing-library/dom queries for behavioral tests (getByRole, getByLabelText, etc.)
Pattern
- Co-locate:
blocks/<block>/<block>.test.js
- Build an HTML fixture matching the markup
decorate receives (block element after authoring/EDS processing)
- Call
decorate(block) with that fixture
- Assert DOM changes (
classes, structure, attributes, text, ARIA)
What to test
- Conditional logic (e.g. cases where a class is added or not added)
- Structural changes (e.g. moved/replaced nodes, added semantic markup)
- Content rendering (e.g. the content is rendered in the correct node)
- Behaviors: after
decorate, query controls with @testing-library/dom (getByRole, getByLabelText) and drive interactions with native DOM APIs (.click(), dispatchEvent)
- e.g.
within(block).getByRole('button', { name: 'Open navigation' }).click()
- e.g. Escape / focusout closes an open menu
- Append the decorated block to
document.body when handlers use document.getElementById or other global lookups
- Always try to query elements by accessible roles first. Fall back to block selectors only when the control has no accessible role or name.
Mocking
Mock /scripts/ and cross-block helpers so tests stay unit-scoped (no network, no full page bootstrap). Prefer jest.mock on the module the block imports.
getMetadata (scripts/aem.js): return authored meta strings (or '') so path/feature branches are covered without <meta> tags. Vary return values per test for default vs custom paths (e.g. footer/nav).
loadFragment (blocks/fragment/fragment.js): resolve to a minimal DOM fragment (DocumentFragment or element with the children decorate expects). Do not hit real fragment URLs.
- Other
aem.js helpers (createOptimizedPicture, loadCSS, etc.): stub to simple passthroughs or no-ops when decorate calls them.
- Assert the block called mocks with the expected args when the contract matters (path, meta name); otherwise only assert decorate’s DOM output.
- Keep mocks local to the test file; reset with
jest.clearAllMocks() in beforeEach when return values change per case.
- Do not reference
document (or other out-of-scope values) inside a jest.mock factory — Jest hoists factories and rejects that. Stub with jest.fn() and set DOM return values in beforeEach.
Example shape:
jest.mock('../../scripts/aem.js', () => ({
getMetadata: jest.fn(() => ''),
}));
jest.mock('../fragment/fragment.js', () => ({
loadFragment: jest.fn(),
}));
function createFragment(html) {
const wrap = document.createElement('div');
wrap.innerHTML = html;
return wrap;
}
beforeEach(() => {
jest.clearAllMocks();
getMetadata.mockReturnValue('');
loadFragment.mockResolvedValue(
createFragment('<div class="section"><p>Footer</p></div>'),
);
});
Test style
- Jest +
@testing-library/jest-dom matchers
- Imperative test names: "adds columns-img-col when …" (not "should …" / "correctly …")
Examples
Decorate
When testing the decorate function, assert that the DOM has been modified as expected:
it('adds a class to the paragraph', () => {
const block = document.createElement('div');
block.innerHTML = '<p>Hello</p>';
decorate(block);
expect(block.querySelector('p')).toHaveClass('decorated-class');
});
Behavior
Mock deps and set up fixtures in beforeEach (see Mocking). Append to document.body when handlers use global lookups. Query by role or accessible name; scope queries with within(block).
import { within } from '@testing-library/dom';
it('toggles the mobile menu on hamburger click', async () => {
const block = document.createElement('div');
await decorate(block);
within(block).getByRole('button', { name: 'Open navigation' }).click();
expect(within(block).getByRole('navigation')).toHaveAttribute('aria-expanded', 'true');
});
Checklist
- [ ] Test file co-located with the block
- [ ] Fixture mirrors decorate input markup
- [ ] decorate(block) called under test
- [ ] Conditional rendering logic for block is covered
- [ ] Assertions target decoration outcomes
- [ ] /scripts/ deps mocked when needed
- [ ] npm test passes