UI library examples
These task workspaces use real component libraries to exercise geometry, portals, scrolling, overlays, hit testing, and dynamic layout. The hosted pages run in a browser; their repository tests run the same workflows in happy-dom with DOM Layout Shim attached.
Current compatibility findings
Agreement measures how often the shim matches Chromium across three equally weighted comparisons: geometry fields within 1 px, visibility state, and the element receiving a center-point hit test. A checkpoint percentage averages those three comparisons; overall agreement averages all checkpoints. Observation coverage is the share of declared elements captured in both environments. Differences are recorded for inspection and are not failures.
Material UI
@mui/material
Use DOM Layout Shim with Material UITest setup and first assertion
The example uses ordinary Material UI components. DOM Layout Shim is attached only in the happy-dom test environment; no adapter or production application change is required.
1. Use happy-dom in Vitest
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Give React a DOM-like window that the layout shim can patch.
environment: 'happy-dom',
include: ['test/**/*.test.tsx'],
setupFiles: ['./test/setup.ts'],
},
});
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import {
attachLayoutEngine,
createUnsupportedCssReporter,
} from 'dom-layout-shim';
// Tell React that state updates are intentionally coordinated with `act()`.
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
// Vitest transforms CSS imports without reliably installing their rules in
// happy-dom, so expose the same authored stylesheet that the browser app loads.
const exampleStyles = await readFile(
resolve(process.cwd(), 'src/styles.css'),
'utf8',
);
const style = window.document.createElement('style');
style.textContent = exampleStyles;
window.document.head.append(style);
// Attach once with shared defaults; tests can override only what they exercise.
export const unsupportedCssReporter = createUnsupportedCssReporter();
export const layoutEngine = await attachLayoutEngine({
window,
unsupportedCss: {
default: 'warn',
onWarning: unsupportedCssReporter.onWarning,
},
});
2. Render React normally
// Mount the real Material UI tree exactly as the consumer test uses it.
document.body.innerHTML = '<div id="app"></div>';
reactRoot = createRoot(requiredElement('#app'));
await act(async () => reactRoot.render(<TaskWorkspace />));
3. Click where the element is
export function clickWhereElementIs(element: HTMLElement): void {
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const hitTarget = element.ownerDocument.elementFromPoint(x, y);
if (!hitTarget || (hitTarget !== element && !element.contains(hitTarget))) {
throw new Error(
`Expected ${element.tagName.toLowerCase()} to receive the click at (${x}, ${y})`,
);
}
hitTarget.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
}),
);
}
The utility measures the element's center, asks the document which element owns
that point, and refuses to click through an overlay. It dispatches the event to
the actual hit target so descendants behave like a coordinate-based browser
click. Call it inside React's act() to synchronize resulting state updates.
The example ignores unsupported declarations because Material UI emits many visual-only rules. Use the default warning policy, or narrow overrides, when unsupported declarations should remain visible in your own suite.
4. Assert the reachable element
const underlyingControl = requiredElement<HTMLElement>(
'[data-layout-key="underlying-control"]',
);
const rect = underlyingControl.getBoundingClientRect();
// Query the visual center instead of bypassing layout with `.click()`.
const point = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
};
expect(rect.width).toBeGreaterThan(0);
expect(document.elementFromPoint(point.x, point.y)).toBe(underlyingControl);
The same approach works for Material UI portals: open the real menu or dialog, then query geometry and hit targets through normal DOM APIs.
Interaction checkpoints
Initial task list86.9%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 60.7%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
7 observations · 11 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · y
[data-layout-key="task-1"]expected164.03· observed196.05 - geometry · height
[data-layout-key="task-1"]expected73.02· observed68.22 - geometry · y
[data-layout-key="task-2"]expected237.05· observed264.27 - geometry · height
[data-layout-key="task-2"]expected73.02· observed68.22 - geometry · y
[data-layout-key="task-3"]expected310.06· observed332.49 - geometry · height
[data-layout-key="task-3"]expected73.02· observed68.22
Active filter87.5%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 62.5%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 9 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · y
[data-layout-key="task-1"]expected164.03· observed196.05 - geometry · height
[data-layout-key="task-1"]expected73.02· observed68.22 - geometry · y
[data-layout-key="task-3"]expected237.05· observed264.27 - geometry · height
[data-layout-key="task-3"]expected73.02· observed68.22
Add dialog open83.3%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 66.7%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 83.3%
6 observations · 9 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · y
[data-layout-key="add-dialog"]expected212.25· observed235.25 - geometry · height
[data-layout-key="add-dialog"]expected295.5· observed249.5 - geometry · y
[data-layout-key="task-title-input"]expected284.25· observed307.25 - hit testing
[data-layout-key="task-title-input"]expected[data-layout-key="task-title-input"]· observed[data-layout-key="add-dialog"]
Task added88.3%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 65.0%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
5 observations · 7 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · y
[data-layout-key="task-4"]expected383.08· observed400.71 - geometry · height
[data-layout-key="task-4"]expected73.02· observed68.22
Action menu open88.9%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 66.7%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 8 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · x
[data-layout-key="delete-action"]expected1031· observed1020 - geometry · y
[data-layout-key="delete-action"]expected230· observed260 - geometry · width
[data-layout-key="delete-action"]expected113.7· observed123.25
Delete dialog open87.5%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 62.5%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 9 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · x
[data-layout-key="delete-dialog"]expected346.91· observed340 - geometry · y
[data-layout-key="delete-dialog"]expected279.75· observed267.75 - geometry · width
[data-layout-key="delete-dialog"]expected586.19· observed600 - geometry · height
[data-layout-key="delete-dialog"]expected160.5· observed184.5
Dialog dismissed87.5%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 62.5%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 9 differences
- geometry · height
[data-layout-key="sidebar"]expected444.03· observed476.05 - geometry · y
[data-layout-key="task-list"]expected156.03· observed188.05 - geometry · x
[data-layout-key="underlying-control"]expected992.08· observed995 - geometry · y
[data-layout-key="underlying-control"]expected95.77· observed111.78 - geometry · width
[data-layout-key="underlying-control"]expected103.92· observed101 - geometry · y
[data-layout-key="task-1"]expected164.03· observed196.05 - geometry · height
[data-layout-key="task-1"]expected73.02· observed68.22 - geometry · y
[data-layout-key="task-4"]expected383.08· observed400.71 - geometry · height
[data-layout-key="task-4"]expected73.02· observed68.22
Most repeated layout differences
-
geometry · height
[data-layout-key="sidebar"]7 checkpoints -
geometry · y
[data-layout-key="task-list"]local-layout 7 checkpoints -
geometry · x
[data-layout-key="underlying-control"]local-layout 7 checkpoints -
geometry · y
[data-layout-key="underlying-control"]local-layout 7 checkpoints -
geometry · width
[data-layout-key="underlying-control"]7 checkpoints -
geometry · y
[data-layout-key="task-1"]ancestor-offset 3 checkpoints -
geometry · height
[data-layout-key="task-1"]3 checkpoints -
geometry · height
[data-layout-key="task-3"]2 checkpoints
Unsupported CSS observed (20 properties)
-
observed layout
displayAuthored: boxComputed: flex, inline-flex 25 occurrences -
observed unclassified
box-alignAuthored: center 17 occurrences -
observed unclassified
flex-alignAuthored: center 17 occurrences -
observed unclassified
box-packAuthored: center, end, justify… 16 occurrences -
observed unclassified
flex-packAuthored: center, end, start 15 occurrences -
observed unclassified
-webkit-tap-highlight-colorAuthored: transparentComputed: transparent 9 occurrences -
observed unclassified
-webkit-overflow-scrollingAuthored: touchComputed: touch 3 occurrences -
observed unclassified
flex-negativeAuthored: 0 3 occurrences -
observed unclassified
text-alignAuthored: center, leftComputed: center, left 3 occurrences -
observed unclassified
text-overflowAuthored: ellipsisComputed: ellipsis 3 occurrences -
observed unclassified
animation-durationComputed: 10ms1 complex parsed value available in the JSON report 2 occurrences -
observed unclassified
animation-nameComputed: mui-auto-fill-cancel1 complex parsed value available in the JSON report 2 occurrences
Ant Design
antd
Use DOM Layout Shim with Ant DesignTest setup and first assertion
The example mounts real Ant Design components and portals. DOM Layout Shim is a test-only attachment; the application does not import it or use an Ant-specific adapter.
1. Configure happy-dom and React tests
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Give React a DOM-like window that the layout shim can patch.
environment: 'happy-dom',
include: ['test/**/*.test.tsx'],
setupFiles: ['./test/setup.ts'],
// Real Ant Design observer and animation phases can exceed Vitest's 5s
// default on slower CI runners while still settling normally in auto mode.
testTimeout: 10_000,
},
});
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import {
attachLayoutEngine,
createUnsupportedCssReporter,
} from 'dom-layout-shim';
// Tell React that state updates are intentionally coordinated with `act()`.
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
// Vitest transforms CSS imports without adding them to happy-dom, so install the
// example's authored stylesheet before the layout engine reads computed styles.
const exampleStyles = await readFile(
resolve(process.cwd(), 'src/styles.css'),
'utf8',
);
const style = window.document.createElement('style');
style.textContent = exampleStyles;
window.document.head.append(style);
// Attach once with shared defaults; tests can override only what they exercise.
export const unsupportedCssReporter = createUnsupportedCssReporter();
export const layoutEngine = await attachLayoutEngine({
window,
// Exercise the default automatic observer delivery used by consumers. Parsed
// stylesheet rules are reused across Ant Design's observer-driven DOM phases.
unsupportedCss: {
default: 'warn',
onWarning: unsupportedCssReporter.onWarning,
},
});
2. Mount the application normally
// Mount the real Ant Design tree, including its normal portal behavior.
document.body.innerHTML = '<main id="app"></main>';
const container = document.querySelector('#app');
if (!container) throw new Error('Missing example application root');
await act(async () => {
root = mountTaskWorkspace(container);
await nextAnimationFrame();
});
3. Click where the element is
export function clickWhereElementIs(element: HTMLElement): void {
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const hitTarget = element.ownerDocument.elementFromPoint(x, y);
if (!hitTarget || (hitTarget !== element && !element.contains(hitTarget))) {
throw new Error(
`Expected ${element.tagName.toLowerCase()} to receive the click at (${x}, ${y})`,
);
}
hitTarget.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
}),
);
}
The utility measures the element's center, asks the document which element owns
that point, and refuses to click through an overlay. It dispatches the event to
the actual hit target so descendants behave like a coordinate-based browser
click. Call it inside React's act() to synchronize resulting state updates.
The example ignores unsupported declarations because Ant Design emits many visual-only rules. Use the default warning policy, or narrow overrides, when unsupported declarations should remain visible in your own suite.
4. Use geometry-aware assertions
Before opening the modal:
// This helper clicks the geometry-derived center rather than bypassing layout.
expectReceivesPointer(addTask);
After opening the real Ant Design modal:
// Ant Design places its full-screen interaction wrapper above the visual mask.
// Verify that this real modal layer wins the same point while the modal is open.
expectBlockedBy(addTask, modalWrap);
The helpers derive interaction points from element geometry, so the test checks
whether overlays actually cover or expose a control instead of bypassing layout
with a direct .click().
Interaction checkpoints
Initial task list97.6%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 92.9%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
7 observations · 2 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110
Active filter97.2%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 91.7%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 2 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110
Add dialog open76.4%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 79.2%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 50.0%
6 observations · 8 differences
- hit testing
[data-layout-key="task-workspace"]expected[data-layout-key="task-title-input"]· observed[data-layout-key="task-description-input"] - hit testing
[data-layout-key="task-list"]expected[data-layout-key="add-dialog"]· observed[data-layout-key="task-description-input"] - geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110 - geometry · y
[data-layout-key="add-dialog"]expected624· observed752 - hit testing
[data-layout-key="add-dialog"]expected[data-layout-key="add-dialog"]· observednull - geometry · y
[data-layout-key="task-title-input"]expected302· observed272 - geometry · height
[data-layout-key="task-title-input"]expected32· observed23
Task added96.7%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 90.0%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
5 observations · 2 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110
Action menu open75.0%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 58.3%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 66.7%
6 observations · 12 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110 - geometry · x
.ant-dropdownexpected938.95· observed-12800 - geometry · y
.ant-dropdownexpected220· observed-6448 - geometry · width
.ant-dropdownexpected124.05· observed340 - geometry · height
.ant-dropdownexpected40· observed168 - hit testing
.ant-dropdownexpectedspan· observedli - geometry · x
.ant-dropdown-menu-item-dangerexpected942.95· observed-12796 - geometry · y
.ant-dropdown-menu-item-dangerexpected224· observed-6444 - geometry · width
.ant-dropdown-menu-item-dangerexpected116.05· observed332 - geometry · height
.ant-dropdown-menu-item-dangerexpected32· observed160 - hit testing
.ant-dropdown-menu-item-dangerexpectedspan· observedli
Delete dialog open90.3%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 87.5%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 83.3%
6 observations · 4 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110 - geometry · y
[data-layout-key="delete-dialog"]expected624· observed771.2 - hit testing
[data-layout-key="delete-dialog"]expected[data-layout-key="delete-dialog"]· observednull
Dialog dismissed97.2%
- Coverage Tracked elements captured in both environments
- 100.0%
- Geometry
x,y, width, and height fields within 1 px - 91.7%
- Visibility Displayed or hidden state matches Chromium
- 100.0%
- Hit testing Center point resolves to the same element
- 100.0%
6 observations · 2 differences
- geometry · x
[data-layout-key="add-task"]expected983.52· observed982 - geometry · width
[data-layout-key="add-task"]expected108.48· observed110
Most repeated layout differences
-
geometry · x
[data-layout-key="add-task"]local-layout 7 checkpoints -
geometry · width
[data-layout-key="add-task"]7 checkpoints -
geometry · x
.ant-dropdownlocal-layout 1 checkpoint -
geometry · y
.ant-dropdownlocal-layout 1 checkpoint -
geometry · width
.ant-dropdown1 checkpoint -
geometry · height
.ant-dropdown1 checkpoint -
hit-testing
.ant-dropdown1 checkpoint -
geometry · x
.ant-dropdown-menu-item-dangerancestor-offset 1 checkpoint
Unsupported CSS observed (26 properties)
-
observed layout
widthAuthored: var(--ant-modal-lg-width), var(--ant-modal-md-width), var(--ant-modal-sm-width)…Computed: 520px 5 occurrences -
observed layout
borderAuthored: var(--ant-btn-border-width) var(--ant-btn-border-style) var(--ant-btn-border-color), var(--ant-line-width) var(--ant-line-type) var(--ant-color-border)Computed: 1px solid #d9d9d9, 1px solid transparent 2 occurrences -
observed layout
font-sizeAuthored: var(--ant-button-only-icon-size)Computed: inherit 1 occurrence -
observed layout
vertical-alignComputed: middle1 complex parsed value available in the JSON report 1 occurrence -
observed unclassified
text-alignAuthored: center, end, left…Computed: center, end, left… 6 occurrences -
observed unclassified
animation-durationAuthored: var(--ant-motion-duration-mid), var(--ant-motion-duration-slow)Computed: 0s 3 occurrences -
observed unclassified
animation-fill-modeAuthored: ["both"]Computed: both 2 occurrences -
observed unclassified
animation-play-stateAuthored: ["paused"]Computed: paused 2 occurrences -
observed unclassified
animation-timing-functionAuthored: var(--ant-motion-ease-out-circ), var(--ant-motion-ease-out-quint)Computed: cubic-bezier(0.08, 0.82, 0.17, 1), cubic-bezier(0.23, 1, 0.32, 1) 2 occurrences -
observed unclassified
border-block-endAuthored: var(--ant-line-width) var(--ant-line-type) var(--ant-color-split)Computed: 1px solid rgba(5,5,5,0.06), none1 complex parsed value available in the JSON report 2 occurrences -
observed unclassified
font-styleComputed: normal1 complex parsed value available in the JSON report 2 occurrences -
observed unclassified
text-renderingAuthored: auto, optimizelegibilityComputed: auto, optimizeLegibility 2 occurrences
These summaries are generated by running the same scripted interaction in Chromium and happy-dom with DOM Layout Shim. Agreement is observational rather than a pass threshold: expand any checkpoint to inspect its recorded geometry, visibility, or hit-testing differences.