148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
|
|
import { __testing } from '@/app/reader/components/ReviewModeController';
|
|
import type { BookDoc } from '@/libs/document';
|
|
|
|
describe('ReviewModeController marks', () => {
|
|
test('marks bilingual source and target paragraphs by file and index', () => {
|
|
const doc = document.implementation.createHTMLDocument('chapter');
|
|
doc.body.innerHTML = [
|
|
'<p class="sourceText">原文一</p>',
|
|
'<p>译文一</p>',
|
|
'<p class="sourceText">原文二</p>',
|
|
'<p>译文二</p>',
|
|
].join('');
|
|
|
|
__testing.applyReviewMarks(
|
|
doc,
|
|
'OEBPS/Text/chapter.xhtml',
|
|
[
|
|
{
|
|
id: 'R00002',
|
|
file: 'Text/chapter.xhtml',
|
|
ja_p_index: 2,
|
|
cn_p_index: 3,
|
|
jp_html: '原文二',
|
|
jp_text: '原文二',
|
|
cn_html: '译文二',
|
|
current_html: '<span>新译文二</span><script>alert(1)</script>',
|
|
},
|
|
],
|
|
'R00002',
|
|
);
|
|
|
|
const paragraphs = Array.from(doc.querySelectorAll('p'));
|
|
expect(paragraphs[2]?.getAttribute('data-readest-review-row-id')).toBe('R00002');
|
|
expect(paragraphs[2]?.classList.contains('readest-review-source')).toBe(true);
|
|
expect(paragraphs[3]?.getAttribute('data-readest-review-row-id')).toBe('R00002');
|
|
expect(paragraphs[3]?.classList.contains('readest-review-target')).toBe(true);
|
|
expect(paragraphs[3]?.innerHTML).toBe('<span>新译文二</span>');
|
|
});
|
|
|
|
test('uses section id when foliate sections do not expose href', () => {
|
|
const bookDoc = {
|
|
sections: [{ id: 'OEBPS/Text/chapter.xhtml' }],
|
|
} as unknown as BookDoc;
|
|
|
|
expect(__testing.sectionHrefAt(bookDoc, 0)).toBe('OEBPS/Text/chapter.xhtml');
|
|
});
|
|
|
|
test('clearReviewMarks restores original target HTML', () => {
|
|
const doc = document.implementation.createHTMLDocument('chapter');
|
|
doc.body.innerHTML = '<p class="sourceText">原文</p><p><ruby>旧<rt>old</rt></ruby>译文</p>';
|
|
|
|
__testing.applyReviewMarks(
|
|
doc,
|
|
'chapter.xhtml',
|
|
[
|
|
{
|
|
id: 'R00001',
|
|
file: 'chapter.xhtml',
|
|
ja_p_index: 0,
|
|
cn_p_index: 1,
|
|
jp_html: '原文',
|
|
jp_text: '原文',
|
|
cn_html: '<ruby>旧<rt>old</rt></ruby>译文',
|
|
current_html: '<span>新译文</span>',
|
|
},
|
|
],
|
|
'',
|
|
);
|
|
|
|
__testing.clearReviewMarks(doc);
|
|
|
|
const target = doc.querySelectorAll('p')[1];
|
|
expect(target?.innerHTML).toBe('<ruby>旧<rt>old</rt></ruby>译文');
|
|
expect(target?.hasAttribute('data-readest-review-row-id')).toBe(false);
|
|
});
|
|
|
|
test('strips event handlers and dangerous links from target HTML', () => {
|
|
const doc = document.implementation.createHTMLDocument('chapter');
|
|
doc.body.innerHTML = '<p>原文</p><p>译文</p>';
|
|
|
|
__testing.applyReviewMarks(
|
|
doc,
|
|
'chapter.xhtml',
|
|
[
|
|
{
|
|
id: 'R00001',
|
|
file: 'chapter.xhtml',
|
|
ja_p_index: 0,
|
|
cn_p_index: 1,
|
|
jp_html: '原文',
|
|
jp_text: '原文',
|
|
cn_html: '译文',
|
|
current_html: '<span onclick="alert(1)"><a href="javascript:alert(1)">新译文</a></span>',
|
|
},
|
|
],
|
|
'R00001',
|
|
);
|
|
|
|
const target = doc.querySelectorAll('p')[1];
|
|
expect(target?.innerHTML).toBe('<span>新译文</span>');
|
|
});
|
|
|
|
test('updates active classes without rewriting translated paragraph HTML', () => {
|
|
const doc = document.implementation.createHTMLDocument('chapter');
|
|
doc.body.innerHTML = '<p>原文一</p><p>译文一</p><p>原文二</p><p>译文二</p>';
|
|
|
|
__testing.applyReviewMarks(
|
|
doc,
|
|
'chapter.xhtml',
|
|
[
|
|
{
|
|
id: 'R00001',
|
|
file: 'chapter.xhtml',
|
|
ja_p_index: 0,
|
|
cn_p_index: 1,
|
|
jp_html: '原文一',
|
|
jp_text: '原文一',
|
|
cn_html: '译文一',
|
|
current_html: '<span>新译文一</span>',
|
|
},
|
|
{
|
|
id: 'R00002',
|
|
file: 'chapter.xhtml',
|
|
ja_p_index: 2,
|
|
cn_p_index: 3,
|
|
jp_html: '原文二',
|
|
jp_text: '原文二',
|
|
cn_html: '译文二',
|
|
current_html: '<span>新译文二</span>',
|
|
},
|
|
],
|
|
'R00001',
|
|
);
|
|
|
|
const target = doc.querySelectorAll('p')[1]!;
|
|
target.dataset['localState'] = 'kept';
|
|
|
|
__testing.applyActiveReviewMark(doc, 'R00002');
|
|
|
|
expect(target.innerHTML).toBe('<span>新译文一</span>');
|
|
expect(target.dataset['localState']).toBe('kept');
|
|
expect(target.classList.contains('readest-review-active')).toBe(false);
|
|
expect(doc.querySelectorAll('p')[3]?.classList.contains('readest-review-active')).toBe(true);
|
|
});
|
|
});
|