Use when developing or modifying the @handsontable/angular-wrapper package - Angular components with decorators, NgZone performance optimization, and ng-packagr build system
日本語の概要は準備中です。原文の説明を表示しています。
Use ONLY when maintaining the FROZEN legacy Jasmine/Puppeteer E2E suite (*.spec.js) – editing an existing spec, or migrating a broken one to Playwright. NOT for new E2E: new E2E is Playwright, use the `handsontable-playwright-e2e` skill. Covers the legacy boilerplate, async/await rules, global helpers, event simulation, and theme-agnostic assertions.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
This suite is frozen. New E2E is Playwright: use the
handsontable-playwright-e2eskill and put specs intests/e2e/. This guide covers maintaining existing*.spec.jsfiles. The presence gate blocks a newly added*.spec.js, and appending three or more newitblocks to a modified frozen spec draws its non-blockingfrozen-suite-growthadvisory (state the justification in the PR if the frozen tier is right). Migrate a broken or flaky legacy spec to Playwright.
describe('MyFeature', () => {
const id = 'testContainer';
beforeEach(function() {
this.$container = $(`<div id="${id}"></div>`).appendTo('body');
});
afterEach(function() {
if (this.$container) {
destroy();
this.$container.remove();
}
});
it('should do something', async() => {
handsontable({ data: createSpreadsheetData(5, 5) });
await selectCell(0, 0);
expect(getDataAtCell(0, 0)).toBe('A1');
});
});
ESLint enforces: every it() callback is async, and every HOT API call (~50+ methods) is await-ed.
Injected automatically; full list in test/helpers/common.js.
handsontable(), destroy(), updateSettings(), render()createSpreadsheetData(), getDataAtCell(), getData(), setDataAtCell()countCols(), countRows(), alter()selectCell(), selectCells(), getSelected(), getSelectedRange()getCell(), spec(), hot()getPlugin()getLoadedTheme(), getThemeLayout() (see handsontable/.ai/TESTING.md)doc.write theme CSS: getE2eThemeStylesheetLinkTagsHtml() (all themes), getE2eThemeStylesheetLinkTagHtml(key), getE2eNormalizeStylesheetLinkTagHtml() from common.js; the theme list is E2E_REGISTERED_THEME_KEYS in themeLayoutFromTokens.js, auto-discovered from src/themes/theme/index.ts.Write the bare global (countCols(), await alter('remove_col', 2, 1)) instead of hot().countCols(). The mutating globals (alter(), setDataAtCell(), selectCell(), ...) auto-render and must be await-ed. Use hot() only for a method with no bare-global wrapper.
Every test passes under every theme. Never branch on getLoadedTheme(), layout.densityLevel, or a theme name in a spec, and never hardcode per-theme pixel values. Build expectations from const layout = getThemeLayout() (token-backed, from test/helpers/themeLayoutFromTokens.js: token primitives, overlayHeight / verticalScrollForRow helpers, and scenario helpers such as e2eGcrEditedCellOuterHeight, e2eManualRowResizerPositionFixedTopMasterFourthRow) or from live DOM measurements. themeLayoutFromTokens(themeName) reads density and tokens from handsontable/src/themes/theme/<name>.ts, so a density change propagates to all tests. Density triplets ({ compact: N, default: N, comfortable: N }) are not used anywhere.
For a value that is not token-derivable (text shaping, autosize widths, pixel rounding):
hot().getColWidth(col), hot().getRowHeight(row), hot().getPlugin('autoColumnSize').getColumnWidth(col)getCell(r, c).offsetWidth/offsetHeight, $el.getBoundingClientRect(), window.getComputedStyle(el).padding*toBeGreaterThan(previousValue), toBeLessThanOrEqual(containerWidth)toBeAroundValue(expected, 2) or expect(Math.abs(actual - expected)).toBeLessThanOrEqual(1)Viewport helpers (globals from common.js):
expectedVisibleRows(containerHeight, colHeaderRows = 1): number of fully visible data rowsexpectedLastFullyVisibleRow(containerHeight, colHeaderRows = 1): 0-based index of the last fully visible rowcontainerHeightForRows(rowCount, colHeaderRows = 1): height that guarantees exactly rowCount fully visible rows (use it instead of hardcoded height: 200)scaleHeight(mainThemeHeight) / scaleHeightWithScrollbar(mainThemeHeight): scale a main-theme pixel height to the current theme's row heightgetPaginationContainerHeight(): live pagination bar heightOrder of preference: (1) a named layout.e2e*() helper when a shared formula exists, (2) a direct formula in primitives (layout.defaultDataRowHeight + layout.cellBorderWidth), (3) a DOM/plugin-API read, (4) a relational assertion. Express theme differences through layout primitives, which already vary per theme.
Adding a new theme: the handsontable-css-dev skill has the four-layer token process. E2E-specific steps: (1) tokens at src/themes/static/variables/tokens/<name>.ts, (2) colors at src/themes/static/variables/colors/<name>.ts, (3) icons at src/themes/static/variables/icons/<name>.ts (or reuse one), (4) CSS source src/themes/static/css/theme/ht-theme-<name>.css + -no-icons.css variant, (5) theme module src/themes/theme/<name>.ts exporting { name, density, icons, colors, tokens }, (6) re-export from src/themes/theme/index.ts, (7) add new token keys to the VALID_TOKEN_KEYS allow-list in src/themes/engine/utils/validation.ts, (8) add new token keys to the TokenKey union in src/themes/types.ts, (9) add E2E matrix jobs in .github/workflows/test.yml. themeLayoutFromTokens.js, common.js, and specs need no edits.
Details: handsontable/.ai/TESTING.md ("Data-Driven Theme Assertions").
mouseDown(), mouseUp(), mouseOver(), mouseClick(), mouseDoubleClick() from test/helpers/mouseEvents.jskeyDown(), keyUp(), keyDownUp() from test/helpers/keyboardEvents.jstest/helpers/common.js, await both): triggerTouchEvent(type, target) dispatches a single event ('touchstart' / 'touchend'); simulateTouch(target) runs the full Android sequence touchstart, touchend, mousedown, mouseup, click (with preventDefault handling). A double-tap is two touchstart + touchend pairs on the same cell.A broken or flaky spec migrates to Playwright; these rules cover an edit you must make in place.
countRenderedRows(), countRenderedCols(), and any tbody tr count depend on the container size, which varies per theme and machine. Size the container with containerHeightForRows(n) or scrollViewportTo() the target into view first.waitUntil(condition, timeout) instead of sleep() and waitForNextAnimationFrames(). It is a spec global from test/helpers/common.js: it polls every frame and rejects with a named reason when the state never arrives. waitForNextAnimationFrames() is a fixed sleep in frames (at most 2 real frames, normalizeFrameCount caps it, plus 16 ms padding per requested frame). Every sleep() call warns (handsontable/no-fixed-sleep-in-spec, warn level); the diff-scoped ratchet (.github/scripts/lint-ratchet.mjs, at pre-push and in the CI Lint / core job, since 2026-09-07) fails a NEW one on an added line.it.flaky() instead of adding retries: new it.flaky() sites are lint-warned (handsontable/no-new-it-flaky), and the same ratchet fails one on an added line.handsontable({ myPlugin: true })updateSettings({ myPlugin: false })getPlugin('myPlugin').enablePlugin() / .disablePlugin()npm run test:e2e --prefix handsontablenpm run test:e2e --prefix handsontable --testPathPattern=<regex>, matched against test file paths during the Rspack .dump step (e.g. collapsibleColumns, ghostTable, textEditor, nestedHeaders/__tests__/hidingColumns)5 specs, 0 failures with exit 0 (measured on four no-match patterns in DEV-2911). test/e2e/index.js tests the pattern (case-insensitive) against webpack context keys, relative to handsontable/src/ or handsontable/test/e2e/: use ./validators/dropdownValidator/__tests__/dropdownValidator.spec.js, not src/validators/.... Use one plain pattern per command with no shell characters: scripts/run.mjs passes it to sh -c unquoted, so (a|b) is a syntax error and a\|b reaches the regex as a literal \| (validat covers every validator and validation spec).--theme=horizon (available: classic, main, horizon; default main).dist/handsontable.js; run npm run build --prefix handsontable after changing src/**.Parallel runs: invocations with different patterns or themes can run simultaneously. The dump step hashes testPathPattern + theme into a run ID and writes test/dist/main.entry.<runId>.js and test/E2ERunner-<runId>.html; the Puppeteer runner picks its own free port starting at 8086.
Iterating on one area: test:e2e.watch keeps the dev server running and re-bundles and re-runs on every source change:
npm run test:e2e.watch --prefix handsontable --testPathPattern=filters --theme=horizon
Split dump + puppeteer (what CI does): pass --testPathPattern AND --theme to both npm run commands. The Puppeteer script recomputes the dump's hash to find the runner HTML, and a mismatch fails with "Runner HTML not found at ...". .github/workflows/test.yml is the canonical example; the same applies to test:production.dump + test:e2e.puppeteer. The one-shot npm run test:e2e passes the flags to both halves itself.
A generic test/E2ERunner.html (no run ID) is regenerated alongside the per-run variant for manual browser testing. Specs that inject iframes with relative CSS paths (e.g. afterRefreshDimensions, Selection) rely on the runner living in test/, which is why the per-run HTML stays there too.
Specs run in a headless browser. The Puppeteer runner (test/scripts/run-puppeteer.mjs) forwards only console messages whose text starts with DEBUG, printed as [BROWSER] <text>:
console.log(`DEBUG state ${JSON.stringify({ labels: getColHeaders(), count: countCols() })}`);
Filter the run output: npm run test:e2e --prefix handsontable --testPathPattern=<regex> 2>&1 | grep DEBUG.
JSON.stringify omits keys whose value is undefined; use String(value) to distinguish undefined/false/null.expect(actual).toEqual('SENTINEL') also works: assertion diffs always reach the terminal.Specs live under src/ next to the code they test. The spec filename matches the method, hook, or setting name exactly (getSourceData.spec.js, afterChange.spec.js, height.spec.js).
| What is tested | Directory |
|---|---|
Core method (e.g., getSourceData) | src/__tests__/core/<methodName>.spec.js |
Hook (e.g., afterChange) | src/__tests__/hooks/<hookName>.spec.js |
Setting (e.g., height) | src/__tests__/settings/<settingName>.spec.js |
| Plugin | src/plugins/{name}/__tests__/*.spec.js |
| Keyboard shortcuts | src/shortcuts/__tests__/keyboardShortcuts/<name>.spec.js |
| i18n | src/i18n/__tests__/<name>.spec.js |
| Mobile-specific | src/__tests__/mobile/<name>.spec.js |
Do not add spec files to test/e2e/; it is no longer their home.
Reference organization: src/plugins/pagination/__tests__/ (separate dirs for options, methods, hooks, strategies).
hot(). form instead of the bare global.updateSettings() cycle.test/helpers/jasmine-bridge-reporter.js, shared with the Walkontable runner), a failing spec with a cyclic expected or actual (toBe(window), toEqual([overlay, ...])) was dropped: Running N specs. in --verbose mode, N-1 specs, 0 failures at the end, exit 0. The bridge now reports it as a failure ([unserializable Window]); if a count comes up short, compare the Running N specs. line against the summary with npm run test:e2e -- --testPathPattern=<file> --verbose.describe body. Jasmine turns it into a suite-level failure the runner does not report, and every later spec in that block never registers. it.skip() does exactly this (Jasmine has no it.skip) and hid five specs of nestedHeaders/__tests__/rowspan.spec.js. After editing a spec file, the spec count of a --testPathPattern run must equal the file's it( calls plus 5, because every run carries the 5 MemoryLeakTest specs; a count equal to the it( calls is 5 short.Full testing docs: handsontable/.ai/TESTING.md. Key files: test/helpers/common.js, test/helpers/mouseEvents.js, test/helpers/keyboardEvents.js.
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use when developing or modifying the @handsontable/angular-wrapper package - Angular components with decorators, NgZone performance optimization, and ng-packagr build system
日本語の概要は準備中です。原文の説明を表示しています。
Use when testing a local or public URL on a real device via BrowserStack, opening a live browser session on Android, iOS, or desktop browsers, or when the user mentions BrowserStack, real device testing, mobile testing, cross-browser testing, touch testing, or wants to verify behavior on a specific phone, tablet, or browser version. Also trigger when the user says "test this on Android/iPhone/Safari/Chrome mobile", asks to open a live session, or mentions demo-mobile.html, even if they don't mention BrowserStack by name.
日本語の概要は準備中です。原文の説明を表示しています。
Use when a source code change needs a changelog entry, or before committing and pushing any bug fix, feature, or behavior change to source code - detecting when entries are required, categorizing changes correctly (added/changed/fixed/deprecated/removed/security), writing user-facing titles, and creating the JSON entry in .changelogs/
日本語の概要は準備中です。原文の説明を表示しています。
Query the pre-built code-review-graph knowledge graph (Tree-sitter, whole monorepo) instead of walking call chains with Grep+Read – 2-6x cheaper, and it catches dynamic dispatch that grep misses. Use for ANY task that spans multiple files, even when the user never mentions a graph or asks for it - fixing or tracing a bug ("fix this bug", "why does X happen", "trace this"), exploring unfamiliar code ("how does X work", "who calls X", "what imports Y", "where is X used or handled"), planning or doing a refactor ("rename X", "is it safe to change or remove X", "what would break", "blast radius", "find dead code"), or reviewing changes ("review this PR", "review this branch or diff"). When you are about to Grep for a symbol's callers, callees, or importers, use this skill.
日本語の概要は準備中です。原文の説明を表示しています。
Use when working with row or column indexes in Handsontable - translating between physical, visual, and renderable coordinates, using IndexMapper, or debugging index-related bugs where rows or columns appear in wrong positions
日本語の概要は準備中です。原文の説明を表示しています。
Use when creating code examples for documentation pages - JavaScript, TypeScript, React, Angular, and Vue variants with proper imports, registration, and license key
日本語の概要は準備中です。原文の説明を表示しています。