Skip to content

Commit 0b563e1

Browse files
committed
fix(douban): inline normalizeText into splitDoubanTitle for page eval
splitDoubanTitle is serialized via toString() into loadDoubanMovieSubject's page.evaluate, so its closure reference to the module-level normalizeText raised ReferenceError in the browser scope and broke douban subject. Fixes #1851
1 parent 944ca3a commit 0b563e1

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

clis/douban/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ function extractDoubanPublishYear(value) {
6868
return match?.[0] || '';
6969
}
7070
function splitDoubanTitle(fullTitle) {
71+
// Inline normalizeText so the toString-injected copy inside
72+
// loadDoubanMovieSubject's page.evaluate runs without a closure reference
73+
// to the module-level helper (which does not survive Function.toString()).
74+
const normalizeText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
7175
const normalized = normalizeText(fullTitle);
7276
if (!normalized)
7377
return { title: '', originalTitle: '' };

clis/douban/utils.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ function runMovieHotEvaluate(script, items) {
7878
return vm.runInNewContext(script, { document, URL });
7979
}
8080

81+
function runMovieSubjectEvaluate(script, elementMap) {
82+
const document = {
83+
querySelector(selector) {
84+
return elementMap[selector] || null;
85+
},
86+
querySelectorAll() {
87+
return [];
88+
},
89+
};
90+
return vm.runInNewContext(script, { document, URL });
91+
}
92+
8193
async function runSearchEvaluate(script, rawItems, domItems) {
8294
const document = {
8395
querySelector(selector) {
@@ -321,6 +333,32 @@ ISBN: 9787544270871
321333
await expect(loadDoubanMovieHot(page, 20)).rejects.toThrow('douban movie-hot returned no data');
322334
});
323335

336+
it('loads a movie subject without ReferenceError when splitDoubanTitle is toString-injected (regression for #1851)', async () => {
337+
const elementMap = {
338+
'span[property="v:itemreviewed"]': createFakeNode('海贼王:黄金大冒险 One Piece Gold'),
339+
'.year': createFakeNode('(2016)'),
340+
'strong[property="v:average"]': createFakeNode('7.5'),
341+
'span[property="v:votes"]': createFakeNode('1234'),
342+
'#info': createFakeNode('制片国家/地区: 日本\n类型: 动画'),
343+
'span[property="v:runtime"]': createFakeNode('120分钟'),
344+
'span[property="v:summary"]': createFakeNode('summary text'),
345+
};
346+
const page = {
347+
goto: vi.fn().mockResolvedValue(undefined),
348+
wait: vi.fn().mockResolvedValue(undefined),
349+
evaluate: vi.fn()
350+
.mockResolvedValueOnce({ blocked: false, title: '海贼王 (豆瓣)', href: 'https://movie.douban.com/subject/37116446/' })
351+
.mockImplementationOnce((script) => runMovieSubjectEvaluate(script, elementMap)),
352+
};
353+
354+
const detail = await loadDoubanSubjectDetail(page, '37116446', 'movie');
355+
356+
expect(detail.id).toBe('37116446');
357+
expect(detail.type).toBe('movie');
358+
expect(detail.title).toBe('海贼王:黄金大冒险');
359+
expect(detail.originalTitle).toBe('One Piece Gold');
360+
});
361+
324362
it('loads book subject details from book.douban.com when type=book', async () => {
325363
const page = {
326364
goto: vi.fn().mockResolvedValue(undefined),

0 commit comments

Comments
 (0)