import {linkShared} from './helpers/shared-modules.mjs'; 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(linkShared(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,homeRecommendations,heroFeed,card,coverCornerBadge,topicCard,imageAttributes,blockSection}=await import(renderUrl); const drama={id:23456,title:'正常剧集 ',cover:'https://example.com/poster.webp',tagNames:['都市']}; const advert={id:'ad-one',title:'有效广告',images:'https://example.com/ad.webp',link:'https://example.com/landing'}; const itemCount=html=>(html.match(/class="(?:hg-card|curated-card drama-data-card|ranked-card drama-data-card|topic-recommendation-card)"/g)||[]).length; test('Swagger homepage modules render actual titles, drama cards and theme destinations',()=>{ const html=renderHome({modules:[{id:31,title:'新接口短剧栏目',slug:'editors',moduleType:1,list:[drama]},{id:32,title:'新接口题材',moduleType:2,topicList:[{id:7,slug:'urban',name:'都市题材',list:[drama]}]}]}); for(const text of ['新接口短剧栏目','新接口题材','href="/config/editors/"','href="/theme/urban/"','href="/detail/23456/"'])assert.ok(html.includes(text),text); assert.equal(homeRecommendations({modules:[{id:31,title:'短剧',moduleType:1,list:[drama]}]})[0].id,drama.id); const emptyModules=renderHome({modules:[],homeConfigData:[{id:1,name:'旧缓存不应复活',type:1,status:true,list:[drama]}]}); assert.ok(!emptyModules.includes('旧缓存不应复活')); }); test('topic cards escape metadata and carry no analytics attributes',()=>{ const html=topicCard({id:7,name:'都市 <甜宠>',icon:'https://example.com/topic.png'}); assert.ok(html.includes('都市 <甜宠>'));assert.ok(!html.includes('data-track'));assert.ok(!html.includes('data-item-')); }); test('drama cards use the 9:16 gallery layout with episode counter, square badge and views/theme meta',()=>{ const series=card({id:5,title:'连载剧',totalEpisode:12,updateStatus:1,isFeatured:true,heatValue:12345,themeNames:['都市'],cover:'https://example.com/c.webp'}); for(const text of ['class="hg-card"','class="hg-card-cover"','>1/12+<','class="hg-card-badge"','cover-corner-badge--hot','class="hg-card-rule"','class="hg-card-views"','1.2万','class="hg-card-theme">都市<'])assert.ok(series.includes(text),text); assert.ok(!series.includes('drama-description'));assert.ok(!series.includes('data-track')); assert.ok(card({id:6,title:'完结',totalEpisode:30,updateStatus:2}).includes('>1/30<')); assert.ok(card({id:7,title:'单集',totalEpisode:1}).includes('>单集<')); }); 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(/]*>/g)];assert.ok(images.length>0); for(const [tag] of images){ assert.match(tag,/\ssrc="\/placeholder\.png"/);assert.match(tag,/\sdata-src="https:\/\//); assert.ok(!/\ssrc="https?:/.test(tag)); } } assert.match(imageAttributes(drama.cover,undefined,'poster'),/^poster="\/placeholder\.png" 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.ok(html.includes('alt="第二个广告"'));assert.ok(!html.includes('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:'第二条 ')); }); 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')); assert.ok(!html.includes('id="library"')); 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.split('id="home-blocks"')[1].match(/class="hg-card-counter"/g)||[]).length,3); assert.ok(html.indexOf('必看好剧'){ const banner=id=>({id,title:'横幅 '+id,images:'https://cdn.example/'+id+'.gif',link:'https://example.com/'+id}); const block=(id,name)=>({id,name,status:true,type:1,list:[drama]}); const html=renderHome({homeConfigData:[block(1,'栏目一'),block(2,'栏目二'),block(3,'栏目三'),block(4,'栏目四'),block(5,'栏目五')],advitise:{detailbannerList:[null,banner('a'),{...banner('b'),images:'javascript:alert(1)'},banner('c'),banner('d'),banner('e'),banner('f')]}}); const blocks=html.split('id="home-blocks"')[1].split('popular-searches')[0]; assert.equal((blocks.match(/class="wide-ad-row"/g)||[]).length,2); assert.ok(blocks.indexOf('栏目二'){ 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('homepage category styles follow API values rather than titles, ranks or module positions',()=>{ const modules=[ {id:1,slug:'first',title:'热门推荐',moduleType:1,rank:99,style:' d ',list:[{...drama,slug:'first-story'}]}, {id:2,title:'热门推荐',moduleType:1,rank:1,style:'C',label:'样式C