Files

79 lines
7.0 KiB
JavaScript
Raw Permalink Normal View History

// Exercise list pagination and lifecycle with synthetic frozen pages; no network/model access.
const fs = require('node:fs'), path = require('node:path'), vm = require('node:vm'), assert = require('node:assert/strict');
const ts = require('D:/Develop/HBuilderX/plugins/uniapp-uts-v1/node_modules/@dcloudio/uni-uts-v1/lib/typescript/lib/typescript.js');
const source = fs.readFileSync(path.join(__dirname, '../packages/finance/matches/matches.uvue'), 'utf8').match(/<script setup lang="uts">([\s\S]*?)<\/script>/)[1];
function item(id) {
return { id: String(id), score: 95, state: 'eligible', stateLabel: '符合已知条件', availability: 'available', availabilityLabel: '当前产品可查看', product: { id: String(id), version: 1, name: '合成产品' + id, bank: '合成银行' }, positive: [], blocking: [], cautions: [], aiText: '', aiStatus: 'not_requested', aiCanRetry: false, aiCanRequest: false, aiGeneratedAt: '' };
}
function frozen(ids, cursor = '', overrides = {}) {
return { assessmentId: '9', revisionId: '11', resultVersion: 2, snapshotId: 'frozen', ruleVersion: 'product-matching-v2', orderingVersion: 'product-score-descending-v1', status: 'completed', message: '', eligible: '8', ineligible: '0', unknown: '0', items: ids.map(item), hasMore: cursor.length > 0, cursor, ...overrides };
}
function harness(respond) {
let token = 'synthetic-owner', onLoad, onShow, onHide;
const requests = [];
const context = { exports: {}, Error, Number, Promise,
ref: value => ({ value }), computed: read => ({ get value() { return read(); } }),
onLoad: fn => { onLoad = fn; }, onShow: fn => { onShow = fn; }, onHide: fn => { onHide = fn; }, onUnload: () => {},
setInterval: () => 1, clearInterval: () => {}, uni: { redirectTo: () => {} },
require: name => name.endsWith('layout.uts') ? { usePageLayout: () => ({}) } : name.endsWith('api.uts') ? { getSession: () => ({ getString: key => key === 'token' ? token : '' }) } : name.endsWith('matches.uts') ? {
validRecordId: value => /^[1-9][0-9]{0,9}$/.test(value),
fetchMatchPage: async (id, group, cursor, revisionId) => { const request = { id, group, cursor, revisionId }; requests.push(request); return respond(request); }
} : name.endsWith('compare.uts') ? { compareSelection: () => [], saveCompareSelection: () => {}, parseCompareIds: () => [], compareQuery: () => '', matchReturnPath: () => '' } : {}
};
// Remove the Web-only address-bar update; the lifecycle and pagination remain the production code.
const executable = source.replace(/\s*\/\/ #ifdef WEB[\s\S]*?\/\/ #endif/g, '');
const script = ts.transpileModule(executable + '\nexports.view={matches,loading,pageError,error,hasMore,revisionId,group}; exports.more=loadMore; exports.select=selectGroup;', { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
vm.runInNewContext(script, context);
onLoad({ assessmentId: '9', revisionId: '11' });
return { ...context.exports, requests, show: () => onShow(), hide: () => onHide(), switchAccount: () => { token = 'other-user'; } };
}
const flush = async () => { for (let i = 0; i < 30; i++) await Promise.resolve(); };
const ids = h => Array.from(h.view.matches.value, row => row.id);
const pages = { '': frozen([1, 2, 3], 'next-3'), 'next-3': frozen([4, 5, 6], 'next-6'), 'next-6': frozen([7, 8]) };
let checks = 0;
async function test(name, action) { await action(); checks++; console.log('PASS ' + name); }
(async () => {
await test('one click appends every remaining page in order', async () => {
const h = harness(({ cursor }) => pages[cursor]); h.show(); await flush();
assert.deepEqual(ids(h), ['1', '2', '3']); assert.equal(h.requests.length, 1);
h.more(); await flush();
assert.deepEqual(ids(h), ['1', '2', '3', '4', '5', '6', '7', '8']); assert.equal(h.view.hasMore.value, false); assert.equal(h.view.loading.value, false);
assert.deepEqual(h.requests.map(r => [r.group, r.cursor, r.revisionId]), [['all', '', '11'], ['all', 'next-3', '11'], ['all', 'next-6', '11']]);
});
await test('failure preserves appended rows and retry resumes at the failing page', async () => {
let fail = true;
const h = harness(({ cursor }) => { if (cursor === 'next-6' && fail) throw Error('synthetic timeout'); return pages[cursor]; });
h.show(); await flush(); h.more(); await flush();
assert.deepEqual(ids(h), ['1', '2', '3', '4', '5', '6']); assert.equal(h.view.pageError.value, 'synthetic timeout'); assert.equal(h.view.loading.value, false);
fail = false; h.more(); await flush();
assert.deepEqual(ids(h), ['1', '2', '3', '4', '5', '6', '7', '8']); assert.equal(h.requests.at(-1).cursor, 'next-6'); assert.equal(h.view.pageError.value, '');
});
await test('duplicate products and changed snapshots cannot enter the existing list', async () => {
for (const bad of [frozen([3, 4, 5], 'next-6'), frozen([4, 5, 6], 'next-6', { snapshotId: 'changed' })]) {
const h = harness(({ cursor }) => cursor === '' ? pages[''] : bad); h.show(); await flush(); h.more(); await flush();
assert.deepEqual(ids(h), ['1', '2', '3']); assert.ok(h.view.pageError.value.length > 0); assert.equal(h.requests.length, 2);
}
});
await test('leaving or switching accounts stops subsequent pagination and discards late rows', async () => {
for (const action of ['hide', 'switchAccount']) {
let resolve;
const h = harness(({ cursor }) => cursor === '' ? pages[''] : new Promise(done => { resolve = done; }));
h.show(); await flush(); h.more(); await flush(); h[action](); resolve(pages['next-3']); await flush();
assert.deepEqual(ids(h), []); assert.equal(h.requests.length, 2);
}
});
await test('group change ignores the old request and keeps the new group', async () => {
let resolve;
const h = harness(({ group, cursor }) => group === 'unknown' ? frozen([20], '', { eligible: '0', unknown: '1' }) : cursor === '' ? pages[''] : new Promise(done => { resolve = done; }));
h.show(); await flush(); h.more(); await flush(); h.select('unknown'); await flush(); resolve(pages['next-3']); await flush();
assert.deepEqual(ids(h), ['20']); assert.equal(h.view.group.value, 'unknown'); assert.equal(h.requests.length, 3); assert.equal(h.view.loading.value, false);
});
await test('repeated clicks share the running load', async () => {
let resolve;
const h = harness(({ cursor }) => cursor === '' ? pages[''] : cursor === 'next-3' ? new Promise(done => { resolve = done; }) : pages[cursor]);
h.show(); await flush(); h.more(); h.more(); await flush(); assert.equal(h.requests.length, 2);
resolve(pages['next-3']); await flush(); assert.deepEqual(ids(h), ['1', '2', '3', '4', '5', '6', '7', '8']); assert.equal(h.requests.length, 3);
});
console.log('PASS: ' + checks + ' match list scenarios; no network/model access');
})().catch(error => { console.error(error); process.exitCode = 1; });