import assert from 'node:assert/strict' import { readFileSync, statSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { assistantCapabilities } from '../src/data/personalWorkbench.js' import { buildWorkbenchCapabilityAssistantPayload, resolveWorkbenchCapabilityAssistantEntry } from '../src/utils/personalWorkbenchAssistantEntry.js' const workbench = readFileSync( fileURLToPath(new URL('../src/components/business/PersonalWorkbench.vue', import.meta.url)), 'utf8' ) const workbenchStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/personal-workbench.css', import.meta.url)), 'utf8' ) const workbenchGlassStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/personal-workbench-glass.css', import.meta.url)), 'utf8' ) const workbenchCardStyles = `${workbenchStyles}\n${workbenchGlassStyles}` const workbenchResponsiveStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/personal-workbench-responsive.css', import.meta.url)), 'utf8' ) const workbenchInsightStyles = readFileSync( fileURLToPath(new URL('../src/assets/styles/components/personal-workbench-insights.css', import.meta.url)), 'utf8' ) const heroBackgroundAsset = fileURLToPath( new URL('../src/assets/personal-workbench-hero-bg-theme-base.webp', import.meta.url) ) const capabilityGlassAsset = fileURLToPath( new URL('../src/assets/personal-workbench-card-glass-capability.webp', import.meta.url) ) const panelGlassAsset = fileURLToPath( new URL('../src/assets/personal-workbench-card-glass-panel.webp', import.meta.url) ) test('workbench assistant greets the current employee without the old helper tag', () => { assert.doesNotMatch(workbench, /assistant-tag/) assert.doesNotMatch(workbench, /AI 报销助手/) assert.match(workbench, /嗨,\{\{ displayUserName \}\},我是您的 AI 费用助手<\/span>/) assert.match(workbench, /placeholder="请输入费用申请、报销问题、预算查询或制度问答\.\.\."/) assert.match(workbench, /const displayUserName = computed/) assert.match(workbench, /user\.name/) }) test('workbench capability cards open assistant without injecting canned prompts', () => { assert.match(workbench, /@click="openCapabilityAssistant\(item\)"/) assert.doesNotMatch(workbench, /openPromptAssistant\(item\.prompt\)/) for (const item of assistantCapabilities) { const payload = buildWorkbenchCapabilityAssistantPayload(item, { prompt: '', source: 'workbench', files: [] }) assert.equal(payload.prompt, '') assert.equal(payload.conversation, null) assert.notEqual(payload.prompt, item.prompt) assert.ok(resolveWorkbenchCapabilityAssistantEntry(item).sessionType) } }) test('workbench capability cards keep user-entered context only', () => { const [expenseApplication] = assistantCapabilities const files = [{ name: 'invoice.pdf' }] const payload = buildWorkbenchCapabilityAssistantPayload(expenseApplication, { prompt: '我需要申请下周出差费用', source: 'workbench', files }) assert.equal(payload.prompt, '我需要申请下周出差费用') assert.equal(payload.source, 'application') assert.equal(payload.sessionType, 'application') assert.equal(payload.files, files) }) test('workbench hero uses theme-tintable background image', () => { assert.match(workbench, /personal-workbench-hero-bg-theme-base\.webp/) assert.doesNotMatch(workbench, /personal-workbench-hero-bg-theme-base\.png/) assert.match(workbench, /--assistant-bg-image.*workbenchHeroBackground/) assert.match(workbenchStyles, /--assistant-theme-tint:[\s\S]*--theme-primary-rgb/) assert.match(workbenchStyles, /var\(--assistant-bg-image\) var\(--assistant-bg-position\) \/ var\(--assistant-bg-size\) no-repeat/) assert.match(workbenchStyles, /background-blend-mode:\s*normal,\s*color,\s*luminosity;/) assert.match(workbenchStyles, /\.assistant-hero::after\s*\{[\s\S]*content:\s*none;/) assert.match(workbenchResponsiveStyles, /--assistant-bg-position:\s*68% center;/) assert.doesNotMatch(workbenchResponsiveStyles, /homepage_backgraound/) assert.ok(statSync(heroBackgroundAsset).size < 120 * 1024) }) test('workbench cards use layered glass material instead of texture-led cards', () => { assert.match(workbench, /personal-workbench-glass\.css/) assert.match(workbenchGlassStyles, /--workbench-capability-bg-image:\s*url\("\.\.\/\.\.\/personal-workbench-card-glass-capability\.webp"\)/) assert.match(workbenchGlassStyles, /--workbench-panel-bg-image:\s*url\("\.\.\/\.\.\/personal-workbench-card-glass-panel\.webp"\)/) assert.match(workbenchGlassStyles, /--workbench-glass-base:/) assert.match(workbenchGlassStyles, /--workbench-glass-highlight:/) assert.match(workbenchGlassStyles, /--workbench-glass-noise-opacity:\s*0\.012;/) assert.match(workbenchGlassStyles, /--workbench-glass-blur:\s*blur\(18px\) saturate\(1\.28\);/) assert.match(workbenchGlassStyles, /\.capability-card\s*\{[\s\S]*background-color:\s*rgba\(255,\s*255,\s*255,\s*0\.64\);[\s\S]*backdrop-filter:\s*var\(--workbench-glass-blur\)/) assert.match(workbenchGlassStyles, /\.workbench-card\s*\{[\s\S]*background-color:\s*rgba\(255,\s*255,\s*255,\s*0\.66\);[\s\S]*backdrop-filter:\s*var\(--workbench-glass-blur\)/) assert.match(workbenchGlassStyles, /\.capability-card::before,[\s\S]*\.capability-card::after/) assert.match(workbenchGlassStyles, /\.capability-card::before\s*\{[\s\S]*var\(--workbench-capability-bg-image\) 0 0 \/ var\(--workbench-capability-tile-size\) repeat;[\s\S]*opacity:\s*var\(--workbench-glass-noise-opacity\);/) assert.match(workbenchGlassStyles, /\.workbench-card::before\s*\{[\s\S]*var\(--workbench-panel-bg-image\) 0 0 \/ var\(--workbench-panel-tile-size\) repeat;[\s\S]*opacity:\s*calc\(var\(--workbench-glass-noise-opacity\) \* 0\.8\);/) assert.match(workbenchGlassStyles, /\.capability-card::after\s*\{[\s\S]*var\(--workbench-glass-highlight\)/) assert.match(workbenchGlassStyles, /\.workbench-card::after\s*\{[\s\S]*var\(--workbench-glass-highlight\)/) assert.doesNotMatch(workbenchGlassStyles, /\.capability-card::after\s*\{[^}]*radial-gradient/) assert.doesNotMatch(workbenchGlassStyles, /\.workbench-card::after\s*\{[^}]*radial-gradient/) assert.match(workbenchGlassStyles, /\.workbench-card > \*\s*\{[\s\S]*z-index:\s*1;/) assert.match(workbenchGlassStyles, /--workbench-capability-tile-size:\s*384px 384px;/) assert.match(workbenchGlassStyles, /--workbench-panel-tile-size:\s*512px 512px;/) assert.doesNotMatch(workbenchCardStyles, /var\(--workbench-capability-bg-image\)[^;]*cover no-repeat/) assert.doesNotMatch(workbenchCardStyles, /var\(--workbench-panel-bg-image\)[^;]*cover no-repeat/) assert.match(workbenchGlassStyles, /--workbench-glass-theme-tint:[\s\S]*--theme-primary-rgb/) assert.doesNotMatch(workbenchCardStyles, /background-blend-mode:\s*normal,\s*color,\s*normal;/) assert.match(workbenchResponsiveStyles, /--workbench-glass-noise-opacity:\s*0\.008;/) assert.match(workbenchResponsiveStyles, /--workbench-glass-blur:\s*blur\(14px\) saturate\(1\.2\);/) assert.match(workbenchGlassStyles, /\.progress-row\s*\{[\s\S]*background:\s*transparent;[\s\S]*box-shadow:\s*inset 0 1px 0 rgba\(var\(--theme-primary-rgb/) assert.doesNotMatch(workbench, /

