UI修改
This commit is contained in:
+27
-26
@@ -1,3 +1,4 @@
|
||||
import {linkShared} from './helpers/shared-modules.mjs';
|
||||
import {routingModuleUrl} from './helpers/routing.mjs';
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -7,16 +8,16 @@ 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');
|
||||
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 historyUrl=await moduleUrl('../src/lib/client/header-history.ts',code=>code.replace("'../api/protocol'",JSON.stringify(protocolUrl)).replace("'./render'",JSON.stringify(renderUrl)));
|
||||
const {headerHistoryMarkup}=await import(historyUrl);
|
||||
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'};
|
||||
|
||||
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);
|
||||
@@ -25,9 +26,17 @@ test('Swagger homepage modules render actual titles, drama cards and theme desti
|
||||
assert.ok(!emptyModules.includes('旧缓存不应复活'));
|
||||
});
|
||||
|
||||
test('topic cards include escaped metadata for content exposure and click tracking',()=>{
|
||||
test('topic cards escape metadata and carry no analytics attributes',()=>{
|
||||
const html=topicCard({id:7,name:'都市 <甜宠>',icon:'https://example.com/topic.png'});
|
||||
assert.match(html,/data-track-item data-item-id="7" data-item-type="topic" data-item-name="都市 <甜宠>"/);
|
||||
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',()=>{
|
||||
@@ -53,8 +62,8 @@ test('shared square ad rows keep real ad data and links and omit empty placement
|
||||
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('data-ad-id="ad-one"'));assert.ok(html.includes('data-ad-id="ad-two"'));
|
||||
assert.ok(html.includes('href="https://example.com/landing"'));assert.ok(html.includes('data-ad-type="icon"'));
|
||||
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),'');
|
||||
});
|
||||
@@ -64,7 +73,7 @@ test('ads ignores malformed entries and keeps valid ads in order with escaped te
|
||||
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('/apps/'));assert.ok(html.includes('<a class="promo-app"><img'));assert.ok(html.includes('第二条 <script>'));
|
||||
assert.ok(!html.includes('javascript:'));assert.ok(!html.includes('<script>'));
|
||||
});
|
||||
|
||||
@@ -88,7 +97,7 @@ test('dynamic home sections share a grid without inheriting the prototype editor
|
||||
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="drama-episodes"/g)||[]).length,1);
|
||||
assert.equal((html.split('id="home-blocks"')[1].match(/class="hg-card-counter"/g)||[]).length,3);
|
||||
assert.ok(html.indexOf('必看好剧')<html.indexOf('热播榜'));
|
||||
});
|
||||
|
||||
@@ -101,7 +110,7 @@ test('homepage banner ads follow each second topic section and keep real links',
|
||||
assert.ok(blocks.indexOf('栏目二')<blocks.indexOf('横幅 a')&&blocks.indexOf('横幅 a')<blocks.indexOf('横幅 c')&&blocks.indexOf('横幅 c')<blocks.indexOf('栏目三'));
|
||||
assert.ok(blocks.indexOf('栏目四')<blocks.indexOf('横幅 d')&&blocks.indexOf('横幅 d')<blocks.indexOf('横幅 e')&&blocks.indexOf('横幅 e')<blocks.indexOf('栏目五'));
|
||||
assert.ok(!blocks.includes('横幅 f'));assert.ok(!blocks.includes('横幅 b'));assert.ok(!blocks.includes('javascript:'));
|
||||
assert.ok(blocks.includes('data-ad-type="banner"'));assert.ok(blocks.includes('href="https://example.com/a"'));
|
||||
assert.ok(!blocks.includes('data-ad-'));assert.ok(blocks.includes('href="https://example.com/a"'));
|
||||
assert.ok(!renderHome({homeConfigData:[block(1,'栏目一'),block(2,'栏目二')]}).includes('wide-ad-row'));
|
||||
assert.ok(!renderHome({homeConfigData:[block(1,'单独栏目')],advitise:{detailbannerList:[banner('a')]}}).includes('wide-ad-row'));
|
||||
});
|
||||
@@ -140,7 +149,7 @@ test('A–E previews use API limits while retaining backend order and full sourc
|
||||
const topics=list.map(item=>({id:item.id,name:'专题 '+item.id,slug:item.slug}));
|
||||
const block={id:1,name:'任意分类',type:style==='E'?3:1,status:true,style,limit,list,themeList:topics,topicKind:'theme'};
|
||||
const html=blockSection(block);
|
||||
assert.equal((html.match(/data-track-item/g)||[]).length,limit,style);
|
||||
assert.equal(itemCount(html),limit,style);
|
||||
const links=[...html.matchAll(/href="\/(?:detail|theme)\/story-(\d+)\/"/g)].map(match=>Number(match[1]));
|
||||
assert.deepEqual(links,Array.from({length:limit},(_,index)=>index),style);
|
||||
assert.equal(list.length,14);assert.equal(topics.length,14);
|
||||
@@ -166,7 +175,7 @@ test('category limits apply to current and legacy modules, including unknown sty
|
||||
{homeConfigData:[{id:1,name:'分类',style,limit,type:topic?3:1,status:true,list:items,themeList:items}]}];
|
||||
for(const home of sources){
|
||||
const html=renderHome(home).split('id="home-blocks"')[1].split('</section>')[0];
|
||||
assert.equal((html.match(/data-track-item/g)||[]).length,Math.min(limit,items.length),String(style)+' '+limit);
|
||||
assert.equal(itemCount(html),Math.min(limit,items.length),String(style)+' '+limit);
|
||||
if(limit===0)assert.ok(html.includes('暂时没有内容'));
|
||||
}
|
||||
}
|
||||
@@ -177,10 +186,10 @@ test('missing or invalid category limits retain all valid returned items without
|
||||
const list=Array.from({length:14},(_,index)=>({...drama,id:100+index}));
|
||||
for(const limit of [undefined,null,-1,1.5,NaN,Infinity,'3','',false,{},Number.MAX_SAFE_INTEGER+1]){
|
||||
const html=blockSection({id:1,name:'分类',type:1,status:true,style:'B',limit,list:[null,false,...list]});
|
||||
assert.equal((html.match(/data-track-item/g)||[]).length,14,String(limit));
|
||||
assert.equal(itemCount(html),14,String(limit));
|
||||
}
|
||||
const html=blockSection({id:1,name:'分类',type:1,status:true,style:'C',limit:2,list:[null,list[0],false,list[1],list[2]]});
|
||||
assert.equal((html.match(/data-track-item/g)||[]).length,2);
|
||||
assert.equal(itemCount(html),2);
|
||||
assert.ok(html.includes('/detail/100/'));assert.ok(html.includes('/detail/101/'));assert.ok(!html.includes('/detail/102/'));
|
||||
});
|
||||
|
||||
@@ -189,7 +198,7 @@ test('missing, reserved, malformed and incompatible category styles preserve the
|
||||
for(const style of [undefined,'','F','Z','AA',0,{},'E','A" onclick="bad']){
|
||||
const html=blockSection({id:1,name:'热门好剧',type:1,status:true,style,list});
|
||||
assert.ok(!html.includes('data-home-style='));assert.ok(html.includes('class="cards"'));
|
||||
assert.equal((html.match(/class="card drama-data-card"/g)||[]).length,12);
|
||||
assert.equal((html.match(/class="hg-card"/g)||[]).length,12);
|
||||
assert.ok(!html.includes('onclick='));
|
||||
}
|
||||
const topics=list.map(item=>({id:item.id,name:'专题'}));
|
||||
@@ -211,10 +220,11 @@ test('cover corner badges prefer 原创 over 爆剧 over 上新',()=>{
|
||||
assert.ok(card({...drama,isNew:true}).includes('>上新</span>'));
|
||||
});
|
||||
|
||||
test('styled category cards keep escaped metadata, native links, tracking and empty states',()=>{
|
||||
test('styled category cards keep escaped metadata, native links and empty states',()=>{
|
||||
for(const style of ['C','D']){
|
||||
const html=blockSection({id:1,name:'分类',type:1,status:true,style,label:'新剧 <b>',list:[null,{...drama,desc:'简介 <script>',recommendTraceId:'trace-123',totalEpisode:36,isNew:true,tagNames:['都市 <img>']}]});
|
||||
for(const value of ['data-recommend-trace-id="trace-123"','data-src="https://example.com/poster.webp"','loading="lazy"','cover-corner-badge--new','上新','36集','简介 <script>','都市 <img>'])assert.ok(html.includes(value),style+' '+value);
|
||||
assert.ok(!html.includes('data-recommend-trace-id'));
|
||||
for(const value of ['data-src="https://example.com/poster.webp"','loading="lazy"','cover-corner-badge--new','上新','36集','简介 <script>','都市 <img>'])assert.ok(html.includes(value),style+' '+value);
|
||||
assert.ok(!html.includes('<script>'));assert.ok(!html.includes('<b>'));
|
||||
if(style==='C'){assert.ok(html.includes('class="pick">最新上架</span>'));assert.ok(!html.includes('新剧 <b>'));assert.ok(!html.includes('class="pick">新剧'));}
|
||||
}
|
||||
@@ -270,12 +280,3 @@ test('homepage landscape recommendations use enabled API content, deduplicate ID
|
||||
});
|
||||
|
||||
|
||||
test('header history shows only valid unique account records, escapes titles and keeps native detail links',()=>{
|
||||
const records=Array.from({length:12},(_,index)=>({...drama,id:index+1,slug:'recent-'+index,totalEpisode:index+3}));
|
||||
const html=headerHistoryMarkup([null,{id:-1},records[0],records[0],...records.slice(1)]);
|
||||
assert.equal((html.match(/class="jg-history-item"/g)||[]).length,9);
|
||||
assert.ok(html.includes('href="/detail/recent-0/"'));assert.ok(html.includes('全 3 集'));
|
||||
assert.ok(html.includes('正常剧集 <script>'));assert.ok(!html.includes('<script>'));
|
||||
assert.ok(html.includes('data-src="https://example.com/poster.webp"'));
|
||||
assert.ok(headerHistoryMarkup(null).includes('暂无观看历史'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user