Files
2026Technology-Competition/tests/test_chat_state.js
T
lhl 520c9e25b0 feat(ui): 前端整改 —— RAG 入口产品化、放开 select、高级字段折叠、字段级红框
按"不考虑时间、考虑正确合理"原则逐块实施。

P0-B 放开 select
  - chat_state.js: shouldHideUploadSelect 始终 false; resolveUploadType 尊重用户选择
  - chat.html: 选项目时 select 不再隐藏 + 不再强改 file_type=requirements
  - 客户端 file_type↔扩展名校验(existing_system=.zip, requirements=.xlsx 等)
  - 项目已预置同类型时 confirm() 显式覆盖确认(不藏起入口)

P0-C 高级字段折叠 + 字段级红框
  - 项目抽屉主面板仅留 项目名/显示名;4 个服务器路径字段收进 details.advanced
  - saveDrawerProject 错误时按 ProjectConfigError label 关键字(模板/做成说明书/
    既有系统代码库/既有设计文档目录/项目名)给对应输入加 .invalid 3s 清除
  - 删除按钮 pf-delete 改用 hidden 而非 style.display

P0-A RAG 入口产品化
  - 后端 RagStore.count(scope) 线程安全读加锁
  - GenesisService.rag_stats(sid) 含 except 兜底(count 抛错返回 0)
  - GET /api/sessions/{sid}/rag-stats 端点
  - 前端顶栏:RAG 开关 + RAG 已索引 N 片段 状态徽标 + 开始影响调查按钮
  - 上传 existing_system 后自动 refreshRagStats 刷新徽标
  - 开关持久化到 localStorage(genesis_rag_enabled)

P1-D loadSession 不再隐式覆盖 draftProject
  - activeProject vs draftProject 分层;不一致时由 renderProjectMismatchHint
    提示用户主动"切到该项目"或"保留当前项目"
  - 保留 写入 sessionStorage 标记,避免每次 load 都提示

P1-F 删除抽屉项目 fallback 收敛
  - 先记 deleted 再清 drawerSelected,修复"先 null 后比较"恒假 bug
  - buildWelcome 唯一来源(chat_state.js 单点,前端内联重复移除)

P2-H 杂项
  - send() in-flight 锁防双击
  - input maxlength=2000
  - 启动恢复前校验项目存在 + loading 占位
  - select 关联 label for
  - esc 转义加 引号
  - sid 全程 encodeURIComponent
  - avatar 按 name hash 选色 + 中文首字(Array.from)+ aria-label 含名字

测试:619 passed / 99.01% 99.0% 达标(+8 覆盖 store.count/service.rag_stats/
rag-stats 端点/rag_stats 异常);Node chat_state 9 passed
2026-08-30 21:16:07 +08:00

50 lines
2.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 前端核心状态逻辑单测(Node 原生 test runnerUMD 模块可在 Node 下 require
// 对应方案 docs/frontend_audit_plan.md §7.1 / §4.4
const test = require('node:test');
const assert = require('node:assert/strict');
const S = require('../src/genesis/server/static/chat_state.js');
test('autoBindProject: 启动无项目且有项目时自动绑首项', () => {
assert.equal(S.autoBindProject(null, [{ name: 'A' }, { name: 'B' }]), 'A');
});
test('autoBindProject: 指向已删除项目时回退首项', () => {
assert.equal(S.autoBindProject('X', [{ name: 'A' }]), 'A');
});
test('autoBindProject: 已选有效项目保持不变', () => {
assert.equal(S.autoBindProject('A', [{ name: 'A' }]), 'A');
});
test('shouldHideUploadSelect: 始终 false(选项目时也允许补传其它类型)', () => {
assert.equal(S.shouldHideUploadSelect(null, null), false);
assert.equal(S.shouldHideUploadSelect(null, 'A'), false);
assert.equal(S.shouldHideUploadSelect('A', null), false);
assert.equal(S.shouldHideUploadSelect('A', 'A'), false);
});
test('resolveUploadType: 始终尊重用户下拉选择(不再被项目绑定强制为 requirements', () => {
assert.equal(S.resolveUploadType(null, 'A', 'template'), 'template');
assert.equal(S.resolveUploadType(null, null, 'template'), 'template');
assert.equal(S.resolveUploadType('A', null, 'existing_system'), 'existing_system');
});
test('computeDrawerSnapshot: 去空白并以单元分隔符连接', () => {
assert.equal(S.computeDrawerSnapshot([' a ', 'b']), 'a\u0001b');
});
test('isDrawerDirty: 快照不同为脏,相同为干净', () => {
assert.equal(S.isDrawerDirty('x', 'y'), true);
assert.equal(S.isDrawerDirty('x', 'x'), false);
});
test('buildWelcome: 无项目提示新建/选择项目', () => {
assert.match(S.buildWelcome(null), /请新建项目|选择项目/);
});
test('buildWelcome: 有项目时提示由项目提供模板/规则/代码库', () => {
const txt = S.buildWelcome('A');
assert.match(txt, /项目「A」/);
assert.match(txt, /要件定义/);
});