feat: add opt-in answer selection and global hidden panel

This commit is contained in:
suanyuit-cyber
2026-10-29 18:29:58 +08:00
parent 6b04243428
commit 83ca85cc17
10 changed files with 1171 additions and 89 deletions
+90
View File
@@ -318,3 +318,93 @@ test('initial HTML parsing and atomic full SPA render do not block a new form',
assert.equal(question.stem, '同步渲染的新题目?');
page.adapter.stop();
});
test('ordinary pages do not observe requests, capture questions or intercept copy events', async () => {
const page = fakePage(async () => new Response(JSON.stringify({ listQues: [single()] }), { headers: { 'content-type': 'application/json' } }));
page.win.location.href = 'https://ordinary.example/account';
page.doc.title = '普通网页标题不应成为题库';
const originalFetch = page.win.fetch;
const originalOpen = page.win.XMLHttpRequest.prototype.open;
const originalSend = page.win.XMLHttpRequest.prototype.send;
const listeners = [];
const observers = [];
page.win.addEventListener = type => listeners.push(type);
page.win.removeEventListener = () => {};
page.win.MutationObserver = class {
constructor(callback) { observers.push(callback); }
observe() {}
};
page.doc.createElement = () => assert.fail('普通网页不能注入复制恢复样式');
const changes = [];
const adapter = create({ window: page.win, document: page.doc, observeNetwork: false,
onQuestions: values => page.received.push(...values), onBankChange: bank => changes.push(bank) });
adapter.start();
adapter.setCopyEnabled(true);
assert.equal(adapter.isSupportedPage(), false);
assert.equal(adapter.getCurrentQuestion(), null);
assert.equal(adapter.captureCurrent(), null);
assert.deepEqual(adapter.getCurrentBank(), { bankId: '', bankName: '', taskId: '', loading: false });
await page.win.fetch('/upm/start?praId=not-a-bank');
const xhr = new page.win.XMLHttpRequest();
xhr.open('GET', '/upm/start?praId=not-a-bank'); xhr.send(); xhr.respond({ listQues: [single()] });
await flush();
adapter.stop();
assert.equal(page.win.fetch, originalFetch);
assert.equal(page.win.XMLHttpRequest.prototype.open, originalOpen);
assert.equal(page.win.XMLHttpRequest.prototype.send, originalSend);
assert.deepEqual(page.received, [], '普通页响应即使碰巧形似练习数据也不能写入题库');
assert.deepEqual(listeners, [], '普通页无需安装copy、selection或导航监听');
assert.deepEqual(observers, [], '普通页不需要观察整个DOM');
assert.ok(changes.every(bank => bank.bankName === '' && bank.bankId === ''));
});
test('network observation can be disabled independently on a supported exercise page', async () => {
const page = bankPage();
const originalFetch = page.win.fetch;
const originalOpen = page.win.XMLHttpRequest.prototype.open;
const adapter = create({ window: page.win, document: page.doc, observeNetwork: false,
onQuestions: values => page.received.push(...values) });
adapter.start();
assert.equal(adapter.isSupportedPage(), true);
assert.equal(page.received.length, 1, '已知站点的页面采集不依赖网络钩子');
assert.equal(page.received[0].stem, '升温时应选择?');
assert.equal(page.received[0].bankName, '判断题库1');
assert.equal(page.received[0].answerStatus, 'missing');
assert.equal(page.win.fetch, originalFetch);
assert.equal(page.win.XMLHttpRequest.prototype.open, originalOpen);
adapter.stop();
});
test('a known root appearing later is detectable without automatically enabling network observation', () => {
const page = fakePage();
page.win.location.href = 'https://ordinary.example/demo';
page.doc.title = '显式题目结构演示';
let exerciseRoot = null;
page.doc.querySelector = selector => selector === ROOT_SELECTOR_FOR_TEST ? exerciseRoot : null;
const originalFetch = page.win.fetch;
const originalOpen = page.win.XMLHttpRequest.prototype.open;
const adapter = create({ window: page.win, document: page.doc, observeNetwork: false,
onQuestions: values => page.received.push(...values) });
adapter.start();
assert.equal(adapter.isSupportedPage(), false);
assert.equal(adapter.getCurrentBank().bankName, '');
exerciseRoot = {};
assert.equal(adapter.isSupportedPage(), true);
assert.equal(adapter.getCurrentBank().bankName, '显式题目结构演示');
assert.equal(page.win.fetch, originalFetch);
assert.equal(page.win.XMLHttpRequest.prototype.open, originalOpen);
assert.deepEqual(page.received, []);
adapter.stop();
});
test('a lookalike hostname is not treated as the supported learning platform', () => {
const page = fakePage();
page.win.location.href = 'https://www.shhuayi-elearning.cn.ordinary.example/';
page.doc.title = '页面标题不是题库';
const adapter = create({ window: page.win, document: page.doc, observeNetwork: false });
adapter.start();
assert.equal(adapter.isSupportedPage(), false);
assert.equal(adapter.getCurrentBank().bankName, '');
assert.equal(adapter.getCurrentQuestion(), null);
adapter.stop();
});