diff --git a/services/oauth/test/console-layout-a.test.mjs b/services/oauth/test/console-layout-a.test.mjs new file mode 100644 index 0000000..ee6e7ac --- /dev/null +++ b/services/oauth/test/console-layout-a.test.mjs @@ -0,0 +1,88 @@ +// Presentation regressions; these do not replace authentication or isolation suites. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import {renderPage,sendPage,serveAsset,pages} from '../../../web/console/render.mjs'; +import {overviewView,appearanceView,icon} from '../../../web/console/visuals.mjs'; +import {text,catalog} from '../../../web/console/catalog.mjs'; + +const view = (data={}) => overviewView(data,{t:text,memoryRows:()=>'
'}); +const response = () => ({writeHead(status,headers){this.status=status;this.headers=headers;},end(body){this.body=body;}}); + +test('Layout A: overview uses source-backed counts, not demo trends or connection claims',()=>{ + const html=view({counts:{memories:42,sources:68,summaries:7,jobs:3}}); + for(const value of [42,68,7,3])assert.ok(html.includes(`${value}`)); + assert.equal((html.match(/class="card metric"/g)||[]).length,4); + assert.match(html,/class="processing-card|class="card processing-card/); + assert.match(html,/class="synthetic-row"/); + assert.doesNotMatch(html,/1,248|24\s*\/\s*24|已连接|已完成|{ + const html=view({counts:{memories:-1,sources:'',summaries:NaN,jobs:Infinity}}); + assert.equal((html.match(/—<\/strong>/g)||[]).length,7); + assert.doesNotMatch(html,/0<\/strong>/); + const zero=view({counts:{memories:0}});assert.ok(zero.includes('0')); +}); +test('Layout A: appearance contains three theme previews and independent mode/language controls',()=>{ + const html=appearanceView(text); + assert.equal((html.match(/class="theme-option"/g)||[]).length,3); + assert.equal((html.match(/class="mini-shell"/g)||[]).length,3); + assert.equal((html.match(/aria-pressed="false" disabled/g)||[]).length,7); + for(const value of ['a','b','c','light','dark','zh-CN','en'])assert.ok(html.includes(`data-pref-value="${value}"`)); + assert.doesNotMatch(html,/{ + assert.deepEqual(Object.keys(catalog.en).sort(),Object.keys(catalog['zh-CN']).sort()); + for(const locale of ['zh-CN','en']){ + const t=key=>text(key,locale),html=appearanceView(t)+overviewView({}, {t,memoryRows:()=>''}); + for(const [,key] of html.matchAll(/data-i18n="([^"]+)"/g))assert.ok(Object.hasOwn(catalog[locale],key),key); + } + assert.ok(appearanceView(()=>'').includes('<script>')); + assert.doesNotMatch(appearanceView(()=>''),/'}}); + assert.match(html,/action="\/console-api\/logout" method="post"/); + assert.match(html,/name="csrf" value="" onfocus="synthetic"/); + assert.ok(html.includes('<script>synthetic</script>')); + assert.doesNotMatch(html,/'); + assert.doesNotMatch(svg,/