@linzjs/step-ag-grid
Version:
[](https://github.com/semantic-release/semantic-release) > Reusable [ag-grid](https://www.ag-grid.com/) component for LINZ / Toitū te whenua.
296 lines (254 loc) • 10.8 kB
text/typescript
import { act, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { isEqual } from 'lodash-es';
import { expect } from 'vitest';
import { findQuick, getAllQuick, getMatcher, getQuick, IQueryQuick, queryQuick } from './testQuick';
let user = userEvent;
/**
* allow external userEvent to be used
* @param customisedUserEvent
*/
export const setUpUserEvent = (customisedUserEvent: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
user = customisedUserEvent;
};
export const countRows = (within?: HTMLElement): number => {
return getAllQuick({ tagName: `div[row-id]:not(:empty)` }, within).length;
};
export const findRowByIndex = async (rowIndex: number | string, within?: HTMLElement): Promise<HTMLDivElement> => {
await waitFor(() => {
expect(getAllQuick({ classes: '.ag-row' }).length > 0).toBe(true);
});
//if this is not wrapped in an act console errors are logged during testing
let row!: HTMLDivElement;
await act(async () => {
row = await findQuick<HTMLDivElement>(
{ tagName: `.ag-center-cols-container div[row-index='${rowIndex}']:not(:empty)` },
within,
);
let combineChildren = [...Array.from(row.children)];
const leftCols = queryQuick<HTMLDivElement>(
{ tagName: `.ag-pinned-left-cols-container div[row-index='${rowIndex}']` },
within,
);
if (leftCols) {
combineChildren = [...Array.from(leftCols.children), ...combineChildren];
}
const rightCols = queryQuick<HTMLDivElement>(
{ tagName: `.ag-pinned-right-cols-container div[row-index='${rowIndex}']` },
within,
);
if (rightCols) {
combineChildren = [...Array.from(rightCols.children), ...combineChildren];
}
row.replaceChildren(...combineChildren);
});
return row;
};
export const findRow = async (rowId: number | string, within?: HTMLElement): Promise<HTMLDivElement> => {
await waitFor(() => {
expect(getAllQuick({ classes: '.ag-row' }).length > 0).toBe(true);
});
//if this is not wrapped in an act console errors are logged during testing
let row!: HTMLDivElement;
await act(async () => {
row = await findQuick<HTMLDivElement>(
{ tagName: `.ag-center-cols-container div[row-id='${rowId}']:not(:empty)` },
within,
);
let combineChildren = [...Array.from(row.children)];
const leftCols = queryQuick<HTMLDivElement>(
{ tagName: `.ag-pinned-left-cols-container div[row-id='${rowId}']` },
within,
);
if (leftCols) {
combineChildren = [...Array.from(leftCols.children), ...combineChildren];
}
const rightCols = queryQuick<HTMLDivElement>(
{ tagName: `.ag-pinned-right-cols-container div[row-id='${rowId}']` },
within,
);
if (rightCols) {
combineChildren = [...Array.from(rightCols.children), ...combineChildren];
}
row.replaceChildren(...combineChildren);
});
return row;
};
export const queryRow = (rowId: number | string, within?: HTMLElement): HTMLDivElement | null => {
return queryQuick<HTMLDivElement>({ tagName: `div[row-id='${rowId}']:not(:empty)` }, within);
};
const _selectRow = async (
select: 'select' | 'deselect' | 'toggle',
rowId: string | number,
within?: HTMLElement,
): Promise<void> => {
const row = await findRow(rowId, within);
const isSelected = row.className.includes('ag-row-selected');
if (select === 'toggle' || (select === 'select' && !isSelected) || (select === 'deselect' && isSelected)) {
const cell = await findCell(rowId, 'ag-Grid-SelectionColumn', within);
await user.click(cell);
await waitFor(async () => {
const row = await findRow(rowId, within);
const nowSelected = row.className.includes('ag-row-selected');
if (nowSelected === isSelected) throw `Row ${rowId} won't select`;
});
}
};
export const selectRow = async (rowId: string | number, within?: HTMLElement): Promise<void> =>
_selectRow('select', rowId, within);
export const deselectRow = async (rowId: string | number, within?: HTMLElement): Promise<void> =>
_selectRow('deselect', rowId, within);
export const findCell = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<HTMLElement> => {
const row = await findRow(rowId, within);
return await findQuick({ tagName: `[col-id='${colId}']` }, row);
};
export const findCellContains = async (
rowId: number | string,
colId: string,
text: string | RegExp,
within?: HTMLElement,
) => {
return await waitFor(
async () => {
const row = await findRow(rowId, within);
return getQuick({ tagName: `[col-id='${colId}']`, text }, row);
},
{ timeout: 10000 },
);
};
export const selectCell = async (rowId: string | number, colId: string, within?: HTMLElement): Promise<void> => {
const cell = await findCell(rowId, colId, within);
await user.click(cell);
};
export const editCell = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<void> => {
await waitFor(
async () => {
const cell = await findCell(rowId, colId, within);
await user.dblClick(cell);
await waitFor(findOpenPopover, { timeout: 1000 });
},
{ timeout: 10000 },
);
};
export const isCellReadOnly = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<boolean> => {
const cell = await findCell(rowId, colId, within);
return cell.className.includes('GridCell-readonly');
};
export const findOpenPopover = () => findQuick({ classes: '.szh-menu--state-open' });
export const queryMenuOption = async (menuOptionText: string | RegExp): Promise<HTMLElement | null> => {
const openMenu = await findOpenPopover();
const els = await within(openMenu).findAllByRole('menuitem');
const matcher = getMatcher(menuOptionText);
const result = els.find(matcher);
return result ?? null;
};
export const findMenuOption = async (menuOptionText: string | RegExp): Promise<HTMLElement> => {
return await waitFor(
async () => {
const menuOption = await queryMenuOption(menuOptionText);
if (menuOption == null) {
throw Error(`Unable to find menu option ${menuOptionText}`);
}
return menuOption;
},
{ timeout: 5000 },
);
};
export const validateMenuOptions = async (
rowId: number | string,
colId: string,
expectedMenuOptions: Array<string>,
): Promise<boolean> => {
await editCell(rowId, colId);
const openMenu = await findOpenPopover();
const actualOptions = (await within(openMenu).findAllByRole('menuitem')).map((menuItem) => menuItem.textContent);
return isEqual(actualOptions, expectedMenuOptions);
};
export const clickMenuOption = async (menuOptionText: string | RegExp): Promise<void> => {
const menuOption = await findMenuOption(menuOptionText);
await user.click(menuOption);
};
export const openAndClickMenuOption = async (
rowId: number | string,
colId: string,
menuOptionText: string | RegExp,
within?: HTMLElement,
): Promise<void> => {
await editCell(rowId, colId, within);
await clickMenuOption(menuOptionText);
};
export const openAndFindMenuOption = async (
rowId: number | string,
colId: string,
menuOptionText: string | RegExp,
within?: HTMLElement,
): Promise<HTMLElement> => {
await editCell(rowId, colId, within);
return await findMenuOption(menuOptionText);
};
export const getMultiSelectOptions = async () => {
const openMenu = await findOpenPopover();
return getAllQuick<HTMLInputElement>({ role: 'menuitem', child: { tagName: 'input,textarea' } }, openMenu).map(
(input) => {
return {
v: input.value,
c: input.checked ?? true,
};
},
);
};
export const findMultiSelectOption = async (value: string): Promise<HTMLElement> => {
const openMenu = await findOpenPopover();
return getQuick({ role: 'menuitem', child: { tagName: `input[value='${value}']` } }, openMenu);
};
export const clickMultiSelectOption = async (value: string): Promise<void> => {
const menuItem = await findMultiSelectOption(value);
menuItem.parentElement && (await user.click(menuItem.parentElement));
};
const typeInput = async (value: string, filter: IQueryQuick): Promise<void> => {
const openMenu = await findOpenPopover();
const input = await findQuick(filter, openMenu);
await user.clear(input);
//'typing' an empty string will cause a console error, and it's also unnecessary after the previous clear call
if (value.length > 0) {
await user.type(input, value);
}
};
export const typeOnlyInput = async (value: string): Promise<void> =>
typeInput(value, { child: { tagName: "input[type='text'], textarea" } });
export const typeInputByLabel = async (value: string, labelText: string): Promise<void> => {
const labels = getAllQuick({ child: { tagName: 'label' } }).filter((l) => l.textContent === labelText);
if (labels.length === 0) {
throw Error(`Label not found for text: ${labelText}`);
}
if (labels.length > 1) {
throw Error(`Multiple labels found for text: ${labelText}`);
}
const inputId = labels[0].getAttribute('for');
await typeInput(value, { child: { tagName: `input[id='${inputId}'], textarea[id='${inputId}']` } });
};
export const typeInputByPlaceholder = async (value: string, placeholder: string): Promise<void> =>
typeInput(value, {
child: { tagName: `input[placeholder='${placeholder}'], textarea[placeholder='${placeholder}']` },
});
export const typeOtherInput = async (value: string): Promise<void> =>
typeInput(value, { classes: '.subComponent', child: { tagName: "input[type='text']" } });
export const typeOtherTextArea = async (value: string): Promise<void> =>
typeInput(value, { classes: '.subComponent', child: { tagName: 'textarea' } });
export const closeMenu = () => user.click(document.body);
export const closePopover = () => user.click(document.body);
export const findActionButton = (text: string, container?: HTMLElement): Promise<HTMLElement> =>
findQuick({ tagName: 'button', child: { classes: '.ActionButton-minimalAreaDisplay', text: text } }, container);
export const clickActionButton = async (text: string, container?: HTMLElement): Promise<void> => {
const button = await findActionButton(text, container);
await user.click(button);
};
export const waitForGridReady = async (props?: { grid?: HTMLElement; timeout?: number }) =>
waitFor(() => expect(getAllQuick({ classes: '.Grid-ready' }, props?.grid)).toBeDefined(), {
timeout: props?.timeout ?? 5000,
});
export const waitForGridRows = async (props?: { grid?: HTMLElement; timeout?: number }) =>
waitFor(() => expect(getAllQuick({ classes: '.ag-row' }, props?.grid).length > 0).toBe(true), {
timeout: props?.timeout ?? 5000,
});