fix: retain question images and repair evidenced legacy records
This commit is contained in:
+88
-6
@@ -22,6 +22,36 @@ test('multiple answer plan preserves the complete shuffled correct set', () => {
|
||||
const plan = planAnswer([record], current({ type: 'multiple' }), core);
|
||||
assert.equal(plan.status, 'ready'); assert.deepEqual(new Set(plan.labels), new Set(['B', 'C']));
|
||||
});
|
||||
|
||||
function repeatedMultiple(extra = {}) {
|
||||
return single({ type: 'multiple',
|
||||
options: [{ id: 'a', text: '降低', label: 'A' }, { id: 'b', text: '下降', label: 'B' },
|
||||
{ id: 'c', text: '增加', label: 'C' }, { id: 'd', text: '下降', label: 'D' }],
|
||||
correctOptionIds: ['a', 'b', 'c', 'd'], answerTexts: ['降低', '下降', '增加'], ...extra });
|
||||
}
|
||||
|
||||
test('multiple answer plan accepts every explicitly correct occurrence of repeated text', () => {
|
||||
const record = repeatedMultiple();
|
||||
const question = repeatedMultiple({ answerStatus: 'missing', correctOptionIds: [], answerTexts: [] });
|
||||
question.options = [question.options[3], question.options[2], question.options[0], question.options[1]]
|
||||
.map((option, index) => ({ ...option, id: `new-${index}`, label: 'ABCD'[index] }));
|
||||
const plan = planAnswer([record], question, core);
|
||||
assert.equal(plan.status, 'ready');
|
||||
assert.deepEqual([...plan.labels].sort(), ['A', 'B', 'C', 'D']);
|
||||
const subset = repeatedMultiple({ correctOptionIds: ['b', 'd'], answerTexts: ['下降'] });
|
||||
assert.deepEqual(planAnswer([subset], question, core).labels, ['A', 'D']);
|
||||
});
|
||||
|
||||
test('repeated text still skips partial answers, text-only answers, ID contradictions and duplicate labels', () => {
|
||||
const question = repeatedMultiple({ answerStatus: 'missing', correctOptionIds: [], answerTexts: [] });
|
||||
assert.equal(planAnswer([repeatedMultiple({ correctOptionIds: ['b'], answerTexts: ['下降'] })], question, core).status, 'ambiguous');
|
||||
assert.equal(planAnswer([repeatedMultiple({ correctOptionIds: [], answerTexts: ['下降'] })], question, core).status, 'ambiguous');
|
||||
const changed = repeatedMultiple();
|
||||
[changed.options[0].id, changed.options[1].id] = [changed.options[1].id, changed.options[0].id];
|
||||
assert.equal(planAnswer([repeatedMultiple()], changed, core).status, 'ambiguous');
|
||||
question.options[3].label = 'B';
|
||||
assert.equal(planAnswer([repeatedMultiple()], question, core).status, 'ambiguous');
|
||||
});
|
||||
test('judgement uses explicit correct and incorrect content, including zero ID', () => {
|
||||
const record = { bankId: 'j', type: 'judge', stem: '这项说法正确吗?', options: [{ id: '1', text: '正确' }, { id: '0', text: '错误' }], correctOptionIds: ['0'], answerStatus: 'confirmed' };
|
||||
const question = { ...record, options: [{ id: '0', text: '错误', label: '错误' }, { id: '1', text: '正确', label: '正确' }] };
|
||||
@@ -74,14 +104,15 @@ test('continuous selection starts disabled and can be destroyed without scanning
|
||||
assert.equal(engine.isEnabled(), false); assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
function checkboxPage({ disabled = false, ariaDisabled = false, duringClick } = {}) {
|
||||
function checkboxPage({ disabled = false, ariaDisabled = false, duringClick, texts = ['检查温度', '检查压力', '跳过巡检'], htmls, stemHTML, richLabels = false } = {}) {
|
||||
const clicks = [];
|
||||
const baseNode = () => ({ nodeType: 1, isConnected: true, parentElement: null, hidden: false, getAttribute: () => null, closest: () => null });
|
||||
const container = { ...baseNode(), matches: () => false, contains: () => true };
|
||||
const title = { ...baseNode(), textContent: '选择全部正确的巡检操作。', querySelector: () => null };
|
||||
const inputs = ['检查温度', '检查压力', '跳过巡检'].map((text, index) => {
|
||||
const label = { ...baseNode(), textContent: text, querySelector: () => null,
|
||||
cloneNode: () => ({ textContent: text, querySelectorAll: () => [] }) };
|
||||
const title = { ...baseNode(), textContent: '选择全部正确的巡检操作。', innerHTML: stemHTML, querySelector: () => null };
|
||||
const inputs = texts.map((text, index) => {
|
||||
const label = { ...baseNode(), textContent: text,
|
||||
querySelector: selector => richLabels && selector.includes('[data-rich-text]') ? { innerHTML: htmls?.[index], textContent: text } : null,
|
||||
cloneNode: () => ({ textContent: text, innerHTML: htmls?.[index], querySelectorAll: () => [] }) };
|
||||
const input = { ...baseNode(), type: 'checkbox', value: String(index), name: 'checks', id: `check-${index}`, checked: false, disabled: false, labels: [label],
|
||||
matches: selector => selector === ':disabled' && disabled,
|
||||
closest: selector => selector === '[aria-disabled="true"]' ? (ariaDisabled ? container : null) : selector === 'label' ? label : selector.startsWith('fieldset') ? container : null,
|
||||
@@ -92,10 +123,61 @@ function checkboxPage({ disabled = false, ariaDisabled = false, duringClick } =
|
||||
container.querySelectorAll = selector => selector.startsWith('input') ? inputs : [title];
|
||||
const doc = { querySelectorAll: selector => selector.startsWith('.yxtulcdsdk') ? [] : [container] };
|
||||
const win = { getComputedStyle: () => ({ display: 'block', visibility: 'visible' }), clearTimeout() {}, clearInterval() {}, setTimeout() {}, setInterval() {} };
|
||||
const record = { type: 'multiple', stem: title.textContent, options: inputs.map((input, index) => ({ id: input.value, text: ['检查温度', '检查压力', '跳过巡检'][index] })), answerTexts: ['检查温度', '检查压力'], correctOptionIds: ['0', '1'], answerStatus: 'confirmed' };
|
||||
const record = { type: 'multiple', stem: title.textContent, options: inputs.map((input, index) => ({ id: input.value, text: texts[index] })), answerTexts: texts.slice(0, 2), correctOptionIds: ['0', '1'], answerStatus: 'confirmed' };
|
||||
return { win, doc, record, inputs, clicks, title };
|
||||
}
|
||||
|
||||
test('automatic selection chooses all correct repeated checkboxes and clears an incorrect choice', async () => {
|
||||
const page = checkboxPage({ texts: ['下降', '增加', '下降'] });
|
||||
page.record.correctOptionIds = ['0', '2']; page.record.answerTexts = ['下降'];
|
||||
page.inputs[1].checked = true;
|
||||
const engine = create({ window: page.win, document: page.doc, core, getRecords: () => [page.record] });
|
||||
const result = await engine.answerCurrent();
|
||||
assert.equal(result.status, 'answered');
|
||||
assert.deepEqual(page.inputs.map(input => input.checked), [true, false, true]);
|
||||
assert.deepEqual(page.clicks, [0, 1, 2]);
|
||||
engine.destroy();
|
||||
});
|
||||
|
||||
test('automatic selection never clicks an ambiguous repeated-text subset', async () => {
|
||||
const page = checkboxPage({ texts: ['下降', '增加', '下降'] });
|
||||
page.record.correctOptionIds = ['0']; page.record.answerTexts = ['下降'];
|
||||
const engine = create({ window: page.win, document: page.doc, core, getRecords: () => [page.record] });
|
||||
const result = await engine.answerCurrent();
|
||||
assert.equal(result.status, 'skipped'); assert.deepEqual(page.clicks, []);
|
||||
engine.destroy();
|
||||
});
|
||||
|
||||
test('automatic selection reads image stems and choices consistently with network records', async () => {
|
||||
const { extractQuestions } = require('../src/adapter.js');
|
||||
for (const richLabels of [false, true]) {
|
||||
const stemHTML = '<p>选择对应结构<img src="https://images.example/curve.jpg"></p>';
|
||||
const htmls = ['<img src="https://images.example/a.png">', '<img src="https://images.example/b.png?x=1&y=2">'];
|
||||
const [record] = extractQuestions({ id: 'image-single', quesType: 0, content: stemHTML,
|
||||
choiceItems: htmls.map((itemContent, index) => ({ id: String(index), itemContent })), correctAnswers: ['1'] });
|
||||
const page = checkboxPage({ texts: ['', ''], htmls, stemHTML, richLabels });
|
||||
page.inputs.forEach(input => { input.type = 'radio'; });
|
||||
const engine = create({ window: page.win, document: page.doc, core, getRecords: () => [record] });
|
||||
const result = await engine.answerCurrent();
|
||||
assert.equal(result.status, 'answered');
|
||||
assert.deepEqual(result.labels, ['B']);
|
||||
assert.deepEqual(result.answerTexts, ['[图片:https://images.example/b.png?x=1&y=2]']);
|
||||
assert.deepEqual(page.clicks, [1]);
|
||||
engine.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test('a different image URL cannot reuse a known image answer', async () => {
|
||||
const page = checkboxPage({ texts: ['', ''], htmls: ['<img src="https://images.example/a.png">', '<img src="https://images.example/c.png">'] });
|
||||
const record = single({ type: 'multiple', stem: page.title.textContent,
|
||||
options: [{ id: '0', text: '[图片:https://images.example/a.png]' }, { id: '1', text: '[图片:https://images.example/b.png]' }],
|
||||
correctOptionIds: ['1'], answerTexts: ['[图片:https://images.example/b.png]'] });
|
||||
const engine = create({ window: page.win, document: page.doc, core, getRecords: () => [record] });
|
||||
const result = await engine.answerCurrent();
|
||||
assert.equal(result.status, 'skipped'); assert.deepEqual(page.clicks, []);
|
||||
engine.destroy();
|
||||
});
|
||||
|
||||
test('disabled fieldsets and aria-disabled ancestors are never clicked', async () => {
|
||||
for (const config of [{ disabled: true }, { ariaDisabled: true }]) {
|
||||
const page = checkboxPage(config);
|
||||
|
||||
Reference in New Issue
Block a user