128 lines
8.2 KiB
JavaScript
128 lines
8.2 KiB
JavaScript
import {routingModuleUrl} from './helpers/routing.mjs';
|
|
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFile} from 'node:fs/promises';
|
|
import ts from 'typescript';
|
|
|
|
async function moduleUrl(path,transform=code=>code){
|
|
const source=await readFile(new URL(path,import.meta.url),'utf8');
|
|
const {outputText}=ts.transpileModule(source,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}});
|
|
return 'data:text/javascript;base64,'+Buffer.from(transform(outputText).replace(/(['"])\.\.\/routing\1/g,JSON.stringify(routingModuleUrl))+'\n//# sourceURL='+new URL(path,import.meta.url).pathname).toString('base64');
|
|
}
|
|
const protocolUrl=await moduleUrl('../src/lib/api/protocol.ts');
|
|
const renderUrl=await moduleUrl('../src/lib/client/render.ts',code=>code.replace(/from ['"]\.\.\/api\/protocol['"]/,`from '${protocolUrl}'`));
|
|
const {ads,promotionRow,renderHome,activeBanners,hero,card,topicCard,imageAttributes}=await import(renderUrl);
|
|
const drama={id:23456,title:'正常剧集 <script>alert(1)</script>',cover:'https://example.com/poster.webp',tagNames:['都市']};
|
|
const advert={id:'ad-one',title:'有效广告',images:'https://example.com/ad.webp',link:'https://example.com/landing'};
|
|
|
|
test('topic cards escape their names and carry no tracking attributes',()=>{
|
|
const html=topicCard({id:7,name:'都市 <甜宠>',icon:'https://example.com/topic.png'});
|
|
assert.ok(html.includes('<h3>都市 <甜宠></h3>')||html.includes('都市 <甜宠>'));assert.doesNotMatch(html,/data-track|data-item-/);
|
|
});
|
|
|
|
test('API images use data sources and local placeholders until decrypted, including video posters',()=>{
|
|
const cases=[card(drama),hero([drama]),topicCard({id:1,icon:drama.cover}),ads([advert])];
|
|
for(const html of cases){
|
|
const images=[...html.matchAll(/<img\b[^>]*>/g)];assert.ok(images.length>0);
|
|
for(const [tag] of images){
|
|
assert.match(tag,/\ssrc="\/assets\/poster-placeholder\.svg"/);assert.match(tag,/\sdata-src="https:\/\//);
|
|
assert.ok(!/\ssrc="https?:/.test(tag));
|
|
}
|
|
}
|
|
assert.match(imageAttributes(drama.cover,undefined,'poster'),/^poster="\/assets\/poster-placeholder\.svg" data-poster="https:\/\//);
|
|
assert.equal(imageAttributes('javascript:alert(1)','/icon.svg'),'src="/icon.svg"');
|
|
assert.ok(imageAttributes('https://example.com/a?one=1&two=2').includes('one=1&two=2'));
|
|
});
|
|
|
|
test('ads tolerates null, missing, and non-array API fields',()=>{
|
|
for(const value of [null,undefined,{},'',false,0,'[]'])assert.equal(ads(value),'');
|
|
assert.equal(ads([]),'');
|
|
});
|
|
|
|
test('shared square ad rows keep real ad data and links and omit empty placements',()=>{
|
|
const html=promotionRow([advert,{...advert,id:'ad-two',title:'第二个广告'}]);
|
|
assert.ok(html.includes('class="promo-marquee" data-promotion-row'));
|
|
assert.equal((html.match(/class="promo-tile"/g)||[]).length,2);
|
|
assert.equal((html.match(/data-ad href=/g)||[]).length,2);assert.doesNotMatch(html,/data-track|data-ad-/);
|
|
assert.ok(html.includes('href="https://example.com/landing"'));
|
|
assert.ok(html.includes('width="132" height="132"'));assert.ok(html.includes('alt="有效广告"'));
|
|
for(const value of [null,{},[],[null]])assert.equal(promotionRow(value),'');
|
|
});
|
|
|
|
test('ads ignores malformed entries and keeps valid ads in order with escaped text',()=>{
|
|
const html=ads([null,undefined,false,42,[],{...advert,images:null},{...advert,images:'javascript:alert(1)'},advert,{...advert,id:'two',title:'第二条 <script>',link:'javascript:alert(1)'}]);
|
|
assert.equal((html.match(/class="promo-app"/g)||[]).length,2);
|
|
assert.ok(html.indexOf('有效广告')<html.indexOf('第二条'));
|
|
assert.ok(html.includes('href="https://example.com/landing"'));
|
|
assert.ok(html.includes('href="/apps/"'));assert.ok(html.includes('第二条 <script>'));
|
|
assert.ok(!html.includes('javascript:'));assert.ok(!html.includes('<script>'));
|
|
});
|
|
|
|
test('home renders when nineGridList is explicitly null, preserving valid content',()=>{
|
|
const html=renderHome({homeBannerData:[drama],homeConfigData:[{id:31,name:'后台栏目',type:1,status:true,list:[drama]}],advitise:{nineGridList:null}});
|
|
assert.ok(html.includes('后台栏目'));assert.ok(html.includes('href="/detail/23456/"'));
|
|
assert.ok(html.includes('正常剧集 <script>'));assert.ok(!html.includes('<script>alert(1)</script>'));
|
|
});
|
|
|
|
test('dynamic home sections share a grid without inheriting the prototype editorial rows',()=>{
|
|
const html=renderHome({homeConfigData:[
|
|
{id:31,name:'必看好剧',status:true,type:1,list:[drama,{...drama,id:2,totalEpisode:10}]},
|
|
{id:32,name:'热播榜',status:true,type:1,list:[{...drama,id:3}]},
|
|
{id:33,name:'剧集',status:true,type:2,videoList:[{...drama,id:4,totalEpisode:8}]},
|
|
{id:34,name:'题材',status:true,type:3,themeList:[{id:8,name:'都市'}]},
|
|
]});
|
|
assert.equal((html.match(/class="topic-section home-block"/g)||[]).length,3);
|
|
assert.equal((html.match(/class="cards"/g)||[]).length,2);
|
|
assert.ok(!html.includes('id="romance-rows"'));assert.ok(!html.includes('romance-editorial'));
|
|
for(const id of [31,32,34])assert.ok(html.includes(`href="/config/${id}/"`));
|
|
for(const id of [23456,2,3])assert.ok(html.includes(`href="/detail/${id}/"`));
|
|
assert.ok(!html.includes('href="/video/4/"'));assert.ok(!html.includes('href="/config/33/"'));assert.ok(html.includes('href="/topics/8/"'));
|
|
assert.equal((html.match(/class="hg-card-eps"/g)||[]).length,1);
|
|
assert.ok(html.indexOf('必看好剧')<html.indexOf('热播榜'));
|
|
});
|
|
|
|
test('empty or malformed homepage lists fall back to empty states instead of throwing',()=>{
|
|
for(const value of [null,undefined,{},'',false,1]){
|
|
const html=renderHome({homeBannerData:value,homeConfigData:value,announcements:value,advitise:{nineGridList:value}});
|
|
assert.ok(html.includes('暂无推荐剧集'));assert.ok(html.includes('暂时没有内容'));
|
|
}
|
|
assert.ok(renderHome(null).includes('暂无推荐剧集'));
|
|
});
|
|
|
|
test('nested home blocks tolerate invalid rows and non-array child lists',()=>{
|
|
const html=renderHome({homeBannerData:[null,false,drama],announcements:[null,{title:'有效公告'},4],homeConfigData:[null,7,
|
|
{id:31,name:'有效栏目',status:true,type:1,list:[null,drama]},
|
|
{id:32,name:'空视频栏目',status:true,type:2,videoList:{}},
|
|
{id:33,name:'空题材栏目',status:true,type:3,themeList:'bad'},
|
|
{id:34,name:'题材栏目',status:true,type:3,themeList:[null,{id:8,name:'悬疑'}]},
|
|
],advitise:{nineGridList:[null,advert]}});
|
|
assert.ok(!html.includes('空视频栏目'));
|
|
for(const content of ['有效栏目','空题材栏目','有效公告','href="/topics/8/"','href="/detail/23456/"','有效广告'])assert.ok(html.includes(content),content);
|
|
});
|
|
|
|
test('banner selection and card metadata tolerate malformed records without losing valid items',()=>{
|
|
assert.deepEqual(activeBanners({homeBannerData:[null,drama,{...drama,id:2,showOnBanner:true}]}).map(item=>item.id),[2]);
|
|
assert.ok(hero([null,drama]).includes('href="/detail/23456/"'));
|
|
assert.ok(hero(null).includes('暂无推荐剧集'));
|
|
assert.ok(card({...drama,tagNames:'bad',themeNames:['悬疑']}).includes('悬疑'));
|
|
assert.equal(card(null),'');assert.equal(topicCard(null),'');
|
|
});
|
|
|
|
test('carousel frames keep their own titles below the stable page heading, including fade snapshots',()=>{
|
|
const items=[drama,{...drama,id:2,title:'下一部短剧'}];
|
|
for(const selected of [0,1]){
|
|
const frame=hero(items,selected);
|
|
assert.equal((frame.match(/<h2\b/g)||[]).length,1);
|
|
assert.ok(!/<h1\b/.test(frame),'live and outgoing frames must not duplicate the page h1');
|
|
assert.ok(frame.includes(`href="/detail/${items[selected].id}/"`));
|
|
assert.ok(frame.includes(`data-feature="${selected}"`));
|
|
}
|
|
assert.ok(!/<h1\b/.test(hero(null)));
|
|
});
|
|
|
|
test('ad links carry the numeric ad ID and slot for click reporting, and invalid identities are omitted',()=>{
|
|
const html=promotionRow([{id:'1138200002',location:2,title:'广告',images:'https://example.com/ad.png',link:'https://example.com/landing'},{id:'bad-id',location:2,title:'无效',images:'https://example.com/b.png',link:'https://example.com/b'}]);
|
|
assert.ok(html.includes('data-ad data-ads-id="1138200002" data-ads-location="2" href="https://example.com/landing"'));
|
|
assert.equal((html.match(/data-ads-id=/g)||[]).length,1);
|
|
});
|