Files
creator-hub/web/tests/monitoring-pagination.test.cjs
T

155 lines
5.8 KiB
JavaScript

const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const path = require('node:path');
const Module = require('node:module');
const test = require('node:test');
const ts = require('typescript');
const listFile = path.join(__dirname, '../src/components/accounts/MonitoringAccountList.tsx');
const detailFile = path.join(__dirname, '../src/pages/accounts/monitoring/$id/index.tsx');
function harness(initialURL = '/accounts/monitoring') {
const entries = [initialURL];
let position = 0;
let pageActions = [];
const location = () => new URL(entries[position], 'http://localhost');
const history = {
push(url) {
entries.splice(position + 1);
entries.push(url);
position += 1;
},
replace(url) { entries[position] = url; },
back() { position -= 1; },
};
const element = (type, props, ...children) => ({ type, props: props || {}, children });
const react = {
createElement: element,
Fragment: 'Fragment',
useState: (value) => [typeof value === 'function' ? value() : value, () => {}],
useEffect: () => {},
useCallback: (callback) => callback,
};
const components = new Proxy({
App: { useApp: () => ({ message: { success() {}, error() {} } }) },
Typography: { Text: 'Text', Link: 'Typography.Link' },
Form: Object.assign('Form', { useForm: () => [{}], Item: 'Form.Item' }),
Grid: { useBreakpoint: () => ({ xl: true }) },
}, { get: (target, key) => target[key] ?? String(key) });
const jsx = (type, props) => element(type, props, props?.children);
const mocks = {
react,
'react/jsx-runtime': { jsx, jsxs: jsx, Fragment: 'Fragment' },
antd: components,
'@ant-design/icons': new Proxy({}, { get: (_, key) => String(key) }),
'@ant-design/pro-components': { PageContainer: 'PageContainer' },
'@umijs/max': {
history,
Link: 'Link',
useParams: () => ({ id: '42' }),
useLocation: location,
useSearchParams: () => [new URLSearchParams(location().search), (params, options) => {
const url = `${location().pathname}?${params}`;
if (options?.replace) history.replace(url);
else history.push(url);
}],
},
'@/services/api': {},
'@/components/PageActions': { usePageActions: (actions) => { pageActions = actions; } },
'@/utils/helpers': { conflictMessage: () => '', dateTime: () => '' },
'@/utils/table': {
TABLE_PAGE_SIZE: 20,
tablePagination: () => ({ pageSize: 20, hideOnSinglePage: true }),
fixedLeft: () => ({}), fixedRight: () => ({}), useTableSticky: () => undefined,
},
'@/components/creator/WorksGallery': { default: 'WorksGallery' },
};
function load(file) {
const compiled = new Module(file, module);
compiled.filename = file;
compiled.paths = module.paths;
compiled.require = (name) => {
assert.ok(Object.hasOwn(mocks, name), `Unmocked dependency: ${name}`);
return mocks[name];
};
compiled._compile(ts.transpileModule(readFileSync(file, 'utf8'), {
compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, target: ts.ScriptTarget.ES2020 },
}).outputText, file);
return compiled.exports.default;
}
function find(node, type) {
if (!node || typeof node !== 'object') return undefined;
if (node.type === type) return node;
return node.children?.flat(Infinity).map((child) => find(child, type)).find(Boolean);
}
const List = load(listFile);
const Detail = load(detailFile);
return {
table: () => find(List(), 'Table').props,
enterDetail() {
const column = find(List(), 'Table').props.columns.find((column) => column.dataIndex === 'nickname');
find(column.render('', { id: '42' }), 'Typography.Link').props.onClick();
},
returnFromDetail() {
Detail();
const button = find(pageActions, 'Button');
button.props.onClick();
},
history,
url: () => entries[position],
entryCount: () => entries.length,
};
}
test('monitoring list starts at page one with the existing page size', () => {
const h = harness();
assert.equal(h.table().pagination.current, 1);
assert.equal(h.table().pagination.pageSize, 20);
});
test('detail return preserves the selected page and page size', () => {
const h = harness();
h.table().pagination.onChange(2, 20);
h.enterDetail();
h.returnFromDetail();
assert.equal(h.table().pagination.current, 2);
assert.equal(h.table().pagination.pageSize, 20);
assert.equal(new URL(h.url(), 'http://localhost').pathname, '/accounts/monitoring');
});
test('browser back restores pagination without stepping through previous page changes', () => {
const h = harness();
h.table().pagination.onChange(2, 20);
h.table().pagination.onChange(3, 20);
assert.equal(h.entryCount(), 1);
h.enterDetail();
h.history.back();
assert.equal(h.table().pagination.current, 3);
});
test('changing page size resets to page one and keeps that size after returning', () => {
const h = harness('/accounts/monitoring?page=3&pageSize=20');
h.table().pagination.onChange(3, 50);
assert.equal(h.table().pagination.current, 1);
h.table().pagination.onChange(2, 50);
h.enterDetail();
h.returnFromDetail();
assert.equal(h.table().pagination.current, 2);
assert.equal(h.table().pagination.pageSize, 50);
});
test('reload and directly opened detail retain the pagination in their URL', () => {
const h = harness('/accounts/monitoring/42?page=4&pageSize=50');
h.returnFromDetail();
assert.equal(h.table().pagination.current, 4);
assert.equal(h.table().pagination.pageSize, 50);
});
for (const value of ['invalid', '-1', '0', '1.5', 'Infinity', '9007199254740992']) {
test(`invalid pagination value ${value} uses valid defaults`, () => {
const h = harness(`/accounts/monitoring?page=${value}&pageSize=${value}`);
assert.equal(h.table().pagination.current, 1);
assert.equal(h.table().pagination.pageSize, 20);
});
}