Files
web_hgdjc_h5/tests/pagination.test.mjs
2026-11-06 00:13:27 +07:00

94 lines
6.5 KiB
JavaScript

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(file,replace=code=>code){
const filename=new URL(file,import.meta.url);
const {outputText}=ts.transpileModule(await readFile(filename,'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}});
return 'data:text/javascript;base64,'+Buffer.from(linkShared(replace(outputText)).replace(/(['"])\.\.\/routing\1/g,JSON.stringify(routingModuleUrl))+'\n//# sourceURL='+filename.pathname).toString('base64');
}
const routingUrl=await moduleUrl('../src/lib/routing.ts');
const {routeFromSegments,parseSitePath,paginationPath,internalPagePath,searchPath}=await import(routingUrl);
const protocolUrl=await moduleUrl('../src/lib/api/protocol.ts');
const renderUrl=await moduleUrl('../src/lib/client/render.ts',code=>code.replace('"../api/protocol"',JSON.stringify(protocolUrl)));
const {pagination}=await import(renderUrl);
const {bindPaginationJumps}=await import(await moduleUrl('../src/lib/client/navigation.ts'));
test('every paginated page type resolves directly from the page suffix',()=>{
const keyword='都市 / page/7/?#%2F';
const bases=['/shorts/','/topics/','/topics/7/','/theme/8/','/tag/9/','/config/31/','/rank/','/rank/hot/','/rank/new/','/rank/follow/','/search/'+encodeURIComponent(keyword)+'/'];
for(const base of bases){
for(const page of [1,2,37]){
const path=base+'page/'+page+'/';
assert.deepEqual(parseSitePath(path),{...parseSitePath(base),page},path);
assert.deepEqual(parseSitePath(path.slice(0,-1)),parseSitePath(path),path);
assert.equal(internalPagePath(path,'https://drama.example/'),path,path);
assert.equal(paginationPath(path,1),base.startsWith('/search/')?searchPath(keyword):base,path);
}
}
assert.deepEqual(routeFromSegments(['search',keyword,'page','3']),{group:'search',key:keyword,page:3});
});
test('invalid page numbers, nested page suffixes, and non-paginated sections are rejected',()=>{
for(const value of ['0','-1','1.5','02','NaN','9007199254740992'])assert.equal(parseSitePath(`/shorts/page/${value}/`),null,value);
for(const base of ['/','/apps/','/home-road/','/navigation/','/info/about-us/','/detail/23456/','/play/23456/90001/'])assert.equal(parseSitePath(base+'page/2/'),null,base);
for(const base of ['/member/center/','/member/favorites/','/info/deposit/','/info/feedback/'])assert.equal(parseSitePath(base),null,base);
assert.equal(parseSitePath('/shorts/page/2/page/3/'),null);
assert.throws(()=>paginationPath('/shorts/',0));assert.throws(()=>paginationPath('/apps/',2));
});
test('page links retain filters and encoded search terms and sorting resets to the first page',()=>{
assert.equal(paginationPath('/shorts/page/4/?sort=3&source=menu',2),'/shorts/page/2/?sort=3&source=menu');
assert.equal(paginationPath('/shorts/page/4/?sort=3&source=menu',1,{sort:2}),'/shorts/?sort=2&source=menu');
assert.equal(paginationPath('/shorts/page/4/?sort=3',1,{sort:null}),'/shorts/');
const base='/search/'+encodeURIComponent('都市 / 爱情?#%')+'/';
assert.equal(paginationPath(base,2),searchPath('都市 / 爱情?#%',2));
const url=new URL(paginationPath(base,2),'https://drama.example');
assert.equal(parseSitePath(url.pathname,url.search).key,'都市 / 爱情?#%');
});
test('pagination renders real page links with correct boundaries, current page, and bounded page numbers',()=>{
const href=page=>paginationPath('/rank/new/page/5/?sort=2&from=menu',page);
const html=pagination(240,5,href);
assert.ok(html.includes('href="/rank/new/page/4/?sort=2&from=menu" rel="prev"'));
assert.ok(html.includes('href="/rank/new/page/6/?sort=2&from=menu" rel="next"'));
assert.ok(html.includes('href="/rank/new/page/5/?sort=2&from=menu" aria-current="page"'));
assert.equal((html.match(/<button/g)||[]).length,1);assert.ok(html.includes('<button type="submit">确定</button>'));assert.ok(!html.includes('data-page-number'));
assert.ok(html.includes('data-page-base="/rank/new/?sort=2&amp;from=menu"'));
assert.ok(html.includes('name="page" min="1" max="10" step="1"'));
assert.ok((html.match(/<a /g)||[]).length<=7);
assert.ok(pagination(240,1,href).includes('aria-disabled="true" aria-label="上一页"'));
assert.ok(pagination(240,10,href).includes('aria-disabled="true" aria-label="下一页"'));
assert.equal(pagination(0,1,href),'');assert.equal(pagination(24,1,href),'');
assert.ok(pagination(0,9,href).includes('href="/rank/new/?sort=2&amp;from=menu"'));
});
test('jump forms navigate to independent routes, retain canonical slugs and filters, and reject invalid pages',()=>{
const previousForm=globalThis.HTMLFormElement,previousInput=globalThis.HTMLInputElement;
class Input {valueAsNumber=3;max='10';}
class Form {dataset={pageBase:'/topics/sweet-love/?sort=2&source=menu'};input=new Input();valid=true;elements={namedItem:()=>this.input};matches=()=>true;reportValidity=()=>this.valid;}
globalThis.HTMLFormElement=Form;globalThis.HTMLInputElement=Input;
try{
let submit;const paths=[];
bindPaginationJumps({addEventListener:(type,handler)=>{assert.equal(type,'submit');submit=handler;}},path=>paths.push(path));
const form=new Form();let prevented=0;
const send=()=>submit({target:form,preventDefault(){prevented++;}});
send();assert.equal(paths.at(-1),'/topics/sweet-love/page/3/?sort=2&source=menu');
form.input.valueAsNumber=1;send();assert.equal(paths.at(-1),'/topics/sweet-love/?sort=2&source=menu');
form.dataset.pageBase=searchPath('都市 / 爱情');form.input.valueAsNumber=2;send();assert.equal(paths.at(-1),searchPath('都市 / 爱情',2));
const count=paths.length;
for(const value of [NaN,0,-1,1.5,11,Infinity,Number.MAX_SAFE_INTEGER+1]){form.input.valueAsNumber=value;send();}
form.input.valueAsNumber=2;form.valid=false;send();assert.equal(paths.length,count);
assert.equal(prevented,11);
// The delegated listener works for forms added after it was registered.
const added=new Form();submit({target:added,preventDefault(){}});assert.equal(paths.length,count+1);
submit({target:{},preventDefault(){assert.fail('unrelated form');}});
}finally{
if(previousForm===undefined)delete globalThis.HTMLFormElement;else globalThis.HTMLFormElement=previousForm;
if(previousInput===undefined)delete globalThis.HTMLInputElement;else globalThis.HTMLInputElement=previousInput;
}
});