Testing AdapTable
Summary
- AdapTable is fully testable and is framework- and runner-agnostic
- Drive tests through the Adaptable API after the Adaptable Ready Event has fired
- Stub State persistence and isolate each test so runs stay deterministic
- The recommended starting point is the AdapTable React Vitest Template
AdapTable is a standard JavaScript / TypeScript library.
There is no AdapTable-specific test framework — use whatever your application already uses.
The fastest, most reliable tests talk to AdapTable through the API.
Reach for end-to-end browser tests only when you specifically need to exercise the UI.
Hint
- Fork the React Vitest template if you want a runnable suite immediately
- The rest of this page is the practice that template follows
Choosing a Test Runner
| Runner | Notes |
|---|---|
| Vitest | Fast and modern, drop-in compatible with the Jest API; the runner used in the public React testing template |
| Jest | Widely used; works equally well for unit and integration tests against the Adaptable API |
| Testing Library | Pairs cleanly with Vitest or Jest when rendering AdapTable inside a component test |
| Playwright | Recommended for end-to-end tests that drive the actual browser UI; this is what we use internally |
Hint
- API tests are the default: apply a Layout, run a search, edit a cell, then assert through the API
- Component tests are useful when you render AdapTable inside your own wrapper
- End-to-end tests are for Dashboard clicks, Settings Panel, menus etc — not for proving a Filter or Layout applied
Wait for Adaptable Ready
AdapTable initialises asynchronously.
Adaptable.init (vanilla) and framework wrappers all expose the instance through Adaptable Ready Event.
Wait for that Event before asserting on Grid state or calling API methods.
Caution
Do not use AG Grid's gridReady event for this — it fires before AdapTable has initialised
Adaptable.init already returns adaptableApi.
The Ready Event is replayed if you subscribe after init has resolved, so this helper is enough:
async function createAdaptableForTest(
adaptableOptions: AdaptableOptions,
agGridConfig: AgGridConfig
): Promise<AdaptableApi> {
const adaptableApi = await Adaptable.init(adaptableOptions, agGridConfig);
await new Promise<void>(resolve => {
adaptableApi.eventApi.on('AdaptableReady', () => resolve());
});
return adaptableApi;
}Drive the Adaptable API
The Adaptable API is the primary test surface.
Anything a user can do in the UI can be done programmatically — applying Filters, switching Layouts, running Exports, editing cells.
This keeps assertions concise and avoids brittle DOM selectors.
const adaptableApi = await createAdaptableForTest(
adaptableOptions,
agGridConfig
);
adaptableApi.layoutApi.setLayout('Cars');
expect(adaptableApi.layoutApi.getCurrentLayoutName()).toBe('Cars');
adaptableApi.quickSearchApi.runQuickSearch('toy');
expect(adaptableApi.quickSearchApi.getQuickSearchValue()).toBe('toy');Note
Each test still needs a valid Initial Adaptable State — including a Layout section with at least one Layout, unless autoCreateLayout is set — see Default Layouts
Isolate State
By default AdapTable persists to localStorage under adaptableStateKey (which defaults to adaptableId). Shared persistence is the usual reason tests leak into each other.
Stub both loadState and persistState (provide them together so localStorage is not used on one side):
const adaptableOptions: AdaptableOptions = {
primaryKey: 'id',
adaptableId: 'layout-test',
adaptableStateKey: `layout-test-${Date.now()}`,
initialState,
stateOptions: {
loadState: () => Promise.resolve({}),
persistState: () => Promise.resolve(),
},
};Note
- Returning
{}fromloadStatemeans each test starts from Initial State - Return a fixture object from
loadStatewhen the test should start from a known User State - Give each test its own
adaptableStateKey(oradaptableId) if anything still writes to storage
Call adaptableApi.destroy() when the test finishes so the Grid and listeners are torn down.
afterEach(() => {
adaptableApi?.destroy();
});Asserting Something Changed
Prefer API getters after you invoked an API method.
When the change is triggered by UI or by another async path, subscribe to the Adaptable State Changed Event instead of polling:
const layoutChanged = new Promise<void>(resolve => {
adaptableApi.eventApi.on(
'AdaptableStateChanged',
(info: AdaptableStateChangedInfo) => {
if (info.actionName === 'LAYOUT_SELECT') {
resolve();
}
}
);
});
adaptableApi.layoutApi.setLayout('Cars');
await layoutChanged;Hint
- For data edits and row add / delete, use the Cell Changed and Row Changed Events
- Change data through the AdapTable Grid API so those Events fire
End-to-end Tests
Use Playwright (or similar) when the thing under test is the UI: a Dashboard button, a Settings Panel page, a Column Menu item.
Keep assertions on visible outcomes, not on AdapTable internals.
Hint
Where you already have adaptableApi in the page (for example from onAdaptableReady), you can still assert through the API after the click
Test Template
The AdapTable React Vitest Template is a complete, runnable repository that shows:
- a minimal React + AG Grid + AdapTable setup
- a Vitest configuration that handles AdapTable's async initialisation
- example tests that wait for Adaptable Ready and then assert against the Adaptable API
Fork it as a starting point for your own test suite.
FAQ
Do I need a special AdapTable test package? No. Use your existing runner. The Adaptable API is the test surface.
Should each test use a different adaptableId?
Use a unique adaptableStateKey (or stub loadState / persistState so nothing hits localStorage). A unique adaptableId also works, and makes console output easier to read.
Why do my tests affect each other?
Almost always leftover localStorage, or a vanilla instance that was not destroyed. Stub persistence and tear down each instance.
Can I query the DOM instead of using the API? You can, but selectors against AdapTable internals are brittle. Use API for behaviour; use the DOM only for UI you own (custom toolbars, your wrappers) or true end-to-end coverage.
Do tests need license keys?
Yes, the same as a running app. Without an AdapTable licenseKey the Grid shows a watermark and runs with reduced functionality — which will fail assertions that depend on those features.