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