我的待办<\/h2>/) assert.doesNotMatch(workbench, /

关键动作<\/h2>/) assert.doesNotMatch(workbenchGlassStyles, /\.todo-row/) assert.doesNotMatch(workbenchGlassStyles, /\.progress-row\s*\{[\s\S]*border-top:\s*1px solid var\(--workbench-line-soft\)/) assert.match(workbenchInsightStyles, /\.insight-metric-row,[\s\S]*\.insight-profile-card\s*\{[\s\S]*backdrop-filter:\s*blur\(10px\) saturate\(1\.16\)/) assert.doesNotMatch(workbenchInsightStyles, /background:\s*#ffffff;/) assert.ok(statSync(capabilityGlassAsset).size > 1024) assert.ok(statSync(panelGlassAsset).size > 1024) assert.ok(statSync(capabilityGlassAsset).size < 24 * 1024) assert.ok(statSync(panelGlassAsset).size < 24 * 1024) }) test('workbench submit shows intent recognition feedback before assistant opens', () => { assert.match(workbench, /class="assistant-intent-status"/) assert.match(workbench, /aria-live="polite"/) assert.match(workbench, /正在识别意图,准备进入对应助手/) assert.match(workbench, /startPendingAction\('intent'\)/) assert.match(workbench, /if \(open\) \{\s*clearPendingAction\(\)/) assert.match(workbench, /:readonly="isComposerPending"/) }) test('workbench progress rows show update time first', () => { assert.match(workbench, /class="progress-time"/) assert.match(workbench, /has-long-duration-divider/) assert.match(workbench, /hasLongDurationDivider/) assert.match(workbench, /const LONG_DURATION_DAYS = 10/) assert.match(workbench, /isLongDurationProgress\(item\?\.updatedAt\)/) assert.match(workbench, /