TUI 交互和渲染测试
这一阶段不是用户真正使用时的主流程,而是幕后给终端界面做“体检”。先用测试工具和假终端搭好舞台,让程序以为自己在真实窗口里运行,再把画面收起来对照。App 和 ChatWidget 的测试负责检查启动、会话恢复、输入提交、审批弹窗、权限、状态栏、斜杠命令等操作会不会乱。渲染、Markdown、历史消息、用量图和布局测试则盯住每一块显示是否对齐、换行正确。最后集成和 VT100 测试模拟真实终端滚动、缩放和历史回放,防止小改动把界面体验悄悄改坏。
共享 TUI 测试基础设施
这些文件定义通用测试框架、后端 shim,以及其余 TUI 测试套件所基于的聚合集成测试入口。
tui/src/test_support.rs源码 ↗
这个文件像测试用的“工具箱”。TUI 是终端用户界面,也就是在命令行窗口里显示和交互的界面;测试它时,经常需要一些固定、可信的输入。这里先准备了一份 TEST_MODEL_PRESETS,也就是测试可用的模型选项列表:它从项目自带的 models.json 里读模型,按优先级排序,再标出默认模型,保证测试看到的模型顺序和默认选择是稳定的。文件还转出了一些路径测试工具,让测试里的路径显示不受不同电脑环境影响。后面几个函数把 app-server 协议里的值转成旧测试辅助类型,重点是保证新旧表示法能对上;如果对不上,就直接让测试失败。这样做的好处是:测试失败会很早、很明确,不会等到界面逻辑里才出现难懂的问题。
test_path_display21–23 ↗
fn test_path_display(path: &str) -> String
作用:把一个测试用的路径字符串变成适合显示的路径文本。有人写测试时如果需要比较“界面上显示出来的路径”,就可以用它生成稳定的结果。
数据流:输入是一个普通路径字符串 → 它交给 test_path_buf 生成测试专用的路径对象,再调用 display 把路径转成可打印的样子 → 输出是一个 String,也就是最终用于断言或展示的路径文字;它不改动外部状态。
调用关系:它是测试代码里的小助手,自己不做复杂判断,而是把核心工作交给外部的 test_path_buf。测试需要检查路径显示时会调用它,用来避免每个测试都重复写同样的路径转换步骤。
调用图:外部调用 1 个(test_path_buf)。
session_source_cli25–30 ↗
fn session_source_cli() -> T
作用:生成一个表示“会话来自命令行”的测试值。测试里需要模拟用户从 CLI,也就是命令行入口启动会话时,会用到它。
数据流:没有普通参数,但它知道要使用协议里的 SessionSource::Cli → 它把这个值交给 from_app_server_wire,先按 app-server 的传输格式转成 JSON 值,再转回测试需要的目标类型 T → 输出是调用者指定的类型;如果转换失败,测试会直接报错。
调用关系:它是更具体的一层包装,专门表示命令行会话来源。调用图里它会被 test_session_telemetry 这类测试使用,而真正的格式转换工作交给 from_app_server_wire。
调用图:调用 1 个内部函数(from_app_server_wire);被 2 处调用(test_session_telemetry, test_session_telemetry)。
skill_scope_user32–37 ↗
fn skill_scope_user() -> T
作用:生成一个表示“技能作用在用户范围”的测试值。这里的技能范围可以理解为某个功能或配置是跟着用户走,而不是只属于某个代码仓库。
数据流:没有普通参数,但它固定拿协议里的 SkillScope::User → 交给 from_app_server_wire 做一次“协议值到测试目标类型”的转换 → 输出是调用者想要的类型 T;转换不成功就让测试失败。
调用关系:它是测试里表达“用户级技能范围”的便捷入口。它不自己处理序列化细节,而是把活儿交给 from_app_server_wire,保证和其他类似测试值走同一套转换规则。
调用图:调用 1 个内部函数(from_app_server_wire)。
skill_scope_repo39–44 ↗
fn skill_scope_repo() -> T
作用:生成一个表示“技能作用在仓库范围”的测试值。也就是说,这个测试值代表某个技能或设置只跟当前项目代码仓库有关。
数据流:没有普通参数,但它固定使用协议里的 SkillScope::Repo → 调用 from_app_server_wire,把协议层的表示转换成测试需要的类型 T → 返回转换后的值;如果两边格式对不上,就 panic,让测试立刻失败。
调用关系:它是测试里表达“仓库级技能范围”的便捷入口。和 skill_scope_user 一样,它只负责选对原始协议值,真正的转换统一交给 from_app_server_wire。
调用图:调用 1 个内部函数(from_app_server_wire)。
from_app_server_wire46–55 ↗
fn from_app_server_wire(value: impl Serialize) -> T
作用:把 app-server 协议里的某个值,转换成测试里旧辅助代码想要的类型。它的作用像一个“转接头”,让新协议值和旧测试类型能接上。
数据流:输入是一个可以被序列化的值,也就是能变成 JSON 的值 → 它先用 serde_json::to_value 转成 JSON 中间格式,再用 serde_json::from_value 转成调用者指定的类型 T → 输出是转换后的 T;如果任一步失败,它会让测试 panic,并说明协议值没有成功映射到旧 helper 类型。
调用关系:它是 session_source_cli、skill_scope_user 和 skill_scope_repo 背后的公共转换工具。上层函数负责挑选具体的协议值,它负责统一走 JSON 转换流程,这样测试能顺便检查 app-server 传输格式和旧测试类型是否仍然兼容。
调用图:被 3 处调用(session_source_cli, skill_scope_repo, skill_scope_user);外部调用 1 个(to_value)。
tui/src/test_backend.rs源码 ↗
做终端界面测试时,最麻烦的是:真实终端会受机器环境影响,比如窗口大小、光标位置、颜色支持都可能不一样。这个文件用 VT100Backend 包了一层,把 ratatui 用来画界面的 CrosstermBackend,接到 vt100::Parser 这个内存里的“终端模拟器”上。可以把它想成一个练习用的假舞台:演员照常表演,但观众席是测试程序,不是真人终端。它特别避免调用那些会直接碰 stdout(真正屏幕输出)的 crossterm 方法,比如读取终端大小和光标位置,而是从 vt100 模拟器里读。这样测试更稳定,也不会污染运行测试的控制台。它还实现了写入、清屏、移动光标、滚动区域、查询大小等终端后台能力,所以大多数界面代码不用知道自己面对的是假终端。
VT100Backend::new27–32 ↗
fn new(width: u16, height: u16) -> Self
作用:创建一个指定宽高的假终端,供测试把界面画进去。它还强制打开彩色输出,让测试里的颜色表现更接近真实终端。
数据流:输入宽度和高度 → 打开彩色输出开关,并用这些尺寸创建一个 vt100 终端模拟器,再把它交给 CrosstermBackend → 返回一个可被 ratatui 当作终端后台使用的 VT100Backend。
调用关系:很多界面快照测试一开始都会调用它来搭建测试舞台。后续渲染、清屏、移动光标等操作都会通过这个对象转给内部的 CrosstermBackend 和 vt100 模拟器。
调用图:被 61 处调用(thread_goal_ephemeral_error_message_renders_snapshot, render_footer_with_mode_indicator_and_context, chained_config_error_wraps_in_history_snapshot, approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot, approval_modal_exec_snapshot, approval_modal_exec_without_reason_snapshot, approval_modal_patch_snapshot, app_server_guardian_review_denied_renders_denied_request_snapshot, app_server_guardian_review_timed_out_renders_timed_out_request_snapshot, guardian_approved_exec_renders_approved_request (+15 more));外部调用 3 个(new, force_color_output, new)。
VT100Backend::vt10034–36 ↗
fn vt100(&self) -> &vt100::Parser
作用:拿到内部的 vt100 终端模拟器,方便读取当前屏幕内容、光标位置和终端大小。它只给只读引用,避免外部随便改坏模拟器状态。
数据流:输入是当前 VT100Backend 本身 → 从内部 CrosstermBackend 里取出它正在写入的 vt100::Parser → 返回这个模拟器的只读视图。
调用关系:它是查询类方法的入口。get_cursor_position、size 和 window_size 都会通过它读取假终端里的状态,而不是去问真实系统终端。
调用图:被 3 处调用(get_cursor_position, size, window_size);外部调用 1 个(writer)。
VT100Backend::write40–42 ↗
fn write(&mut self, buf: &[u8]) -> io::Result<usize>
作用:把一段原始字节写进假终端。终端控制序列、文字、颜色指令等都会先作为字节流进入这里。
数据流:输入一段字节 buf → 找到内部 vt100 模拟器的可写入口,把字节写进去 → 返回写入了多少字节,或者返回写入错误。
调用关系:这是实现标准写入接口的一部分。上层终端库需要输出内容时,可以像写真实终端一样写到这里,但实际接收者是内存里的 vt100 模拟器。
调用图:外部调用 1 个(writer_mut)。
VT100Backend::fmt50–52 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把当前假终端屏幕转换成普通字符串,方便调试或做快照对比。简单说,就是把“屏幕上现在长什么样”打印出来。
数据流:输入是格式化器和当前 VT100Backend → 从 vt100 模拟器里取当前屏幕内容 → 写入格式化器,最终变成可显示的文本。
调用关系:当测试或调试代码需要把后端直接转成文本时会用到它。它不重新渲染界面,只是读取已经被 vt100 模拟器记录下来的屏幕内容。
调用图:外部调用 1 个(write!)。
VT100Backend::draw56–62 ↗
fn draw(&mut self, content: I) -> io::Result<()>
作用:把 ratatui 准备好的一个个单元格画到假终端上。单元格可以理解成终端屏幕上的一个字符格子,里面有字符和样式。
数据流:输入一串带坐标的 Cell → 交给内部 CrosstermBackend 按照正常终端绘制方式输出 → 输出结果进入 vt100 模拟器,函数返回成功或错误。
调用关系:这是界面渲染的核心通道。测试里的界面组件调用 ratatui 绘制时,最终会走到这里,再由内部 CrosstermBackend 完成实际的终端指令生成。
调用图:外部调用 1 个(draw)。
VT100Backend::hide_cursor64–67 ↗
fn hide_cursor(&mut self) -> io::Result<()>
作用:隐藏假终端里的光标。这样测试出来的画面不会因为光标显示而多出不稳定的内容。
数据流:没有额外输入 → 把“隐藏光标”的请求转给内部 CrosstermBackend → vt100 模拟器记录这个终端状态变化,函数返回成功或错误。
调用关系:界面绘制流程中需要控制光标显示时会调用它。它只是转交给内部后端,不直接碰真实终端。
调用图:外部调用 1 个(hide_cursor)。
VT100Backend::show_cursor69–72 ↗
fn show_cursor(&mut self) -> io::Result<()>
作用:让假终端里的光标重新显示。通常在界面绘制结束或需要用户输入位置可见时使用。
数据流:没有额外输入 → 把“显示光标”的请求交给内部 CrosstermBackend → vt100 模拟器更新状态,函数返回成功或错误。
调用关系:它和 hide_cursor 是一对。上层 ratatui 流程决定什么时候显示光标,这里负责把这个动作安全地落到假终端里。
调用图:外部调用 1 个(show_cursor)。
VT100Backend::get_cursor_position74–76 ↗
fn get_cursor_position(&mut self) -> io::Result<Position>
作用:读取当前光标在假终端里的位置。它不会询问真实终端,所以测试结果不受本机控制台影响。
数据流:输入是当前后端 → 通过 VT100Backend::vt100 取得模拟器,再读取模拟屏幕中的光标坐标 → 返回 ratatui 使用的 Position。
调用关系:这是 Backend 接口要求提供的查询方法。它调用 VT100Backend::vt100,从内存模拟器取数据,避免走 crossterm 那些可能直接访问 stdout 的真实终端查询。
调用图:调用 1 个内部函数(vt100)。
VT100Backend::set_cursor_position78–80 ↗
fn set_cursor_position(&mut self, position: P) -> io::Result<()>
作用:把光标移动到指定位置。界面库需要在某个格子继续写字时,会用这个能力。
数据流:输入一个可转换成 Position 的位置 → 转交给内部 CrosstermBackend 生成移动光标的终端操作 → vt100 模拟器接收并更新光标位置,返回成功或错误。
调用关系:它属于绘制过程中的基础动作。上层代码只按 Backend 接口调用,VT100Backend 再把动作交给内部 CrosstermBackend。
调用图:外部调用 1 个(set_cursor_position)。
VT100Backend::clear82–84 ↗
fn clear(&mut self) -> io::Result<()>
作用:清空整个假终端屏幕。测试开始新一帧或重画界面时,经常需要先把旧内容擦掉。
数据流:没有额外输入 → 调用内部 CrosstermBackend 的清屏能力 → vt100 模拟器里的屏幕内容被清掉,返回成功或错误。
调用关系:它让测试后端表现得像真实终端。界面库发起清屏时走到这里,具体清屏指令仍由内部 CrosstermBackend 处理。
调用图:外部调用 1 个(clear)。
VT100Backend::clear_region86–88 ↗
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()>
作用:清掉屏幕上的某一类区域,比如当前行、光标之后等。它比整屏清空更精细。
数据流:输入 ClearType,也就是要清理哪种范围的说明 → 交给内部 CrosstermBackend → vt100 模拟器按这个范围更新屏幕内容,返回成功或错误。
调用关系:当界面库只想擦掉局部内容时会调用它。VT100Backend 不自己解释所有清理规则,而是让内部 CrosstermBackend 执行。
调用图:外部调用 1 个(clear_region)。
VT100Backend::append_lines90–92 ↗
fn append_lines(&mut self, line_count: u16) -> io::Result<()>
作用:在终端底部追加若干空行,模拟真实终端滚动或扩展输出的效果。
数据流:输入要追加的行数 → 转给内部 CrosstermBackend → vt100 模拟器根据终端行为更新屏幕,返回成功或错误。
调用关系:它是 Backend 接口的一部分,主要服务于需要新增行内容的绘制场景。这个文件只负责把请求接到假终端上。
调用图:外部调用 1 个(append_lines)。
VT100Backend::size94–97 ↗
VT100Backend::window_size99–108 ↗
fn window_size(&mut self) -> io::Result<WindowSize>
作用:返回窗口大小信息,包括字符格子的列行数和一个固定的像素大小。测试主要关心列和行,像素大小只是占位。
数据流:输入是当前后端 → 从 vt100 模拟器读取字符尺寸 → 组合成 WindowSize,并填入固定的 640×480 像素值 → 返回这个窗口大小对象。
调用关系:这是 Backend 接口需要的窗口查询方法。它通过 VT100Backend::vt100 取模拟终端的大小,避免接触真实窗口;像素值在测试中不依赖,所以给了一个固定值。
调用图:调用 1 个内部函数(vt100)。
VT100Backend::flush110–112 ↗
fn flush(&mut self) -> io::Result<()>
作用:把内部写入缓冲里的内容冲刷到 vt100 模拟器里。可以把它理解成“别先攒着了,现在就写进去”。
数据流:没有额外输入 → 取得内部 writer 的可写引用并调用 flush → 确保等待中的输出被提交,返回成功或错误。
调用关系:当终端库或上层绘制流程要求刷新输出时会调用它。它把刷新动作转给内部的 vt100 写入器,而不是刷新真实 stdout。
调用图:外部调用 1 个(writer_mut)。
VT100Backend::scroll_region_up114–116 ↗
fn scroll_region_up(&mut self, region: std::ops::Range<u16>, scroll_by: u16) -> io::Result<()>
作用:让屏幕上的某一段区域向上滚动指定行数。比如列表内容往上挪时,会需要这种终端能力。
数据流:输入一个行范围 region 和滚动行数 scroll_by → 交给内部 CrosstermBackend → vt100 模拟器更新该范围内的屏幕内容,返回成功或错误。
调用关系:它是局部滚动操作的转接点。上层界面代码通过 Backend 接口提出滚动需求,这里交给内部后端生成和执行对应终端行为。
调用图:外部调用 1 个(scroll_region_up)。
VT100Backend::scroll_region_down118–124 ↗
fn scroll_region_down(
&mut self,
region: std::ops::Range<u16>,
scroll_by: u16,
) -> io::Result<()>
作用:让屏幕上的某一段区域向下滚动指定行数。它和向上滚动相反,用来模拟真实终端里的局部下移。
数据流:输入一个行范围 region 和滚动行数 scroll_by → 转给内部 CrosstermBackend → vt100 模拟器按要求移动该区域内容,返回成功或错误。
调用关系:它补齐了 Backend 接口里的向下滚动能力。需要测试界面滚动效果时,上层调用会到这里,再由内部 CrosstermBackend 和 vt100 模拟器完成实际状态变化。
调用图:外部调用 1 个(scroll_region_down)。
tui/src/chatwidget/tests.rs源码 ↗
ChatWidget 是终端里那块“聊天窗口”。这个文件本身不实现聊天功能,而是给测试用。它像一个测试大厅:先把测试里常用的命令、事件、配置、协议消息、临时文件、截图比对工具等都引进来,再把不同主题的测试模块挂上,比如审批请求、权限、历史回放、斜杠命令、状态栏和布局等。这里还有一个截图测试的小工具,会把界面渲染结果和已经保存好的快照文本比较。快照可以理解成“标准答案照片”,如果界面多了一行、少了提示、标题变了,测试就能立刻发现。另一个辅助函数会从测试事件队列里取出目标草稿,方便检查组件有没有发出正确事件。没有这个文件,ChatWidget 的测试会分散、重复、难维护,也更容易漏掉界面回归问题。
chatwidget_snapshot_dir183–192 ↗
fn chatwidget_snapshot_dir() -> PathBuf
作用:找到 ChatWidget 快照测试文件所在的文件夹。测试做界面对比时会用它来确定“标准答案照片”放在哪里。
数据流:它不需要外部传入参数。进去的是源码里写死的一个快照文件路径;它调用资源查找工具确认这个快照文件存在,然后取这个文件的父目录。出来的是一个 PathBuf,也就是快照目录路径;如果找不到文件,或者文件没有父目录,测试会直接失败并给出明确报错。
调用关系:它主要服务于本文件里的 assert_chatwidget_snapshot 测试宏。测试要比较界面快照时,宏会先调用这个函数拿到快照目录,再交给 insta 这个快照测试库去读取或匹配对应快照。它内部把查找资源的工作交给 find_resource!。
调用图:外部调用 1 个(find_resource!)。
next_goal_draft217–231 ↗
fn next_goal_draft(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
expected_thread_id: ThreadId,
) -> crate::goal_files::GoalDraft
作用:从测试用的事件队列里取出下一条“设置线程目标草稿”的事件,并返回里面的草稿内容。它用来确认 ChatWidget 是否真的发出了预期的目标草稿更新。
数据流:输入是一条测试事件接收队列 rx,以及期望的线程编号 expected_thread_id。函数会不断从队列里尝试取事件;如果取到的是 SetThreadGoalDraft,就检查事件里的线程编号必须等于期望编号,然后把 draft 草稿返回。它会消耗队列中的事件;如果队列里没有可取事件,测试会失败。
调用关系:它是测试里的小助手,通常在某个操作触发 ChatWidget 发事件之后使用。测试代码先模拟用户操作或服务器消息,ChatWidget 把事件发到队列里,然后这个函数从队列中捞出目标草稿事件,并用 assert_eq! 做基本校验。它读取队列的具体动作交给 try_recv。
调用图:外部调用 2 个(try_recv, assert_eq!)。
tui/src/chatwidget/tests/helpers.rs源码 ↗
ChatWidget 是终端里的聊天界面,测试它时要模拟很多现实情况:用户发消息、服务器返回文字、命令开始和结束、补丁应用、插件列表弹出、钩子运行等。这个文件就像拍电影时的道具间,提供现成道具和按钮。它会创建隔离的测试配置,避免读到真实电脑上的配置;会把不同系统的路径统一成快照测试好比较的样子;还会把各种服务器通知包装成简单函数,让测试只说“开始执行命令”或“收到错误”就行。它本身不是产品功能,而是让测试更短、更稳定、更接近真实流程的重要支撑。
test_config5–23 ↗
async fn test_config() -> Config
作用:创建一份干净的测试配置。它会用临时目录代替真实用户目录,避免测试被本机配置影响。
数据流:进去没有业务输入 → 它新建临时目录,加载默认配置,再把工作目录、日志目录、数据库目录等都改到测试专用位置 → 出来一份可安全用于测试的 Config。
调用关系:make_chatwidget_manual_with_auth 在搭建测试版 ChatWidget 时会先调用它,保证后面的聊天窗口跑在隔离环境里。
调用图:被 1 处调用(make_chatwidget_manual_with_auth);外部调用 5 个(from, new, load_default_with_cli_overrides_for_codex_home, default, new)。
test_project_path25–27 ↗
fn test_project_path() -> PathBuf
作用:返回测试里统一使用的项目路径。这样测试不用到处硬编码同一个路径。
数据流:进去没有输入 → 它把测试用的 /tmp/project 按当前系统格式转成 PathBuf → 出来一个路径对象。
调用关系:它是路径小工具,供需要项目路径的测试直接拿来用。
调用图:外部调用 1 个(from)。
truncated_path_variants29–34 ↗
fn truncated_path_variants(path: &str) -> Vec<String>
作用:把一个路径拆成从短到长的前缀列表。它主要用于处理被界面截断显示的路径。
数据流:进去一个路径字符串 → 它按字符逐步截取前 1 个、前 2 个直到倒数前一个字符 → 出来一组路径前缀字符串。
调用关系:normalize_snapshot_paths 会用它来替换带省略号的 Windows 或其他平台路径,让快照测试稳定。
调用图:被 1 处调用(normalize_snapshot_paths)。
normalize_snapshot_paths36–63 ↗
fn normalize_snapshot_paths(text: impl Into<String>) -> String
作用:把测试输出里的平台路径统一成类 Unix 的 /tmp/project 形式。这样同一份快照在不同操作系统上都能通过。
数据流:进去一段文本 → 它查找测试路径和 hooks 文件路径的系统写法,并替换成固定写法;遇到被截断的路径也会对应替换 → 出来一段路径已标准化的文本。
调用关系:normalized_backend_snapshot 和很多快照断言会依赖这种统一结果,避免 Windows 反斜杠之类差异导致测试误失败。
调用图:调用 1 个内部函数(truncated_path_variants);外部调用 3 个(into, replace, format!)。
normalized_backend_snapshot65–89 ↗
fn normalized_backend_snapshot(value: &T) -> String
作用:把后端渲染出来的内容转成适合快照比较的文本。它会特别保留带引号行的宽度。
数据流:进去一个能显示成字符串的值 → 它先格式化成文本,再按行标准化路径;如果一整行被引号包住,就在替换后补齐原来的显示宽度 → 出来稳定的快照字符串。
调用关系:它调用 normalize_snapshot_paths 做核心路径替换,是后端显示内容进入快照测试前的清洗步骤。
调用图:外部调用 1 个(format!)。
invalid_value91–101 ↗
fn invalid_value(
candidate: impl Into<String>,
allowed: impl Into<String>,
) -> ConstraintError
作用:快速造一个“配置值不合法”的错误对象。测试可以用它模拟约束检查失败。
数据流:进去候选值和允许值说明 → 它把它们装进 ConstraintError::InvalidValue,并填上默认字段名和来源 → 出来一个错误对象。
调用关系:它是测试造假错误的便捷函数,不需要真的跑完整配置校验流程。
调用图:外部调用 1 个(into)。
snapshot103–118 ↗
fn snapshot(percent: f64) -> RateLimitSnapshot
作用:快速造一个限流快照。限流就是系统限制使用频率,这里只关心使用百分比。
数据流:进去一个百分比数字 → 它四舍五入成整数,放进一小时窗口的 RateLimitSnapshot → 出来一个可传给界面的限流状态。
调用关系:测试聊天界面显示配额或限流提示时,可以直接用它准备输入数据。
test_session_telemetry120–135 ↗
fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry
作用:创建测试用的会话遥测信息。遥测就是记录这次会话用的模型、来源等元数据。
数据流:进去配置和模型名 → 它离线构造模型信息,再创建带测试来源、无账号信息的 SessionTelemetry → 出来一个会话记录对象。
调用关系:make_chatwidget_manual_with_auth 创建 ChatWidget 时会调用它,让组件内部需要的会话信息齐全。
调用图:调用 3 个内部函数(new, new, session_source_cli);被 1 处调用(make_chatwidget_manual_with_auth);外部调用 1 个(to_models_manager_config)。
test_model_catalog137–141 ↗
fn test_model_catalog(_config: &Config) -> Arc<ModelCatalog>
作用:创建测试用的模型目录。模型目录就是可选模型的清单。
数据流:进去配置但实际不读取它 → 它拿测试预设模型创建 ModelCatalog,并放进 Arc(可共享引用)里 → 出来一个可共享的模型目录。
调用关系:make_chatwidget_manual_with_auth 和 set_chatgpt_auth 会用它给聊天窗口提供模型列表。
调用图:调用 1 个内部函数(new);被 2 处调用(make_chatwidget_manual_with_auth, set_chatgpt_auth);外部调用 1 个(new)。
make_chatwidget_manual144–157 ↗
async fn make_chatwidget_manual(
model_override: Option<&str>,
) -> (
ChatWidget,
tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
tokio::sync::mpsc::UnboundedReceiver<Op>,
)
作用:用默认未登录状态创建一个可手动驱动的测试聊天窗口。测试可以拿到窗口本体和它发出的事件通道。
数据流:进去可选模型名 → 它转交给 make_chatwidget_manual_with_auth,并固定账号状态为未登录 → 出来 ChatWidget、应用事件接收器和操作接收器。
调用关系:很多测试从这里开始搭建场景;它把真正的初始化细节交给 make_chatwidget_manual_with_auth。
调用图:调用 1 个内部函数(make_chatwidget_manual_with_auth);被 3 处调用(assert_hook_events_snapshot, assert_shift_left_edits_most_recent_queued_message_for_terminal, make_chatwidget_manual_with_sender)。
make_chatwidget_manual_with_auth159–212 ↗
async fn make_chatwidget_manual_with_auth(
model_override: Option<&str>,
has_chatgpt_account: bool,
has_codex_backend_auth: bool,
) -> (
ChatWidget,
tokio::sync::mpsc::UnboundedRec
作用:创建一个可控制登录状态的测试 ChatWidget。这是本文件最核心的搭建函数之一。
数据流:进去模型覆盖值、是否有 ChatGPT 账号、是否有后端认证 → 它创建事件通道、测试配置、模型目录、遥测信息和初始化参数,再生成 ChatWidget 并调整占位文字等测试状态 → 出来窗口和两个接收队列。
调用关系:make_chatwidget_manual 调用它;后续测试通过返回的接收器检查 ChatWidget 有没有发出用户提交、打断等操作。
调用图:调用 6 个内部函数(new, new, test_config, test_model_catalog, test_session_telemetry, test_dummy);被 1 处调用(make_chatwidget_manual);外部调用 4 个(new, new, new_with_op_target, Direct)。
next_submit_op216–225 ↗
fn next_submit_op(op_rx: &mut tokio::sync::mpsc::UnboundedReceiver<Op>) -> Op
作用:从操作队列里取出下一条“用户提交消息”的操作。它会跳过其他无关操作。
数据流:进去一个操作接收队列 → 它反复尝试读取,遇到 UserTurn 就返回;如果队列空了或关闭就直接让测试失败 → 出来一条提交操作。
调用关系:测试在模拟用户发送后会用它确认聊天窗口真的发出了提交请求。
调用图:外部调用 2 个(try_recv, panic!)。
next_interrupt_op227–236 ↗
fn next_interrupt_op(op_rx: &mut tokio::sync::mpsc::UnboundedReceiver<Op>)
作用:确认操作队列里出现了“打断当前任务”的操作。它不返回内容,只检查这件事发生了。
数据流:进去一个操作接收队列 → 它逐条读取并跳过无关操作,直到看到 Interrupt;如果没有就让测试失败 → 出来没有值,但完成时表示断言通过。
调用关系:测试按下中断键或触发停止流程后,会用它验证 ChatWidget 发出了正确指令。
调用图:外部调用 2 个(try_recv, panic!)。
assert_no_submit_op238–245 ↗
fn assert_no_submit_op(op_rx: &mut tokio::sync::mpsc::UnboundedReceiver<Op>)
作用:检查队列里没有用户提交操作。用于确认某些按键或状态不应该真的发消息。
数据流:进去一个操作接收队列 → 它把当前队列里能读到的操作都看一遍,只要发现 UserTurn 就断言失败 → 出来没有值,成功代表没有误提交。
调用关系:它常用于负向测试,比如输入还不完整、任务正在限制发送时。
调用图:外部调用 2 个(try_recv, assert!)。
set_chatgpt_auth247–251 ↗
fn set_chatgpt_auth(chat: &mut ChatWidget)
作用:把测试聊天窗口改成“已经有 ChatGPT 账号和后端认证”的状态。
数据流:进去一个可修改的 ChatWidget → 它把两个认证标记设为 true,并刷新测试模型目录 → 出来同一个窗口,但认证状态已变化。
调用关系:需要测试登录后功能时会调用它;它内部复用 test_model_catalog。
调用图:调用 1 个内部函数(test_model_catalog)。
test_model_info253–290 ↗
fn test_model_info(slug: &str, priority: i32, supports_fast_mode: bool) -> ModelInfo
作用:造一条测试模型的详细信息。可以指定模型名、排序优先级,以及是否支持快速模式。
数据流:进去模型 slug、优先级、是否支持 fast mode → 它拼出一份 JSON,再反序列化成 ModelInfo → 出来一条完整模型信息。
调用关系:set_fast_mode_test_catalog 用它构造包含快慢模式差异的模型列表。
调用图:外部调用 3 个(new, json!, from_value)。
set_fast_mode_test_catalog292–311 ↗
make_chatwidget_manual_with_sender313–322 ↗
async fn make_chatwidget_manual_with_sender() -> (
ChatWidget,
AppEventSender,
tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
tokio::sync::mpsc::UnboundedReceiver<Op>,
)
作用:创建测试聊天窗口,同时额外返回它的事件发送器。这样测试既能看事件,也能主动发事件。
数据流:进去没有输入 → 它先用 make_chatwidget_manual 创建窗口,再从窗口里克隆 AppEventSender → 出来窗口、发送器、事件接收器和操作接收器。
调用关系:当测试需要模拟外部组件向 ChatWidget 发应用事件时,会用这个版本。
调用图:调用 1 个内部函数(make_chatwidget_manual)。
drain_insert_history324–338 ↗
fn drain_insert_history(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
) -> Vec<Vec<ratatui::text::Line<'static>>>
作用:从应用事件队列里取出所有“插入历史单元”的事件,并转成可读的文本行。
数据流:进去应用事件接收队列 → 它不断读取现有事件,只保留 InsertHistoryCell,把每个单元按 80 宽度渲染成行,必要时补空行 → 出来多组显示行。
调用关系:assert_hook_events_snapshot 用它检查钩子完成后写入聊天历史的内容。
调用图:被 1 处调用(assert_hook_events_snapshot);外部调用 2 个(try_recv, new)。
lines_to_single_string340–349 ↗
fn lines_to_single_string(lines: &[ratatui::text::Line<'static>]) -> String
作用:把终端界面的多行富文本压成普通字符串。富文本里的样式会被丢掉,只保留可见文字。
数据流:进去一组 Line,每行里有多个 Span → 它逐个拼接 span 的文字,并在每行后加换行 → 出来一个普通字符串。
调用关系:active_blob 和 active_hook_blob 用它把界面单元转换成快照好比较的文本。
调用图:被 2 处调用(active_blob, active_hook_blob);外部调用 1 个(new)。
status_line_text351–353 ↗
fn status_line_text(chat: &ChatWidget) -> Option<String>
作用:取出聊天窗口当前状态栏文字。它只是给测试提供一个更短的访问入口。
数据流:进去一个 ChatWidget 引用 → 它调用窗口自己的 status_line_text 方法 → 出来可选的状态栏字符串。
调用关系:测试需要检查底部状态提示时会用它;实际逻辑仍在 ChatWidget 内部。
调用图:调用 1 个内部函数(status_line_text)。
make_token_info355–368 ↗
fn make_token_info(total_tokens: i64, context_window: i64) -> TokenUsageInfo
作用:快速造一份 token 使用信息。token 可以理解成模型读写文本时的计量单位。
数据流:进去总 token 数和上下文窗口大小 → 它把总量填到 total 和 last 两份用量里,并设置模型窗口 → 出来 TokenUsageInfo。
调用关系:handle_token_count 可接收它生成的数据,模拟服务器更新 token 统计。
thread_id370–372 ↗
fn thread_id(chat: &ChatWidget) -> String
作用:取出聊天窗口当前线程 id,没有就返回空字符串。线程 id 可以理解成一段对话的编号。
数据流:进去 ChatWidget → 它查看 chat.thread_id,有值就转成字符串,没有就给空串 → 出来线程编号字符串。
调用关系:大量 handle_* 辅助函数都用它给伪造的服务器通知补齐 thread_id。
调用图:被 21 处调用(handle_agent_message_delta, handle_agent_reasoning_delta, handle_agent_reasoning_final, handle_entered_review_mode, handle_error, handle_exec_begin, handle_exec_end, handle_exited_review_mode, handle_hook_completed, handle_hook_started (+11 more))。
token_usage_breakdown374–382 ↗
fn token_usage_breakdown(usage: TokenUsage) -> codex_app_server_protocol::TokenUsageBreakdown
作用:把内部 token 用量格式转成服务器协议需要的格式。
数据流:进去 TokenUsage → 它逐项复制总量、输入、缓存输入、输出、推理输出等字段 → 出来协议层的 TokenUsageBreakdown。
调用关系:handle_token_count 在组装 ThreadTokenUsageUpdated 通知时调用它。
调用图:被 1 处调用(handle_token_count)。
handle_token_count384–408 ↗
fn handle_token_count(chat: &mut ChatWidget, info: Option<TokenUsageInfo>)
作用:模拟服务器告诉聊天窗口 token 用量更新,或者清空 token 信息。
数据流:进去 ChatWidget 和可选 TokenUsageInfo → 有信息时,它组装 ThreadTokenUsageUpdated 通知并交给窗口处理;没有信息时直接清空窗口 token 状态 → 出来没有返回值,但窗口显示状态会变化。
调用关系:它使用 thread_id 和 token_usage_breakdown,是测试 token 计数显示的入口。
调用图:调用 3 个内部函数(set_token_info, thread_id, token_usage_breakdown);外部调用 2 个(ThreadTokenUsageUpdated, handle_server_notification)。
handle_error410–432 ↗
fn handle_error(
chat: &mut ChatWidget,
message: impl Into<String>,
codex_error_info: Option<CodexErrorInfo>,
)
作用:模拟一次不可重试的服务器错误。测试用它看错误提示怎么显示。
数据流:进去窗口、错误消息和可选错误详情 → 它包装成 ErrorNotification,标记 will_retry 为 false,再交给 ChatWidget → 出来没有值,但聊天窗口会处理错误。
调用关系:它调用 thread_id 补对话编号,最终把事件交给 handle_server_notification。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, Error, handle_server_notification)。
handle_stream_error434–440 ↗
fn handle_stream_error(
chat: &mut ChatWidget,
message: impl Into<String>,
additional_details: Option<String>,
)
作用:模拟流式回复中的可重试错误。流式回复就是文字一点点传回来。
数据流:进去窗口、错误消息和附加详情 → 它把工作转交给 handle_stream_error_with_replay,并不指定回放类型 → 出来没有值。
调用关系:这是简化入口;真正组装通知的工作在 handle_stream_error_with_replay。
调用图:调用 1 个内部函数(handle_stream_error_with_replay)。
handle_stream_error_with_replay442–465 ↗
fn handle_stream_error_with_replay(
chat: &mut ChatWidget,
message: impl Into<String>,
additional_details: Option<String>,
replay_kind: Option<ReplayKind>,
)
作用:模拟带回放标记的流式错误。回放标记用于区分这是实时收到的,还是从历史记录恢复出来的。
数据流:进去窗口、消息、附加详情、可选 ReplayKind → 它创建 will_retry 为 true 的 ErrorNotification,再交给 ChatWidget → 出来没有值,窗口状态会更新。
调用关系:handle_stream_error 调用它;它直接进入 ChatWidget 的服务器通知处理流程。
调用图:调用 1 个内部函数(thread_id);被 1 处调用(handle_stream_error);外部调用 3 个(into, Error, handle_server_notification)。
handle_warning467–475 ↗
fn handle_warning(chat: &mut ChatWidget, message: impl Into<String>)
作用:模拟服务器发来一条警告。警告不是失败,但需要显示给用户。
数据流:进去窗口和警告文字 → 它包装成 WarningNotification 并交给 ChatWidget → 出来没有值,但界面可能出现警告内容。
调用关系:测试警告展示时会用它;它依赖 thread_id 填对话编号。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, Warning, handle_server_notification)。
handle_model_verification477–493 ↗
fn handle_model_verification(
chat: &mut ChatWidget,
verifications: Vec<AppServerModelVerification>,
)
作用:模拟服务器返回模型验证结果。模型验证可以理解成检查所选模型是否满足要求。
数据流:进去窗口和一组验证结果 → 它组装 ModelVerificationNotification,带上当前 turn_id → 出来没有值,但 ChatWidget 会收到验证消息。
调用关系:它直接把构造好的通知交给 handle_server_notification。
调用图:调用 1 个内部函数(thread_id);外部调用 2 个(ModelVerification, handle_server_notification)。
handle_agent_message_delta495–511 ↗
fn handle_agent_message_delta(chat: &mut ChatWidget, delta: impl Into<String>)
作用:模拟助手回复新增了一小段文字。delta 就是“增量片段”。
数据流:进去窗口和文字片段 → 它创建 AgentMessageDelta 通知,固定 item_id 为 msg-1 → 出来没有值,但窗口会把片段追加到当前回复。
调用关系:测试流式回答显示时调用它;它通过 ChatWidget 的通知入口生效。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, AgentMessageDelta, handle_server_notification)。
handle_agent_reasoning_delta513–528 ↗
fn handle_agent_reasoning_delta(chat: &mut ChatWidget, delta: impl Into<String>)
作用:模拟助手推理摘要新增一小段文字。推理摘要是模型思考过程的简短展示。
数据流:进去窗口和文字片段 → 它包装成 ReasoningSummaryTextDeltaNotification → 出来没有值,窗口会更新推理摘要区域。
调用关系:它和 handle_agent_message_delta 类似,但目标是 reasoning-1 这个推理项。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, ReasoningSummaryTextDelta, handle_server_notification)。
handle_agent_reasoning_final530–548 ↗
fn handle_agent_reasoning_final(chat: &mut ChatWidget)
作用:模拟助手的推理项结束。用于测试推理显示从进行中变成完成。
数据流:进去窗口 → 它创建一个 Reasoning 类型的完成项,内容为空但 id 固定 → 出来没有值,窗口会收到 ItemCompleted。
调用关系:它调用 thread_id 并把完成通知交给 ChatWidget。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(ItemCompleted, new, handle_server_notification)。
handle_entered_review_mode550–567 ↗
fn handle_entered_review_mode(chat: &mut ChatWidget, review: impl Into<String>)
作用:模拟服务器通知聊天进入 review mode,也就是审查或复核模式。
数据流:进去窗口和 review 文本 → 它创建 EnteredReviewMode 的 ItemStarted 通知 → 出来没有值,窗口会显示进入复核状态。
调用关系:测试 review mode 现场通知时会用它;回放场景则用 replay_entered_review_mode。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, ItemStarted, handle_server_notification)。
replay_entered_review_mode569–578 ↗
fn replay_entered_review_mode(chat: &mut ChatWidget, review: impl Into<String>)
作用:模拟从历史记录回放“进入 review mode”的事件。
数据流:进去窗口和 review 文本 → 它直接调用 replay_thread_item,传入固定 turn-1 和 ThreadSnapshot 回放类型 → 出来没有值,窗口按历史项方式恢复显示。
调用关系:它不走普通服务器通知入口,而是走 ChatWidget 的回放入口。
调用图:外部调用 2 个(into, replay_thread_item)。
handle_exited_review_mode580–597 ↗
fn handle_exited_review_mode(chat: &mut ChatWidget)
作用:模拟服务器通知退出 review mode。
数据流:进去窗口 → 它创建 ExitedReviewMode 的 ItemCompleted 通知 → 出来没有值,窗口会结束复核状态显示。
调用关系:它和 handle_entered_review_mode 配套,用于测试 review mode 的完整进出流程。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(ItemCompleted, new, handle_server_notification)。
handle_exec_approval_request599–605 ↗
fn handle_exec_approval_request(
chat: &mut ChatWidget,
id: impl Into<String>,
event: ExecApprovalRequestEvent,
)
作用:模拟服务器请求用户批准执行命令。比如工具想运行 shell 命令时需要用户点同意。
数据流:进去窗口、请求 id 和审批事件 → 它把 id 转成字符串并调用窗口的 on_exec_approval_request → 出来没有值,但窗口会显示或记录审批请求。
调用关系:它直接触发 ChatWidget 的命令审批入口,不通过通用服务器通知包装。
调用图:外部调用 2 个(into, on_exec_approval_request)。
handle_apply_patch_approval_request607–613 ↗
fn handle_apply_patch_approval_request(
chat: &mut ChatWidget,
id: impl Into<String>,
event: ApplyPatchApprovalRequestEvent,
)
作用:模拟服务器请求用户批准应用补丁。补丁就是对文件的修改集合。
数据流:进去窗口、请求 id 和补丁审批事件 → 它调用 on_apply_patch_approval_request → 出来没有值,窗口会进入补丁审批相关状态。
调用关系:测试补丁审批 UI 时会用它,和命令审批辅助函数结构相同。
调用图:外部调用 2 个(into, on_apply_patch_approval_request)。
file_update_changes_from_tui615–634 ↗
fn file_update_changes_from_tui(changes: HashMap<PathBuf, FileChange>) -> Vec<FileUpdateChange>
作用:把 TUI 内部的文件变更格式转成应用服务器协议格式。TUI 是文本用户界面。
数据流:进去路径到 FileChange 的映射 → 它逐个判断是新增、删除还是更新,并把路径、类型和 diff 文本装成 FileUpdateChange → 出来一组协议层文件变更。
调用关系:handle_patch_apply_begin 和 handle_patch_apply_end 都用它来生成补丁应用通知里的 changes 字段。
调用图:被 2 处调用(handle_patch_apply_begin, handle_patch_apply_end)。
handle_patch_apply_begin636–655 ↗
fn handle_patch_apply_begin(
chat: &mut ChatWidget,
call_id: impl Into<String>,
turn_id: impl Into<String>,
changes: HashMap<PathBuf, FileChange>,
)
作用:模拟补丁开始应用。用于测试界面如何显示正在修改文件。
数据流:进去窗口、调用 id、turn id 和文件变更 → 它把变更转成协议格式,创建状态为 InProgress 的 FileChange 项并发送 ItemStarted → 出来没有值,窗口显示补丁进行中。
调用关系:它依赖 file_update_changes_from_tui,并把通知交给 ChatWidget。
调用图:调用 2 个内部函数(file_update_changes_from_tui, thread_id);外部调用 3 个(into, ItemStarted, handle_server_notification)。
handle_patch_apply_end657–677 ↗
fn handle_patch_apply_end(
chat: &mut ChatWidget,
call_id: impl Into<String>,
turn_id: impl Into<String>,
changes: HashMap<PathBuf, FileChange>,
status: AppServerPatchApplyStatus,
作用:模拟补丁应用结束,可以是成功或失败等状态。
数据流:进去窗口、调用 id、turn id、文件变更和最终状态 → 它创建 FileChange 完成项并发送 ItemCompleted → 出来没有值,窗口更新补丁结果。
调用关系:它和 handle_patch_apply_begin 配套,共同测试补丁生命周期。
调用图:调用 2 个内部函数(file_update_changes_from_tui, thread_id);外部调用 3 个(into, ItemCompleted, handle_server_notification)。
handle_view_image_tool_call679–696 ↗
fn handle_view_image_tool_call(
chat: &mut ChatWidget,
call_id: impl Into<String>,
path: AbsolutePathBuf,
)
作用:模拟查看图片工具调用完成。用于测试聊天里出现图片查看记录。
数据流:进去窗口、调用 id 和图片绝对路径 → 它创建 ImageView 完成项并交给 ChatWidget → 出来没有值,窗口会显示图片查看相关内容。
调用关系:它通过普通 ItemCompleted 通知进入 ChatWidget。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, ItemCompleted, handle_server_notification)。
handle_image_generation_end698–720 ↗
fn handle_image_generation_end(
chat: &mut ChatWidget,
call_id: impl Into<String>,
status: impl Into<String>,
revised_prompt: Option<String>,
saved_path: Option<AbsolutePathBuf>,
)
作用:模拟图片生成任务结束。可以带上状态、改写后的提示词和保存路径。
数据流:进去窗口、调用 id、状态文本、可选 revised_prompt、可选保存路径 → 它创建 ImageGeneration 完成项 → 出来没有值,窗口显示生成结果。
调用关系:测试图片生成 UI 时用它直接注入服务器完成通知。
调用图:调用 1 个内部函数(thread_id);外部调用 4 个(into, ItemCompleted, new, handle_server_notification)。
replay_user_message_inputs722–737 ↗
fn replay_user_message_inputs(
chat: &mut ChatWidget,
item_id: &str,
content: Vec<AppServerUserInput>,
replay_kind: ReplayKind,
)
作用:从历史记录里回放一条用户消息,内容可以包含多种输入。
数据流:进去窗口、消息项 id、输入内容列表和回放类型 → 它创建 UserMessage 线程项并调用 replay_thread_item → 出来没有值,窗口按历史方式显示用户消息。
调用关系:replay_user_message_text 调用它处理纯文本情况。
调用图:被 1 处调用(replay_user_message_text);外部调用 1 个(replay_thread_item)。
replay_user_message_text739–754 ↗
fn replay_user_message_text(
chat: &mut ChatWidget,
item_id: &str,
text: impl Into<String>,
replay_kind: ReplayKind,
)
作用:从历史记录里回放一条纯文本用户消息。
数据流:进去窗口、消息项 id、文本和回放类型 → 它把文本包装成 AppServerUserInput::Text,再交给 replay_user_message_inputs → 出来没有值。
调用关系:这是 replay_user_message_inputs 的简便版本,适合大多数只测文字的场景。
调用图:调用 1 个内部函数(replay_user_message_inputs);外部调用 1 个(vec!)。
replay_agent_message756–772 ↗
fn replay_agent_message(
chat: &mut ChatWidget,
item_id: &str,
text: impl Into<String>,
replay_kind: ReplayKind,
)
作用:从历史记录里回放一条助手最终回答。
数据流:进去窗口、消息项 id、文本和回放类型 → 它创建 AgentMessage,phase 固定为 FinalAnswer,再调用 replay_thread_item → 出来没有值,窗口恢复助手消息。
调用关系:它用于测试历史快照或会话恢复,不模拟实时增量。
调用图:外部调用 2 个(into, replay_thread_item)。
replay_turn_started774–787 ↗
fn replay_turn_started(chat: &mut ChatWidget, replay_kind: ReplayKind)
作用:以回放方式模拟一个 turn 开始。turn 可以理解成用户提问到助手回答的一轮交互。
数据流:进去窗口和回放类型 → 它用 app_server_turn 创建进行中的 turn,并包装成 TurnStarted 通知 → 出来没有值,窗口按回放语境处理开始事件。
调用关系:它调用 app_server_turn,并把 replay_kind 放进 handle_server_notification。
调用图:调用 2 个内部函数(app_server_turn, thread_id);外部调用 2 个(TurnStarted, handle_server_notification)。
replay_agent_message_delta789–805 ↗
fn replay_agent_message_delta(
chat: &mut ChatWidget,
delta: impl Into<String>,
replay_kind: ReplayKind,
)
作用:以回放方式模拟助手消息的增量片段。
数据流:进去窗口、片段文本和回放类型 → 它创建 AgentMessageDelta 通知,并带上 Some(replay_kind) → 出来没有值,窗口按历史回放处理这段文字。
调用关系:它和实时的 handle_agent_message_delta 类似,但明确告诉 ChatWidget 这是回放事件。
调用图:调用 1 个内部函数(thread_id);外部调用 3 个(into, AgentMessageDelta, handle_server_notification)。
begin_exec_with_source808–835 ↗
fn begin_exec_with_source(
chat: &mut ChatWidget,
call_id: &str,
raw_cmd: &str,
source: ExecCommandSource,
) -> AppServerThreadItem
作用:模拟一个命令执行开始,并指定命令来源。命令来源可区分是助手发起还是其他机制发起。
数据流:进去窗口、调用 id、原始命令和来源 → 它组装 bash -lc 命令,解析命令动作,创建 InProgress 的 CommandExecution 项,并发送开始通知 → 出来这条执行项,方便后续结束时复用。
调用关系:begin_exec 是它的简化包装;它内部调用 handle_exec_begin 发通知。
调用图:调用 3 个内部函数(parse_command, shlex_join, handle_exec_begin);被 1 处调用(begin_exec);外部调用 1 个(vec!)。
begin_unified_exec_startup837–858 ↗
fn begin_unified_exec_startup(
chat: &mut ChatWidget,
call_id: &str,
process_id: &str,
raw_cmd: &str,
) -> AppServerThreadItem
作用:模拟统一执行器启动阶段的命令开始,并带上进程 id。
数据流:进去窗口、调用 id、进程 id 和原始命令 → 它创建来源为 UnifiedExecStartup 的 CommandExecution 项,然后发送开始通知 → 出来这条执行项。
调用关系:它和 begin_exec_with_source 类似,也把最终通知交给 handle_exec_begin。
调用图:调用 2 个内部函数(shlex_join, handle_exec_begin);外部调用 2 个(new, vec!)。
handle_exec_begin860–874 ↗
fn handle_exec_begin(chat: &mut ChatWidget, item: AppServerThreadItem)
作用:把一个命令执行项作为“开始事件”送进 ChatWidget。
数据流:进去窗口和线程项 → 它包装成 ItemStartedNotification,补上 thread_id 和 turn_id → 出来没有值,窗口显示命令正在运行。
调用关系:begin_exec_with_source 和 begin_unified_exec_startup 都调用它。
调用图:调用 1 个内部函数(thread_id);被 2 处调用(begin_exec_with_source, begin_unified_exec_startup);外部调用 2 个(ItemStarted, handle_server_notification)。
terminal_interaction876–898 ↗
fn terminal_interaction(
chat: &mut ChatWidget,
call_id: &str,
process_id: &str,
stdin: &str,
)
作用:模拟用户向正在运行的终端进程输入内容。比如给交互式命令发送 stdin。
数据流:进去窗口、调用 id、进程 id 和输入文本 → 它创建 TerminalInteractionNotification 并交给 ChatWidget → 出来没有值,窗口记录这次终端交互。
调用关系:测试统一执行器或交互式命令显示时会用它。
调用图:调用 1 个内部函数(thread_id);外部调用 2 个(TerminalInteraction, handle_server_notification)。
complete_assistant_message900–920 ↗
fn complete_assistant_message(
chat: &mut ChatWidget,
item_id: &str,
text: &str,
phase: Option<MessagePhase>,
)
作用:模拟助手消息一次性完成,而不是一点点流式输出。
数据流:进去窗口、消息 id、文本和可选阶段 → 它创建 AgentMessage 完成项并发送 ItemCompleted → 出来没有值,窗口加入或更新助手消息。
调用关系:适合测试最终回答、阶段显示等,不需要先调用 delta。
调用图:外部调用 2 个(ItemCompleted, handle_server_notification)。
pending_steer922–931 ↗
fn pending_steer(text: &str) -> PendingSteer
作用:创建一条待发送或待调整的用户指令。steer 可以理解成用户给助手的引导消息。
数据流:进去文本 → 它生成 UserMessage、历史记录类型和用于比较去重的 key → 出来 PendingSteer。
调用关系:测试输入队列或待处理用户消息时可用它造数据。
调用图:调用 1 个内部函数(from)。
complete_user_message933–942 ↗
fn complete_user_message(chat: &mut ChatWidget, item_id: &str, text: &str)
作用:模拟用户消息完成,内容是普通文本。
数据流:进去窗口、消息 id 和文本 → 它把文本包装成 UserInput::Text,然后交给 complete_user_message_for_inputs → 出来没有值。
调用关系:这是 complete_user_message_for_inputs 的简便入口。
调用图:调用 1 个内部函数(complete_user_message_for_inputs);外部调用 1 个(vec!)。
complete_user_message_for_inputs944–962 ↗
fn complete_user_message_for_inputs(
chat: &mut ChatWidget,
item_id: &str,
content: Vec<UserInput>,
)
作用:模拟用户消息完成,内容可以是多种输入。
数据流:进去窗口、消息 id 和输入列表 → 它创建 UserMessage 完成项,并通过 ItemCompleted 交给 ChatWidget → 出来没有值,窗口记录用户消息。
调用关系:complete_user_message 调用它处理纯文本消息。
调用图:被 1 处调用(complete_user_message);外部调用 2 个(ItemCompleted, handle_server_notification)。
app_server_turn964–980 ↗
fn app_server_turn(
turn_id: &str,
status: AppServerTurnStatus,
duration_ms: Option<i64>,
error: Option<AppServerTurnError>,
) -> AppServerTurn
作用:快速创建一轮对话的服务器对象。它只填测试关心的状态、耗时和错误。
数据流:进去 turn id、状态、可选耗时、可选错误 → 它创建 items 为空、视图为 Full 的 AppServerTurn → 出来一个 turn 对象。
调用关系:handle_turn_started、handle_turn_completed、handle_turn_interrupted 和 replay_turn_started 都用它减少重复代码。
调用图:被 4 处调用(handle_turn_completed, handle_turn_interrupted, handle_turn_started, replay_turn_started);外部调用 1 个(new)。
handle_turn_started982–995 ↗
fn handle_turn_started(chat: &mut ChatWidget, turn_id: &str)
作用:模拟服务器通知一轮对话开始。
数据流:进去窗口和 turn id → 它用 app_server_turn 创建 InProgress 状态的 turn,再发送 TurnStarted → 出来没有值,窗口进入一轮任务进行中状态。
调用关系:它是测试 turn 生命周期开始阶段的入口。
调用图:调用 1 个内部函数(app_server_turn);外部调用 2 个(TurnStarted, handle_server_notification)。
handle_turn_completed997–1014 ↗
fn handle_turn_completed(
chat: &mut ChatWidget,
turn_id: &str,
duration_ms: Option<i64>,
)
作用:模拟服务器通知一轮对话正常完成。
数据流:进去窗口、turn id 和可选耗时 → 它创建 Completed 状态的 turn,并发送 TurnCompleted → 出来没有值,窗口结束当前轮次。
调用关系:它和 handle_turn_started 配套,用于测试从开始到完成的状态变化。
调用图:调用 1 个内部函数(app_server_turn);外部调用 2 个(TurnCompleted, handle_server_notification)。
handle_turn_interrupted1016–1029 ↗
fn handle_turn_interrupted(chat: &mut ChatWidget, turn_id: &str)
作用:模拟一轮对话被中断。中断不是正常完成,通常表示用户停止或系统停止。
数据流:进去窗口和 turn id → 它创建 Interrupted 状态的 turn,并发送 TurnCompleted 通知 → 出来没有值,窗口按中断处理。
调用关系:handle_budget_limited_turn 会先标记预算受限,再调用它完成中断通知。
调用图:调用 1 个内部函数(app_server_turn);被 1 处调用(handle_budget_limited_turn);外部调用 2 个(TurnCompleted, handle_server_notification)。
handle_budget_limited_turn1031–1034 ↗
fn handle_budget_limited_turn(chat: &mut ChatWidget, turn_id: &str)
作用:模拟因为预算限制导致一轮对话被中断。
数据流:进去窗口和 turn id → 它先在 turn_lifecycle 里标记该 turn 受预算限制,再调用 handle_turn_interrupted → 出来没有值,窗口会带着预算限制语义处理中断。
调用关系:它复用 handle_turn_interrupted,只额外加上预算原因。
调用图:调用 1 个内部函数(handle_turn_interrupted)。
begin_exec1036–1042 ↗
fn begin_exec(
chat: &mut ChatWidget,
call_id: &str,
raw_cmd: &str,
) -> AppServerThreadItem
作用:用默认“助手发起”的来源模拟命令开始执行。
数据流:进去窗口、调用 id 和原始命令 → 它调用 begin_exec_with_source,并把来源固定为 Agent → 出来命令执行项。
调用关系:这是最常用的命令开始辅助函数,隐藏了来源参数。
调用图:调用 1 个内部函数(begin_exec_with_source)。
end_exec1044–1087 ↗
fn end_exec(
chat: &mut ChatWidget,
begin_item: AppServerThreadItem,
stdout: &str,
stderr: &str,
exit_code: i32,
)
作用:模拟命令执行结束,并根据退出码决定成功还是失败。
数据流:进去窗口、开始时返回的执行项、stdout、stderr 和退出码 → 它合并输出,拆出原执行项字段,创建 Completed 或 Failed 状态的新执行项,再发送结束通知 → 出来没有值;如果传入的不是命令项会让测试失败。
调用关系:它通常接在 begin_exec 后面使用,并通过 handle_exec_end 交给 ChatWidget。
调用图:调用 1 个内部函数(handle_exec_end);外部调用 2 个(format!, panic!)。
handle_exec_end1089–1103 ↗
fn handle_exec_end(chat: &mut ChatWidget, item: AppServerThreadItem)
作用:把一个命令执行项作为“完成事件”送进 ChatWidget。
数据流:进去窗口和线程项 → 它包装成 ItemCompletedNotification,补上 thread_id 和 turn_id → 出来没有值,窗口显示命令结果。
调用关系:end_exec 调用它;它是命令结束通知的底层辅助函数。
调用图:调用 1 个内部函数(thread_id);被 1 处调用(end_exec);外部调用 2 个(ItemCompleted, handle_server_notification)。
active_blob1105–1113 ↗
fn active_blob(chat: &ChatWidget) -> String
作用:取出当前活动聊天单元的可见文本。活动单元通常是正在更新的那块内容。
数据流:进去窗口 → 它要求 active_cell 必须存在,把它按 80 宽度渲染,再转成普通字符串 → 出来可用于断言的文本。
调用关系:它依赖 lines_to_single_string,常用于快照或精确文本断言。
调用图:调用 1 个内部函数(lines_to_single_string)。
active_hook_blob1115–1121 ↗
fn active_hook_blob(chat: &ChatWidget) -> String
作用:取出当前活动钩子单元的可见文本。钩子是某些事件发生时自动运行的小任务。
数据流:进去窗口 → 如果没有活动钩子单元就返回 <empty>;否则渲染成行并转成字符串 → 出来钩子区域文本。
调用关系:assert_hook_events_snapshot 用它检查正在运行的钩子是否显示在 live cell 里。
调用图:调用 1 个内部函数(lines_to_single_string)。
expire_quiet_hook_linger1123–1128 ↗
fn expire_quiet_hook_linger(chat: &mut ChatWidget)
作用:强制让安静钩子的停留时间过期。这样测试不用真的等待时间流逝。
数据流:进去窗口 → 如果有活动钩子单元,就调用测试专用过期方法,然后触发 pre_draw_tick → 出来没有值,但界面内部计时状态被推进。
调用关系:它用于测试钩子显示何时消失,最后通过 ChatWidget 的绘制前刷新生效。
调用图:调用 1 个内部函数(pre_draw_tick)。
reveal_running_hooks1130–1135 ↗
fn reveal_running_hooks(chat: &mut ChatWidget)
作用:强制显示正在运行的钩子。避免测试等待正常延迟。
数据流:进去窗口 → 如果有活动钩子单元,就把运行中钩子设为立刻可见,再调用 pre_draw_tick → 出来没有值,钩子显示状态刷新。
调用关系:assert_hook_events_snapshot 调用它来确认 hook start 更新的是活动钩子单元。
调用图:调用 1 个内部函数(pre_draw_tick);被 1 处调用(assert_hook_events_snapshot)。
reveal_running_hooks_after_delayed_redraw1137–1142 ↗
fn reveal_running_hooks_after_delayed_redraw(chat: &mut ChatWidget)
作用:模拟延迟重绘之后才显示运行中钩子的情况。
数据流:进去窗口 → 它把活动钩子单元推进到“延迟重绘后可见”的测试状态,再触发 pre_draw_tick → 出来没有值。
调用关系:用于覆盖比 reveal_running_hooks 更接近真实延迟刷新的一类测试。
调用图:调用 1 个内部函数(pre_draw_tick)。
get_available_model1144–1154 ↗
fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset
作用:从聊天窗口的模型目录里找指定模型。找不到就让测试失败。
数据流:进去窗口和模型名 → 它读取模型目录列表,按 model 字段查找并克隆结果 → 出来 ModelPreset;如果没有找到就 panic。
调用关系:测试需要确认某个模型预设内容时会用它。
assert_shift_left_edits_most_recent_queued_message_for_terminal1156–1191 ↗
async fn assert_shift_left_edits_most_recent_queued_message_for_terminal(
terminal_info: TerminalInfo,
)
作用:验证在特定终端里按 Shift+Left 会编辑最近排队的消息。
数据流:进去终端信息 → 它创建聊天窗口,设置快捷键提示,模拟任务运行和两条排队消息,再发送 Shift+Left 按键 → 出来没有值,但会断言输入框变成第二条消息、队列只剩第一条。
调用关系:它是一个较完整的测试辅助断言,内部用 make_chatwidget_manual 搭环境。
调用图:调用 2 个内部函数(make_chatwidget_manual, from);外部调用 2 个(new, assert_eq!)。
render_bottom_first_row1193–1213 ↗
fn render_bottom_first_row(chat: &ChatWidget, width: u16) -> String
作用:渲染聊天窗口底部区域,并返回第一行非空文本。
数据流:进去窗口和宽度 → 它计算需要的高度,创建缓冲区,让 ChatWidget 渲染进去,再从上到下找第一行有内容的文本 → 出来这一行字符串。
调用关系:测试底部输入区或状态条首行显示时会用它。
调用图:外部调用 5 个(empty, new, new, desired_height, render)。
render_bottom_popup1215–1244 ↗
fn render_bottom_popup(chat: &ChatWidget, width: u16) -> String
作用:渲染聊天窗口底部弹窗,并返回去掉上下空白后的文本。
数据流:进去窗口和宽度 → 它创建渲染区域和缓冲区,调用 ChatWidget 渲染,收集每一行并裁掉首尾空行 → 出来弹窗文本。
调用关系:render_loaded_plugins_popup 调用它获取插件弹窗内容。
调用图:被 1 处调用(render_loaded_plugins_popup);外部调用 4 个(empty, new, desired_height, render)。
strip_osc8_for_snapshot1246–1278 ↗
fn strip_osc8_for_snapshot(text: &str) -> String
作用:移除终端超链接转义码,只保留用户看得见的文字。OSC8 是终端里表示超链接的控制序列。
数据流:进去一段文本 → 它逐字节扫描,遇到 OSC8 超链接控制段就跳过,普通字符照常保留 → 出来没有隐藏超链接码的字符串。
调用关系:快照测试用它避免不可见控制码影响比较结果。
调用图:外部调用 1 个(with_capacity)。
plugins_test_absolute_path1280–1285 ↗
fn plugins_test_absolute_path(path: &str) -> AbsolutePathBuf
作用:生成插件测试用的绝对路径。所有路径都放在系统临时目录下的固定子目录里。
数据流:进去相对路径字符串 → 它拼到 temp_dir/codex-plugin-menu-tests 后面,并转成绝对路径 → 出来 AbsolutePathBuf。
调用关系:插件摘要、市场、详情等构造函数都会用它填路径字段。
调用图:被 4 处调用(plugins_test_curated_marketplace, plugins_test_detail, plugins_test_repo_marketplace, plugins_test_summary);外部调用 1 个(temp_dir)。
plugins_test_interface1287–1311 ↗
fn plugins_test_interface(
display_name: Option<&str>,
short_description: Option<&str>,
long_description: Option<&str>,
) -> PluginInterface
作用:创建插件展示信息。比如显示名、短描述、长描述。
数据流:进去三个可选文本 → 它填入对应字段,其他品牌、截图、能力等字段都给空或 None → 出来 PluginInterface。
调用关系:plugins_test_summary 和 plugins_test_remote_summary 用它给插件补界面元数据。
调用图:被 2 处调用(plugins_test_remote_summary, plugins_test_summary);外部调用 1 个(new)。
plugins_test_summary1313–1343 ↗
fn plugins_test_summary(
id: &str,
name: &str,
display_name: Option<&str>,
description: Option<&str>,
installed: bool,
enabled: bool,
install_policy: PluginInstallPolicy,
)
作用:创建本地插件的测试摘要。摘要是插件列表里看到的那份简短信息。
数据流:进去插件 id、名称、显示名、描述、安装状态、启用状态和安装策略 → 它生成本地来源路径、接口信息和默认策略字段 → 出来 PluginSummary。
调用关系:测试插件列表、插件弹窗和市场内容时会用它造本地插件。
调用图:调用 2 个内部函数(plugins_test_absolute_path, plugins_test_interface);外部调用 2 个(new, format!)。
plugins_test_remote_summary1345–1371 ↗
fn plugins_test_remote_summary(
remote_plugin_id: &str,
name: &str,
display_name: Option<&str>,
description: Option<&str>,
installed: bool,
) -> PluginSummary
作用:创建远程插件的测试摘要。远程插件来自市场,不是本地路径。
数据流:进去远程插件 id、名称、显示名、描述和安装状态 → 它设置 source 为 Remote、enabled 为 true,并补接口信息 → 出来 PluginSummary。
调用关系:它和 plugins_test_summary 类似,但用于远程市场插件测试。
调用图:调用 1 个内部函数(plugins_test_interface);外部调用 1 个(new)。
plugins_test_curated_marketplace1373–1384 ↗
fn plugins_test_curated_marketplace(
plugins: Vec<PluginSummary>,
) -> PluginMarketplaceEntry
作用:创建 OpenAI 精选插件市场的测试对象。
数据流:进去一组插件摘要 → 它填入固定市场名、测试路径、显示名 ChatGPT Marketplace 和插件列表 → 出来 PluginMarketplaceEntry。
调用关系:plugins_test_response 可把它和其他市场一起包装成插件列表响应。
调用图:调用 1 个内部函数(plugins_test_absolute_path)。
plugins_test_repo_marketplace1386–1395 ↗
fn plugins_test_repo_marketplace(plugins: Vec<PluginSummary>) -> PluginMarketplaceEntry
作用:创建仓库本地插件市场的测试对象。
数据流:进去一组插件摘要 → 它填入 repo 名称、测试路径、显示名 Repo Marketplace 和插件列表 → 出来 PluginMarketplaceEntry。
调用关系:用于测试多个市场来源时和 curated marketplace 搭配使用。
调用图:调用 1 个内部函数(plugins_test_absolute_path)。
plugins_test_response1397–1405 ↗
fn plugins_test_response(
marketplaces: Vec<PluginMarketplaceEntry>,
) -> PluginListResponse
作用:把多个插件市场包装成一次插件列表响应。
数据流:进去市场列表 → 它创建 PluginListResponse,并把加载错误和 featured 插件列表设为空 → 出来响应对象。
调用关系:render_loaded_plugins_popup 会接收这种响应来模拟插件加载完成。
调用图:外部调用 1 个(new)。
render_loaded_plugins_popup1407–1415 ↗
fn render_loaded_plugins_popup(
chat: &mut ChatWidget,
response: PluginListResponse,
) -> String
作用:模拟插件加载完成并渲染插件弹窗文本。
数据流:进去窗口和插件列表响应 → 它调用 on_plugins_loaded 写入结果,再让窗口添加插件输出,最后渲染底部弹窗 → 出来弹窗字符串。
调用关系:它把插件数据构造、ChatWidget 状态更新和 render_bottom_popup 串起来,方便测试直接断言显示结果。
调用图:调用 1 个内部函数(render_bottom_popup);外部调用 2 个(add_plugins_output, on_plugins_loaded)。
plugins_test_detail1417–1469 ↗
fn plugins_test_detail(
summary: PluginSummary,
description: Option<&str>,
skills: &[&str],
hooks: &[(codex_app_server_protocol::HookEventName, usize)],
apps: &[&str],
mcp_serv
作用:创建插件详情页测试数据。详情比摘要多了技能、钩子、应用、MCP 服务器等内容。
数据流:进去插件摘要、描述、技能名列表、钩子事件和数量、应用名、MCP 服务器名 → 它逐项生成对应 summary,并填入测试路径和默认字段 → 出来 PluginDetail。
调用关系:测试插件详情弹窗或详情页排序、展示时会用它。
调用图:调用 1 个内部函数(plugins_test_absolute_path);外部调用 2 个(new, iter)。
plugins_test_popup_row_position1471–1475 ↗
fn plugins_test_popup_row_position(popup: &str, needle: &str) -> usize
作用:查找某段文字在插件弹窗文本里的位置。找不到就让测试失败。
数据流:进去弹窗文本和要找的字符串 → 它调用 find,找到就返回字节位置;找不到就 panic 并带上完整弹窗 → 出来位置数字。
调用关系:测试插件弹窗内容顺序时会用它比较不同文字的位置。
type_plugins_search_query1477–1481 ↗
fn type_plugins_search_query(chat: &mut ChatWidget, query: &str)
作用:模拟用户在插件搜索框里逐字输入查询词。
数据流:进去窗口和查询字符串 → 它把每个字符变成 KeyEvent,并交给 ChatWidget 的按键处理 → 出来没有值,窗口搜索状态会变化。
调用关系:插件搜索相关测试用它代替真实键盘输入。
调用图:外部调用 3 个(Char, from, handle_key_event)。
handle_hook_started1483–1492 ↗
fn handle_hook_started(chat: &mut ChatWidget, run: AppServerHookRunSummary)
作用:模拟钩子开始运行。钩子是某些事件前后自动执行的脚本或处理器。
数据流:进去窗口和钩子运行摘要 → 它包装成 HookStarted 通知并交给 ChatWidget → 出来没有值,窗口更新钩子运行显示。
调用关系:assert_hook_events_snapshot 用它触发钩子开始场景。
调用图:调用 1 个内部函数(thread_id);被 1 处调用(assert_hook_events_snapshot);外部调用 2 个(HookStarted, handle_server_notification)。
handle_hook_completed1494–1503 ↗
fn handle_hook_completed(chat: &mut ChatWidget, run: AppServerHookRunSummary)
作用:模拟钩子运行完成。
数据流:进去窗口和钩子运行摘要 → 它包装成 HookCompleted 通知并交给 ChatWidget → 出来没有值,窗口把钩子结果写入或更新显示。
调用关系:assert_hook_events_snapshot 在钩子开始后调用它,检查完成后历史输出。
调用图:调用 1 个内部函数(thread_id);被 1 处调用(assert_hook_events_snapshot);外部调用 2 个(HookCompleted, handle_server_notification)。
hook_run1505–1542 ↗
fn hook_run(
run_id: &str,
event_name: codex_app_server_protocol::HookEventName,
status: codex_app_server_protocol::HookRunStatus,
status_message: &str,
entries: Vec<codex_app_serv
作用:创建一条钩子运行摘要。可以指定事件名、状态、状态消息和输出条目。
数据流:进去运行 id、钩子事件名、状态、状态消息和输出列表 → 它填入命令型、同步、turn 范围、测试 hooks 路径等字段;如果状态是结束态就填完成时间和耗时 → 出来 HookRunSummary。
调用关系:assert_hook_events_snapshot 用它分别创建 running 和 completed 两种钩子数据。
调用图:被 1 处调用(assert_hook_events_snapshot);外部调用 2 个(from, matches!)。
assert_hook_events_snapshot1544–1601 ↗
async fn assert_hook_events_snapshot(
event_name: codex_app_server_protocol::HookEventName,
run_id: &str,
status_message: &str,
snapshot_name: &str,
)
作用:完整测试某类钩子事件的显示快照。它验证开始时显示在活动钩子区,完成后写入历史。
数据流:进去钩子事件名、运行 id、状态消息和快照名 → 它创建聊天窗口,发送钩子开始,断言没有写历史,再强制显示运行中钩子,随后发送完成事件并收集历史文本做快照断言 → 出来没有值,失败时测试中断。
调用关系:它串起 make_chatwidget_manual、handle_hook_started、hook_run、reveal_running_hooks、handle_hook_completed 和 drain_insert_history,是钩子测试的高层编排函数。
调用图:调用 6 个内部函数(drain_insert_history, handle_hook_completed, handle_hook_started, hook_run, make_chatwidget_manual, reveal_running_hooks);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, vec!)。
hook_event_label1603–1616 ↗
fn hook_event_label(event_name: codex_app_server_protocol::HookEventName) -> &'static str
作用:把钩子事件枚举转成固定英文标签。枚举就是一组预定义选项。
数据流:进去 HookEventName → 它按不同事件返回对应的静态字符串,比如 PreToolUse 或 SessionStart → 出来标签文本。
调用关系:assert_hook_events_snapshot 用它拼出预期的运行中钩子提示文字。
tui/tests/all.rs源码 ↗
这个文件本身不写具体测试,而是像一本书的目录页:它告诉 Rust 的测试系统要把哪些测试章节加载进来。mod test_backend; 和 mod suite; 会引入其他测试代码,其中主要测试放在 tests/suite/ 下面。开头允许使用 expect,是为了让测试代码里可以直接说“这里必须成功”,失败就立刻报错。中间那句 use codex_cli as _; 看起来像没用,其实是故意保留对 codex_cli 这个开发依赖的引用,避免清理依赖的工具误以为它没被用到;这些测试会启动 codex 命令行程序,所以这个依赖是需要的。没有这个文件,测试模块可能不会被统一编进这个集成测试二进制里,相关测试也就不会按预期运行。
tui/tests/suite/mod.rs源码 ↗
这个文件本身不写具体测试,而是告诉 Rust 的测试系统:这里有几组测试要一起算进来。可以把它理解成一本练习册的目录,真正的题目在后面的章节里。这里列出的模块包括窗口大小变化后的重新排版、状态提示、VT100 终端历史记录、以及实时提交显示等测试。VT100 可以理解为一种老牌终端显示规则,很多命令行界面都会按它的方式输出文字和控制光标。这个文件重要的地方在于“汇总”:只要测试入口引用了这个目录页,下面这些测试文件就会被编译并参与运行,从而确保命令行界面在这些关键场景下没有坏掉。
tui/tests/test_backend.rs源码 ↗
这个文件很小,但作用很明确:它把 tui/src/test_backend.rs 里的测试后端代码挂到当前测试模块里。这里的 #[path = "../src/test_backend.rs"] 是在告诉 Rust:不要按默认位置找模块,而是去指定路径加载 test_backend.rs。随后 pub use inner::VT100Backend; 又把里面的 VT100Backend 公开出来,外面的测试就可以直接使用。可以把它理解成图书馆前台:真正的书在别的房间,这个文件负责把入口指给你,并把常用的那本书摆到柜台上。没有它,测试代码可能就得重复写路径,或者无法方便地拿到这个模拟终端后端。
App 编排和支持测试
本组涵盖顶层 App 行为、启动和会话辅助工具、记录渲染,以及聊天组件之外若干聚焦的 TUI 支持回归测试。
tui/src/app/test_support.rs源码 ↗
这个文件只服务测试,不是正常产品运行时的主流程。它解决的问题很实际:App 这个对象很大,里面塞了聊天界面、配置、模型信息、文件搜索、反馈、线程状态等很多零件。测试如果每次都自己组装,很容易漏东西,也会让测试代码又长又脆。这里的 make_test_app 就像给测试准备的一辆“样车”:用离线模型、测试用环境、默认状态,把 App 拼到一个能跑测试的状态,但不依赖真实网络或真实账号。test_session_telemetry 专门造一份测试用的会话记录信息,也就是“这次会话是谁、用什么模型、从哪里来”的说明。app_enabled_in_effective_config 则是一个小检查器,用来从最终生效的配置里看某个 app 是否开启。
make_test_app12–70 ↗
async fn make_test_app() -> App
作用:创建一个测试用的 App 实例,让 App 子模块的测试可以直接拿来用。它避免测试到处重复搭建复杂对象,也避免测试误碰真实外部服务。
数据流:进去几乎没有外部输入 → 它先创建一个测试用聊天窗口和事件发送器,再从聊天窗口拿配置;接着建立文件搜索器,找一个离线测试模型,生成测试遥测信息;最后把大量 App 字段填成测试安全的默认值、空列表、空状态或测试专用对象 → 出来的是一个完整的 App,测试可以马上拿它验证配置刷新、线程切换、历史回放等行为。
调用关系:它是很多 App 单元测试的起点。调用图显示,像配置重建、从磁盘刷新配置、关闭 agent 线程时的清单检查、自动审查 companion 规则等测试都会先调用它拿到基础 App。它内部会把造测试聊天窗口、拿离线模型、创建 SessionTelemetry、创建文件搜索器和各种默认状态这些杂活串起来。
调用图:调用 8 个内部函数(default, without_managed_config_for_tests, default_for_tests, new, get_model_offline_for_tests, test_session_telemetry, new, defaults);被 18 处调用(mcp_inventory_omits_thread_id_for_closed_agent_thread, overridden_disabled_guardian_does_not_apply_auto_review_companions, rebuild_config_for_resume_or_fallback_errors_when_cwd_changes, rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error, refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error, refresh_in_memory_config_from_disk_keeps_cloud_requirements_for_thread_transitions, refresh_in_memory_config_from_disk_loads_latest_apps_state, refresh_in_memory_config_from_disk_updates_resize_reflow_config, refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd, sync_tui_pet_disabled_updates_chat_widget_config_copy (+8 more));外部调用 13 个(new, new, new, new, new, default, make_chatwidget_manual_with_sender, default, default, default (+3 more))。
test_session_telemetry72–88 ↗
fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry
作用:生成一份测试用的会话遥测信息。遥测可以理解成“这次会话的背景记录”,比如用的模型、来源、线程编号等;这里生成的是假的、离线的、适合测试的记录。
数据流:进去的是 Config 配置和模型名字符串 → 它把 Config 转成模型管理器能看懂的配置,离线构造模型信息;然后新建一个线程编号,填入模型名、模型标识、测试来源、测试环境等固定值,不填真实账号和邮箱 → 出来的是一个 SessionTelemetry 对象,供测试 App 像真实 App 一样携带会话信息。
调用关系:它只被 make_test_app 调用,是 make_test_app 组装测试 App 时的一个小零件。它自己又会调用离线模型信息构造函数和 SessionTelemetry::new,把“模型是什么”和“会话从哪里来”这两类信息合成一份测试记录。
调用图:调用 3 个内部函数(construct_model_info_offline_for_tests, new, new);被 1 处调用(make_test_app);外部调用 3 个(to_models_manager_config, from_value, json!)。
app_enabled_in_effective_config90–101 ↗
fn app_enabled_in_effective_config(config: &Config, app_id: &str) -> Option<bool>
作用:从最终生效的配置里查某个 app 是否被打开。它适合测试配置合并后的结果,而不是只看某一层原始配置。
数据流:进去的是 Config 配置和 app_id → 它沿着配置里的 config_layer_stack 找到 effective_config,也就是多层配置叠加后的最终结果;然后一层层查 apps、指定 app、enabled 字段,并确认 enabled 真的是布尔值 → 出来是 Some(true) 或 Some(false),表示明确开关;如果中间任何一层不存在或类型不对,就出来 None。
调用关系:它是测试里用来验收配置结果的小工具。调用图没有显示它再调用本项目里的其他函数,它主要靠一串安全的取值步骤读取配置表,避免测试因为字段缺失而直接崩掉。
tui/src/app/tests.rs源码 ↗
这段测试主要在模拟真实用户使用终端聊天界面时会遇到的复杂场景。比如:后台先来了一个审批请求,用户稍后才进入对应线程;旧会话被重新打开时,草稿、排队消息、大段粘贴内容、协作模式是否还在;子代理列表里哪些线程该显示、哪些该清掉;改“记忆”或“权限”设置后,配置文件、当前界面和正在跑的会话是否同步更新。这里大量使用异步测试,因为 App 和后台服务像两个人用管道传纸条一样互相发事件。测试的重点不是算法,而是顺序和归属:某个事件必须送到正确线程,某个排队输入必须在任务真正结束后才提交,某个已解决审批不能重新变成可点击按钮。没有这些测试,用户可能看到错线程的内容、重复提交消息、丢草稿,或者设置看起来改了但后台没改。 这一段测试主要围绕两类事情:一类是“Guardian 审批”功能开关,另一类是多线程/子代理/旁路对话里的待审批请求。可以把 App 想成一个前台窗口,背后同时有多个聊天线程在跑;有些线程会突然要求用户批准命令、文件改动、联网或打开链接。这些测试会造出假的 App、假的服务端、假的线程事件,然后检查界面状态、配置文件、发送给后端的操作、历史消息提示是否都正确。它还特别验证了一些边界情况,比如关闭 Guardian 后不能留下旧配置、非当前线程的审批要显示小徽标但不能打断旁路对话、非法 http 链接要自动拒绝、旁路对话不能继承父线程的隐藏历史等。 这段测试主要围绕 TUI,也就是“终端里的图形界面”,检查 App 在复杂场景下是否做对事。比如 MCP 服务器通知,MCP 可以理解成外部工具服务;主线程不该被子线程的失败提示打扰,但切到对应侧边线程时又必须看得到。它还测侧边线程关闭、反馈提交、历史回放、窗口缩放后的重新排版、清屏后只显示新页头、不重放旧聊天等行为。很多函数会先造一个假的 App 和假的服务器,再模拟通知、请求、用户输入或历史记录,最后看界面事件、内部状态和保存的文件是否符合预期。整体作用不是给产品增加功能,而是保证这些容易混乱的边界情况一直稳定。 这段测试主要围绕 App,也就是终端界面的“大管家”。它模拟用户编辑旧消息、回退对话、重放历史、切换线程、退出程序、更新模型和权限设置等操作,然后检查界面状态、历史记录、输入框、后台命令队列是否符合预期。可以把它想成一套“事故演练”:比如用户取消编辑时,输入框要恢复原文;回退对话时,旧的临时显示不能残留;服务器晚到的通知不能把界面弄脏;清屏只能清界面,不能丢掉当前会话。这里很多测试还会启动一个嵌入式 app server,也就是测试用的小型后台服务,来确认前端和后台之间传递的命令是对的。
test_absolute_path119–121 ↗
fn test_absolute_path(path: &str) -> AbsolutePathBuf
作用:把测试里写的字符串路径变成“绝对路径”类型。这样测试可以明确表示:这里需要的是完整路径,不是相对路径。
数据流:输入一个路径字符串 → 先转成普通路径对象,再尝试转成 AbsolutePathBuf → 成功就返回绝对路径;如果传入的不是绝对路径,测试会直接失败。
调用关系:这是测试辅助函数。它被其他测试用来准备文件系统权限或线程设置相关的路径数据,避免每个测试都重复写路径转换代码。
调用图:调用 1 个内部函数(try_from);被 2 处调用(inactive_thread_permissions_approval_preserves_file_system_permissions, inactive_thread_settings_notification_updates_cached_collaboration_mode);外部调用 1 个(from)。
next_thread_settings_updated123–144 ↗
async fn next_thread_settings_updated(
app_server: &mut AppServerSession,
thread_id: ThreadId,
) -> ThreadSettingsUpdatedNotification
作用:从测试用 App 服务端事件流里等一个指定线程的“线程设置已更新”通知。它用来确认后台确实发出了设置更新,而且发给了正确线程。
数据流:输入 AppServerSession 和线程 ID → 最多尝试读取 20 个事件,每次最多等 2 秒 → 找到线程 ID 匹配的 ThreadSettingsUpdatedNotification 就返回;如果一直没有,就让测试失败。
调用关系:这是异步测试辅助函数。调用它的测试先触发设置覆盖或更新,然后靠它从服务端事件里捞出对应通知,验证整条通知链路没有断。
调用图:调用 1 个内部函数(next_event);被 1 处调用(override_turn_context_sends_thread_settings_update);外部调用 4 个(panic!, to_string, from_secs, timeout)。
handle_mcp_inventory_result_respects_origin_thread147–182 ↗
async fn handle_mcp_inventory_result_respects_origin_thread()
作用:检查 MCP 清单结果只会影响它原本所属的线程。MCP 可以理解成外部工具服务清单;这个测试防止别的线程的工具列表把当前界面误清掉。
数据流:先创建测试 App,并放入一个“MCP 正在加载”的历史单元 → 传入没有线程来源的结果,确认加载单元被移除 → 再设置当前活动线程,传入另一个线程的结果,确认当前界面不被改动。
调用关系:测试直接调用 App 的 handle_mcp_inventory_result。它关注的是 App 收到后台工具清单结果时,是否先检查来源线程,再决定要不要更新当前聊天记录。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 5 个(new, new, assert_eq!, new_mcp_inventory_loading, vec!)。
bypass_hook_trust_startup_warning_snapshot185–195 ↗
fn bypass_hook_trust_startup_warning_snapshot()
作用:检查启动时的危险选项警告文案长什么样。这里的快照测试像给界面拍照片,防止警告被意外改坏或消失。
数据流:输入一段固定警告文字 → 生成 warning 历史单元并按 80 列宽渲染 → 合成单行字符串 → 和保存好的快照对比。
调用关系:它测试 history_cell 的警告渲染效果。测试框架运行它时,会确认启用绕过 hook 信任检查的提示仍然清楚可见。
调用图:调用 1 个内部函数(lines_to_single_string);外部调用 2 个(assert_app_snapshot!, new_warning_event)。
enqueue_primary_thread_session_replays_buffered_approval_after_attach197–248 ↗
async fn enqueue_primary_thread_session_replays_buffered_approval_after_attach() -> Result<()>
作用:确认主线程会话接上以后,之前缓存的审批请求会重新送到界面,并且用户同意后会提交到正确线程。
数据流:先造一个审批请求并记录到待处理请求表 → 在会话还没接上时把请求排进主线程 → 接上对应线程会话 → 从线程接收器里拿到缓存事件 → 用户按 y 同意 → 检查发出的操作带着正确线程 ID。
调用关系:这个测试覆盖“后台请求先到、线程稍后挂载”的流程。它把事件交给 enqueue_primary_thread_request、enqueue_primary_thread_session 和 handle_thread_event_now,再观察 AppEvent 输出。
调用图:调用 4 个内部函数(new, exec_approval_request, make_test_app_with_channels, test_thread_session);外部调用 8 个(from_millis, Char, new, new, assert!, assert_eq!, panic!, timeout)。
resolved_buffered_approval_does_not_become_actionable_after_drain251–308 ↗
async fn resolved_buffered_approval_does_not_become_actionable_after_drain() -> Result<()>
作用:确认一个已经被解决的缓存审批,即使后来从队列里被播放出来,也不会重新变成可点击、可同意的请求。
数据流:先接上线程 → 放入审批请求 → 立刻把这个请求标记为已解决并从聊天组件里移除 → 再从线程队列取出同一个旧事件 → 用户按 y → 检查没有产生新的提交操作。
调用关系:它验证 pending_app_server_requests 和 chat_widget 的配合。队列可以重放旧事件,但 App 必须记得这个请求已经处理过,不能让它复活。
调用图:调用 4 个内部函数(new, exec_approval_request, make_test_app_with_channels, test_thread_session);外部调用 8 个(Integer, from_millis, Char, new, new, assert!, assert_eq!, timeout)。
enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit311–394 ↗
async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit() -> Result<()>
作用:确认恢复已有会话时,旧对话会先显示出来,然后才提交启动时用户预填的新消息。这样用户看到的顺序不会颠倒。
数据流:创建带初始用户消息的聊天组件 → 接入一个包含旧用户消息的线程会话 → 收集 AppEvent → 确认先插入了旧历史单元,再提交了初始 prompt 作为新的用户轮次。
调用关系:它测试 enqueue_primary_thread_session 的启动顺序。这个顺序很重要:如果先提交新消息再回放旧消息,界面会像时间线错乱。
调用图:调用 7 个内部函数(new, get_model_offline_for_tests, new, lines_to_single_string, make_test_app_with_channels, test_thread_session, test_dummy);外部调用 6 个(new, assert!, assert_eq!, create_initial_user_message, new_with_app_event, vec!)。
reset_thread_event_state_aborts_listener_tasks397–429 ↗
async fn reset_thread_event_state_aborts_listener_tasks()
作用:确认重置线程事件状态时,后台监听任务会被取消。监听任务就是一直等事件的小工作线程,不取消会造成泄漏或重复收消息。
数据流:创建一个永远不结束的异步任务,并在任务被丢弃时发送通知 → 把它登记到 App 的线程监听任务表 → 调用 reset_thread_event_state → 检查任务表清空,并收到任务被取消的通知。
调用关系:它直接测试 App 的清理动作。重置状态通常发生在切换或重建线程事件系统时,这个测试保证旧监听不会残留。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 5 个(from_millis, assert_eq!, timeout, spawn, channel)。
history_lookup_response_is_routed_to_requesting_thread432–457 ↗
async fn history_lookup_response_is_routed_to_requesting_thread() -> Result<()>
作用:确认查询历史记录的响应会带回请求它的线程 ID。这样多线程同时查历史时,不会把结果塞到错误聊天里。
数据流:输入线程 ID、偏移量和日志 ID → 调用 lookup_message_history_entry → 从 AppEvent 通道取出响应 → 检查响应里的线程 ID、offset、log_id 都和请求一致,且没有找到条目时 entry 为空。
调用关系:它测试历史查询的路由。App 发起查询后,结果通过 AppEvent::ThreadHistoryEntryResponse 回来,界面靠线程 ID 决定显示在哪里。
调用图:调用 2 个内部函数(new, make_test_app_with_channels);外部调用 5 个(from_secs, assert!, assert_eq!, panic!, timeout)。
enqueue_thread_event_does_not_block_when_channel_full460–496 ↗
async fn enqueue_thread_event_does_not_block_when_channel_full() -> Result<()>
作用:确认线程事件通道满了时,继续塞事件不会卡死。卡死会让整个终端界面像被堵住的水管一样不响应。
数据流:创建容量只有 1 的线程事件通道并设为活动 → 连续塞入两个线程关闭通知 → 第二次塞入用超时包住,确认没有阻塞 → 再从接收端读出两个事件。
调用关系:它测试 enqueue_thread_notification 和 ThreadEventChannel 的缓冲策略。即使通道容量小,App 也要能安全排队或转存事件。
调用图:调用 4 个内部函数(new, make_test_app, thread_closed_notification, new);外部调用 2 个(from_millis, timeout)。
replay_thread_snapshot_restores_draft_and_queued_input499–557 ↗
async fn replay_thread_snapshot_restores_draft_and_queued_input()
作用:确认线程快照回放能恢复用户正在编辑的草稿,但不会把只有草稿的内容自动提交出去。
数据流:先在聊天框写入草稿并排入一个后续输入 → 捕获输入状态并保存到线程快照 → 换一个新的聊天组件 → 回放快照 → 检查草稿回到输入框,排队消息为空,并且没有自动发出 UserTurn 操作。
调用关系:它测试 store_active_thread_receiver 与 replay_thread_snapshot 的配合。快照就像临时存档,回放时必须恢复用户可见输入,但不能擅自替用户发送。
调用图:调用 4 个内部函数(new, make_test_app, test_thread_session, new_with_session);外部调用 4 个(new, assert!, assert_eq!, make_chatwidget_manual_with_sender)。
active_turn_id_for_thread_uses_snapshot_turns560–577 ↗
async fn active_turn_id_for_thread_uses_snapshot_turns()
作用:确认 App 能从线程快照里的轮次列表判断当前正在运行的是哪一轮。轮次可以理解成一次用户提问和助手回答的完整回合。
数据流:创建一个线程通道,里面放入状态为 InProgress 的 turn-1 → 调用 active_turn_id_for_thread → 得到 Some("turn-1")。
调用关系:它测试 App 查询活动轮次的逻辑。后续恢复排队输入时,需要知道当前是否还有任务没完成。
调用图:调用 4 个内部函数(new, make_test_app, test_thread_session, new_with_session);外部调用 2 个(assert_eq!, vec!)。
replayed_turn_complete_submits_restored_queued_follow_up580–629 ↗
async fn replayed_turn_complete_submits_restored_queued_follow_up()
作用:确认如果回放证明上一轮已经完成,那么恢复出来的排队追问会自动提交。这样用户之前按下 Tab 排队的消息不会丢。
数据流:先模拟 turn-1 正在流式回答,用户输入“queued follow-up”并排队 → 捕获输入状态 → 换新聊天组件 → 回放一个 turn-1 完成通知,并允许恢复队列继续执行 → 检查发出了包含该文本的 UserTurn。
调用关系:它测试 replay_thread_snapshot 对“已完成轮次 + 恢复队列”的处理。完成通知是触发排队消息继续发送的关键。
调用图:调用 6 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, next_user_turn_op, test_thread_session, turn_started_notification);外部调用 6 个(new, new, assert_eq!, make_chatwidget_manual_with_sender, panic!, vec!)。
replay_only_thread_keeps_restored_queue_visible632–680 ↗
async fn replay_only_thread_keeps_restored_queue_visible()
作用:确认只做回放、不继续运行的线程,会把恢复出的排队消息显示出来,但不会自动提交。
数据流:先造出一个正在运行时排队的 follow-up 输入状态 → 换新聊天组件 → 回放完成通知,但 resume_restored_queue 设为 false → 检查队列里仍显示这条消息,操作通道没有收到提交。
调用关系:它测试 replay_thread_snapshot 的“只看历史”模式。这个模式适合查看旧线程,不能偷偷继续执行用户原本排队的动作。
调用图:调用 5 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, test_thread_session, turn_started_notification);外部调用 6 个(new, new, assert!, assert_eq!, make_chatwidget_manual_with_sender, vec!)。
replay_thread_snapshot_keeps_queue_when_running_state_only_comes_from_snapshot683–729 ↗
async fn replay_thread_snapshot_keeps_queue_when_running_state_only_comes_from_snapshot()
作用:确认如果快照没有证明最新任务已经结束,恢复出的排队消息必须继续排着,不能提前提交。
数据流:先捕获一个在运行中排队的输入状态 → 回放一个没有 turn 列表、也没有完成事件的快照 → 检查 queued follow-up 仍在队列里,并且没有提交操作。
调用关系:它保护 replay_thread_snapshot 的保守策略。系统宁可让消息继续排队,也不能在不确定任务状态时提前发出去。
调用图:调用 5 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, test_thread_session, turn_started_notification);外部调用 6 个(new, new, assert!, assert_eq!, make_chatwidget_manual_with_sender, vec!)。
replay_thread_snapshot_in_progress_turn_restores_running_queue_state732–778 ↗
async fn replay_thread_snapshot_in_progress_turn_restores_running_queue_state()
作用:确认快照明确说当前轮次还在运行时,恢复出的排队消息会保持排队状态。
数据流:先准备一条运行中的 turn-1 和一条排队 follow-up → 回放包含 InProgress turn-1 的快照 → 检查排队文本仍可见,没有自动提交。
调用关系:它测试 replay_thread_snapshot 读取 turns 状态的分支。只要当前轮次还没结束,chat_widget 就应该保持“任务正在跑,消息在排队”。
调用图:调用 5 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, test_thread_session, turn_started_notification);外部调用 6 个(new, new, assert!, assert_eq!, make_chatwidget_manual_with_sender, vec!)。
replay_thread_snapshot_in_progress_turn_restores_running_state_without_input_state781–800 ↗
async fn replay_thread_snapshot_in_progress_turn_restores_running_state_without_input_state()
作用:确认即使没有保存输入框状态,只要快照里有进行中的轮次,界面也会恢复成“任务正在运行”的状态。
数据流:创建新聊天组件并挂上线程会话 → 回放一个含 InProgress turn-1、但 input_state 为空的快照 → 检查聊天组件认为任务仍在运行。
调用关系:它测试任务状态恢复不完全依赖输入状态。线程事件本身也能告诉界面:现在还有活没干完。
调用图:调用 3 个内部函数(new, make_test_app_with_channels, test_thread_session);外部调用 4 个(new, assert!, make_chatwidget_manual_with_sender, vec!)。
replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up803–872 ↗
async fn replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up()
作用:确认回放还没追到最新运行轮次结束前,排队消息不能提前提交;等真正收到完成通知后才提交。
数据流:先捕获运行中排队的输入状态 → 回放 turn-0 完成和 turn-1 开始事件 → 检查队列保留、没有提交 → 再手动送入 turn-1 完成通知 → 检查这时才发出 UserTurn。
调用关系:它测试回放进度和实时通知的衔接。系统必须等到最新 turn 确认结束,才能释放用户排队的下一条消息。
调用图:调用 7 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, next_user_turn_op, test_thread_session, turn_completed_notification, turn_started_notification);外部调用 7 个(new, new, assert!, assert_eq!, make_chatwidget_manual_with_sender, panic!, vec!)。
replay_thread_snapshot_restores_pending_pastes_for_submit875–929 ↗
async fn replay_thread_snapshot_restores_pending_pastes_for_submit()
作用:确认大段粘贴内容在恢复线程后还能正常提交。大段粘贴通常会被特殊暂存,容易在切换线程时丢失。
数据流:向聊天框粘贴 1005 个字符 → 捕获并保存输入状态到快照 → 换新聊天组件并回放快照 → 检查输入框仍显示完整粘贴内容 → 按 Enter 后检查提交的文本完整一致。
调用关系:它测试 pending paste 这类特殊输入状态和 replay_thread_snapshot 的配合。恢复后,用户按回车应和从未切换过线程一样。
调用图:调用 5 个内部函数(new, make_test_app_with_channels, next_user_turn_op, test_thread_session, new_with_session);外部调用 5 个(new, new, assert_eq!, make_chatwidget_manual_with_sender, panic!)。
replay_thread_snapshot_restores_collaboration_mode_for_draft_submit932–1013 ↗
async fn replay_thread_snapshot_restores_collaboration_mode_for_draft_submit()
作用:确认草稿恢复时,连同当时选择的协作模式、模型和推理强度一起恢复,并用于后续提交。
数据流:先设置 Plan 模式、高推理强度和模型 gpt-restored,再写草稿并捕获状态 → 换成默认模式的新组件 → 回放旧状态 → 按 Enter → 检查提交操作使用旧草稿、旧模型、旧推理强度和 Plan 协作模式。
调用关系:它测试 input_state 不只是文字,还包含提交时的上下文设置。否则用户恢复草稿后可能不知不觉用错模型或模式。
调用图:调用 4 个内部函数(new, make_test_app_with_channels, next_user_turn_op, test_thread_session);外部调用 6 个(new, new, assert_eq!, make_chatwidget_manual_with_sender, panic!, vec!)。
replay_thread_snapshot_restores_collaboration_mode_without_input1016–1069 ↗
async fn replay_thread_snapshot_restores_collaboration_mode_without_input()
作用:确认即使没有草稿文字,恢复线程时也能恢复协作模式、模型和推理强度。
数据流:先设置 Plan 模式、模型 gpt-restored 和高推理强度并捕获状态 → 换成默认模式的新组件 → 回放状态 → 检查当前界面已切回 Plan、gpt-restored 和 High。
调用关系:它测试协作设置可以独立于输入文字被保存和恢复。这样切换线程时,每个线程自己的工作模式不会混在一起。
调用图:调用 3 个内部函数(new, make_test_app_with_channels, test_thread_session);外部调用 4 个(new, assert_eq!, make_chatwidget_manual_with_sender, vec!)。
replayed_interrupted_turn_restores_queued_input_to_composer1072–1121 ↗
async fn replayed_interrupted_turn_restores_queued_input_to_composer()
作用:确认如果上一轮是被中断的,原本排队的输入会回到输入框让用户编辑,而不是自动发出去。
数据流:先模拟 turn-1 运行中,用户输入 follow-up 并提交到队列 → 捕获状态 → 回放 turn-1 Interrupted 完成状态 → 检查文本回到输入框,队列清空,没有提交操作。
调用关系:它测试中断场景下的安全行为。任务被打断时,系统不应假装一切正常继续发下一条,而应把控制权还给用户。
调用图:调用 5 个内部函数(new, agent_message_delta_notification, make_test_app_with_channels, test_thread_session, turn_started_notification);外部调用 6 个(new, new, assert!, assert_eq!, make_chatwidget_manual_with_sender, vec!)。
token_usage_update_refreshes_status_line_with_runtime_context_window1124–1143 ↗
async fn token_usage_update_refreshes_status_line_with_runtime_context_window()
作用:确认收到 token 使用量通知后,底部状态栏会显示运行时上下文窗口大小。token 可以理解成模型处理文字的计量单位。
数据流:先设置状态栏显示 ContextWindowSize → 初始状态栏为空 → 送入包含 950000 窗口大小的 token usage 通知 → 检查状态栏变成“950K window”。
调用关系:它测试 handle_thread_event_now 对 ServerNotification 的 UI 更新。后台告诉界面运行时窗口大小,聊天组件负责刷新底部文字。
调用图:调用 3 个内部函数(new, make_test_app, token_usage_notification);外部调用 3 个(assert_eq!, Notification, vec!)。
collab_receiver_notification_caches_thread_without_app_server_read1146–1180 ↗
async fn collab_receiver_notification_caches_thread_without_app_server_read()
作用:确认协作工具调用里提到的接收方线程,会被缓存到代理导航列表里,即使还没有从 App 服务端读取该线程详情。
数据流:构造一个 ItemStarted 通知,里面包含一个 CollabAgentToolCall 和 receiver_thread_id → 交给 App 处理 → 检查 agent_navigation 已出现这个接收线程,且带默认空元数据。
调用关系:它测试协作通知对代理列表的即时补全。这样用户能在选择器里看到被提到的子代理线程,而不用等后台读完。
调用图:调用 3 个内部函数(from_string, new, make_test_app);外部调用 5 个(new, ItemStarted, assert_eq!, Notification, vec!)。
collab_receiver_notification_does_not_cache_not_found_thread1183–1214 ↗
async fn collab_receiver_notification_does_not_cache_not_found_thread()
作用:确认如果协作工具结果明确说接收方线程不存在,就不要把它加入代理导航列表。
数据流:构造一个 ItemCompleted 通知,工具调用状态失败,并在 agents_states 里标记 receiver_thread_id 为 NotFound → 交给 App 处理 → 检查 agent_navigation 没有这个线程。
调用关系:它测试协作通知的过滤逻辑。不是所有被提到的线程都应该显示,明确不存在的要忽略。
调用图:调用 3 个内部函数(from_string, new, make_test_app);外部调用 5 个(from, ItemCompleted, assert_eq!, Notification, vec!)。
open_agent_picker_keeps_missing_threads_for_replay1217–1243 ↗
async fn open_agent_picker_keeps_missing_threads_for_replay() -> Result<()>
作用:确认打开代理选择器时,本地已有但服务端暂时读不到的线程不会被删掉,而是标为已关闭、可回放。
数据流:创建测试 App 和内嵌 App 服务端 → 手动放入一个线程事件通道 → 打开代理选择器 → 检查通道还在,导航条目存在,is_closed 为 true,并按顺序列出。
调用关系:它测试 open_agent_picker 的刷新策略。选择器打开时会向服务端同步线程状态,但不能因为临时读不到就丢掉本地可回放历史。
调用图:调用 3 个内部函数(new, make_test_app, new);外部调用 3 个(pin, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_preserves_cached_metadata_for_replay_threads1246–1277 ↗
async fn open_agent_picker_preserves_cached_metadata_for_replay_threads() -> Result<()>
作用:确认回放线程在打开代理选择器后,原来缓存的昵称和角色不会被抹掉。
数据流:先放入线程通道,并在 agent_navigation 里写入 Robie/explorer → 打开代理选择器 → 检查线程仍在,昵称和角色仍保持,且标为关闭。
调用关系:它测试 open_agent_picker 对本地元数据的保护。服务端缺少信息时,界面应保留用户已经看到过的描述。
调用图:调用 3 个内部函数(new, make_test_app, new);外部调用 3 个(pin, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_clears_completed_path_backed_agent_running_state1280–1319 ↗
async fn open_agent_picker_clears_completed_path_backed_agent_running_state() -> Result<()>
作用:确认基于路径记录的子代理,如果快照显示任务已完成,打开选择器时要清掉“正在运行”的显示。
数据流:在线程存储里放入 turn started 和 completed 通知 → 记录一个带 agent_path 且提示正在运行的子代理活动 → 打开代理选择器 → 检查该条目的 is_running 变为 false,路径保留。
调用关系:它测试选择器刷新时会根据线程事件重新判断活跃状态,而不是永远相信旧的 running hint。
调用图:调用 5 个内部函数(new, make_test_app, turn_completed_notification, turn_started_notification, new);外部调用 3 个(pin, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_refreshes_replay_only_path_backed_liveness1322–1357 ↗
async fn open_agent_picker_refreshes_replay_only_path_backed_liveness() -> Result<()>
作用:确认只用于回放的路径型子代理,打开选择器时会被标成关闭,并且不显示正在运行。
数据流:创建 replay-only 线程通道,里面只有 turn started 通知 → 记录路径型子代理活动 → 打开代理选择器 → 检查条目路径保留、is_running 为 false、is_closed 为 true。
调用关系:它测试 replay-only 通道在代理选择器里的展示规则。只能看历史的线程不应该显示成还活着。
调用图:调用 4 个内部函数(new, make_test_app, turn_started_notification, new);外部调用 3 个(pin, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_prunes_terminal_metadata_only_threads1360–1380 ↗
async fn open_agent_picker_prunes_terminal_metadata_only_threads() -> Result<()>
作用:确认只有元数据、没有本地通道也没有服务端可读内容的终止线程,会在打开代理选择器时被清理掉。
数据流:只在 agent_navigation 里放入 Ghost/worker,没有线程通道 → 打开代理选择器 → 检查该条目被删除,导航列表为空。
调用关系:它测试 open_agent_picker 的清理规则。没有可回放内容、也无法实时连接的幽灵条目不该继续占位置。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_marks_terminal_read_errors_closed1383–1413 ↗
async fn open_agent_picker_marks_terminal_read_errors_closed() -> Result<()>
作用:确认打开代理选择器时,如果线程读不到但本地有通道和元数据,就把它标为关闭,而不是删掉。
数据流:放入一个线程通道,并缓存 Robie/explorer 元数据且标为未关闭 → 打开代理选择器 → 检查条目仍在,但 is_closed 变为 true。
调用关系:它测试读服务端失败时的降级行为。只要本地还有回放资料,用户仍应该能看到它。
调用图:调用 3 个内部函数(new, make_test_app, new);外部调用 3 个(pin, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_marks_loaded_threads_open1416–1454 ↗
fn open_agent_picker_marks_loaded_threads_open() -> Result<()>
作用:确认服务端能正常加载到的线程,在代理选择器里会显示为未关闭。
数据流:创建 Tokio 运行时 → 启动测试 App 和内嵌服务端 → 在服务端启动一个线程,并在 App 里放入对应通道 → 打开代理选择器 → 检查该线程 is_closed 为 false。
调用关系:它用真实异步运行时测试 open_agent_picker 和内嵌服务端的配合。能 live attach 的线程应该显示成打开状态。
调用图:调用 2 个内部函数(make_test_app, new);外部调用 4 个(pin, assert_eq!, start_embedded_app_server_for_picker, new_multi_thread)。
attach_live_thread_for_selection_rejects_empty_non_ephemeral_fallback_threads1457–1497 ↗
fn attach_live_thread_for_selection_rejects_empty_non_ephemeral_fallback_threads() -> Result<()>
作用:确认选择一个只有元数据、但没有可回放内容的普通线程时,App 不会创建一个空白的假线程。
数据流:服务端启动一个普通线程,但新 App 只缓存它的导航元数据 → 尝试 attach_live_thread_for_selection → 得到错误信息 → 检查没有创建线程事件通道。
调用关系:它测试 attach_live_thread_for_selection 的防呆逻辑。没有实际历史或实时材料时,宁可报错,也不能让用户看到空白误导页面。
调用图:调用 1 个内部函数(make_test_app);外部调用 4 个(assert!, assert_eq!, start_embedded_app_server_for_picker, new_multi_thread)。
attach_live_thread_for_selection_rejects_unmaterialized_fallback_threads1500–1537 ↗
fn attach_live_thread_for_selection_rejects_unmaterialized_fallback_threads() -> Result<()>
作用:确认选择一个未真正落地的临时线程时,也不会附加成空白 live 线程。
数据流:用 ephemeral 配置在服务端启动临时线程 → App 只缓存它的导航元数据 → 尝试附加 → 得到“不可回放或实时附加”的错误 → 检查没有创建通道。
调用关系:它补充测试临时线程场景。attach_live_thread_for_selection 必须确认线程确实可用,不能只凭导航条目。
调用图:调用 1 个内部函数(make_test_app);外部调用 4 个(assert!, assert_eq!, start_embedded_app_server_for_picker, new_multi_thread)。
should_attach_live_thread_for_selection_skips_closed_metadata_only_threads1540–1563 ↗
async fn should_attach_live_thread_for_selection_skips_closed_metadata_only_threads()
作用:检查 App 判断“是否应该尝试实时附加某线程”的规则:关闭的纯元数据线程跳过,打开的可尝试,已有本地通道的也跳过。
数据流:先插入一个关闭的 metadata-only 线程 → 判断为 false → 改成未关闭 → 判断为 true → 再给它加线程通道 → 判断又变成 false。
调用关系:它测试 should_attach_live_thread_for_selection 这个小决策函数。open_agent_picker 或选择线程时会依赖这个判断,避免多余或错误的附加。
调用图:调用 3 个内部函数(new, make_test_app, new);外部调用 1 个(assert!)。
refresh_agent_picker_thread_liveness_prunes_closed_metadata_only_threads1566–1588 ↗
async fn refresh_agent_picker_thread_liveness_prunes_closed_metadata_only_threads() -> Result<()>
作用:确认刷新某个代理线程活跃状态时,如果它只是无效元数据,就会被移除。
数据流:只在 agent_navigation 中插入 Ghost/worker → 调用 refresh_agent_picker_thread_liveness → 返回不可用 → 检查导航项被删,且没有线程通道。
调用关系:它测试 open_agent_picker 里更细的一步刷新逻辑。单个线程被判定不可用时,导航列表要同步清理。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
open_agent_picker_prompts_to_enable_multi_agent_when_disabled1591–1620 ↗
async fn open_agent_picker_prompts_to_enable_multi_agent_when_disabled() -> Result<()>
作用:确认当多代理功能被关闭时,打开代理选择器会提示用户启用,而不是直接进入不可用界面。
数据流:创建带事件通道的 App → 禁用 Collab 功能 → 打开代理选择器并按 Enter → 检查发出启用 Collab 的功能开关事件,再检查历史里出现“下次会话启用子代理”的提示。
调用关系:它测试功能开关和 UI 提示的衔接。open_agent_picker 发现功能关闭后,会通过聊天组件让用户确认启用。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 6 个(pin, new, assert!, assert_matches!, start_embedded_app_server_for_picker, panic!)。
update_memory_settings_persists_and_updates_widget_config1623–1663 ↗
async fn update_memory_settings_persists_and_updates_widget_config() -> Result<()>
作用:确认从 TUI 菜单修改记忆设置时,会同时写入配置文件,并更新 App 和聊天组件里的当前配置。
数据流:创建临时 codex_home → 调用 update_memory_settings_with_app_server 关闭 use_memories 和 generate_memories → 检查 App 配置、聊天组件配置都变成 false → 读取 config.toml,确认只写入这两个 memories 字段,没有写外部上下文相关字段。
调用关系:它测试设置菜单、配置保存和 app_server 的配合。用户改设置后,界面和磁盘文件必须一致。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 6 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker, read_to_string, tempdir)。
update_memory_settings_updates_current_thread_memory_mode1666–1712 ↗
fn update_memory_settings_updates_current_thread_memory_mode() -> Result<()>
作用:确认修改记忆生成设置时,当前正在使用的线程也会更新自己的记忆模式。
数据流:创建临时状态目录,并把旧设置设成 generate_memories=true → 启动服务端和线程,把该线程设为 active → 调用 update_memory_settings_with_app_server,关闭生成记忆 → 打开状态数据库读取该线程 memory_mode,确认是 disabled。
调用关系:它测试全局设置变更对当前线程状态库的影响。否则用户关掉记忆生成后,当前线程可能还继续按旧规则保存记忆。
调用图:调用 2 个内部函数(init, make_test_app_with_channels);外部调用 5 个(pin, assert_eq!, start_embedded_app_server_for_picker, tempdir, new_multi_thread)。
reset_memories_clears_local_memory_directories1715–1744 ↗
async fn reset_memories_clears_local_memory_directories() -> Result<()>
作用:确认“重置记忆”会清空本地记忆目录里的旧文件和子目录内容。
数据流:在临时 codex_home 下手动创建 memories、rollout_summaries、extensions 和几个旧文件 → 启动服务端 → 调用 reset_memories_with_app_server → 检查 memories 目录已经空了。
调用关系:它测试重置记忆菜单项的磁盘清理效果。App 通过 app_server 执行重置,最后必须真的删掉本地残留。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 6 个(pin, assert_eq!, start_embedded_app_server_for_picker, create_dir_all, write, tempdir)。
apply_permission_profile_selection_preserves_loader_overrides1747–1829 ↗
async fn apply_permission_profile_selection_preserves_loader_overrides() -> Result<()>
作用:确认切换权限配置时,会保留启动加载器的覆盖项,并把新权限同步到 App、聊天组件和当前回合上下文。
数据流:创建临时配置文件,定义 locked-down 权限档案 → 设置 loader_overrides 和 harness_overrides → 调用 apply_permission_profile_selection → 检查 App 与 chat_widget 的活动权限都是 locked-down,运行时权限覆盖被更新 → 再检查发出的 OverrideTurnContext 操作和提示历史单元。
调用关系:它测试权限菜单选择后的完整链路。权限不仅要改配置,还要通知正在运行的模型回合,否则后台仍会按旧权限行动。
调用图:调用 2 个内部函数(workspace_write, make_test_app_with_channels);外部调用 5 个(assert!, assert_eq!, panic!, write, tempdir)。
update_feature_flags_enabling_guardian_selects_auto_review1832–1930 ↗
async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<()>
作用:测试打开 GuardianApproval 功能时,应用会自动切到“自动评审”模式。这里的“自动评审”就是让系统按预设方式替用户处理权限审批,而不是继续保持原来的人工选择。
数据流:测试先创建一个假 App 和临时配置目录,再调用 update_feature_flags 打开 GuardianApproval。之后它检查内存里的配置、聊天窗口里的配置、运行时覆盖设置、发给后端的 OverrideTurnContext 操作,以及写入 config.toml 的内容,确认都从普通状态变成自动评审所需的审批策略、权限配置和 reviewer。
调用关系:这是由异步测试运行器直接执行的场景测试。它用 make_test_app_with_channels 搭好带消息通道的 App,用 start_config_write_test_app_server 提供可写配置的假服务端,然后把真正要测的动作交给 app.update_feature_flags。
调用图:调用 2 个内部函数(make_test_app_with_channels, start_config_write_test_app_server);外部调用 6 个(assert!, assert_eq!, panic!, read_to_string, tempdir, vec!)。
update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default1933–2025 ↗
async fn update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default() -> Result<()>
作用:测试关闭 GuardianApproval 时,应用会清掉 Guardian 专用的评审人设置,并恢复到默认人工审批状态。这样可以避免用户关了功能,配置文件里还残留会影响行为的旧开关。
数据流:测试先写入一份带 guardian_approval、auto review、on-request 和 workspace-write 的配置,再把 App 和聊天窗口都伪装成 Guardian 已开启。调用 update_feature_flags 关闭功能后,它检查功能标记变成关闭,reviewer 回到 User,发给后端的覆盖只保留 reviewer 恢复信息,并确认 config.toml 删除 guardian_approval 和 approvals_reviewer,但保留原有审批策略和沙箱设置。
调用关系:测试运行器启动它后,它通过 make_test_app_with_channels 和 start_config_write_test_app_server 造环境,核心行为仍由 app.update_feature_flags 完成。测试同时观察 app_event_rx 和 op_rx,确认界面提示和后端操作都符合预期。
调用图:调用 4 个内部函数(legacy, workspace_write, make_test_app_with_channels, start_config_write_test_app_server);外部调用 7 个(assert!, assert_eq!, panic!, read_to_string, write, tempdir, vec!)。
update_feature_flags_enabling_guardian_overrides_explicit_manual_review_policy2028–2095 ↗
async fn update_feature_flags_enabling_guardian_overrides_explicit_manual_review_policy() -> Result<()>
作用:测试即使用户配置里明确写了手动评审人,打开 GuardianApproval 后也会被自动评审模式覆盖。重点是确认功能开关的含义比旧的手动 reviewer 配置更优先。
数据流:测试先把 config.toml 写成 approvals_reviewer = user,并让 App 和聊天窗口也处于 User 状态。打开 GuardianApproval 后,它检查 App、聊天窗口和发给后端的上下文都改成 auto_review 对应的审批策略和权限配置,最后确认配置文件也写入了 auto_review、guardian_approval、on-request 和 workspace-write。
调用关系:它是针对 update_feature_flags 的一个优先级测试。前置状态由 make_test_app_with_channels、临时配置文件和假配置写入服务端准备,结果通过 op_rx 和磁盘配置文件一起验证。
调用图:调用 2 个内部函数(make_test_app_with_channels, start_config_write_test_app_server);外部调用 6 个(assert!, assert_eq!, read_to_string, write, tempdir, vec!)。
update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history2098–2157 ↗
async fn update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history() -> Result<()>
作用:测试关闭 GuardianApproval 时,如果实际状态本来就是默认人工评审,就不要多插一条“权限变化”的历史消息。这样界面不会因为没有实际变化而打扰用户。
数据流:测试先准备一份配置:Guardian 开着,但 reviewer 是 user。关闭功能后,它检查 Guardian 标记关闭、reviewer 仍是 User、后端收到恢复 User 的上下文覆盖,同时 app_event_rx 没有收到历史消息。最后它确认配置文件里 guardian_approval 和 approvals_reviewer 都被删掉。
调用关系:这个测试同样围绕 app.update_feature_flags,但更关注“不要发多余 UI 提示”。它通过 app_event_rx 验证没有历史更新,通过 op_rx 验证后端仍收到必要的上下文调整。
调用图:调用 2 个内部函数(make_test_app_with_channels, start_config_write_test_app_server);外部调用 6 个(assert!, assert_eq!, read_to_string, write, tempdir, vec!)。
open_agent_picker_allows_existing_agent_threads_when_feature_is_disabled2160–2180 ↗
async fn open_agent_picker_allows_existing_agent_threads_when_feature_is_disabled() -> Result<()>
作用:测试即使某个相关功能开关关闭,已有的代理线程仍然可以从代理选择器里选中。换句话说,关闭新功能不能让用户失去回到旧线程的入口。
数据流:测试创建 App、嵌入式假服务端和一个已存在的线程通道,然后打开代理选择器并模拟按下 Enter。最后它从事件通道里读到 SelectAgentThread,确认选中的就是刚才已有的线程。
调用关系:测试运行器调用它后,它把流程交给 app.open_agent_picker,再由 chat_widget.handle_key_event 模拟用户选择。结果通过 AppEvent::SelectAgentThread 回到 app_event_rx。
调用图:调用 3 个内部函数(new, make_test_app_with_channels, new);外部调用 4 个(pin, new, assert_matches!, start_embedded_app_server_for_picker)。
refresh_pending_thread_approvals_only_lists_inactive_threads2183–2223 ↗
async fn refresh_pending_thread_approvals_only_lists_inactive_threads()
作用:测试“待审批线程列表”只显示非当前活动线程。当前正在看的线程不需要在旁边再挂一个提醒徽标。
数据流:测试创建主线程和代理线程,把主线程设为当前活动线程,并在代理线程里塞入一个命令审批请求。刷新待审批列表后,聊天窗口显示 Robie [explorer]。然后把活动线程切到代理线程,再刷新,列表就变空。
调用关系:它直接测试 app.refresh_pending_thread_approvals。待审批数据来自 ThreadEventChannel 的内部存储,显示名称来自 agent_navigation 中登记的代理昵称和角色。
调用图:调用 4 个内部函数(from_string, exec_approval_request, make_test_app, new);外部调用 2 个(assert!, assert_eq!)。
inactive_thread_approval_bubbles_into_active_view2226–2275 ↗
async fn inactive_thread_approval_bubbles_into_active_view() -> Result<()>
作用:测试非当前线程发来的审批请求,会冒泡到当前界面里让用户能处理,同时也显示待审批线程提示。这样后台代理卡住时,用户不会完全不知道。
数据流:测试把主线程设为当前活动线程,给代理线程建立会话状态并登记名字。随后向代理线程加入一个命令审批请求。结果是聊天窗口出现活动审批视图,同时待审批列表里显示 Robie [explorer]。
调用关系:它通过 app.enqueue_thread_request 模拟服务端给后台线程发来请求。App 内部会把请求转换成交互审批,再更新 chat_widget 的活动视图和待审批提示。
调用图:调用 7 个内部函数(workspace_write, from_string, exec_approval_request, make_test_app, test_thread_session, new, new_with_session);外部调用 2 个(new, assert_eq!)。
side_defers_parent_approval_overlay_until_parent_replay2278–2333 ↗
async fn side_defers_parent_approval_overlay_until_parent_replay() -> Result<()>
作用:测试当用户正在旁路对话里时,父线程的审批请求不会立刻覆盖当前旁路界面。它只会记录父线程“需要审批”,等用户回到父线程并重放快照时才显示审批界面。
数据流:测试创建父线程和旁路线程,把当前活动线程设为旁路线程。父线程收到审批请求后,聊天窗口没有活动审批视图,也没有普通待审批列表,但旁路线程状态标记为 NeedsApproval。之后测试移除旁路状态、切回父线程并重放父线程快照,审批视图才出现。
调用关系:它通过 app.enqueue_thread_request 模拟父线程请求,通过 ThreadEventChannel 的 snapshot 取出父线程历史,再交给 app.replay_thread_snapshot。这个测试保护的是旁路对话和父线程审批之间的切换规则。
调用图:调用 6 个内部函数(from_string, new, exec_approval_request, make_test_app, test_thread_session, new_with_session);外部调用 3 个(new, assert!, assert_eq!)。
replay_snapshot_with_pending_request_suppresses_replay_notices2336–2380 ↗
async fn replay_snapshot_with_pending_request_suppresses_replay_notices()
作用:测试重放线程快照时,如果快照里已经有待处理审批,就不要先显示过期警告之类的提示。这样真正需要用户操作的审批不会被旧通知挡住。
数据流:测试构造一个快照,里面先放一个 Warning 通知,再放一个审批请求。重放后聊天窗口有活动审批视图。随后它把事件通道里的历史消息都收集起来,确认没有插入那条旧 warning 的展示内容。
调用关系:它直接调用 app.replay_thread_snapshot。辅助函数 lines_to_single_string 只用来把历史单元渲染成字符串,方便确认没有不该出现的重放提示。
调用图:调用 4 个内部函数(from_string, lines_to_single_string, make_test_app_with_channels, test_thread_session);外部调用 5 个(new, new, assert!, assert_eq!, vec!)。
side_defers_subagent_approval_overlay_until_side_exits2383–2441 ↗
async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()>
作用:测试用户在旁路对话中时,子代理线程的审批不会立刻弹出覆盖层,但会显示待审批提示;等退出旁路后才真正弹出审批界面。这样旁路对话不会被后台代理突然打断。
数据流:测试创建主线程、旁路线程和代理线程,把当前活动线程设为旁路线程。代理线程收到审批请求后,聊天窗口没有活动审批视图,但待审批列表有 Robie [explorer]。当测试移除旁路状态、切回主线程,并调用 surface_pending_inactive_thread_interactive_requests 后,活动审批视图出现。
调用关系:它用 app.enqueue_thread_request 放入后台审批,用 app.surface_pending_inactive_thread_interactive_requests 模拟离开旁路后把挂起请求浮到前台。
调用图:调用 7 个内部函数(workspace_write, from_string, new, exec_approval_request, make_test_app, test_thread_session, new_with_session);外部调用 2 个(new, assert_eq!)。
inactive_thread_exec_approval_preserves_context2444–2524 ↗
async fn inactive_thread_exec_approval_preserves_context()
作用:测试后台线程的命令执行审批会保留关键上下文,比如网络审批信息、额外文件权限和可选择的网络策略修改。没有这些信息,用户看到审批时就可能无法做出正确决定。
数据流:测试先造一个命令审批请求,再手动塞入 Socks5 网络上下文、读写目录权限和允许 example.com 的网络策略修正。调用 interactive_request_for_thread_request 后,它取出生成的 Exec 审批对象,检查这些上下文都原样保留,并且可选决定里包含接受、会话内接受、应用网络策略修改和取消。
调用关系:这个测试直接验证 App 把 ServerRequest 转成 ThreadInteractiveRequest 的转换逻辑。它不走完整界面,只检查 app.interactive_request_for_thread_request 的输出是否完整。
调用图:调用 3 个内部函数(new, exec_approval_request, make_test_app);外部调用 3 个(assert_eq!, panic!, vec!)。
inactive_thread_exec_approval_splits_shell_wrapped_command2527–2559 ↗
async fn inactive_thread_exec_approval_splits_shell_wrapped_command()
作用:测试后台线程的命令审批能把 shell 包装命令拆回用户能看懂的参数列表。比如“/bin/zsh -lc 脚本”不能变成一团难读字符串。
数据流:测试把一个 python 脚本包成 /bin/zsh -lc 的命令字符串,塞进命令审批请求。转换成交互审批后,它检查 command 被拆成三个部分:shell 路径、-lc、原始脚本。
调用关系:它调用 app.interactive_request_for_thread_request 来验证请求转换。shlex::try_join 用来构造一个符合 shell 转义规则的输入字符串。
调用图:调用 3 个内部函数(new, exec_approval_request, make_test_app);外部调用 3 个(assert_eq!, panic!, try_join)。
inactive_thread_file_change_approval_recovers_buffered_changes2562–2631 ↗
async fn inactive_thread_file_change_approval_recovers_buffered_changes()
作用:测试后台线程申请批准文件改动时,App 能从之前缓存的文件变更通知里找回具体 diff。这样审批界面能展示“要改哪个文件、改了什么”,而不是只给一个空请求。
数据流:测试先给某线程加入一个 FileChange 开始通知,表示 README.md 要新增 hello。随后构造一个 FileChangeRequestApproval。转换请求后,它检查得到的是 ApplyPatch 审批,里面包含 README.md 的新增内容和失败重试原因。最后把审批推到界面,确认历史单元能显示新增文件和 diff 行。
调用关系:它先用 app.enqueue_thread_notification 缓存文件改动,再用 app.interactive_request_for_thread_request 从缓存恢复审批内容,最后用 app.push_thread_interactive_request 推进聊天界面。
调用图:调用 3 个内部函数(new, lines_to_single_string, make_test_app_with_channels);外部调用 6 个(Integer, ItemStarted, assert!, assert_eq!, panic!, vec!)。
inactive_thread_permissions_approval_preserves_file_system_permissions2634–2686 ↗
async fn inactive_thread_permissions_approval_preserves_file_system_permissions()
作用:测试后台线程请求额外权限时,文件系统权限不会在转换过程中丢失。这里包括哪些目录可读、哪些目录可写,以及网络是否启用。
数据流:测试构造一个 PermissionsRequestApproval,请求 remote 环境、/tmp/read-only 读权限、/tmp/write 写权限和网络开启。转换成交互审批后,它检查 environment_id 仍是 remote,权限对象也准确表达相同的读写根目录和网络设置。
调用关系:它专门覆盖 app.interactive_request_for_thread_request 对 PermissionsRequestApproval 的转换。输入是服务端协议对象,输出是界面层能展示和处理的 ApprovalRequest::Permissions。
调用图:调用 3 个内部函数(new, make_test_app, test_absolute_path);外部调用 4 个(Integer, assert_eq!, panic!, vec!)。
inactive_thread_url_elicitation_routes_to_app_link2689–2726 ↗
async fn inactive_thread_url_elicitation_routes_to_app_link()
作用:测试后台线程发来的安全 HTTPS 链接请求会变成 AppLink,也就是界面里可点击的“需要操作”链接。这样外部服务需要用户打开网页确认时,App 能正确引导用户。
数据流:测试构造一个 MCP 服务的 Url elicitation 请求,链接是 https://payments.example。转换后,它检查标题是 Action required,描述里写了服务名 payments,URL 原样保留,并且携带 thread_id、server_name 和 request_id,方便用户操作后回传结果。
调用关系:它验证 app.interactive_request_for_thread_request 对 McpServerElicitationRequest::Url 的路由。合法 HTTPS 链接会进入 ThreadInteractiveRequest::AppLink。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 3 个(Integer, assert_eq!, panic!)。
inactive_thread_invalid_url_elicitation_is_declined2729–2766 ↗
async fn inactive_thread_invalid_url_elicitation_is_declined()
作用:测试后台线程发来的不安全 URL 会被自动拒绝。这里用的是 http 链接,不是 https,所以 App 不应该把它显示成可点击操作。
数据流:测试构造一个 http://payments.example 的 Url elicitation 请求。转换函数返回 None,表示不生成界面请求;同时事件通道收到 SubmitThreadOp,里面是 ResolveElicitation 的 Decline 决定,说明 App 已经替用户拒绝这次不安全请求。
调用关系:它通过 app.interactive_request_for_thread_request 触发校验,再通过 app_event_rx 验证拒绝操作被提交给对应线程。assert_matches 用来确认事件内容完全匹配。
调用图:调用 2 个内部函数(new, make_test_app_with_channels);外部调用 3 个(Integer, assert!, assert_matches!)。
inactive_thread_approval_badge_clears_after_turn_completion_notification2769–2827 ↗
async fn inactive_thread_approval_badge_clears_after_turn_completion_notification() -> Result<()>
作用:测试后台线程的待审批徽标会在该轮任务结束时立刻清掉。否则用户会看到一个已经过期的“还要审批”的提醒。
数据流:测试先让代理线程收到一个命令审批请求,确认待审批列表显示 Robie [explorer]。然后给同一线程发送 turn completed 通知。之后待审批列表变空,说明结束通知清理了旧审批提示。
调用关系:它用 app.enqueue_thread_request 产生待审批状态,再用 app.enqueue_thread_notification 发送完成通知。聊天窗口的 pending_thread_approvals 是最终被验证的 UI 状态。
调用图:调用 8 个内部函数(workspace_write, from_string, exec_approval_request, make_test_app, test_thread_session, turn_completed_notification, new, new_with_session);外部调用 3 个(new, assert!, assert_eq!)。
inactive_thread_started_notification_initializes_replay_session2830–2933 ↗
async fn inactive_thread_started_notification_initializes_replay_session() -> Result<()>
作用:测试后台代理线程收到 ThreadStarted 通知时,App 能为它建立可重放的会话状态。这样用户稍后切过去时,系统知道这个线程的目录、模型、权限和工作区范围。
数据流:测试先准备主线程会话,包含审批策略、workspace-write 权限和两个工作区根目录。然后写一个 rollout 文件,里面记录代理线程使用的 cwd 和模型。发送 ThreadStarted 通知后,它从代理线程通道的 store 里取出推断出的 session,检查线程名、模型、模型提供方、审批策略、cwd、工作区根目录、rollout 路径都正确,并确认 agent_navigation 登记了 Robie [explorer]。
调用关系:它通过 app.enqueue_thread_notification 模拟服务端宣布新线程。App 内部会读取 ThreadStartedNotification 和 rollout 文件,再初始化 ThreadEventChannel 的 session 与代理选择器条目。
调用图:调用 5 个内部函数(workspace_write, from_string, make_test_app, test_thread_session, new_with_session);外部调用 8 个(ThreadStarted, new, assert_eq!, format!, json!, write, tempdir, vec!)。
inactive_thread_started_notification_preserves_primary_model_when_path_missing2936–3004 ↗
async fn inactive_thread_started_notification_preserves_primary_model_when_path_missing() -> Result<()>
作用:测试后台线程启动通知里没有历史文件路径时,不要乱改模型,而是沿用主线程的模型。这样缺少 rollout 文件时也有稳定的默认设置。
数据流:测试创建一个主线程会话,并把它保存为 primary_session_configured。随后发送一个代理线程 ThreadStarted 通知,但 path 是 None。最后从代理线程 store 里取出推断 session,确认 session.model 等于主线程的 model。
调用关系:它仍然通过 app.enqueue_thread_notification 触发后台线程会话初始化。和前一个测试相比,这里重点是缺少 path 时的回退规则。
调用图:调用 5 个内部函数(workspace_write, from_string, make_test_app, test_thread_session, new_with_session);外部调用 4 个(ThreadStarted, new, assert_eq!, vec!)。
thread_read_session_state_does_not_reuse_primary_permission_profile3010–3069 ↗
async fn thread_read_session_state_does_not_reuse_primary_permission_profile()
作用:测试读取某个线程信息时,不会错误复用主线程缓存的权限配置。因为 thread/read 返回的信息不一定带最新权限,回退时应使用当前界面配置,而不是旧主线程会话。
数据流:测试先把 primary_session_configured 设置成 workspace-write 权限,再构造一个新的 read thread。调用 session_state_for_thread_read 后,它检查线程 id、cwd、工作区根目录来自被读取线程;权限配置则等于当前聊天窗口配置里的权限,而不是 primary_session_configured 里的权限。
调用关系:它直接测试 app.session_state_for_thread_read。这个函数处在“服务端线程信息”转换成“本地会话状态”的入口处,测试防止权限从错误来源继承。
调用图:调用 4 个内部函数(workspace_write, from_string, make_test_app, test_thread_session);外部调用 3 个(new, assert_eq!, vec!)。
agent_picker_item_name_snapshot3072–3122 ↗
fn agent_picker_item_name_snapshot()
作用:测试代理选择器里条目的显示名字保持稳定。它用快照测试记录不同昵称、角色、主线程标记组合下应该显示成什么样。
数据流:测试固定一个线程 id,然后分别调用 format_agent_picker_item_name 生成五种名称:有昵称有角色且是主线程、有昵称有角色但不是主线程、只有昵称、只有角色、都没有。它把结果拼成多行字符串,并交给 assert_app_snapshot 与保存的快照比对。
调用关系:这是一个纯显示格式测试,不启动 App。它保护 format_agent_picker_item_name 的输出,避免以后改动让选择器里的名字意外变化。
调用图:调用 1 个内部函数(from_string);外部调用 2 个(assert_app_snapshot!, format!)。
side_fork_config_is_ephemeral_and_appends_developer_guardrails3125–3167 ↗
async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails()
作用:测试创建旁路对话的配置是临时的,并且会追加开发者护栏提示。旁路对话像“临时岔路聊天”,不能误以为自己能继续执行主线程任务或随便改文件。
数据流:测试从 App 当前配置里记下原审批策略和沙箱策略,然后调用 side_fork_config。它检查 fork_config.ephemeral 为真,审批和沙箱策略继承原值,并且 developer_instructions 里包含多条约束:这是旁路对话、继承历史只作参考、不能继续任务、外部工具输出只作参考、只做非破坏性检查、不要改文件、不要请求升级权限。最后确认 App 自身 transcript_cells 没被改动。
调用关系:它直接验证 app.side_fork_config。这个函数会被创建旁路对话的流程使用,本测试确保生成配置时既继承必要设置,又加上安全边界。
调用图:调用 1 个内部函数(make_test_app);外部调用 2 个(assert!, assert_eq!)。
side_fork_config_inherits_parent_thread_runtime_settings3170–3212 ↗
async fn side_fork_config_inherits_parent_thread_runtime_settings()
作用:测试旁路对话会继承父线程当前正在用的运行时设置,而不是只拿磁盘里的默认配置。比如模型、推理强度、服务档位、审批方式和权限配置都应该跟当前父线程一致。
数据流:测试先把持久配置设成一个默认模型,再把聊天窗口当前状态改成父线程模型、高推理强度、Fast 服务档位、on-request 审批、workspace-write 权限和 AutoReview。调用 side_fork_config 后,它检查生成的 fork_config 全部使用父线程当前值。
调用关系:它验证 app.side_fork_config 对 chat_widget 当前配置的读取。旁路对话启动时会用这个配置,所以测试确保不会错误回退到旧的 app.config 默认值。
调用图:调用 3 个内部函数(legacy, workspace_write, make_test_app);外部调用 1 个(assert_eq!)。
side_start_block_message_tracks_open_side_conversation3215–3239 ↗
async fn side_start_block_message_tracks_open_side_conversation()
作用:测试 /side 命令什么时候应该被拦住,并返回清楚的提示。没有主线程时不能开旁路;已经有旁路对话时也不能再开一个。
数据流:测试一开始没有 primary_thread_id,side_start_block_message 返回“主线程未就绪”。设置 primary_thread_id 后返回 None,表示可用。再登记一个已打开旁路线程后,返回“已有旁路对话,请先 Ctrl+C 返回”。移除旁路线程后又返回 None。
调用关系:它直接测试 app.side_start_block_message。这个函数会被 /side 命令入口用来决定是否允许创建旁路对话,以及给用户显示什么阻止原因。
调用图:调用 3 个内部函数(new, new, make_test_app);外部调用 1 个(assert_eq!)。
side_parent_status_tracks_parent_turn_lifecycle3242–3288 ↗
async fn side_parent_status_tracks_parent_turn_lifecycle() -> Result<()>
作用:测试用户在旁路对话里时,父线程的运行状态会被正确记录:完成、开始新一轮、失败。这样旁路界面可以知道父线程那边发生了什么。
数据流:测试创建父线程和旁路线程,并让当前活动线程是旁路。父线程收到 completed 通知后,旁路状态变成 Finished;收到 turn started 后,父状态清空;再收到 failed 完成通知后,父状态变成 Failed。
调用关系:它通过 app.enqueue_thread_notification 向父线程送入 turn_started 和 turn_completed 通知。App 处理通知时会更新 side_threads 里对应 SideThreadState 的 parent_status。
调用图:调用 5 个内部函数(new, new, make_test_app, turn_completed_notification, turn_started_notification);外部调用 1 个(assert_eq!)。
side_parent_status_prioritizes_input_over_approval3291–3364 ↗
async fn side_parent_status_prioritizes_input_over_approval() -> Result<()>
作用:测试父线程同时需要审批和需要用户输入时,旁路状态优先显示“需要输入”。因为用户输入通常比审批更像正在等待人的直接回复。
数据流:测试先给父线程加入审批请求,parent_status 变为 NeedsApproval。再加入用户输入请求,parent_status 变为 NeedsInput。随后模拟输入请求被解决,状态退回 NeedsApproval;再模拟审批请求被解决,状态清空。
调用关系:它用 app.enqueue_thread_request 放入两类请求,再用 ServerRequestResolved 通知逐个清除。这个测试保护 side_threads 状态汇总的优先级规则。
调用图:调用 5 个内部函数(new, new, exec_approval_request, make_test_app, request_user_input_request);外部调用 3 个(Integer, ServerRequestResolved, assert_eq!)。
side_thread_snapshot_hides_forked_parent_transcript3367–3396 ↗
async fn side_thread_snapshot_hides_forked_parent_transcript()
作用:测试安装旁路线程快照时,会隐藏从父线程继承来的历史内容。旁路对话可以参考父线程上下文,但界面里不应该把父线程原始对话当作旁路自己的聊天记录展示。
数据流:测试创建一个 ThreadEventStore、一个标记 forked_from_id 的旁路线程 session,以及一轮父线程用户消息。调用 App::install_side_thread_snapshot 后,它检查存储里的 session 仍是旁路线程,但 forked_from_id 被清掉,turns 为空,也没有活动 turn。
调用关系:它直接测试 App::install_side_thread_snapshot。这个函数负责把旁路线程快照放进事件存储,同时过滤掉不该展示的父线程 fork 历史。
调用图:调用 4 个内部函数(new, test_thread_session, test_turn, new);外部调用 3 个(assert_eq!, install_side_thread_snapshot, vec!)。
side_thread_snapshot_does_not_refresh_from_fork_history3399–3421 ↗
async fn side_thread_snapshot_does_not_refresh_from_fork_history()
作用:测试旁路线程快照不会因为 fork 历史而触发会话刷新。旁路对话的父线程历史只是参考,不应该反过来刷新当前会话配置。
数据流:测试把一个旁路线程登记到 app.side_threads,然后构造一个普通空快照。调用 should_refresh_snapshot_session 后,结果是 false,说明不需要基于这个快照刷新 session。
调用关系:它直接验证 app.should_refresh_snapshot_session。这个判断会在重放或加载快照时使用,测试确保旁路线程走特殊规则。
调用图:调用 4 个内部函数(new, new, make_test_app, test_thread_session);外部调用 2 个(new, assert!)。
side_thread_snapshot_skips_session_header_preamble3424–3459 ↗
async fn side_thread_snapshot_skips_session_header_preamble()
作用:测试重放旁路线程快照时,不会插入普通会话开头说明,也不会显示活动单元。这样旁路对话打开时不会被多余的 session header 干扰。
数据流:测试先清空已有事件,再建立父线程和旁路线程关系,构造一个带 forked_from_id 的空旁路快照。调用 replay_thread_snapshot 后,它收集所有 InsertHistoryCell 事件,确认没有任何渲染内容;同时确认聊天窗口线程 id 切到旁路线程,active_cell_transcript_lines 为空。
调用关系:它通过 app.replay_thread_snapshot 走真实快照重放路径,通过 app_event_rx 观察是否插入历史单元。测试目标是旁路线程重放时的 UI 静默行为。
调用图:调用 5 个内部函数(new, new, lines_to_single_string, make_test_app_with_channels, test_thread_session);外部调用 2 个(new, assert_eq!)。
primary_thread_ignores_child_mcp_startup_notifications3462–3542 ↗
async fn primary_thread_ignores_child_mcp_startup_notifications()
作用:测试主线程正在显示时,来自子线程的 MCP 启动失败通知不会直接插到主界面里。这样用户不会在当前主对话里看到不属于它的错误。
数据流:它创建测试 App、配置一个名叫 sentry 的 MCP 服务,再伪造一条属于子线程的失败通知。结果是主界面事件队列保持空,通知先存进子线程缓冲区;等切到子线程并回放快照后,界面才显示一次失败原因和汇总提示。
调用关系:这个测试围绕 App 接收服务器通知、缓存线程事件、刷新线程快照和回放线程内容这一整条链路,确认子线程消息只在对应线程里出现。
调用图:调用 4 个内部函数(new, lines_to_single_string, make_test_app_with_channels, test_thread_session);外部调用 7 个(McpServerStatusUpdated, new, ServerNotification, assert!, assert_eq!, start_embedded_app_server_for_picker, from)。
app_scoped_mcp_startup_notifications_do_not_render_in_active_thread3545–3573 ↗
async fn app_scoped_mcp_startup_notifications_do_not_render_in_active_thread()
作用:测试没有指定线程的 MCP 启动通知,不会误显示到当前活动线程里。它防止全局级别的状态消息污染用户正在看的聊天。
数据流:它创建 App 和嵌入式服务器,设置一个活动主线程,然后发送一条没有 thread_id 的 MCP 失败通知。处理后没有新的界面事件,聊天区域也没有出现活动单元内容。
调用关系:它直接检查 App 处理服务器通知的过滤规则,和上一类按线程分发通知的测试互相补充。
调用图:调用 2 个内部函数(new, make_test_app_with_channels);外部调用 5 个(McpServerStatusUpdated, ServerNotification, assert!, assert_eq!, start_embedded_app_server_for_picker)。
active_side_thread_renders_live_mcp_startup_notifications3576–3660 ↗
async fn active_side_thread_renders_live_mcp_startup_notifications()
作用:测试当用户正在看侧边线程时,这个侧边线程自己的 MCP 启动通知会实时显示出来。也就是说,不属于主线程的消息不是丢掉,而是要送到当前正确的地方。
数据流:它创建主线程和侧边线程,把侧边线程激活,然后发送 sentry 从启动中到失败的两条通知。之后它把活动线程收到的事件交给 App 处理,并从界面事件里收集渲染文本,确认只出现一次失败详情和一次失败汇总。
调用关系:这个测试把线程通道、活动线程接收器、通知处理和历史单元渲染串起来,验证侧边对话的实时通知路径。
调用图:调用 5 个内部函数(new, new, lines_to_single_string, make_test_app_with_channels, test_thread_session);外部调用 8 个(McpServerStatusUpdated, new, ServerNotification, assert!, assert_eq!, start_embedded_app_server_for_picker, matches!, from)。
side_restore_user_message_puts_inline_question_back_in_composer3663–3673 ↗
async fn side_restore_user_message_puts_inline_question_back_in_composer()
作用:测试恢复侧边提问时,用户之前输入的问题会回到输入框里。这样用户切换或恢复侧边对话后不会丢草稿。
数据流:它创建测试 App,把一条内容为 side question 的用户消息交给恢复函数。之后输入框里的文字变成这条问题。
调用关系:它检查侧边线程草稿恢复功能,主要关系到聊天输入框的状态。
调用图:调用 2 个内部函数(make_test_app, from);外部调用 1 个(assert_eq!)。
side_discard_selection_keeps_current_side_thread3676–3692 ↗
async fn side_discard_selection_keeps_current_side_thread()
作用:测试判断要不要丢弃侧边线程时,不会把当前正在看的侧边线程自己误删。只有从侧边线程切回它的父线程时,才会提示或选择丢弃这个侧边线程。
数据流:它设置一个父线程和一个侧边线程,并把活动线程设为侧边线程。查询以侧边线程为目标时返回空,查询以父线程为目标时返回需要丢弃的侧边线程编号。
调用关系:它验证线程切换前的清理判断,避免当前会话被错误当成待丢弃对象。
调用图:调用 3 个内部函数(new, new, make_test_app);外部调用 1 个(assert_eq!)。
discard_side_thread_keeps_local_state_when_server_close_fails3726–3759 ↗
async fn discard_side_thread_keeps_local_state_when_server_close_fails() -> Result<()>
作用:测试如果服务器端关闭侧边线程失败,本地不要假装已经删掉。这样界面状态不会和服务器状态对不上。
数据流:它只在本地造一个侧边线程编号,没有让服务器真正拥有它,然后尝试丢弃。函数返回失败,活动线程、侧边线程记录和导航入口都保持原样。
调用关系:它验证丢弃侧边线程的失败保护逻辑,和成功删除的测试形成对照。
调用图:调用 3 个内部函数(new, new, make_test_app);外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
discard_closed_side_thread_removes_local_state_without_server_rpc3762–3784 ↗
async fn discard_closed_side_thread_removes_local_state_without_server_rpc()
作用:测试已经关闭的侧边线程可以只清本地状态,不需要再找服务器关闭一次。这样能处理服务器已经通知关闭后的收尾。
数据流:它建立一个活动侧边线程、事件通道和导航入口,然后调用本地清理函数。结果活动线程被清空,侧边线程、通道和导航入口都被移除。
调用关系:它检查 ThreadClosed 之后的本地打扫流程,不走服务器远程调用。
调用图:调用 4 个内部函数(new, new, make_test_app, new);外部调用 2 个(assert!, assert_eq!)。
active_non_primary_shutdown_target_returns_none_for_non_shutdown_event3787–3799 ↗
async fn active_non_primary_shutdown_target_returns_none_for_non_shutdown_event() -> Result<()>
作用:测试普通通知不会触发“非主线程关闭后切回主线程”的逻辑。只有线程关闭通知才该参与这个判断。
数据流:它设置活动线程和主线程为不同编号,然后传入一个技能变化通知。判断函数返回空,表示不需要切换。
调用关系:它验证 active_non_primary_shutdown_target 的入口筛选,防止无关服务器通知造成线程切换。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
active_non_primary_shutdown_target_returns_none_for_primary_thread_shutdown3802–3814 ↗
async fn active_non_primary_shutdown_target_returns_none_for_primary_thread_shutdown() -> Result<()>
作用:测试如果关闭的是主线程,就不要按“活动非主线程关闭”来处理。主线程关闭属于另一类流程。
数据流:它把活动线程和主线程设成同一个编号,并传入这个线程的关闭通知。结果返回空。
调用关系:它确认关闭判断会区分主线程和非主线程,避免错误切换。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
active_non_primary_shutdown_target_returns_ids_for_non_primary_shutdown3817–3829 ↗
async fn active_non_primary_shutdown_target_returns_ids_for_non_primary_shutdown() -> Result<()>
作用:测试当前活动线程不是主线程、且它收到关闭通知时,系统能找出要关闭的线程和要切回的主线程。这个结果用于后续自动回到主对话。
数据流:它设置不同的活动线程和主线程,再传入活动线程关闭通知。函数返回这两个线程编号组成的结果。
调用关系:它验证非主线程关闭后的切换目标计算,是后续切回主线程流程的前置判断。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
active_non_primary_shutdown_target_returns_none_when_shutdown_exit_is_pending3832–3846 ↗
async fn active_non_primary_shutdown_target_returns_none_when_shutdown_exit_is_pending() -> Result<()>
作用:测试如果这个活动线程已经在等待退出处理,就不要重复触发切换逻辑。这样可以避免同一个关闭事件被处理两遍。
数据流:它设置活动线程、主线程,并把待退出线程设为活动线程。收到活动线程关闭通知后,函数返回空。
调用关系:它检查关闭流程里的去重保护,避免 pending_shutdown_exit_thread_id 引发重复动作。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
active_non_primary_shutdown_target_still_switches_for_other_pending_exit_thread3849–3863 ↗
async fn active_non_primary_shutdown_target_still_switches_for_other_pending_exit_thread() -> Result<()>
作用:测试如果等待退出的是另一个线程,当前活动非主线程关闭仍然要切回主线程。也就是说,保护逻辑只挡同一个线程。
数据流:它设置活动线程、主线程和一个无关的待退出线程。收到活动线程关闭通知后,函数返回活动线程和主线程编号。
调用关系:它补齐上一条测试的反面情况,确认关闭切换不会被无关待退出状态误拦截。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
render_clear_ui_header_after_long_transcript_for_snapshot3865–3979 ↗
async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String
作用:生成清屏后页头的测试文本,并确认它不会把很长的旧聊天或启动提示重新显示出来。它是多个快照测试共用的准备函数。
数据流:它创建 App,填入模型、工作目录、推理强度和一段很长的模拟聊天记录,再调用清屏页头渲染函数。输出是一段拼好的纯文本,同时断言里面没有旧提示和旧故事内容。
调用关系:它被两个清屏快照测试调用,用同一份结果验证普通清屏和快捷键清屏显示一致。
调用图:调用 1 个内部函数(make_test_app);被 2 处调用(clear_ui_after_long_transcript_snapshots_fresh_header_only, ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot);外部调用 2 个(assert!, vec!)。
clear_ui_after_long_transcript_snapshots_fresh_header_only3986–3989 ↗
async fn clear_ui_after_long_transcript_snapshots_fresh_header_only()
作用:测试长聊天记录清屏后,只留下新的页头快照。它防止清屏功能偷偷把旧内容又画回来。
数据流:它调用共用渲染函数拿到清屏后的文本,然后和名为 clear_ui_after_long_transcript_fresh_header_only 的快照比对。
调用关系:它依赖 render_clear_ui_header_after_long_transcript_for_snapshot 提供场景,专门检查快照内容。
调用图:调用 1 个内部函数(render_clear_ui_header_after_long_transcript_for_snapshot);外部调用 1 个(assert_app_snapshot!)。
ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot3996–3999 ↗
async fn ctrl_l_clear_ui_after_long_transcript_reuses_clear_header_snapshot()
作用:测试按 Ctrl-L 清屏时,显示效果和普通清屏使用同一份页头快照。这样两种清屏入口不会越改越不一样。
数据流:它复用清屏页头渲染结果,再和同一个快照名比较。输出没有新数据,只通过快照断言结果正确。
调用关系:它和 clear_ui_after_long_transcript_snapshots_fresh_header_only 共享同一个辅助函数和同一个期望画面。
调用图:调用 1 个内部函数(render_clear_ui_header_after_long_transcript_for_snapshot);外部调用 1 个(assert_app_snapshot!)。
clear_ui_header_shows_fast_status_for_fast_capable_models4006–4034 ↗
async fn clear_ui_header_shows_fast_status_for_fast_capable_models()
作用:测试支持快速模式的模型在清屏页头里会显示快速状态。快速模式可以理解成服务档位里更快的一种选择。
数据流:它创建 App,设置模型、工作目录、最高推理强度、Fast 服务档位和测试认证信息,然后渲染清屏页头。最后用快照确认显示文字符合预期。
调用关系:它检查清屏页头、模型目录、认证状态和服务档位显示之间的组合行为。
调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert_app_snapshot!, set_chatgpt_auth, set_fast_mode_test_catalog)。
make_test_app4036–4094 ↗
async fn make_test_app() -> App
作用:创建一个没有外部界面通道返回值的测试 App。测试用它快速拿到一个结构完整、但不会真的连线上服务的应用对象。
数据流:它先创建测试聊天组件和发送器,复制配置,建立文件搜索、离线模型信息和测试遥测数据。最后填好 App 的各个字段,返回一个可直接操作的 App。
调用关系:它是大量测试的基础工厂函数,负责把 App 初始化到一个干净、可控的测试状态。
调用图:调用 8 个内部函数(default, without_managed_config_for_tests, default_for_tests, new, get_model_offline_for_tests, test_session_telemetry, new, defaults);被 64 处调用(active_non_primary_shutdown_target_returns_ids_for_non_primary_shutdown, active_non_primary_shutdown_target_returns_none_for_non_shutdown_event, active_non_primary_shutdown_target_returns_none_for_primary_thread_shutdown, active_non_primary_shutdown_target_returns_none_when_shutdown_exit_is_pending, active_non_primary_shutdown_target_still_switches_for_other_pending_exit_thread, active_turn_id_for_thread_uses_snapshot_turns, attach_live_thread_for_selection_rejects_empty_non_ephemeral_fallback_threads, attach_live_thread_for_selection_rejects_unmaterialized_fallback_threads, backtrack_esc_does_not_steal_empty_vim_insert_escape, clear_only_ui_reset_allows_active_skill_warning_to_render_again (+15 more));外部调用 13 个(new, new, new, new, new, default, make_chatwidget_manual_with_sender, default, default, default (+3 more))。
make_test_app_with_channels4096–4162 ↗
async fn make_test_app_with_channels() -> (
App,
tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
tokio::sync::mpsc::UnboundedReceiver<Op>,
)
作用:创建测试 App,并把界面事件接收器和操作接收器也一起返回。需要观察 App 发出了什么事件或命令的测试会用它。
数据流:它和 make_test_app 一样组装 App,但保留聊天组件产生的 AppEvent 接收端和 Op 接收端。返回值是 App、界面事件队列和操作队列。
调用关系:它服务于需要检查渲染事件、用户回合命令、回滚命令等输出的测试。
调用图:调用 8 个内部函数(default, without_managed_config_for_tests, default_for_tests, new, get_model_offline_for_tests, test_session_telemetry, new, defaults);被 52 处调用(active_side_thread_renders_live_mcp_startup_notifications, app_scoped_mcp_startup_notifications_do_not_render_in_active_thread, apply_permission_profile_selection_preserves_loader_overrides, backtrack_remote_image_only_selection_clears_existing_composer_draft, backtrack_resubmit_preserves_data_image_urls_in_user_turn, backtrack_selection_with_duplicate_history_targets_unique_turn, cancelled_turn_edit_restores_prompt_and_rolls_back_latest_turn, capped_resize_reflow_renders_recent_suffix_only, enqueue_primary_thread_session_replays_buffered_approval_after_attach, enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit (+15 more));外部调用 13 个(new, new, new, new, new, default, make_chatwidget_manual_with_sender, default, default, default (+3 more))。
set_thread_goal_draft_materializes_long_objective_and_confirms_before_paste4165–4366 ↗
async fn set_thread_goal_draft_materializes_long_objective_and_confirms_before_paste() -> Result<()>
作用:测试设置线程目标时,长目标、粘贴内容、图片附件和替换确认都按规则处理。线程目标可以理解成这条会话接下来要完成的任务说明。
数据流:它启动测试服务器和线程,先提交超长目标,确认目标被写成受管理的文件引用;再尝试带粘贴内容的替换,确认在需要确认时不会覆盖;随后强制替换,确认粘贴文本写入文件;最后测试空白粘贴不会覆盖、图片会复制、本地和远程图片引用会写进目标。
调用关系:它覆盖 App、目标文件工具、嵌入式服务器和附件目录的完整交互,是线程目标功能的综合测试。
调用图:调用 2 个内部函数(from_app_server, make_test_app);外部调用 9 个(default, assert!, assert_eq!, start_embedded_app_server_for_picker, format!, read_dir, write, tempdir, vec!)。
replace_goal_confirmation_snapshot4369–4382 ↗
async fn replace_goal_confirmation_snapshot()
作用:测试当已有线程目标需要替换时,底部弹窗的确认界面长什么样。这样用户确认覆盖目标的提示不会意外变坏。
数据流:它创建 App,打开替换目标确认弹窗,并把底部弹窗渲染成宽度 80 的文本。最后和快照比较。
调用关系:它专门检查 show_replace_thread_goal_confirmation 产生的 UI,不涉及服务器保存。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 2 个(default, assert_app_snapshot!)。
test_thread_session4384–4407 ↗
fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSessionState
作用:生成一个测试用线程会话状态。它把模型、权限、工作目录等字段填成稳定的假数据,方便别的测试复用。
数据流:输入线程编号和工作目录,函数把它们连同固定模型名、只读权限、无审批等默认测试值组装成 ThreadSessionState。输出是完整会话对象。
调用关系:它被许多线程回放、通知和反馈测试调用,用来模拟服务器返回的线程会话。
调用图:调用 1 个内部函数(read_only);被 32 处调用(active_side_thread_renders_live_mcp_startup_notifications, active_turn_id_for_thread_uses_snapshot_turns, enqueue_primary_thread_session_replays_buffered_approval_after_attach, enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit, feedback_submission_for_inactive_thread_replays_into_origin_thread, inactive_thread_approval_badge_clears_after_turn_completion_notification, inactive_thread_approval_bubbles_into_active_view, inactive_thread_settings_notification_updates_cached_collaboration_mode, inactive_thread_started_notification_initializes_replay_session, inactive_thread_started_notification_preserves_primary_model_when_path_missing (+15 more));外部调用 3 个(abs, new, new)。
plain_line_cell4409–4411 ↗
rendered_line_text4413–4419 ↗
fn rendered_line_text(line: &crate::terminal_hyperlinks::HyperlinkLine) -> String
作用:从一行已经渲染好的终端内容里提取纯文字。测试用它忽略样式,只比较用户能看到的文本。
数据流:输入一个带超链接结构的渲染行,函数遍历其中所有文本片段并拼接。输出是一条普通字符串。
调用关系:它服务于重排和缓冲测试,用来把复杂渲染结构转成容易断言的文本。
capped_resize_reflow_renders_recent_suffix_only4422–4446 ↗
async fn capped_resize_reflow_renders_recent_suffix_only()
作用:测试窗口缩放重排有行数上限时,只重新渲染最近的尾部内容。这样历史很长时不会为了重排拖慢界面。
数据流:它设置最多保留 5 行,造 20 个历史单元,然后请求重排。结果只有 5 行,并且内容来自最后几个单元。
调用关系:它检查 render_transcript_lines_for_reflow 在行数限制下的裁剪行为。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 2 个(assert_eq!, Limit)。
uncapped_resize_reflow_renders_all_cells_when_row_cap_absent4449–4461 ↗
async fn uncapped_resize_reflow_renders_all_cells_when_row_cap_absent()
作用:测试没有行数上限时,窗口缩放重排会渲染全部历史单元。这样用户禁用限制后不会丢历史显示。
数据流:它关闭重排行数限制,造 20 个历史单元并重排。结果行数覆盖全部内容,第一行是 cell 0,最后一行是 cell 19。
调用关系:它和有上限的重排测试形成对照,确认配置开关有效。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 1 个(assert_eq!)。
resize_reflow_wraps_transcript_early_when_pet_is_enabled4464–4487 ↗
async fn resize_reflow_wraps_transcript_early_when_pet_is_enabled()
作用:测试启用终端宠物图像后,聊天历史会按更窄宽度提前换行。宠物占用屏幕空间,所以文字区域不能还按原宽度计算。
数据流:它先用宽度 40 渲染一段长文本,再启用测试宠物并取得更窄的历史换行宽度。第二次重排产生更多行,说明文本更早换行。
调用关系:它检查聊天组件的宠物显示能力和历史重排宽度计算是否配合。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 3 个(assert!, Supported, vec!)。
uncapped_resize_reflow_renders_all_cells_under_row_limit4490–4513 ↗
async fn uncapped_resize_reflow_renders_all_cells_under_row_limit()
作用:测试虽然设置了行数上限,但实际内容少于上限时不会被裁掉。上限只该限制过长内容,不该影响短历史。
数据流:它设置上限 100,造 3 个历史单元并重排。输出包含 cell 0、空行、cell 1、空行、cell 2 的完整顺序。
调用关系:它补充验证重排上限的边界行为,确保短内容完整显示。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 2 个(assert_eq!, Limit)。
initial_replay_buffer_keeps_recent_rows_when_row_cap_present4516–4547 ↗
async fn initial_replay_buffer_keeps_recent_rows_when_row_cap_present()
作用:测试初始历史回放缓冲区在有行数上限时,只保存最新的几行。这样启动时回放很长历史也不会撑爆界面缓冲。
数据流:它开启初始回放缓冲,依次塞入 line 0 到 line 4,并设置最多 3 行。最后缓冲里只剩 line 2、line 3、line 4。
调用关系:它直接测试 App 的初始历史回放缓冲工具函数,服务于启动或恢复会话时的显示控制。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 4 个(assert_eq!, buffer_initial_history_replay_display_lines, Limit, vec!)。
thread_switch_replay_buffer_uses_transcript_tail_mode_when_row_cap_present4550–4562 ↗
async fn thread_switch_replay_buffer_uses_transcript_tail_mode_when_row_cap_present()
作用:测试在线程切换且有行数上限时,回放缓冲会进入“从历史尾部渲染”的模式。这样切换到长线程时能优先显示最近内容。
数据流:它设置上限 3,然后开始线程切换回放缓冲。缓冲被创建,标记为从 transcript 尾部渲染,并且暂时没有保留行。
调用关系:它验证线程切换回放的初始化策略,和初始回放缓冲的逐行保留模式不同。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 2 个(assert!, Limit)。
thread_switch_replay_buffer_is_disabled_without_row_cap4565–4572 ↗
async fn thread_switch_replay_buffer_is_disabled_without_row_cap()
作用:测试没有行数上限时,线程切换不启用这类尾部缓冲。因为没有限制时可以正常渲染全部历史。
数据流:它关闭重排行数限制并开始线程切换回放缓冲。结果 App 里没有创建 initial_history_replay_buffer。
调用关系:它和有上限的线程切换测试配套,确认配置决定是否启用缓冲。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 1 个(assert!)。
height_shrink_schedules_resize_reflow4575–4591 ↗
async fn height_shrink_schedules_resize_reflow()
作用:测试终端高度变矮时会安排一次历史重排。高度缩小后,不重排可能导致显示区域里的内容位置不对。
数据流:它先传入相同的新旧尺寸,确认不需要重排;再把高度从 35 变成 24,函数返回需要处理,并且重排状态变成有待执行。
调用关系:它检查绘制尺寸变化处理和 transcript_reflow 状态之间的连接。
调用图:调用 2 个内部函数(make_test_app_with_channels, test_dummy);外部调用 1 个(assert!)。
test_turn4593–4604 ↗
fn test_turn(turn_id: &str, status: TurnStatus, items: Vec<ThreadItem>) -> Turn
作用:生成一个测试用回合对象。回合就是用户和助手之间一次问答过程的记录。
数据流:输入回合编号、状态和条目列表,函数填入默认时间、错误和视图字段,输出完整 Turn。它不修改外部状态。
调用关系:它被开始回合和完成回合通知的辅助函数复用,避免每个测试重复拼 Turn。
调用图:被 3 处调用(side_thread_snapshot_hides_forked_parent_transcript, turn_completed_notification, turn_started_notification)。
turn_started_notification4606–4614 ↗
fn turn_started_notification(thread_id: ThreadId, turn_id: &str) -> ServerNotification
作用:生成一条“某线程的某回合开始了”的测试服务器通知。它用来模拟服务器告诉界面任务正在运行。
数据流:输入线程编号和回合编号,函数创建一个进行中的 Turn,并包进 TurnStarted 类型通知。输出是 ServerNotification。
调用关系:它被回放、队列恢复和代理运行状态相关测试使用,作为模拟服务器事件。
调用图:调用 1 个内部函数(test_turn);被 9 处调用(open_agent_picker_clears_completed_path_backed_agent_running_state, open_agent_picker_refreshes_replay_only_path_backed_liveness, replay_only_thread_keeps_restored_queue_visible, replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up, replay_thread_snapshot_in_progress_turn_restores_running_queue_state, replay_thread_snapshot_keeps_queue_when_running_state_only_comes_from_snapshot, replayed_interrupted_turn_restores_queued_input_to_composer, replayed_turn_complete_submits_restored_queued_follow_up, side_parent_status_tracks_parent_turn_lifecycle);外部调用 3 个(TurnStarted, new, to_string)。
turn_completed_notification4616–4629 ↗
fn turn_completed_notification(
thread_id: ThreadId,
turn_id: &str,
status: TurnStatus,
) -> ServerNotification
作用:生成一条“某线程的某回合完成了”的测试服务器通知。测试可以用它模拟任务结束、成功或失败。
数据流:输入线程编号、回合编号和完成状态,函数创建带完成时间和耗时的 Turn,再包进 TurnCompleted 通知。输出是 ServerNotification。
调用关系:它服务于线程生命周期、审批徽标清理和父子线程状态跟踪测试。
调用图:调用 1 个内部函数(test_turn);被 4 处调用(inactive_thread_approval_badge_clears_after_turn_completion_notification, open_agent_picker_clears_completed_path_backed_agent_running_state, replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up, side_parent_status_tracks_parent_turn_lifecycle);外部调用 3 个(TurnCompleted, new, to_string)。
thread_closed_notification4631–4635 ↗
fn thread_closed_notification(thread_id: ThreadId) -> ServerNotification
作用:生成一条线程关闭通知。测试用它模拟服务器说某个线程已经结束。
数据流:输入线程编号,函数把编号转成字符串并放入 ThreadClosedNotification,输出 ServerNotification。
调用关系:它被关闭处理、通道满时入队等测试作为标准关闭事件使用。
调用图:被 1 处调用(enqueue_thread_event_does_not_block_when_channel_full);外部调用 2 个(ThreadClosed, to_string)。
token_usage_notification4637–4663 ↗
fn token_usage_notification(
thread_id: ThreadId,
turn_id: &str,
model_context_window: Option<i64>,
) -> ServerNotification
作用:生成一条 token 用量更新通知。token 可以理解成模型处理文字时使用的计量单位。
数据流:输入线程编号、回合编号和可选的模型上下文窗口大小,函数填入固定的总量、输入量、缓存输入量和输出量。输出是 ThreadTokenUsageUpdated 通知。
调用关系:它用于状态栏显示上下文窗口和用量刷新相关测试。
调用图:被 1 处调用(token_usage_update_refreshes_status_line_with_runtime_context_window);外部调用 2 个(ThreadTokenUsageUpdated, to_string)。
agent_message_delta_notification4665–4677 ↗
fn agent_message_delta_notification(
thread_id: ThreadId,
turn_id: &str,
item_id: &str,
delta: &str,
) -> ServerNotification
作用:生成一条助手消息增量通知。增量就是助手回答还没完整结束时,一小段一小段吐出来的文字。
数据流:输入线程编号、回合编号、条目编号和文本片段,函数把它们包装成 AgentMessageDelta 通知。输出是 ServerNotification。
调用关系:它被线程回放和队列恢复测试用来模拟助手正在流式输出。
调用图:被 6 处调用(replay_only_thread_keeps_restored_queue_visible, replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up, replay_thread_snapshot_in_progress_turn_restores_running_queue_state, replay_thread_snapshot_keeps_queue_when_running_state_only_comes_from_snapshot, replayed_interrupted_turn_restores_queued_input_to_composer, replayed_turn_complete_submits_restored_queued_follow_up);外部调用 2 个(AgentMessageDelta, to_string)。
exec_approval_request4679–4704 ↗
fn exec_approval_request(
thread_id: ThreadId,
turn_id: &str,
item_id: &str,
approval_id: Option<&str>,
) -> ServerRequest
作用:生成一条命令执行审批请求。审批请求就是工具想运行命令前,先问用户允不允许。
数据流:输入线程编号、回合编号、条目编号和可选审批编号,函数填入固定命令 echo hello、工作目录、原因和请求编号。输出是 ServerRequest。
调用关系:它被审批气泡、 inactive 线程审批、缓冲审批恢复等测试用来模拟服务器发来的权限请求。
调用图:被 10 处调用(enqueue_primary_thread_session_replays_buffered_approval_after_attach, inactive_thread_approval_badge_clears_after_turn_completion_notification, inactive_thread_approval_bubbles_into_active_view, inactive_thread_exec_approval_preserves_context, inactive_thread_exec_approval_splits_shell_wrapped_command, refresh_pending_thread_approvals_only_lists_inactive_threads, resolved_buffered_approval_does_not_become_actionable_after_drain, side_defers_parent_approval_overlay_until_parent_replay, side_defers_subagent_approval_overlay_until_side_exits, side_parent_status_prioritizes_input_over_approval);外部调用 2 个(Integer, to_string)。
request_user_input_request4706–4717 ↗
fn request_user_input_request(thread_id: ThreadId, turn_id: &str, item_id: &str) -> ServerRequest
作用:生成一条工具请求用户输入的测试请求。它模拟后台工具需要用户回答问题才能继续。
数据流:输入线程编号、回合编号和条目编号,函数填入空问题列表和固定请求编号。输出是 ServerRequest。
调用关系:它用于侧边父线程状态测试,特别是比较“等待用户输入”和“等待审批”哪个优先显示。
调用图:被 1 处调用(side_parent_status_prioritizes_input_over_approval);外部调用 3 个(Integer, new, to_string)。
feedback_submission_without_thread_emits_error_history_cell4720–4739 ↗
async fn feedback_submission_without_thread_emits_error_history_cell()
作用:测试反馈上传失败且没有来源线程时,会在当前历史里显示错误消息。这样用户能知道反馈没发出去。
数据流:它创建带事件通道的 App,调用反馈提交处理函数并传入错误 boom。随后从界面事件里取出一个历史单元,确认文本是上传失败提示。
调用关系:它检查反馈结果处理和历史消息插入之间的连接,属于无来源线程的直接显示路径。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 2 个(assert_eq!, panic!)。
feedback_submission_for_inactive_thread_replays_into_origin_thread4742–4806 ↗
async fn feedback_submission_for_inactive_thread_replays_into_origin_thread()
作用:测试从非当前线程提交的反馈成功后,提示会存回原线程,而不是插到当前活动线程。这样多线程时反馈结果不会跑错地方。
数据流:它创建来源线程和活动线程的事件通道,激活活动线程后提交来源线程的反馈成功结果。当前界面没有新事件;随后回放来源线程快照,才看到包含上传链接的反馈提示。
调用关系:它覆盖反馈处理、线程缓冲和线程快照回放,确认异步反馈按来源线程归档。
调用图:调用 5 个内部函数(new, lines_to_single_string, make_test_app_with_channels, test_thread_session, new_with_session);外部调用 3 个(new, assert!, assert_matches!)。
next_user_turn_op4808–4817 ↗
fn next_user_turn_op(op_rx: &mut tokio::sync::mpsc::UnboundedReceiver<Op>) -> Op
作用:从操作队列里取出下一条用户回合操作。测试用它跳过无关操作,只关心是否真的提交了用户输入。
数据流:输入一个操作接收器,函数不断尝试读取队列;遇到 UserTurn 就返回,其他操作记录下来。如果队列里没有 UserTurn,就直接让测试失败并打印看到的操作。
调用关系:它被回放后自动提交、草稿恢复提交等测试用来验证 App 是否发出了正确的用户回合命令。
调用图:被 4 处调用(replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up, replay_thread_snapshot_restores_collaboration_mode_for_draft_submit, replay_thread_snapshot_restores_pending_pastes_for_submit, replayed_turn_complete_submits_restored_queued_follow_up);外部调用 5 个(try_recv, new, format!, matches!, panic!)。
lines_to_single_string4819–4830 ↗
fn lines_to_single_string(lines: &[Line<'_>]) -> String
作用:把多行终端渲染内容合成一个普通字符串。测试用它更方便地比较屏幕上实际显示的文字。
数据流:输入一组 Line,函数取出每行的所有文本片段拼成行文本,再用换行符连接。输出是纯文本字符串。
调用关系:它被许多 UI 渲染测试复用,用来把历史单元的显示结果转成断言文本。
调用图:被 9 处调用(active_side_thread_renders_live_mcp_startup_notifications, bypass_hook_trust_startup_warning_snapshot, enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit, feedback_submission_for_inactive_thread_replays_into_origin_thread, inactive_thread_file_change_approval_recovers_buffered_changes, primary_thread_ignores_child_mcp_startup_notifications, replace_chat_widget_reseeds_collab_agent_metadata_for_replay, replay_snapshot_with_pending_request_suppresses_replay_notices, side_thread_snapshot_skips_session_header_preamble);外部调用 1 个(iter)。
test_session_telemetry4832–4847 ↗
fn test_session_telemetry(config: &Config, model: &str) -> SessionTelemetry
作用:生成测试用会话遥测信息。遥测可以理解成记录会话来源、模型等元数据,但这里不会真的记录用户提示。
数据流:输入配置和模型名,函数离线构造模型信息,再填入固定来源、测试标识、无账号信息和不记录提示的设置。输出是 SessionTelemetry。
调用关系:它被 make_test_app 和 make_test_app_with_channels 调用,保证测试 App 也有完整的会话元数据。
调用图:调用 4 个内部函数(construct_model_info_offline_for_tests, new, new, session_source_cli);被 2 处调用(make_test_app, make_test_app_with_channels);外部调用 1 个(to_models_manager_config)。
active_turn_not_steerable_turn_error_extracts_structured_server_error4850–4871 ↗
fn active_turn_not_steerable_turn_error_extracts_structured_server_error()
作用:测试当服务器返回“当前回合不能被引导”的结构化错误时,客户端能把里面的详细原因解析出来。这里的引导可以理解成继续控制正在运行的回合。
数据流:它构造一个包含 Review 类型原因的服务器错误,并把它交给解析函数。结果返回原来的 AppServerTurnError,而不是只剩普通错误文字。
调用关系:它验证错误解析函数 active_turn_not_steerable_turn_error 对 JSON-RPC 错误数据的识别能力。
调用图:外部调用 2 个(assert_eq!, to_value)。
session_start_error_surfaces_archived_guidance_without_rollout_path4874–4897 ↗
fn session_start_error_surfaces_archived_guidance_without_rollout_path()
作用:测试恢复或分叉已归档会话失败时,最终错误只显示清楚的解档提示,不暴露内部 rollout 文件路径。这样错误对用户更友好。
数据流:它构造一个指向 archived_sessions 的目标会话和包含归档提示的底层错误。对 resume 和 fork 两种动作处理后,输出错误文字都只保留“运行 codex unarchive”的提示。
调用关系:它检查 TUI 启动阶段的会话错误整理逻辑,防止内部路径泄漏到用户提示。
调用图:调用 1 个内部函数(from_string);外部调用 4 个(assert_eq!, eyre!, format!, from)。
active_turn_steer_race_detects_missing_active_turn4900–4915 ↗
fn active_turn_steer_race_detects_missing_active_turn()
作用:测试当服务器说“没有可引导的活动回合”时,客户端能识别这是一次竞态情况。竞态就是两边状态变化太快,客户端以为还在,服务器已经不在了。
数据流:它构造 turn/steer 的服务器错误,消息为 no active turn to steer。解析后得到 Missing 类型,同时确认它不会被误判成不可引导回合的结构化错误。
调用关系:它验证 active_turn_steer_race 和 active_turn_not_steerable_turn_error 两个错误识别函数的边界。
调用图:外部调用 1 个(assert_eq!)。
active_turn_steer_race_extracts_actual_turn_id_from_mismatch4918–4934 ↗
fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch()
作用:测试当引导回合时发现预期回合和实际回合不一致,客户端能提取服务器说的实际回合编号。这样后续可以按真实状态恢复。
数据流:它构造一条包含 expected 和 found 的 turn/steer 错误消息。解析函数返回 ExpectedTurnMismatch,并带上 actual_turn_id 为 turn-actual。
调用关系:它专门检查 active_turn_steer_race 对文字错误消息的解析能力。
调用图:外部调用 1 个(assert_eq!)。
active_turn_interrupt_race_extracts_actual_turn_id_from_mismatch4937–4951 ↗
fn active_turn_interrupt_race_extracts_actual_turn_id_from_mismatch()
作用:测试中断回合时如果回合编号不一致,客户端能从错误文字里找出实际活动回合编号。这样不会盲目按旧编号继续操作。
数据流:它构造 turn/interrupt 的服务器错误,消息里说明 expected 和 found。解析函数返回 turn-actual。
调用关系:它验证 active_turn_interrupt_race 对中断竞态错误的处理,和 steer 的竞态测试相对应。
调用图:外部调用 1 个(assert_eq!)。
fresh_session_config_uses_current_service_tier4954–4972 ↗
async fn fresh_session_config_uses_current_service_tier()
作用:测试新建会话配置会使用聊天组件当前选择的服务档位。比如用户切到 Fast,新会话就应该按 Fast 创建。
数据流:它创建 App,把聊天组件服务档位设为 Fast,然后生成 fresh_session_config。结果配置里的 service_tier 也是 Fast 的请求值。
调用关系:它检查 App 从当前 UI 状态生成新会话配置的逻辑。
调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。
backtrack_selection_with_duplicate_history_targets_unique_turn4975–5117 ↗
async fn backtrack_selection_with_duplicate_history_targets_unique_turn()
作用:测试历史里有重复对话时,回退编辑能选中真正要回退的那一次用户消息,而不是同样文字的旧消息。回退编辑就是把会话倒回某个提问,改后重来。
数据流:它造出一段包含重复问题和编辑后问题的历史,带本地图片和远程图片,再设置回退状态。确认选择结果指向第二个用户消息,保留编辑文本和附件;应用回退后,输入框保留远程图片,并向操作队列发出回滚 1 个回合。
调用关系:它覆盖历史单元统计、回退选择、附件恢复、输入框更新和 ThreadRollback 操作发送。
调用图:调用 4 个内部函数(read_only, new, make_test_app_with_channels, user_count);外部调用 5 个(new, new, assert_eq!, format!, vec!)。
backtrack_remote_image_only_selection_clears_existing_composer_draft5120–5151 ↗
async fn backtrack_remote_image_only_selection_clears_existing_composer_draft()
作用:测试回退选择只有远程图片、没有文字时,会清掉输入框里旧草稿,但保留远程图片。这样旧文字不会和新选择的图片混在一起。
数据流:它先放入一个用户历史单元,并把输入框设成 stale draft。应用一个空文本但含远程图片的回退选择后,输入框文字变空,远程图片列表更新,并发出回滚 1 个回合的操作。
调用关系:它检查 apply_backtrack_rollback 对特殊附件场景的处理,是回退编辑功能的边界测试。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 4 个(new, new, assert_eq!, vec!)。
cancelled_turn_edit_restores_prompt_and_rolls_back_latest_turn5154–5182 ↗
async fn cancelled_turn_edit_restores_prompt_and_rolls_back_latest_turn()
作用:测试用户取消编辑上一轮消息时,输入框会恢复被编辑的文字和图片链接,同时后台会收到“回退一轮对话”的请求。这样用户不会因为取消编辑而丢掉原本想改的内容。
数据流:测试先造出一个带历史消息的假 App,再准备一条要恢复的用户消息。调用取消编辑的方法后,它检查输入框文字变成“edit me”,远程图片链接还在,并且操作通道里出现了 ThreadRollback,表示要把最近一轮对话撤掉。
调用关系:这个测试由测试框架运行。它通过 make_test_app_with_channels 搭出 App 和消息通道,然后直接调用 app.apply_cancelled_turn_edit,最后用断言和快照检查界面与后台命令是否同步。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 5 个(new, assert_eq!, assert_matches!, assert_snapshot!, vec!)。
first_cancelled_turn_edit_restores_prompt_without_local_history5185–5206 ↗
async fn first_cancelled_turn_edit_restores_prompt_without_local_history()
作用:测试第一条消息被取消编辑时,即使本地还没有历史记录,输入框也能恢复原提示词和图片链接,并请求回退。它保证新会话的第一轮编辑也不会特殊出错。
数据流:测试创建一个空历史的假 App,准备一条带文字和远程图片的用户消息。执行取消编辑后,输入框拿回这段文字,图片链接也回到输入区,同时后台队列收到回退一轮的命令。
调用关系:它和前一个测试验证同一个功能的另一种情况:没有本地历史。测试框架启动它,它用 make_test_app_with_channels 建环境,再让 apply_cancelled_turn_edit 完成恢复和回退通知。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 4 个(new, assert_eq!, assert_matches!, vec!)。
backtrack_resubmit_preserves_data_image_urls_in_user_turn5209–5274 ↗
async fn backtrack_resubmit_preserves_data_image_urls_in_user_turn()
作用:测试用户回退到旧消息后重新提交时,消息里的 data:image 这种内嵌图片地址不会丢。没有这个保护,用户重发带图片的问题时,后台可能只收到文字,看不到图片。
数据流:测试先建立一个线程会话,并在历史里放入一条带 data:image/png;base64 图片地址的用户消息。然后执行回退,把旧消息放回输入区,再模拟按 Enter 重新发送。最后从操作通道里确认既发生了回退,也提交了新的用户轮次,并且提交内容里仍然包含原图片地址。
调用关系:测试框架运行它。它先用 ChatWidget 接收线程会话,再调用 app.apply_backtrack_rollback 准备回退输入,随后把按键事件交给 chat_widget,最后检查 App 发出的 Op::ThreadRollback 和 Op::UserTurn。
调用图:调用 3 个内部函数(read_only, new, make_test_app_with_channels);外部调用 5 个(new, new, new, assert!, vec!)。
replay_thread_snapshot_replays_turn_history_in_order5277–5356 ↗
async fn replay_thread_snapshot_replays_turn_history_in_order()
作用:测试从线程快照恢复历史时,用户消息会按原来的先后顺序出现在聊天记录里。这样重新打开会话时,用户看到的历史不会颠倒或漏掉。
数据流:测试构造一个包含两个历史轮次的快照:第一轮是用户消息,第二轮包含用户消息和助手回复。调用 replay_thread_snapshot 后,它从 App 事件通道里取出插入历史单元的事件,塞回 transcript_cells,再抽取所有用户消息,确认顺序是 first prompt 后 third prompt。
调用关系:这个测试验证 app.replay_thread_snapshot。它借助 test_thread_session 准备会话信息,重放产生的 InsertHistoryCell 事件由测试手动收集,用来确认历史渲染顺序。
调用图:调用 3 个内部函数(new, make_test_app_with_channels, test_thread_session);外部调用 3 个(new, assert_eq!, vec!)。
replace_chat_widget_reseeds_collab_agent_metadata_for_replay5359–5438 ↗
async fn replace_chat_widget_reseeds_collab_agent_metadata_for_replay()
作用:测试替换聊天组件后,再重放协作代理相关事件时,代理的名字和角色仍能显示出来。否则界面可能只显示一串线程 ID,用户看不懂是谁在等待。
数据流:测试先在 agent_navigation 里登记一个接收方线程,名字是 Robie,角色是 explorer。然后新建一个 ChatWidget 替换旧的,再重放一个协作代理等待事件。最后读取插入的历史单元文本,确认里面包含“Robie [explorer]”。
调用关系:测试框架运行它。它用 ChatWidget::new_with_app_event 做替换组件,调用 app.replace_chat_widget 后,再通过 app.replay_thread_snapshot 触发历史生成,最后从 AppEvent::InsertHistoryCell 里检查显示结果。
调用图:调用 4 个内部函数(from_string, lines_to_single_string, make_test_app_with_channels, test_dummy);外部调用 4 个(new, assert!, new_with_app_event, vec!)。
refreshed_snapshot_session_persists_resumed_turns5441–5502 ↗
async fn refreshed_snapshot_session_persists_resumed_turns()
作用:测试后台刷新线程快照时,新的会话信息和恢复出来的历史轮次会同时写回内存快照和线程事件存储。这样恢复会话后,App 不会继续拿旧目录或旧历史。
数据流:测试先给某个线程放入旧 session。然后准备一个 cwd 不同的新 session,以及一条恢复出的用户历史轮次。调用 apply_refreshed_snapshot_thread 后,它检查传入的 snapshot 被改成新 session 和新 turns,也检查线程通道内部 store 里的快照同样更新了。
调用关系:它直接测试 app.apply_refreshed_snapshot_thread。线程事件通道由 ThreadEventChannel::new_with_session 建好,函数执行后既改外部 snapshot,也改内部缓存。
调用图:调用 4 个内部函数(new, make_test_app, test_thread_session, new_with_session);外部调用 3 个(new, assert_eq!, vec!)。
queued_rollback_syncs_overlay_and_clears_deferred_history5505–5580 ↗
async fn queued_rollback_syncs_overlay_and_clears_deferred_history()
作用:测试当回退请求后来才生效时,聊天记录、覆盖层预览、延迟显示的历史行、以及正在显示的用量面板都会一起清理到正确状态。它防止界面出现“历史已回退,但浮层还显示旧内容”的错乱。
数据流:测试先放入两轮用户与助手历史,并打开 transcript 覆盖层,还塞入一条过期的缓冲历史行。随后模拟打开 token usage 的加载显示,再执行 apply_non_pending_thread_rollback 回退一轮。最后检查回退确实发生,延迟历史被清空,usage 临时输出消失,只剩第一条用户消息,覆盖层的单元数量也和真实历史一致。
调用关系:这个测试围绕 app.apply_non_pending_thread_rollback。前面通过 ChatWidget 的按键处理制造一个待显示的用量单元,回退函数负责同步 App 主历史和 Overlay。
调用图:调用 2 个内部函数(make_test_app, new_transcript);外部调用 6 个(new, new, assert!, assert_eq!, panic!, vec!)。
late_usage_result_can_follow_finalized_plan5583–5612 ↗
async fn late_usage_result_can_follow_finalized_plan()
作用:测试 token 用量查询结果即使晚于计划内容完成,也还能被正常接收和插入。这样后台慢一点返回用量信息时,界面不会误以为它被计划流式输出挡住了。
数据流:测试先让聊天组件添加一个每日用量输出,并拿到刷新请求 ID。然后模拟流式内容合并:插入一个最终计划单元并标记合并完成。最后用同一个请求 ID 结束用量刷新,即使结果是错误,也确认它被接收、没有被阻塞,并且可取出完成的用量输出。
调用关系:它测试 ChatWidget 和 App 对“流式内容合并”与“用量结果”两条异步路径的协调。add_token_activity_output 发出 RefreshTokenActivity,finish_token_activity_refresh 接回结果,app.pending_usage_output_insertion_blocked 用来确认没有卡住。
调用图:调用 1 个内部函数(make_test_app_with_channels);外部调用 5 个(new, assert!, new_proposed_plan_stream, panic!, vec!)。
thread_rollback_response_discards_queued_active_thread_events5615–5667 ↗
async fn thread_rollback_response_discards_queued_active_thread_events()
作用:测试线程回退响应到达后,当前活动线程里已经排队但过时的事件会被丢掉。否则回退后旧警告、旧通知还可能跳出来,污染新状态。
数据流:测试创建活动线程和一个事件接收通道,先塞入一条过时的配置警告通知。随后调用 handle_thread_rollback_response 处理回退响应。最后检查活动线程接收器还在,但里面已经没有那条旧事件。
调用关系:它直接验证 app.handle_thread_rollback_response。测试手动把 ThreadBufferedEvent 放进 active_thread_rx 对应的队列,再确认回退处理会清空这些排队事件。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 6 个(ConfigWarning, new, new, assert!, channel, Notification)。
new_session_requests_shutdown_for_previous_conversation5670–5716 ↗
async fn new_session_requests_shutdown_for_previous_conversation()
作用:测试开始新会话前关闭旧会话时,会走 app server 的关闭流程,而不是再往旧的操作通道提交一个 Shutdown 命令。这样旧会话不会被重复关闭或通过错误通道关闭。
数据流:测试先建立一个 App、事件通道和操作通道,再让聊天组件接收一个线程 session。清空已有事件后启动嵌入式 app server,并调用 shutdown_current_thread。最后确认 op_rx 没有收到 Op::Shutdown。
调用关系:它验证 app.shutdown_current_thread 和嵌入式 app server 的配合。make_test_app_with_channels 提供旧式操作通道,start_embedded_app_server_for_picker 提供新的后台关闭路径。
调用图:调用 3 个内部函数(read_only, new, make_test_app_with_channels);外部调用 5 个(pin, new, new, assert!, start_embedded_app_server_for_picker)。
shutdown_first_exit_returns_immediate_exit_when_shutdown_submit_fails5719–5736 ↗
async fn shutdown_first_exit_returns_immediate_exit_when_shutdown_submit_fails()
作用:测试在“先关闭再退出”模式下,如果没有办法提交关闭请求,程序会直接按用户要求退出,而不是卡住等待。这样退出流程更可靠。
数据流:测试创建 App,并设置一个活动线程 ID。启动测试用 app server 后,调用 handle_exit_mode,退出模式是 ShutdownFirst。最后确认没有留下 pending_shutdown_exit_thread_id,并且返回值是用户请求退出。
调用关系:它直接测试 app.handle_exit_mode 在失败路径上的行为。嵌入式 app server 提供调用对象,AppRunControl::Exit 表示主运行循环应该立刻结束。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
shutdown_first_exit_uses_app_server_shutdown_without_submitting_op5739–5760 ↗
async fn shutdown_first_exit_uses_app_server_shutdown_without_submitting_op()
作用:测试“先关闭再退出”会使用 app server 的关闭接口,而不会往传统操作队列里塞 Op::Shutdown。它保证新退出路径不会和旧机制混用。
数据流:测试创建带操作通道的 App,设置活动线程,启动嵌入式 app server,然后调用 handle_exit_mode。结果应当是立即退出,并且操作通道里没有任何 Shutdown 操作。
调用关系:它和前一个测试一起覆盖退出流程。这里额外观察 op_rx,确认 handle_exit_mode 把工作交给 app server,而不是提交给旧的 Op 通道。
调用图:调用 2 个内部函数(new, make_test_app_with_channels);外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
interrupt_without_active_turn_is_treated_as_handled5763–5792 ↗
async fn interrupt_without_active_turn_is_treated_as_handled()
作用:测试没有正在运行的回答时发送 interrupt,也会被当成“已经处理”。这样用户按中断键不会触发错误提示,只是安静地结束这个无事可做的操作。
数据流:测试启动嵌入式 app server,创建并注册一个线程,但不启动任何活跃轮次。然后构造 interrupt 命令,交给 try_submit_active_thread_op_via_app_server。最后确认返回 handled 为 true。
调用关系:它验证 AppCommand::interrupt 通过 app.try_submit_active_thread_op_via_app_server 发送到 app server 时的空闲场景。enqueue_primary_thread_session 负责先把线程登记到 App。
调用图:调用 1 个内部函数(make_test_app);外部调用 4 个(pin, assert_eq!, interrupt, start_embedded_app_server_for_picker)。
override_turn_context_sends_thread_settings_update5795–5929 ↗
async fn override_turn_context_sends_thread_settings_update()
作用:测试临时覆盖本轮上下文时,App 会向后台发送线程设置更新,并在收到后台通知后正确缓存新设置。这里的设置包括审批策略、模型、推理强度、协作模式、人格和权限配置。
数据流:测试先启动 app server 和线程,记录初始模型与推理强度。然后构造 override_turn_context 命令,包含新的审批规则、模型 gpt-5.4、高推理强度、快速服务档位、Plan 协作模式和 Pragmatic 人格。提交后先确认只是收到确认,缓存状态还没立刻改;接着读取后台发出的 ThreadSettingsUpdated 通知,检查通知内容正确;最后把通知交给 App 处理,再确认本地 session 缓存更新了相关设置,同时模型和默认推理字段保持预期。
调用关系:它覆盖了完整链路:AppCommand::override_turn_context 生成命令,app.try_submit_active_thread_op_via_app_server 提交给后台,next_thread_settings_updated 等后台通知,app.handle_app_server_event 再把通知合并回本地缓存。
调用图:调用 3 个内部函数(new, make_test_app, next_thread_settings_updated);外部调用 6 个(pin, ThreadSettingsUpdated, ServerNotification, assert_eq!, override_turn_context, start_embedded_app_server_for_picker)。
thread_setting_update_params_sync_model_and_default_reasoning5932–5980 ↗
async fn thread_setting_update_params_sync_model_and_default_reasoning()
作用:测试当用户修改模型或默认推理强度时,发给活动线程的设置更新参数会同步更新协作模式里的对应字段。这样主模型选择和协作模式不会显示一套、发送另一套。
数据流:测试先设置活动线程 ID,把聊天组件模型改成 gpt-5.4,然后生成模型更新参数,确认参数里的 thread_id、model 和 collaboration_mode.settings.model 都正确。接着设置推理强度和一个 Plan 协作遮罩,再调用 on_update_reasoning_effort 改成 High,生成推理更新参数,确认 effort 是 High,协作模式被同步为 Default 且推理强度也是 High。
调用关系:它直接测试 app.active_thread_model_setting_update_params 和 app.active_thread_reasoning_setting_update_params。ChatWidget 提供当前模型、推理强度和协作遮罩,App 把这些整理成发送给后台的参数。
调用图:调用 2 个内部函数(new, make_test_app);外部调用 1 个(assert_eq!)。
inactive_thread_settings_notification_updates_cached_collaboration_mode5983–6081 ↗
async fn inactive_thread_settings_notification_updates_cached_collaboration_mode()
作用:测试不在当前界面里的线程收到设置更新通知时,App 也会更新它的缓存;等用户切回这个线程,协作模式、推理强度和人格能正确恢复。这样后台更新不会只对当前线程生效。
数据流:测试建立一个主线程和一个非活动线程,并分别放入线程事件通道。然后给非活动线程发送 ThreadSettingsUpdated 通知,里面包含 Plan 协作模式、高推理强度和 Pragmatic 人格。通知入队后,测试读取非活动线程缓存,确认模型保持原 session 的 gpt-test,但人格和协作模式已更新。最后把该缓存 session 交给 ChatWidget,确认界面显示 Plan 模式、当前模型、协作模式模型、推理强度和人格都符合预期。
调用关系:它测试 app.enqueue_thread_notification 对非活动线程的缓存更新。之后 ChatWidget.handle_thread_session 使用这份缓存,说明这些后台通知会影响用户以后切回线程时看到的状态。
调用图:调用 6 个内部函数(read_only, new, make_test_app, test_absolute_path, test_thread_session, new_with_session);外部调用 3 个(ThreadSettingsUpdated, new, assert_eq!)。
clear_only_ui_reset_preserves_chat_session_state6084–6141 ↗
async fn clear_only_ui_reset_preserves_chat_session_state()
作用:测试“只清界面”的重置不会清掉当前聊天会话和输入框内容。也就是说,清屏只是擦黑板,不是换教室。
数据流:测试先给聊天组件设置线程 session 和草稿输入,再放入旧历史、覆盖层、延迟历史、回退状态等界面临时数据。调用 reset_app_ui_state_after_clear 后,检查覆盖层、历史、缓冲行和回退预览都清空了,但 chat_widget 的 thread_id 还在,输入框里仍是 draft prompt。
调用关系:它直接验证 app.reset_app_ui_state_after_clear。测试把 App 的多个 UI 状态手动设成“脏”的,再确认这个函数只清 UI 临时物,不破坏 ChatWidget 持有的会话和草稿。
调用图:调用 5 个内部函数(read_only, new, make_test_app, defaults, new_transcript);外部调用 5 个(new, new, assert!, assert_eq!, vec!)。
clear_only_ui_reset_allows_active_skill_warning_to_render_again6144–6169 ↗
async fn clear_only_ui_reset_allows_active_skill_warning_to_render_again()
作用:测试清界面后,同一个当前仍存在的技能加载警告可以再次显示。否则用户清屏后,重要警告可能因为“已经显示过”而再也看不到。
数据流:测试准备一个 SkillErrorInfo。第一次查询 newly_active_errors 会返回这个错误,第二次因为已记录过而返回空。调用 reset_app_ui_state_after_clear 后,再次查询同一个错误,确认它又会作为新警告返回。
调用关系:它验证 app.reset_app_ui_state_after_clear 会重置 skill_load_warnings 的显示记忆。newly_active_errors 用来模拟界面判断哪些技能错误需要渲染。
调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。
backtrack_esc_does_not_steal_empty_vim_insert_escape6172–6195 ↗
async fn backtrack_esc_does_not_steal_empty_vim_insert_escape()
作用:测试在 Vim 输入模式下,Esc 如果应该用来退出插入模式,就不会被回退功能抢走。这样熟悉 Vim 的用户按 Esc 时,行为符合直觉。
数据流:测试先确认普通空输入框下 Esc 可以触发回退处理。然后打开 Vim 模式并进入插入模式,此时 ChatWidget 判断 Esc 应该退出插入模式,App 的回退 Esc 判断就应返回 false。实际处理 Esc 后,插入模式结束,回退未被启动,之后 Esc 又可以被回退逻辑处理。
调用关系:它测试 app.should_handle_backtrack_esc 和 chat_widget.should_handle_vim_insert_escape 的优先级关系。按键先由 ChatWidget 的 Vim 状态决定是否消费,回退逻辑只能在不抢 Vim Esc 的时候接手。
调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert!, Char, new)。
side_conversations_reject_backtrack_esc_without_stealing_vim_insert_escape6198–6218 ↗
async fn side_conversations_reject_backtrack_esc_without_stealing_vim_insert_escape()
作用:测试在侧边会话里,回退快捷键会被拒绝,但同样不能抢走 Vim 插入模式的 Esc。这样侧边会话既不会支持不该支持的回退,也不会破坏编辑体验。
数据流:测试先把侧边会话标记为活跃,空输入框下 Esc 不应进入回退处理,而应被识别为侧边回退拒绝。随后打开 Vim 模式并进入插入模式,这时 Esc 应优先用于退出插入模式,所以既不处理回退,也不显示侧边回退拒绝。
调用关系:它同时验证 app.should_handle_backtrack_esc 和 app.should_reject_side_backtrack_esc。ChatWidget 的侧边会话状态和 Vim 插入状态共同决定 Esc 最终交给谁。
调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert!, Char, new)。
start_config_write_test_app_server6243–6245 ↗
async fn start_config_write_test_app_server(app: &App) -> Result<AppServerSession>
作用:这是测试里的小工具函数,用当前 App 的配置启动一个嵌入式 app server。它让多条配置写入相关测试不用重复写同一段启动代码。
数据流:它接收一个 App 引用,从里面读取 config,然后调用 start_embedded_app_server_for_picker。成功时返回 AppServerSession,失败时把错误交回调用者。
调用关系:它被多条 feature flag 更新测试复用,比如开启或关闭 guardian 时检查审批策略怎么变化。它本身不做断言,只是把启动测试后台服务这件事包装成一个方便的 helper。
调用图:被 4 处调用(update_feature_flags_disabling_guardian_clears_manual_review_policy_without_history, update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default, update_feature_flags_enabling_guardian_overrides_explicit_manual_review_policy, update_feature_flags_enabling_guardian_selects_auto_review);外部调用 2 个(pin, start_embedded_app_server_for_picker)。
tui/src/app/tests/model_catalog.rs源码 ↗
这个测试文件像一套验收清单,盯着应用启动时和切换模型时几个容易出错的地方。第一类测试检查“新模型可用提示”(NUX,意思是给用户看的首次/新功能提示):哪些模型有提示、提示最多显示几次、多个模型同时符合条件时先显示谁。第二类测试检查“模型迁移提示”:老模型或隐藏模型该不该提示用户升级到新模型,用户已经看过或目标模型不可用时就不该再打扰。第三类测试检查用户接受迁移后,配置里的模型名和推理强度是否更新,并且是否发出保存事件。文件里还有几个小工具函数,用来拿测试模型清单、临时拼配置、把富文本提示转成普通文字,方便测试直接比较结果。
all_model_presets8–10 ↗
fn all_model_presets() -> Vec<ModelPreset>
作用:拿一份测试用的模型预设清单。测试会改这份清单里的字段,所以这里返回克隆出来的新副本,避免一个测试污染另一个测试。
数据流:它不接收参数,只读取项目测试支持代码里准备好的 TEST_MODEL_PRESETS → 复制一份完整列表 → 返回一个新的模型预设数组,后面的测试可以放心改。
调用关系:很多测试一开始都会调用它,先拿到一份“假装真实”的模型目录。之后测试会把这份目录交给选择新模型提示、判断迁移提示、查找升级目标等逻辑。
调用图:被 7 处调用(model_migration_prompt_shows_for_hidden_model, model_migration_prompt_skips_when_target_missing_or_hidden, prepare_startup_tooltip_override_persists_model_availability_nux_count, select_model_availability_nux_picks_only_eligible_model, select_model_availability_nux_returns_none_when_all_models_are_exhausted, select_model_availability_nux_skips_missing_and_exhausted_models, select_model_availability_nux_uses_existing_model_order_as_priority)。
model_availability_nux_config12–19 ↗
fn model_availability_nux_config(shown_count: &[(&str, u32)]) -> ModelAvailabilityNuxConfig
作用:快速造一份“某个新模型提示已经显示过几次”的测试配置。这样测试不用手写一大段配置对象。
数据流:输入是一组模型名和次数,比如 gpt-5.4 显示过 3 次 → 它把模型名转成字符串,把这些键值放进 shown_count 表里 → 输出 ModelAvailabilityNuxConfig,供选择提示的逻辑判断是否还能再显示。
调用关系:几个检查新模型提示的测试会用它来模拟用户历史记录。它把简单的测试数据包装成正式配置,再交给 select_model_availability_nux 相关逻辑。
调用图:被 4 处调用(select_model_availability_nux_picks_only_eligible_model, select_model_availability_nux_returns_none_when_all_models_are_exhausted, select_model_availability_nux_skips_missing_and_exhausted_models, select_model_availability_nux_uses_existing_model_order_as_priority)。
model_migration_copy_to_plain_text21–38 ↗
fn model_migration_copy_to_plain_text(copy: &crate::model_migration::ModelMigrationCopy) -> String
作用:把模型迁移提示里的富文本内容转成普通文字,方便测试做快照比较。富文本就是带标题、段落、样式片段的文字;普通文字更容易看差异。
数据流:输入是一份 ModelMigrationCopy,也就是迁移提示文案 → 如果里面已经有 markdown 文本,就直接返回;否则把标题片段和正文每一行的文字片段按顺序拼起来 → 输出一整段普通字符串,不改原对象。
调用关系:隐藏模型迁移提示的测试会用它把生成出来的提示文案压平成文字,然后交给快照断言。它内部只新建字符串并拼接内容,不参与真正的界面显示。
调用图:外部调用 1 个(new)。
model_migration_prompt_only_shows_for_deprecated_models41–61 ↗
async fn model_migration_prompt_only_shows_for_deprecated_models()
作用:确认只有需要迁移的旧模型才会弹升级提示。当前模型已经是目标模型时,不应该再提示用户升级到自己。
数据流:它准备一份空的“已看过迁移提示”记录和测试模型清单 → 分别询问几个当前模型到目标模型的组合是否该显示迁移提示 → 用断言确认旧模型会显示、自身到自身不会显示。
调用关系:这是模型迁移判断逻辑的基础测试。它直接验证 should_show_model_migration_prompt 的决定是否符合产品预期,依赖 all_model_presets 提供完整模型目录。
select_model_availability_nux_picks_only_eligible_model64–86 ↗
fn select_model_availability_nux_picks_only_eligible_model()
作用:确认当只有一个模型配置了“新模型可用提示”时,系统会选中它。这样启动时不会漏掉该给用户看的提示。
数据流:它先拿到测试模型清单 → 清空所有模型的提示配置,只给 gpt-5.4 填一条提示 → 构造一份没有历史显示次数的配置 → 调用选择逻辑 → 检查返回的提示模型和提示文字正好是 gpt-5.4。
调用关系:这个测试把 all_model_presets 和 model_availability_nux_config 作为准备工具,然后验证 select_model_availability_nux 的最简单成功路径。
调用图:调用 2 个内部函数(all_model_presets, model_availability_nux_config);外部调用 1 个(assert_eq!)。
select_model_availability_nux_skips_missing_and_exhausted_models89–121 ↗
fn select_model_availability_nux_skips_missing_and_exhausted_models()
作用:确认已经显示到上限的模型会被跳过,系统会继续找下一个还能显示的新模型提示。这样用户不会被同一条提示反复打扰。
数据流:它准备两个带提示的模型:gpt-5.4 和 gpt-5.4-mini → 在配置里标记 gpt-5.4 已经达到最大显示次数 → 调用选择逻辑 → 结果应该跳过 gpt-5.4,返回 gpt-5.4-mini 的提示。
调用关系:这个测试用 model_availability_nux_config 模拟“某提示已经用完额度”。它检查 select_model_availability_nux 不只是找第一个有提示的模型,还会参考显示次数。
调用图:调用 2 个内部函数(all_model_presets, model_availability_nux_config);外部调用 1 个(assert_eq!)。
select_model_availability_nux_uses_existing_model_order_as_priority124–153 ↗
fn select_model_availability_nux_uses_existing_model_order_as_priority()
作用:确认多个模型都能显示提示时,优先级来自现有模型列表的顺序。这样产品方只要调整模型清单顺序,就能控制先提示哪个。
数据流:它拿到测试模型清单 → 清掉所有提示,再给 gpt-5.4-mini 和 gpt-5.4 都加提示 → 用空历史配置调用选择逻辑 → 检查最终选中的是模型清单顺序里优先的那个提示。
调用关系:这个测试仍然围绕 select_model_availability_nux。all_model_presets 提供带顺序的模型表,model_availability_nux_config 提供空的显示历史,断言用来锁住优先级规则。
调用图:调用 2 个内部函数(all_model_presets, model_availability_nux_config);外部调用 1 个(assert_eq!)。
select_model_availability_nux_returns_none_when_all_models_are_exhausted156–175 ↗
fn select_model_availability_nux_returns_none_when_all_models_are_exhausted()
作用:确认所有可提示的模型都已经达到显示上限时,不会再返回任何启动提示。也就是说,没内容可说时就安静。
数据流:它只给 gpt-5.4 设置一条可用提示 → 配置里标记这条提示已经显示到最大次数 → 调用选择逻辑 → 输出应该是 None,表示没有要展示的提示。
调用关系:这是新模型提示选择逻辑的收尾场景测试。它通过 all_model_presets 准备模型,通过 model_availability_nux_config 模拟显示次数已耗尽。
调用图:调用 2 个内部函数(all_model_presets, model_availability_nux_config);外部调用 1 个(assert_eq!)。
prepare_startup_tooltip_override_persists_model_availability_nux_count178–215 ↗
async fn prepare_startup_tooltip_override_persists_model_availability_nux_count()
作用:确认启动时如果展示了新模型提示,显示次数会立刻写进配置文件。这样下次启动时系统知道这条提示已经出现过,不会无限重复。
数据流:它创建一个临时配置目录 → 加载一份配置 → 准备只有 gpt-5.4 带提示的模型清单 → 调用 prepare_startup_tooltip_override → 检查返回给界面的提示文字,同时检查内存里的显示次数变成 1 → 再重新加载配置,确认磁盘上的配置也保存了这个次数。
调用关系:这个测试连接了提示选择逻辑和配置持久化逻辑。它用 all_model_presets 准备数据,然后通过 ConfigBuilder 重新加载配置,验证 prepare_startup_tooltip_override 不只是返回文字,还真的把次数存下来了。
调用图:调用 1 个内部函数(all_model_presets);外部调用 2 个(assert_eq!, default)。
accepted_model_migration_persists_target_default_reasoning_effort218–270 ↗
async fn accepted_model_migration_persists_target_default_reasoning_effort()
作用:确认用户接受模型迁移后,默认模型和默认推理强度都会改成目标模型要求的值,并且相关保存事件都会发出去。推理强度可以理解为模型回答前“多用多少脑力”。
数据流:它创建临时配置,把当前模型设成 gpt-5.2,推理强度设成 XHigh → 建一个事件通道,像邮箱一样接收应用内部通知 → 调用 apply_accepted_model_migration,把目标设为 gpt-5.4,目标推理强度设为 Medium → 检查配置已经改好 → 再从通道里逐个取事件,确认包括“已确认迁移提示”“更新模型”“更新推理强度”“保存模型选择”。
调用关系:这个测试验证接受迁移这一动作的完整后果。它使用 unbounded_channel 建立测试用事件通道,用 AppEventSender 发事件,再用断言检查 apply_accepted_model_migration 是否把配置更新和界面/保存通知都安排好了。
调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, assert_matches!, default, unbounded_channel)。
model_migration_prompt_respects_hide_flag_and_self_target273–288 ↗
async fn model_migration_prompt_respects_hide_flag_and_self_target()
作用:确认迁移提示会尊重“已经看过/不再显示”的记录,也不会在当前模型和目标模型相同的时候弹出。这样用户不会被无意义提示骚扰。
数据流:它准备一份已看过记录,表示 gpt-5.2 到 gpt-5.4 的提示已经处理过 → 调用迁移提示判断逻辑 → 检查这种情况不显示;再测试 gpt-5.4 到 gpt-5.4 → 也应该不显示。
调用关系:这个测试直接约束 should_show_model_migration_prompt 的两个拦截条件:已隐藏提示、自我迁移。它用空或简单的记录表模拟用户历史。
tui/src/app/tests/session_summary.rs源码 ↗
这个测试文件专门盯着 session_summary 这个功能:聊天或任务结束后,界面要不要显示一段总结。总结里可能有两类信息:一类是 token 用量,也就是模型读写文字大概花了多少“额度”;另一类是恢复提示,告诉用户以后可以用什么命令继续这个会话。这里测试了几个容易出错的边界:如果完全没有用量、也没有可恢复的会话,就不该显示摘要;如果有会话编号,但对应的记录文件还没真正写到磁盘,也不该提前提示恢复;只有记录文件已经存在时,才给出恢复命令。最后还检查一种更友好的情况:如果会话有名字,提示语会告诉用户在恢复列表里选哪个名字,而不是只丢一个编号给用户。
session_summary_skips_when_no_usage_or_resume_hint5–15 ↗
async fn session_summary_skips_when_no_usage_or_resume_hint()
作用:这个测试确认:当既没有 token 用量,也没有可恢复会话信息时,程序应该什么摘要都不显示。这样用户不会看到一段空洞或误导性的结束提示。
数据流:进去的是默认的 TokenUsage,也就是各项用量都是 0;会话编号、会话名、记录文件路径也都没有。测试把这些交给 session_summary,然后检查结果是 None,意思是“没有摘要可显示”。它不改动文件,也不创建任何会话数据。
调用关系:这是 session_summary 最基础的空输入检查。测试本身只用断言来确认结果为空,用来守住一个底线:没有实质内容时,界面层不应该硬凑一条总结。
调用图:外部调用 1 个(assert!)。
session_summary_skips_resume_hint_until_rollout_exists18–33 ↗
async fn session_summary_skips_resume_hint_until_rollout_exists()
作用:这个测试确认:光有会话编号还不够,只有会话记录文件真的存在后,才可以提示用户恢复。这样可以避免用户按提示恢复时却发现没有东西可恢复。
数据流:进去的是零用量的 TokenUsage、一个固定格式的会话编号、没有会话名,以及一个临时目录里的 rollout.jsonl 路径。测试故意不创建这个文件,然后调用 session_summary。出来的结果应该还是 None,表示程序不会因为只有编号就贸然显示恢复命令。
调用关系:它接在空输入测试之后,检查另一个边界:有恢复线索但还没落盘。这里用 ThreadId::from_string 把字符串变成会话编号,用默认用量制造“没有 token 消耗”的场景,最后用断言确认 session_summary 没有生成摘要。
调用图:调用 1 个内部函数(from_string);外部调用 2 个(assert!, default)。
session_summary_includes_resume_hint_for_persisted_rollout36–63 ↗
async fn session_summary_includes_resume_hint_for_persisted_rollout()
作用:这个测试确认:当确实有 token 用量,并且会话记录文件已经存在时,摘要里应该同时显示用量和恢复命令。它保证用户结束后能看到花了多少额度,也知道下次怎么继续。
数据流:进去的是一份明确的用量数据:输入 10、输出 2、总计 12;还有一个会话编号和一个临时记录文件路径。测试先往这个路径写入一行内容,模拟会话已经保存到磁盘。然后调用 session_summary,出来的 summary 应该包含固定格式的用量行,以及 codex resume 加会话编号的恢复提示。
调用关系:这是对正常成功路径的测试。它用文件写入来制造“会话已经持久化”的条件,再用 assert_eq! 精确比较 session_summary 生成的文字,防止以后有人改坏提示格式或漏掉 token 用量。
调用图:调用 1 个内部函数(from_string);外部调用 3 个(default, assert_eq!, write)。
session_summary_names_picker_item_when_thread_has_name66–92 ↗
async fn session_summary_names_picker_item_when_thread_has_name()
作用:这个测试确认:如果会话有名字,恢复提示应该更像给人看的说明,告诉用户恢复后选择这个名字对应的项目。这样比只显示一串长编号更友好。
数据流:进去的是同样的 token 用量、同一个会话编号、一个会话名 my-session,以及已经写好的临时记录文件。session_summary 处理后会返回摘要。测试重点检查 resume_hint:它不再是简单的 codex resume 加编号,而是提示用户运行 codex resume 后选择 my-session,并附上编号用于确认。
调用关系:它补充了有会话名时的分支。和前一个测试一样,它先写入记录文件,确保恢复条件成立;然后用 assert_eq! 锁定用户将看到的恢复提示文字,保证命名会话在选择器里的提示不会退化成难懂的编号。
调用图:调用 1 个内部函数(from_string);外部调用 3 个(default, assert_eq!, write)。
tui/src/app/tests/startup.rs源码 ↗
这个文件不写正式功能,而是专门测试应用启动时的几个关键判断。这里的“会话”可以理解成一次聊天工作现场,“线程”就是这个现场的编号。应用刚启动时,有时要先等主线程准备好,不能马上处理后台事件;但如果是恢复已有会话或从已有会话分叉,就不应该再傻等。文件还检查一个细节:用户在启动没完成前按下回车输入的话,要先排队,等新线程真正建好后再自动提交,不能丢。另一个重点是失败和过期消息:如果新线程启动失败,要明确报错;如果收到一个已经不是当前目标的旧线程启动结果,要清掉本地残留的路由状态。最后,它还测试“重复恢复当前正在看的会话”时,应用应该提示用户已经在看了,而不是重复打开。
startup_waiting_gate_is_only_for_fresh_or_exit_session_selection8–35 ↗
fn startup_waiting_gate_is_only_for_fresh_or_exit_session_selection()
作用:这个测试确认:只有“新开会话”和“退出”这两种启动选择需要进入等待门。恢复旧会话或分叉旧会话时,不应该被这个等待机制挡住。
数据流:进去的是四种不同的启动选择:从头开始、退出、恢复、分叉。测试把它们交给 App::should_wait_for_initial_session 这个判断函数,看它返回 true 还是 false。出来的结果必须是:新开和退出要等,恢复和分叉不用等;否则说明启动流程会在错误场景下卡住。
调用关系:它是在测试启动前置判断的最小规则。后面的测试会继续验证这个等待门一旦打开或关闭,会不会影响线程事件处理;这个测试先保证门本身只在该出现的时候出现。
调用图:外部调用 1 个(assert_eq!)。
startup_paused_goal_prompt_gate_is_only_for_quiet_resume38–71 ↗
fn startup_paused_goal_prompt_gate_is_only_for_quiet_resume()
作用:这个测试确认:只有“安静地恢复旧会话”时,应用才需要追问用户接下来要做什么。所谓安静,是指没有预填文字,也没有启动时带入图片。
数据流:进去的是恢复或分叉的目标会话、可选的初始文字、以及启动时带入的图片列表。测试把这些组合交给 App::should_prompt_for_paused_goal_after_startup_resume。出来的结果必须是:恢复旧会话且没有文字、没有图片时才提示;只要已有文字、已有图片、不是恢复而是新开或分叉,就不提示。
调用关系:它测试的是启动后是否弹出“下一步目标”提示的开关。这个判断和等待线程不是一回事,但同样发生在应用刚进入会话时,用来避免没必要的打扰。
调用图:调用 1 个内部函数(new);外部调用 6 个(from, new, assert!, Fork, Resume, vec!)。
startup_waiting_gate_holds_active_thread_events_until_primary_thread_configured74–106 ↗
fn startup_waiting_gate_holds_active_thread_events_until_primary_thread_configured()
作用:这个测试确认:新开会话时,在主线程编号还没确定前,应用不能处理活跃线程事件。这样可以防止后台事件先跑进来,导致消息挂到错误线程上。
数据流:一开始输入的是“从头开始”的启动选择,得到 wait_for_initial_session 为 true。接着测试有事件接收器时是否能处理事件,结果应为 false。然后它分别用“没有主线程编号”和“有主线程编号”去问是否该停止等待:没有编号不能停,有编号才能停。最后等待状态变成 false 后,再检查线程事件可以被处理。
调用关系:它把前面测试过的等待门放进更完整的小流程里:先挡住事件,再等主线程配置好,最后放行事件。这里会调用 should_wait_for_initial_session、should_stop_waiting_for_initial_session 和 should_handle_active_thread_events 这几个启动判断函数。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_eq!, should_stop_waiting_for_initial_session, should_wait_for_initial_session)。
startup_waiting_gate_not_applied_for_resume_or_fork_session_selection109–136 ↗
fn startup_waiting_gate_not_applied_for_resume_or_fork_session_selection()
作用:这个测试确认:恢复旧会话和分叉会话时,不应该套用“等待新主线程”的规则。因为这两种情况已经有目标线程,不需要像新会话那样等编号生成。
数据流:进去的是一个恢复目标和一个分叉目标,各自带有路径和线程编号。测试先问 App::should_wait_for_initial_session 是否需要等待,再把结果交给 App::should_handle_active_thread_events。出来的结果必须是:两种情况下都允许处理活跃线程事件。
调用关系:它补足了等待门规则的反面场景。前一个测试证明新开会话需要先挡住事件;这个测试证明恢复和分叉不能被误挡。
调用图:调用 1 个内部函数(new);外部调用 5 个(from, assert_eq!, should_wait_for_initial_session, Fork, Resume)。
startup_thread_started_submits_queued_startup_input139–180 ↗
async fn startup_thread_started_submits_queued_startup_input()
作用:这个测试确认:用户在启动线程还没准备好时输入并提交的文字,不会丢掉。等线程真正启动成功后,这段文字应该自动作为用户消息发出去。
数据流:进去的是一个测试用应用、事件通道和操作通道。测试先把应用标成“正在等待启动线程”,再让聊天输入框进入排队模式,然后塞入一段文字并模拟按回车。此时文字应留在队列里。接着启动一个嵌入式应用服务器,假装新线程成功创建。处理完成后,操作通道里应该出现一次用户回合,内容正是之前排队的那段文字。
调用关系:它测试 App::handle_startup_thread_started 成功路径上的重要副作用:线程接上以后,要把 chat_widget 里暂存的用户输入提交出去。这个测试通过 next_user_turn_op 从输出通道里取结果,验证应用真的把活交给了后续执行流程。
调用图:调用 1 个内部函数(new);外部调用 6 个(pin, new, new, assert_eq!, start_embedded_app_server_for_picker, panic!)。
startup_thread_start_failure_returns_error183–203 ↗
async fn startup_thread_start_failure_returns_error()
作用:这个测试确认:新线程启动失败时,应用要明确返回错误,并清掉“还在启动中”的标记。这样用户不会留在一个看似可用、其实没有会话的聊天界面里。
数据流:进去的是一个处于 pending_startup_thread_start 状态的测试应用,以及一个模拟的启动失败结果 Err("boom")。测试把失败结果交给 handle_startup_thread_started。出来的应该是错误信息,里面说明通过应用服务器启动新会话失败;同时 pending 标记变成 false,primary_thread_id 仍然是 None。
调用关系:它覆盖的是 App::handle_startup_thread_started 的失败分支。和成功提交排队输入的测试相对,这个测试保证失败不会被悄悄吞掉,也不会留下半配置状态。
调用图:外部调用 4 个(pin, assert!, assert_eq!, start_embedded_app_server_for_picker)。
stale_startup_thread_started_removes_local_routing_state206–251 ↗
fn stale_startup_thread_started_removes_local_routing_state() -> Result<()>
作用:这个测试确认:如果启动线程的结果已经过时,应用要把这个旧线程的本地状态清掉。否则界面里可能残留一个不该出现的代理或事件通道,后面就容易串线。
数据流:进去的是一个已经有主线程的应用。测试先人为放入两个线程:一个当前主线程,一个过期线程;还给过期线程放入事件通道和导航条目。然后它把“过期线程启动成功”的结果交给 handle_startup_thread_started。出来的状态应该是:过期线程的事件通道被移除,导航记录被删除,当前活跃线程仍然是原来的主线程。
调用关系:它测试的是启动回调到达太晚时的清理逻辑。这里会启动测试运行时和嵌入式应用服务器,再调用 handle_startup_thread_started,确认这个函数不会把旧结果误接成当前会话。
调用图:调用 2 个内部函数(new, new);外部调用 5 个(new, assert!, assert_eq!, start_embedded_app_server_for_picker, new_multi_thread)。
ignore_same_thread_resume_reports_noop_for_current_thread254–281 ↗
async fn ignore_same_thread_resume_reports_noop_for_current_thread()
作用:这个测试确认:如果用户想恢复的会话,其实就是当前正在看的会话,应用应该什么都不重开,只给用户一条提示。这个行为叫 noop,也就是“没有实际操作”。
数据流:进去的是一个已经显示某个线程会话的应用,并且这个线程也被设为当前活跃线程。测试清空之前的界面事件后,调用 app.ignore_same_thread_resume,传入同一个线程编号和路径。出来的结果应为 true,表示已忽略;事件通道里还应出现一条历史提示,内容类似“已经在查看这个项目”。
调用关系:它测试恢复会话前的防重复检查。ignore_same_thread_resume 被用来拦住“恢复当前会话”这种没必要的操作,并把说明文字交给界面历史区显示。
调用图:调用 2 个内部函数(new, new_with_session);外部调用 3 个(new, assert!, panic!)。
ignore_same_thread_resume_allows_reattaching_displayed_inactive_thread284–297 ↗
async fn ignore_same_thread_resume_allows_reattaching_displayed_inactive_thread()
作用:这个测试确认:如果一个会话虽然显示在聊天框里,但并不是当前活跃线程,应用仍然允许重新挂接它。也就是说,不能只看画面上像不像同一个会话,还要看它是不是当前真正活跃的线程。
数据流:进去的是一个测试应用和一个线程会话。测试把这个会话交给聊天框显示,但没有把它注册成活跃线程。然后调用 ignore_same_thread_resume,传入同一个线程目标。出来的结果应为 false,表示不能忽略,后续应该允许重新连接;同时不会往历史记录里塞提示消息。
调用关系:它补充了前一个测试的边界情况。前一个测试说“当前活跃线程相同就忽略”,这个测试说明“只是显示相同但未激活”不能忽略,避免应用错过真正需要重新挂接的场景。
tui/src/app/agent_status_feed_tests.rs源码 ↗
这个测试文件像是给 TUI(终端用户界面,也就是在命令行里显示的界面)做体检。它先伪造一个子代理的事件记录,比如执行了命令、发了消息、产生了推理内容,然后把这些记录交给状态预览组件渲染成屏幕上的文字。重点有两个:第一,命令执行可能带有非常长的输出,如果直接显示,界面会被刷爆,所以这里只应该显示命令行和简短活动,不显示完整输出;第二,模型推理可能包含不该直接给用户看的原始内容,所以界面只能显示安全的 summary(摘要),没有摘要时就不显示原始推理。测试用快照断言,也就是把渲染结果和一份固定样子对比,防止以后改代码时不小心把敏感或庞大的内容显示出来。
agent_status_uses_bounded_buffered_activity8–61 ↗
fn agent_status_uses_bounded_buffered_activity()
作用:这个测试确认子代理状态界面不会显示命令执行产生的超长输出。它只应该展示“运行了什么命令”和后续简短消息,避免终端界面被大量日志淹没。
数据流:进去的是一组伪造的事件:一个已完成的命令执行事件,里面故意放了大量重复的输出;再加上一条代理消息。测试把这些事件放进 ThreadEventStore(一块有容量限制的事件存储区),再用 AgentStatusThreadPreview 把事件整理成预览,最后用 AgentStatusHistoryCell 渲染成文字。出来的是一段界面文本;测试检查它和预期快照一致,并确认里面不包含“unbounded output”这段超长输出内容。
调用关系:这个函数由测试框架在跑测试时调用。它先用 ThreadEventStore::new 准备事件容器,再通过 ServerNotification::ItemCompleted 放入完成通知,用 AbsolutePathBuf::try_from 准备命令所在目录,然后交给 AgentStatusThreadPreview::from_store 和 AgentStatusHistoryCell::new 生成可显示内容。最后它用 insta::assert_snapshot! 对比界面样子,并用 assert! 再补一刀,确保超长输出没有混进去。
调用图:调用 4 个内部函数(new, from_store, new, try_from);外部调用 5 个(ItemCompleted, new, assert!, assert_snapshot!, vec!)。
agent_status_uses_reasoning_summaries_only64–109 ↗
fn agent_status_uses_reasoning_summaries_only()
作用:这个测试确认子代理状态界面只显示推理摘要,不显示原始推理内容。这样可以避免把内部思考过程或不适合展示的信息泄露到界面上。
数据流:进去的是两个伪造的推理事件:第一个有安全摘要,也有隐藏的原始推理;第二个没有摘要,只有原始推理。测试把它们放进事件存储区,再生成子代理预览并渲染成文字。出来的结果应该只包含“safe summary”这条摘要;测试还明确检查文本里没有“hidden raw reasoning”和“raw-only reasoning”。
调用关系:这个函数同样由测试框架自动运行。它用 ThreadEventStore::new 建立事件记录,用 ServerNotification::ItemCompleted 塞入推理完成事件,然后让 AgentStatusThreadPreview::from_store 从记录里提取可展示内容,再由 AgentStatusHistoryCell::new 变成界面单元。最后通过 insta::assert_snapshot! 固定正确显示效果,并通过 assert! 确认原始推理没有被显示出来。
调用图:调用 3 个内部函数(new, from_store, new);外部调用 5 个(ItemCompleted, new, assert!, assert_snapshot!, vec!)。
tui/src/app/history_ui_tests.rs源码 ↗
这个文件是界面快照测试。所谓“快照测试”,可以理解成先给界面拍一张标准照片,以后每次改代码都拿新照片和旧照片比一比。这里测试的是历史记录里的两类消息:桌面线程成功打开的提示,以及打开失败的错误提示。测试会先造出一个历史记录单元格,也就是界面里一条消息;再用固定宽度 80 把它排版成多行文字;最后交给 insta 的快照断言来比对。如果文字、换行、错误提示格式等发生变化,测试就会提醒开发者确认:这是有意修改,还是无意破坏。它不负责真正打开桌面线程,只关心这些事件在终端历史区里显示出来是否稳定、可读。
desktop_thread_opened_history_snapshot6–13 ↗
fn desktop_thread_opened_history_snapshot()
作用:这个测试检查“桌面线程已打开”这条普通提示在历史记录里显示得是否正确。有人改了提示文字或排版时,它会第一时间暴露变化。
数据流:进去的是固定的成功提示文字 DESKTOP_THREAD_OPENED_MESSAGE,并且没有额外提示 hint → 它调用 new_info_event 造出一条普通信息类历史消息,再把这条消息渲染成文本 → 最后 assert_snapshot! 把渲染结果和名为 desktop_thread_opened_history 的标准快照对比,不直接返回业务结果,只通过测试通过或失败来说明显示是否一致。
调用关系:它是测试框架运行时直接调用的测试用例。它把造消息的工作交给 new_info_event,把最终对比工作交给 insta 的 assert_snapshot!;中间依赖 render_cell 把历史消息变成可比较的纯文本。
调用图:外部调用 2 个(new_info_event, assert_snapshot!)。
desktop_thread_open_error_history_snapshot16–20 ↗
fn desktop_thread_open_error_history_snapshot()
作用:这个测试检查“桌面线程打开失败”这类错误消息的显示效果。它保证错误原因,比如 launch failed,会被放进历史记录里并按预期格式显示。
数据流:进去的是一段模拟失败原因 launch failed → 它先通过 desktop_thread_open_error_message 形成完整错误文案,再用 new_error_event 包成一条错误类历史消息,然后渲染成多行文本 → assert_snapshot! 把结果和 desktop_thread_open_error_history 这个标准快照比较,测试失败就表示错误显示样式变了。
调用关系:它是另一个由测试框架启动的快照测试,专门覆盖失败场景。它依赖错误消息构造函数和 new_error_event 准备测试对象,再把比对交给 insta 的 assert_snapshot!,用来补足成功提示测试没有覆盖到的错误显示路径。
调用图:外部调用 2 个(new_error_event, assert_snapshot!)。
render_cell22–29 ↗
fn render_cell(cell: &impl HistoryCell) -> String
作用:这个小工具函数把一条历史记录消息变成普通字符串,方便快照测试比较。它相当于把界面上的一格内容“抄成文本”。
数据流:进去的是一个实现了 HistoryCell 的对象,也就是一条能在历史区显示的消息 → 它要求这个对象按宽度 80 生成显示用的多行内容,然后把每一行转成字符串 → 最后用换行符拼成一个完整文本返回,不修改原来的消息对象。
调用关系:它服务于本文件里的快照测试,是测试和真实显示逻辑之间的桥。具体怎么按宽度排版,它不自己决定,而是交给 cell.display_lines;它只负责把排版结果整理成 assert_snapshot! 能比较的一整段文字。
调用图:外部调用 1 个(display_lines)。
tui/src/config_update_tests.rs源码 ↗
这个文件不负责真正修改配置,而是像“验收清单”一样,检查几个容易出错的小规则。第一,应用的编号里如果带点号,比如 plugin.linear,写进配置路径时必须加引号,否则系统可能把它误会成多层路径。第二,给某个项目设为“可信”时,应该精确写到这个项目自己的 trust_level 字段,而不是别的地方。第三,当服务器拒绝某个配置改动时,界面包装错误信息后,仍然要保留服务器给出的具体原因。这里用 assert_eq!(断言相等,也就是测试两个结果是否完全一样)来确认实际结果和预期结果一字不差。
app_scoped_key_path_quotes_dotted_app_ids7–12 ↗
fn app_scoped_key_path_quotes_dotted_app_ids()
作用:这个测试确认:当应用 ID 里带点号时,生成配置路径时会把整个应用 ID 用引号包起来。这样可以避免把 plugin.linear 错当成 plugin 下面的 linear。
数据流:进去的是应用 ID“plugin.linear”和配置项名“enabled” → 测试调用生成应用专属配置路径的函数 → 出来应该是 apps."plugin.linear".enabled;如果结果不是这个字符串,测试就失败。
调用关系:它直接检查 app_scoped_key_path 这个配置路径拼装工具的行为,并用 assert_eq! 做最终比对。它不参与程序运行时流程,只在测试阶段防止以后有人改代码时破坏这个路径规则。
调用图:外部调用 1 个(assert_eq!)。
trusted_project_edit_targets_project_trust_level15–24 ↗
fn trusted_project_edit_targets_project_trust_level()
作用:这个测试确认:把一个项目标记为可信时,程序生成的配置修改命令会指向这个项目的 trust_level 字段,并把值设成 trusted。
数据流:进去的是项目路径 /workspace/team.project → 测试调用 trusted_project_edit 生成一条配置编辑请求 → 出来应该是一份 ConfigEdit:键路径是 projects."/workspace/team.project".trust_level,值是 trusted,合并方式是 Replace(替换旧值)。
调用关系:它检查 trusted_project_edit 这个生成配置编辑内容的函数,并用 assert_eq! 对比完整结构。这个测试保护的是“信任项目”这类界面操作背后的落点,避免它写到错误的配置字段。
调用图:外部调用 1 个(assert_eq!)。
format_config_error_preserves_server_validation_message27–40 ↗
fn format_config_error_preserves_server_validation_message()
作用:这个测试确认:配置写入失败时,即使界面又包了一层错误说明,最终展示出来的文字仍然包含服务器给出的详细拒绝原因。
数据流:进去的是一个人为造出的错误:里面包含服务器说的“某个配置违反托管要求” → 测试再给它包上一层“TUI 中写配置失败”的上下文 → 调用 format_config_error 格式化错误 → 出来的字符串必须同时保留外层说明和内层服务器验证消息。
调用关系:它用 eyre! 构造测试用错误,再用 assert_eq! 检查 format_config_error 的输出。它保护的是错误展示链路:当配置服务拒绝请求时,用户不能只看到笼统失败,而要看到真正原因。
调用图:外部调用 2 个(assert_eq!, eyre!)。
tui/src/external_agent_config_migration_flow_tests.rs源码 ↗
这个文件关心的不是程序怎么迁移配置,而是迁移过程中显示给用户看的文字是否稳定。可以把它想成给公告栏拍一张标准照片:以后每次改代码,都拿新照片和旧照片比一比,看看有没有意外变化。测试里先准备了几种成功迁移的数量情况,比如 0、1、2 个项目;再加上迁移完成、没有项目、远端不可用、后台服务不可用、导入正在进行等固定提示。然后把这些文字用换行拼成一整段,交给 insta 的快照测试。快照测试就是“把当前结果和已经认可的样本比较”的测试方式。如果文案确实要改,开发者需要主动更新快照;如果不是故意改的,测试就会提醒大家。
external_agent_config_migration_messages_snapshot4–21 ↗
fn external_agent_config_migration_messages_snapshot()
作用:这个测试函数检查外部代理配置迁移相关的所有用户提示文案是否和预期一致。有人改到这些文字时,它能提醒开发者:这是有意修改,还是不小心改坏了。
数据流:进去的是几种测试数量 0、1、2,以及当前文件上层提供的一组迁移提示文字常量和生成消息的函数。它把成功迁移消息和各种固定状态消息合并成一个文本块,中间用换行隔开。出来的是一次快照断言结果:如果文本和保存的标准快照一致,测试通过;如果不一致,测试失败并提示差异。
调用关系:这个函数在测试运行时由 Rust 的测试框架自动调用。它自己负责拼出要检查的完整文案,然后把最后的比对工作交给 insta::assert_snapshot! 这个快照测试宏;这个宏会拿当前文本和名为 external_agent_config_migration_messages 的已保存快照做比较。
调用图:外部调用 1 个(assert_snapshot!)。
tui/src/local_chatgpt_auth.rs源码 ↗
这个文件像一个“门卫”,专门在测试里检查本地保存的登录信息能不能当作 ChatGPT 登录来用。它会从指定的 Codex 主目录里读取 auth.json,也就是本机保存的认证文件;如果里面是 API Key 登录,或者根本没有登录信息,就直接报错。读到 ChatGPT 登录后,它会拿出访问令牌、ChatGPT 账号/工作区 ID,以及套餐类型。工作区 ID 很重要,因为有些测试要求只能使用指定工作区,拿错账号就像拿别人的门禁卡进公司。文件后半部分是测试:先造假的 JWT(JSON Web Token,一种带用户信息的登录票据),写入临时认证文件,再验证各种正常和异常场景。
load_local_chatgpt_auth17–59 ↗
fn load_local_chatgpt_auth(
codex_home: &Path,
auth_credentials_store_mode: AuthCredentialsStoreMode,
forced_chatgpt_workspace_id: Option<&[String]>,
) -> Result<LocalChatgptAuth, String>
作用:读取本机保存的 ChatGPT 登录信息,并把测试真正需要的几项内容取出来。有人会用它来确认当前测试环境确实有可用的 ChatGPT 登录,而不是误用了 API Key 或错误工作区。
数据流:输入是 Codex 主目录路径、凭证保存方式,以及可选的允许工作区列表。它先读取本地 auth.json;如果没有文件、读取失败、是 API Key 登录、缺少 token,都会变成清楚的错误信息。成功时,它取出 access token、账号/工作区 ID,并把套餐类型转成小写,最后返回一个 LocalChatgptAuth;它不修改文件,只读取和校验。
调用关系:这是本文件的核心函数,被多个测试直接调用。正常路径由 tests::loads_local_chatgpt_auth_from_managed_auth、tests::prefers_managed_auth_over_external_ephemeral_tokens、tests::preserves_usage_based_plan_type_wire_name 检查;失败路径由 tests::rejects_missing_local_auth 和 tests::rejects_api_key_auth 检查。它把实际读文件的工作交给外部的 load_auth_dot_json。
调用图:调用 1 个内部函数(default);被 5 处调用(loads_local_chatgpt_auth_from_managed_auth, prefers_managed_auth_over_external_ephemeral_tokens, preserves_usage_based_plan_type_wire_name, rejects_api_key_auth, rejects_missing_local_auth);外部调用 3 个(load_auth_dot_json, format!, matches!)。
tests::fake_jwt77–100 ↗
fn fake_jwt(email: &str, account_id: &str, plan_type: &str) -> String
作用:生成一个假的 JWT 登录票据,方便测试不用真的去登录 ChatGPT。它把邮箱、工作区 ID、套餐类型塞进票据里,让后面的读取逻辑有东西可解析。
数据流:输入是邮箱、账号/工作区 ID 和套餐类型。它组装一个 JWT 头部和一段 JSON 载荷,把它们转成 JSON 字节,再用 base64 URL 安全格式编码,最后拼成“头部.载荷.签名”这样的字符串。输出是一串假的 token;它不写文件。
调用关系:它是测试里的造数据工具。tests::write_chatgpt_auth 用它来生成 id token 和 access token;tests::prefers_managed_auth_over_external_ephemeral_tokens 也用它制造另一套外部临时 token,用来测试选择规则。
tests::write_chatgpt_auth102–127 ↗
fn write_chatgpt_auth(codex_home: &Path, plan_type: &str)
作用:往临时目录里写一份看起来像真实 ChatGPT 登录的认证文件。测试用它先搭好“已经登录”的现场,再调用真正的读取函数。
数据流:输入是 Codex 主目录路径和套餐类型。它先用 tests::fake_jwt 造出 id token 和 access token,再解析 id token 里的声明,组装 AuthDotJson 认证对象,填入工作区 ID、刷新 token 和当前时间,最后用 save_auth 保存到文件。结果是磁盘上多了一份测试用的 ChatGPT 登录记录。
调用关系:它是多个成功路径测试的准备步骤。tests::loads_local_chatgpt_auth_from_managed_auth、tests::prefers_managed_auth_over_external_ephemeral_tokens、tests::preserves_usage_based_plan_type_wire_name 都先调用它写入认证信息,再交给 load_local_chatgpt_auth 读取验证。
调用图:调用 2 个内部函数(default, parse_chatgpt_jwt_claims);外部调用 3 个(now, save_auth, fake_jwt)。
tests::loads_local_chatgpt_auth_from_managed_auth130–144 ↗
fn loads_local_chatgpt_auth_from_managed_auth()
作用:验证最基本的成功场景:本地有 ChatGPT 登录时,读取函数应该能拿到正确的工作区、套餐类型和访问令牌。
数据流:它先创建一个临时目录,再用 tests::write_chatgpt_auth 写入 business 套餐的 ChatGPT 登录。然后调用 load_local_chatgpt_auth,并要求工作区必须是 workspace-1。最后断言返回的账号 ID 是 workspace-1,套餐是 business,access token 不是空的。
调用关系:这是对 load_local_chatgpt_auth 的主路径测试。它依赖 tests::write_chatgpt_auth 搭建本地认证文件,然后检查核心函数读出的结果是否符合预期。
调用图:调用 1 个内部函数(load_local_chatgpt_auth);外部调用 4 个(new, assert!, assert_eq!, write_chatgpt_auth)。
tests::rejects_missing_local_auth147–158 ↗
fn rejects_missing_local_auth()
作用:验证没有本地登录信息时,读取函数会明确失败,而不是假装成功或返回空数据。
数据流:它创建一个全新的临时目录,但不写任何认证文件。然后调用 load_local_chatgpt_auth。函数应该返回错误,测试再检查错误文字正好是“no local auth available”。
调用关系:这是 load_local_chatgpt_auth 的缺文件/未登录场景测试。它不需要任何辅助写入函数,只用空目录来确认核心函数能把“没有登录”说清楚。
调用图:调用 1 个内部函数(load_local_chatgpt_auth);外部调用 2 个(new, assert_eq!)。
tests::rejects_api_key_auth161–187 ↗
fn rejects_api_key_auth()
作用:验证 API Key 登录不能被当成 ChatGPT 登录使用。这样可以避免测试拿错凭证类型,导致后续逻辑误判。
数据流:它先创建临时目录,然后手动保存一份 auth.json,里面标明认证方式是 ApiKey,并带有 openai_api_key。接着调用 load_local_chatgpt_auth。预期结果是失败,并返回“local auth is not a ChatGPT login”。
调用关系:这是 load_local_chatgpt_auth 的错误类型测试。它用 save_auth 准备一份 API Key 凭证,再确认核心函数会拒绝它,而不是继续读取 token。
调用图:调用 2 个内部函数(default, load_local_chatgpt_auth);外部调用 3 个(new, assert_eq!, save_auth)。
tests::prefers_managed_auth_over_external_ephemeral_tokens190–210 ↗
fn prefers_managed_auth_over_external_ephemeral_tokens()
作用:验证同时存在两类登录信息时,读取函数会优先使用本地托管的 ChatGPT 登录,而不是外部临时 token。
数据流:它先在临时目录写入 workspace-1、business 套餐的本地登录。然后又用 login_with_chatgpt_auth_tokens 写入 workspace-2、enterprise 套餐的外部临时登录。接着调用 load_local_chatgpt_auth,并允许两个工作区。最后检查读出来的是 workspace-1 和 business,说明本地托管登录胜出。
调用关系:这是选择优先级的测试。它用 tests::write_chatgpt_auth 和 tests::fake_jwt 准备两套凭证,然后通过 load_local_chatgpt_auth 的返回值确认系统没有被外部临时 token 覆盖。
调用图:调用 1 个内部函数(load_local_chatgpt_auth);外部调用 5 个(new, assert_eq!, login_with_chatgpt_auth_tokens, fake_jwt, write_chatgpt_auth)。
tests::preserves_usage_based_plan_type_wire_name213–228 ↗
fn preserves_usage_based_plan_type_wire_name()
作用:验证特殊的套餐类型名字会被原样保留下来。这里关心的是传输层使用的原始名字,不能被改成别的友好显示名。
数据流:它创建临时目录,写入套餐类型为 self_serve_business_usage_based 的 ChatGPT 登录。然后调用 load_local_chatgpt_auth 读取。最后断言返回的套餐类型仍然是同一个字符串。
调用关系:这是 load_local_chatgpt_auth 对套餐字段处理方式的测试。它通过 tests::write_chatgpt_auth 准备特定套餐名,再确认核心函数只做小写化,不乱改这个面向接口的名字。
调用图:调用 1 个内部函数(load_local_chatgpt_auth);外部调用 3 个(new, assert_eq!, write_chatgpt_auth)。
tui/src/updates_cache_tests.rs源码 ↗
这个测试模拟了一个全新的 Codex 用户目录,就像刚装好软件、还没有任何更新记录一样。它先用临时文件夹当作假的主目录,避免碰到真实用户的数据。然后它构造配置,算出版本缓存文件应该放在哪里。接着调用 dismiss_version,把版本号 “999.0.0” 标记成“用户已经不想再看到”。测试最后读取刚生成的缓存文件,检查两件事:第一,文件确实被创建并能读出来;第二,里面记录的最新版本和已忽略版本都是 “999.0.0”。它还检查 last_checked_at 是 UNIX_EPOCH,也就是一个固定的初始时间点,表示这次只是忽略版本,不是假装已经完成了一次正常更新检查。
dismiss_version_creates_cache_file_when_missing7–29 ↗
async fn dismiss_version_creates_cache_file_when_missing()
作用:这个测试确认:当版本缓存文件不存在时,忽略某个版本会自动创建这个文件,并把忽略信息正确写进去。它保护的是第一次使用或缓存被删掉后的场景。
数据流:进去的是一个临时创建的空目录和要忽略的版本号 “999.0.0” → 测试用这个目录生成配置,找到版本缓存文件路径,然后调用忽略版本的功能 → 出来的是一个新建的缓存文件;测试读回文件后确认里面的 latest_version、dismissed_version 和 last_checked_at 都符合预期。
调用关系:这个函数是由测试框架在运行测试时自动调用的。它会用 tempdir 创建安全的临时目录,用 ConfigBuilder 的默认配置作为起点,再触发 dismiss_version 的真实写入行为,最后用 assert_eq! 比较读回来的结果和预期值。
调用图:外部调用 3 个(assert_eq!, default, tempdir)。
tui/src/bottom_pane/custom_prompt_view_tests.rs源码 ↗
这个文件不是给正式运行用的,而是给测试用的。它模拟用户在终端输入框里打字、按 Tab、按 Enter,并且给每个按键都安排一个时间点。这样可以检查输入框能不能分清两种情况:一种是真正慢慢打完后按回车提交;另一种是粘贴一大段文字时,很多字符和换行几乎同时涌进来。后者像是把一张纸一下子塞进机器,不能看到第一行换行就以为用户说“好了,提交”。测试里用一个通道(channel,可以理解成测试和输入框之间传纸条的管道)接收提交出来的文字,看看有没有过早收到内容。最后确认:快速粘贴中的换行不会提交,等用户稍后再按一次回车才会提交;而正常打字后隔了一会儿按回车,则会立刻提交。
paste_burst_newline_does_not_submit_short_first_line6–35 ↗
fn paste_burst_newline_does_not_submit_short_first_line()
作用:这个测试确认:如果用户像粘贴一样快速输入短短的第一行、马上出现换行、再接着输入第二行,输入框不能把第一个换行当成提交。它保护的是“多行粘贴不要被截断”的体验。
数据流:它先准备一个新的输入框和一个接收提交结果的通道,再按毫秒级间隔模拟输入第一行、回车、第二行。中途它检查通道里没有收到提交,输入框也没有结束。然后它模拟隔了更久之后再按一次回车,最后应该收到完整的“第一行换行第二行”,输入框也标记为完成。
调用关系:它是一个独立测试用例。开始时通过 custom_prompt_view 搭好测试用输入框,用 elapsed 造出不同的时间间隔,再把这些带时间的按键交给输入框。它不处理真正的界面显示,只验证 CustomPromptView 对“快速粘贴”和“真正提交”的判断是否正确。
调用图:调用 2 个内部函数(custom_prompt_view, elapsed);外部调用 5 个(now, Char, from, assert!, assert_eq!)。
paste_burst_newline_after_tab_does_not_submit38–61 ↗
fn paste_burst_newline_after_tab_does_not_submit()
作用:这个测试确认:快速输入里即使夹了一个 Tab 键,紧接着的回车也不应该被误认为提交。它防止某些带缩进或特殊按键的粘贴内容把输入框提前结束。
数据流:它创建输入框后,按很短的时间间隔模拟输入 x、Tab、Enter、rest。随后检查没有任何提交发生,输入框也还没完成。等到稍后再按一次 Enter,才应该收到最终文本“x\nrest”,并且输入框进入完成状态。
调用关系:它和其他测试一样,依靠 custom_prompt_view 创建可观察的输入框,依靠 elapsed 控制按键之间的时间差。它专门补充覆盖了 Tab 参与时的场景,确保输入框的粘贴判断不会因为 Tab 这种特殊键而失效。
调用图:调用 2 个内部函数(custom_prompt_view, elapsed);外部调用 5 个(now, Char, from, assert!, assert_eq!)。
delayed_enter_after_typing_submits64–75 ↗
fn delayed_enter_after_typing_submits()
作用:这个测试确认:如果用户正常打完字,然后隔了一小会儿按回车,输入框应该把它当成提交。它保证防误提交机制不会过头,不能让正常提交失灵。
数据流:它创建输入框,模拟用户以比较正常的节奏输入 foo,然后在稍后的时间点按 Enter。结果应该是通道收到“foo”,输入框也变成完成状态。
调用关系:它同样使用 custom_prompt_view 准备测试环境,用 elapsed 安排按键时间。前两个测试验证“太快的回车别提交”,这个测试验证“慢下来的回车要提交”,三者一起把这个输入框的核心判断边界夹住。
调用图:调用 2 个内部函数(custom_prompt_view, elapsed);外部调用 5 个(now, Char, from, assert!, assert_eq!)。
custom_prompt_view77–89 ↗
fn custom_prompt_view() -> (CustomPromptView, Receiver<String>)
作用:这个辅助函数给每个测试准备一个新的 CustomPromptView 和一个用来接收提交文本的通道。这样每个测试都不用重复写一遍搭建输入框的代码。
数据流:它没有外部输入。它先创建一个发送端和接收端组成的通道,然后新建一个标题为“Edit goal”的输入框,并给输入框塞入一个回调函数:一旦输入框提交文字,就把文字发进通道。最后它把输入框和通道接收端一起交给测试用例。
调用关系:它被三个测试函数调用,是这些测试的共同准备步骤。测试函数负责模拟按键;CustomPromptView 在被提交时会调用这里传入的回调;回调再把提交内容送到通道里,让测试函数可以检查到底有没有提交、提交了什么。
调用图:调用 1 个内部函数(new);被 3 处调用(delayed_enter_after_typing_submits, paste_burst_newline_after_tab_does_not_submit, paste_burst_newline_does_not_submit_short_first_line);外部调用 3 个(new, new, channel)。
elapsed91–93 ↗
fn elapsed(ms: usize) -> std::time::Duration
作用:这个小工具把毫秒数字变成程序能用的时间长度。测试用它来精确模拟按键之间隔了多久。
数据流:它接收一个毫秒数,比如 20 或 200,然后把这个数字转换成 Duration,也就是 Rust 里表示“一段时间”的对象。它不改动别的东西,只返回这个时间长度。
调用关系:它被三个测试函数反复使用。测试先拿当前时间作为起点,再用 elapsed 加上几毫秒,得到每次按键发生的模拟时间。CustomPromptView 正是根据这些时间差来判断这是快速粘贴,还是用户真正停顿后按下提交。
调用图:被 3 处调用(delayed_enter_after_typing_submits, paste_burst_newline_after_tab_does_not_submit, paste_burst_newline_does_not_submit_short_first_line);外部调用 1 个(from_millis)。
聊天组件交互流程
这些测试覆盖聊天组件中由用户驱动的流程,包括提交、命令、审批、审查、侧边线程和 app-server 事件处理。
tui/src/chatwidget/tests/app_server.rs源码 ↗
这个文件像一套“验收清单”。它不实现聊天功能本身,而是模拟服务器发来的各种通知:线程设置变了、模型回答完成了、命令执行了、协作代理启动了、报错了、线程关闭了等等。测试会先造一个假的聊天窗口,再把这些通知喂进去,然后检查两件事:一是界面历史里新增了什么文字,二是聊天窗口内部状态有没有变,比如当前模型、审批策略、正在工作标志、反馈关联的回合编号。它还特别盯住一些细节坑:用户刚发出的消息不要因为服务器回放再显示一遍;失败回合的错误不要重复;网络重连后状态栏要恢复;安全策略类错误要显示专门说明。简单说,这个文件保证“服务器说了什么”和“用户最终看到什么”之间不会走样。
thread_settings_for_test4–36 ↗
fn thread_settings_for_test(
model: &str,
thread_id: ThreadId,
) -> codex_app_server_protocol::ThreadSettingsUpdatedNotification
作用:这个辅助函数用来快速造一份“线程设置已更新”的假服务器通知。测试不想每次手写一大坨设置,所以把常用设置集中在这里。
数据流:进去的是模型名和线程编号 → 它把这些填进一份线程设置里,同时放入工作目录、审批方式、沙箱权限、服务档位、推理强度、协作模式和性格等固定测试值 → 出来的是一条可以直接喂给聊天窗口的设置更新通知,不改动外部状态。
调用关系:它被两个线程设置相关测试调用。那些测试先用它生成服务器通知,再交给聊天窗口处理,用来确认界面只更新目标线程的设置。
调用图:调用 1 个内部函数(read_only);被 2 处调用(thread_settings_updated_preserves_default_settings_for_plan_mode, thread_settings_updated_updates_visible_state_without_transcript);外部调用 1 个(to_string)。
configured_thread_session38–61 ↗
fn configured_thread_session(thread_id: ThreadId) -> crate::session_state::ThreadSessionState
作用:这个辅助函数用来造一个已经配置好的假线程会话。它相当于给测试里的聊天窗口准备一份初始档案。
数据流:进去的是线程编号 → 它填好默认模型、服务商、权限配置、工作目录、历史记录等会话字段 → 出来的是一份线程会话状态,供测试先把聊天窗口放到某个已打开线程中。
调用关系:它被两个线程设置测试调用。测试先用它建立当前线程,再用 thread_settings_for_test 模拟服务器改设置,最后检查聊天窗口有没有正确覆盖或保留对应状态。
调用图:调用 1 个内部函数(read_only);被 2 处调用(thread_settings_updated_preserves_default_settings_for_plan_mode, thread_settings_updated_updates_visible_state_without_transcript);外部调用 2 个(new, vec!)。
invalid_url_elicitation_is_declined64–98 ↗
async fn invalid_url_elicitation_is_declined()
作用:这个测试确认:如果服务器请求用户打开一个 URL 进行确认,但这个请求不属于当前可见线程,聊天界面会直接拒绝。这样可以避免用户在一个聊天里误处理另一个聊天的敏感请求。
数据流:进去的是一个假的聊天窗口、当前可见线程编号,以及另一个发起请求的线程编号 → 测试把 URL 请求交给聊天窗口 → 出来的是一条提交给对应线程的“拒绝请求”操作,内容为空,并确认目标线程是请求所属线程。
调用关系:这个测试直接调用聊天窗口的 elicitation 请求处理函数,然后从事件通道里取结果。它不依赖本文件的辅助函数,只验证这条安全边界是否成立。
调用图:调用 1 个内部函数(new);外部调用 2 个(Integer, assert_matches!)。
thread_settings_updated_updates_visible_state_without_transcript101–154 ↗
async fn thread_settings_updated_updates_visible_state_without_transcript()
作用:这个测试确认:当前正在看的线程收到设置更新时,聊天窗口会更新模型、推理强度、服务档位、审批和性格等状态,但不会把历史聊天内容重新渲染一遍。
数据流:进去的是一个假聊天窗口和一个线程编号 → 测试先建立该线程会话,再发送一条线程设置更新通知 → 出来的是聊天窗口当前设置变成新值,历史插入队列保持为空;随后给另一个线程发设置更新,当前界面不变。
调用关系:它先调用 configured_thread_session 准备初始线程,再调用 thread_settings_for_test 生成更新通知,最后通过一组断言检查聊天窗口处理 ServerNotification::ThreadSettingsUpdated 的效果。
调用图:调用 3 个内部函数(new, configured_thread_session, thread_settings_for_test);外部调用 3 个(ThreadSettingsUpdated, assert!, assert_eq!)。
thread_settings_updated_preserves_default_settings_for_plan_mode157–190 ↗
async fn thread_settings_updated_preserves_default_settings_for_plan_mode()
作用:这个测试确认:进入“计划模式”时可以显示计划模式要求的模型和推理强度,但原来的默认模式设置不会被覆盖丢失。
数据流:进去的是一个带默认模型和低推理强度的假线程会话 → 测试发送计划模式的线程设置更新 → 聊天窗口暂时显示计划模型和高推理强度;当测试切回默认模式时,又回到原来的默认模型和低推理强度。
调用关系:它用 configured_thread_session 建初始会话,用 thread_settings_for_test 制造计划模式更新,再调用协作模式的 default_mask 切回默认模式,验证两套设置互不污染。
调用图:调用 4 个内部函数(new, configured_thread_session, thread_settings_for_test, default_mask);外部调用 2 个(ThreadSettingsUpdated, assert_eq!)。
collab_spawn_end_shows_requested_model_and_effort193–259 ↗
async fn collab_spawn_end_shows_requested_model_and_effort()
作用:这个测试确认:协作代理被创建完成时,历史记录里会显示创建时请求的模型和推理强度。这样用户能知道这个代理是用什么配置启动的。
数据流:进去的是假聊天窗口、发起线程编号和被创建代理线程编号 → 测试先登记代理名字和角色,再模拟创建代理的开始和完成通知 → 出来的是历史渲染文本,里面包含代理名、角色、模型 gpt-5 和 high 推理强度。
调用关系:它直接向聊天窗口发送 ItemStarted 和 ItemCompleted 通知。测试最后读取历史插入事件,确认协作代理工具调用的展示文字没有丢掉请求参数。
调用图:调用 1 个内部函数(new);外部调用 7 个(from, new, ItemCompleted, ItemStarted, new, assert!, vec!)。
live_app_server_user_message_item_completed_does_not_duplicate_rendered_prompt262–297 ↗
async fn live_app_server_user_message_item_completed_does_not_duplicate_rendered_prompt()
作用:这个测试确认:用户按回车发送的文字已经显示过后,服务器稍后回传同一条用户消息完成通知时,不会再显示第二遍。
数据流:进去的是一个假聊天窗口和输入框文字 → 测试模拟用户按回车,先确认界面插入了一条用户消息,并且发送操作已提交 → 再模拟服务器回传同样的用户消息 → 出来的是没有新的历史插入事件。
调用关系:它先走真实的键盘输入路径,再处理 ItemCompleted 用户消息通知。这样覆盖的是实际使用中“本地乐观显示”和“服务器确认”之间的去重关系。
调用图:调用 1 个内部函数(new);外部调用 7 个(new, ItemCompleted, new, assert!, assert_eq!, panic!, vec!)。
live_app_server_turn_completed_clears_working_status_after_answer_item300–366 ↗
async fn live_app_server_turn_completed_clears_working_status_after_answer_item()
作用:这个测试确认:模型回答出现后,界面仍然保持“工作中”,直到整个回合完成通知到达才清掉状态栏。这样不会过早让用户以为任务已经完全结束。
数据流:进去的是一个假聊天窗口 → 测试先发送回合开始通知,状态栏显示 Working;再发送最终回答条目,历史里出现回答但状态仍是运行中;最后发送回合完成通知 → 出来的是运行状态关闭,状态栏消失。
调用关系:它按服务器真实顺序发送 TurnStarted、ItemCompleted 和 TurnCompleted。测试验证聊天窗口把“回答文本完成”和“整个回合结束”区分开。
调用图:外部调用 6 个(ItemCompleted, TurnCompleted, TurnStarted, new, assert!, assert_eq!)。
live_app_server_turn_started_sets_feedback_turn_id369–404 ↗
async fn live_app_server_turn_started_sets_feedback_turn_id()
作用:这个测试确认:当用户在某个回合运行中提交反馈时,反馈会带上这个回合的编号。这样后台才能知道用户是在反馈哪一次回答。
数据流:进去的是假聊天窗口和一个回合开始通知 → 聊天窗口记录当前回合编号 → 测试打开反馈输入并按回车提交 → 出来的是 SubmitFeedback 事件,其中 turn_id 是刚开始的 turn-1。
调用关系:它先处理 TurnStarted,再走反馈提交流程。测试从事件通道里检查反馈事件,确认回合编号被聊天窗口正确串起来。
调用图:外部调用 4 个(new, TurnStarted, new, assert_matches!)。
live_app_server_warning_notification_renders_message407–432 ↗
async fn live_app_server_warning_notification_renders_message()
作用:这个测试确认:普通警告通知会被显示到聊天历史里,而且长警告里的主要信息不会丢。
数据流:进去的是一个包含技能上下文预算警告的服务器通知 → 聊天窗口把它转换成一条历史单元 → 出来的是一段可见文字,包含预算超限和后续处理说明。
调用关系:它直接发送 Warning 通知,然后读取历史插入结果。这个测试保护的是服务器警告到用户可见提示的展示链路。
调用图:外部调用 3 个(Warning, assert!, assert_eq!)。
live_app_server_guardian_warning_notification_renders_message435–453 ↗
async fn live_app_server_guardian_warning_notification_renders_message()
作用:这个测试确认:自动审批审查一类的安全警告会显示给用户。否则用户可能不知道为什么某个操作被挡住了。
数据流:进去的是一条 GuardianWarning 通知,里面写着自动审批审查拒绝了操作 → 聊天窗口渲染一条警告历史 → 出来的是包含原始警告文字的可见内容。
调用关系:它直接测试 GuardianWarning 通知的展示路径,确保安全审查模块发来的信息不会被聊天界面吞掉。
调用图:外部调用 3 个(GuardianWarning, assert!, assert_eq!)。
live_app_server_config_warning_prefixes_summary456–476 ↗
async fn live_app_server_config_warning_prefixes_summary()
作用:这个测试确认:配置文件有问题时,聊天界面会把配置警告的摘要显示出来。这样用户能知道系统正在用默认值兜底。
数据流:进去的是一条 ConfigWarning 通知,摘要为配置无效、使用默认值 → 聊天窗口把它插入历史 → 出来的是包含这句摘要的警告文本。
调用关系:它直接发送 ConfigWarning 并检查历史输出。这个测试覆盖配置加载问题传到界面的提醒路径。
调用图:外部调用 3 个(ConfigWarning, assert!, assert_eq!)。
live_app_server_file_change_item_started_preserves_changes479–507 ↗
async fn live_app_server_file_change_item_started_preserves_changes()
作用:这个测试确认:文件变更刚开始时,界面就能显示具体改了哪个文件。这样用户不会只看到一个空泛的“正在改文件”。
数据流:进去的是一个 FileChange 开始通知,里面包含新增 foo.txt 和 diff 内容 → 聊天窗口生成补丁历史展示 → 出来的是历史文本中至少能看到 foo.txt 被新增或编辑。
调用关系:它发送 ItemStarted 类型的文件变更通知。测试关注的是进行中的补丁也要保留变更明细,而不是只在完成时才知道文件名。
live_app_server_command_execution_strips_shell_wrapper510–572 ↗
async fn live_app_server_command_execution_strips_shell_wrapper()
作用:这个测试确认:命令通过 shell 包装执行时,界面展示的是用户真正关心的脚本,而不是冗长的 /bin/zsh -lc 外壳命令。
数据流:进去的是一个被 shell 包起来的 python 命令,以及命令完成后的输出、退出码和耗时 → 聊天窗口处理开始和完成通知 → 出来的是一条命令历史单元,快照检查它的显示格式符合预期。
调用关系:它用 shlex 生成可还原的 shell 包装命令,再发送 ItemStarted 和 ItemCompleted。最后用快照测试固定界面文本,防止以后显示格式被无意改坏。
调用图:外部调用 6 个(ItemCompleted, ItemStarted, assert_chatwidget_snapshot!, assert_eq!, try_join, vec!)。
live_app_server_collab_wait_items_render_history575–661 ↗
async fn live_app_server_collab_wait_items_render_history()
作用:这个测试确认:等待多个协作代理的工具调用,会把各个代理的名字、角色和状态清楚显示在历史里。
数据流:进去的是两个接收方代理线程编号及其元数据 → 测试模拟等待开始,再模拟等待完成,其中一个代理完成并带消息,另一个仍在运行 → 出来的是合并后的历史文本,并用快照确认展示内容。
调用关系:它先设置协作代理元数据,再发送 Wait 工具的 ItemStarted 和 ItemCompleted 通知。这个测试保证多代理协作状态能被用户看懂。
调用图:调用 1 个内部函数(from_string);外部调用 6 个(from, new, ItemCompleted, ItemStarted, assert_chatwidget_snapshot!, vec!)。
live_app_server_collab_spawn_completed_renders_requested_model_and_effort664–726 ↗
async fn live_app_server_collab_spawn_completed_renders_requested_model_and_effort()
作用:这个测试确认:协作代理创建完成后的历史记录,会保留并显示请求的模型和推理强度。
数据流:进去的是发起线程编号、被创建代理编号、请求模型 gpt-5 和高推理强度 → 聊天窗口处理创建开始和完成通知 → 出来的是一段历史文本,快照验证它包含正确的创建信息。
调用关系:它和另一个 spawn 测试类似,但这里完成通知里也带着模型和推理强度。它通过快照固定 SpawnAgent 完成态的渲染效果。
调用图:调用 1 个内部函数(from_string);外部调用 7 个(from, new, ItemCompleted, ItemStarted, new, assert_chatwidget_snapshot!, vec!)。
live_app_server_failed_turn_does_not_duplicate_error_history729–790 ↗
async fn live_app_server_failed_turn_does_not_duplicate_error_history()
作用:这个测试确认:失败回合如果已经收到过错误通知,后面的回合完成通知里再带同一个错误时,不会在历史里重复显示。
数据流:进去的是一个回合开始通知、一个 permission denied 错误通知、以及一个失败的回合完成通知 → 第一次错误会插入历史 → 完成通知到达后不再插入新错误,并且工作状态被清掉。
调用关系:它按 TurnStarted、Error、TurnCompleted 的顺序模拟失败流程。测试保护错误展示的去重逻辑,避免用户看到两条一模一样的失败消息。
调用图:外部调用 6 个(Error, TurnCompleted, TurnStarted, new, assert!, assert_eq!)。
live_app_server_failed_turn_consolidates_streamed_answer793–824 ↗
async fn live_app_server_failed_turn_consolidates_streamed_answer()
作用:这个测试确认:如果模型答案正在流式输出时突然失败,已经输出的半截内容会先被收拢成历史,而不是直接丢掉。
数据流:进去的是一个开始的回合、一段流式追加的回答内容,以及一个断流错误 → 聊天窗口先把流式内容提交整理 → 出来的是 ConsolidateAgentMessage 事件,其中包含那段已经流出的 patch 文本。
调用关系:它使用测试辅助流程触发回合开始、消息增量、提交节拍和错误。测试重点是失败清理流控制器之前,必须先保存用户已经看到的部分回答。
调用图:外部调用 1 个(assert!)。
live_app_server_stream_recovery_restores_previous_status_header827–882 ↗
async fn live_app_server_stream_recovery_restores_previous_status_header()
作用:这个测试确认:网络重连提示出现后,如果又收到了模型输出,状态栏会从“重连中”恢复成原来的“工作中”。
数据流:进去的是回合开始通知、一个可重试错误通知,以及后续的消息增量 → 聊天窗口先进入重试提示状态,再在新内容到来时恢复状态栏 → 出来的是状态标题为 Working、详情为空,并且临时重试标题被清除。
调用关系:它按 TurnStarted、Error、AgentMessageDelta 的顺序模拟断线恢复。测试验证聊天窗口没有把临时错误状态永久留在底部状态栏。
调用图:外部调用 6 个(AgentMessageDelta, Error, TurnStarted, new, assert!, assert_eq!)。
live_app_server_server_overloaded_error_renders_warning885–924 ↗
async fn live_app_server_server_overloaded_error_renders_warning()
作用:这个测试确认:服务器过载错误会以简短警告显示,并且会结束当前工作状态。
数据流:进去的是回合开始通知和一个 ServerOverloaded 类型错误 → 聊天窗口把错误渲染成“ server overloaded” → 出来的是一条警告历史,任务运行标志关闭。
调用关系:它模拟正常回合中收到不可重试错误。测试覆盖特定错误类型到用户提示的映射,以及错误后状态清理。
调用图:外部调用 5 个(Error, TurnStarted, new, assert!, assert_eq!)。
live_app_server_cyber_policy_error_renders_dedicated_notice927–969 ↗
async fn live_app_server_cyber_policy_error_renders_dedicated_notice()
作用:这个测试确认:网络安全策略拦截类错误不会简单显示服务器的兜底文字,而是显示专门写给用户看的安全说明。
数据流:进去的是回合开始通知和一个 CyberPolicy 类型错误,原始消息是 server fallback message → 聊天窗口识别错误类型后替换成专用说明 → 出来的是历史文本包含网络安全风险和 Trusted Access for Cyber,但不包含原始兜底消息,任务状态也结束。
调用关系:它发送 Error 通知并检查特定文案。这个测试保护安全策略错误的特殊展示逻辑,避免用户看到不清楚或不合规的泛泛报错。
调用图:外部调用 5 个(Error, TurnStarted, new, assert!, assert_eq!)。
live_app_server_model_verification_renders_warning972–991 ↗
async fn live_app_server_model_verification_renders_warning()
作用:这个测试确认:模型验证提示会显示额外安全检查的信息和相关链接。用户因此能知道为什么聊天进入了更谨慎的模式。
数据流:进去的是一条 ModelVerification 通知,验证项为 TrustedAccessForCyber → 聊天窗口插入一条警告历史 → 出来的是文本包含多次网络安全风险标记、额外安全检查、Trusted Access for Cyber 和说明链接。
调用关系:它直接测试 ModelVerification 通知的渲染路径。这个通知不是错误,但仍需要作为重要安全提示进入聊天历史。
调用图:外部调用 4 个(ModelVerification, assert!, assert_eq!, vec!)。
live_app_server_invalid_thread_name_update_is_ignored994–1012 ↗
async fn live_app_server_invalid_thread_name_update_is_ignored()
作用:这个测试确认:线程名称更新里的线程编号如果不是合法编号,聊天窗口会忽略它,不会把当前聊天标题改坏。
数据流:进去的是当前线程编号、原始线程名,以及一条线程编号为 not-a-thread-id 的名称更新通知 → 聊天窗口尝试处理但无法识别编号 → 出来的是当前线程编号和线程名都保持原样。
调用关系:它直接发送 ThreadNameUpdated 通知。测试保护的是输入校验:服务器或网络层给到坏数据时,界面不要误更新。
调用图:调用 1 个内部函数(new);外部调用 2 个(ThreadNameUpdated, assert_eq!)。
live_app_server_thread_name_update_shows_resume_hint1015–1036 ↗
async fn live_app_server_thread_name_update_shows_resume_hint()
作用:这个测试确认:当前线程名称被成功更新后,聊天历史会显示一条恢复会话的提示。这样用户以后能知道如何继续这个命名线程。
数据流:进去的是一个合法线程编号和新名称 review-fix → 聊天窗口确认通知属于当前线程,更新 thread_name → 出来的是线程名变成 review-fix,并插入一条提示文本,快照固定它的样子。
调用关系:它用 from_string 构造合法线程编号,再发送 ThreadNameUpdated。测试连接了线程标题状态更新和用户可见恢复提示。
调用图:调用 1 个内部函数(from_string);外部调用 3 个(ThreadNameUpdated, assert_chatwidget_snapshot!, assert_eq!)。
live_app_server_thread_closed_requests_immediate_exit1039–1050 ↗
async fn live_app_server_thread_closed_requests_immediate_exit()
作用:这个测试确认:服务器通知线程已关闭时,聊天界面会立刻请求退出。这样用户不会继续停在一个已经被服务端关掉的会话里。
数据流:进去的是一条 ThreadClosed 通知 → 聊天窗口处理后向应用事件通道发送退出事件 → 出来的是 ExitMode::Immediate,表示马上退出。
调用关系:它直接发送 ThreadClosed 通知,然后检查事件通道。这个测试覆盖服务端关闭会话到前端退出动作的最后一环。
调用图:外部调用 2 个(ThreadClosed, assert_matches!)。
tui/src/chatwidget/tests/approval_requests.rs源码 ↗
聊天界面有时会收到一个请求:模型想执行命令,或者想访问网络、文件系统,需要先问用户同不同意。这个文件就是一组自动测试,像质检员一样反复模拟这些场景。它会造出假的审批请求,塞进 ChatWidget 里,再模拟用户按 y、n、a 这样的键,然后检查界面有没有正确显示弹窗、历史记录有没有写清楚“批准了什么”或“拒绝了什么”。它还测试从 app server(应用服务器,也就是外部发请求的一端)传来的命令和权限能不能被正确转换,比如 shell 包起来的命令要拆成参数,读写目录权限不能丢,网络主机名也要保留下来。重要的一点是,这些测试不只是看程序有没有返回值,还会用快照测试(把界面文字保存下来,下次对比)防止用户看到的文案悄悄变坏。
exec_approval_emits_proposed_command_and_decision_history8–48 ↗
async fn exec_approval_emits_proposed_command_and_decision_history()
作用:测试普通命令审批的完整用户体验:请求出现时先用弹窗展示,不急着写进聊天历史;用户批准后,历史里才出现一条清楚的决定记录。
数据流:测试先创建一个假的聊天界面和事件接收器,再构造一个要执行 echo hello world 的审批请求。请求交给聊天界面后,它检查历史消息还是空的,然后把界面渲染到一个虚拟屏幕里做快照对比。最后模拟用户按下 y 表示同意,再从事件流里取出新增的历史记录,确认这条记录长得符合预期。
调用关系:这是测试运行器启动的异步测试。它把请求交给 handle_exec_approval_request,再通过 chat.render 看弹窗,通过 chat.handle_key_event 模拟用户决定,最后用 drain_insert_history 和快照断言检查聊天界面的结果。
调用图:调用 1 个内部函数(current_dir);外部调用 7 个(Char, new, new, assert!, assert_snapshot!, empty, vec!)。
app_server_exec_approval_request_splits_shell_wrapped_command51–84 ↗
fn app_server_exec_approval_request_splits_shell_wrapped_command()
作用:测试从应用服务器传来的“被 shell 包了一层”的命令,能不能被正确拆回真正的参数列表。这样界面和后续执行逻辑不会把整串命令误当成一个参数。
数据流:测试先准备一段脚本 python3 -c ...,再用 shell 转义工具把 /bin/zsh -lc 脚本 拼成一条字符串。然后把这条字符串放进应用服务器格式的审批参数里,交给转换函数。出来的结果应该是三个清楚的命令片段:shell 路径、-lc、脚本文本。
调用关系:它主要盯住 exec_approval_request_from_params 这个转换入口。测试本身由 Rust 测试框架调用,内部用 shlex::try_join 造出接近真实服务端会发送的命令字符串,再用断言确认转换结果。
调用图:外部调用 2 个(assert_eq!, try_join)。
app_server_exec_approval_request_preserves_permissions_context87–148 ↗
fn app_server_exec_approval_request_preserves_permissions_context()
作用:测试命令审批请求里的网络和额外文件权限不会在格式转换时丢失。没有这个保障,用户批准时看到或系统收到的权限范围可能就不完整。
数据流:测试先造出两个绝对路径,一个代表只读目录,一个代表可写目录,并把它们转成应用服务器使用的路径字符串。接着构造一个带网络主机、网络协议、读写权限的审批参数。转换完成后,它检查结果里的网络访问上下文和额外权限,必须和输入保持一致。
调用关系:它围绕 exec_approval_request_from_params 做检查。路径转换会用到绝对路径构造和 API 路径包装;最后由断言把转换前后信息一项项对齐,确保权限数据没有被中间层吞掉。
调用图:调用 2 个内部函数(try_from, from_abs_path);外部调用 3 个(from, assert_eq!, vec!)。
network_exec_approval_history_describes_session_host_allowance151–189 ↗
async fn network_exec_approval_history_describes_session_host_allowance()
作用:测试用户选择“本次会话都允许访问某个网络主机”时,聊天历史里的说明是否准确。它关心的是用户以后回看时能不能明白自己放行了什么。
数据流:测试创建聊天界面,然后构造一个访问 example.com 的 HTTPS 网络审批请求,并声明可选决定里有“本会话接受”和“取消”。请求进入界面后,测试模拟用户按 a 选择本会话允许。最后从历史记录里取出决定说明,用快照确认文案写的是对这个主机的会话级放行。
调用关系:测试运行器调用它后,它通过 exec_approval_request_from_params 生成内部请求,再交给 handle_exec_approval_request。用户按键由 chat.handle_key_event 模拟,历史文案通过 drain_insert_history 取出并交给快照测试。
调用图:外部调用 4 个(Char, new, assert_snapshot!, vec!)。
network_exec_approval_history_describes_one_time_host_allowance192–230 ↗
async fn network_exec_approval_history_describes_one_time_host_allowance()
作用:测试用户只允许某个网络主机访问一次时,历史记录会不会写成“一次性允许”,而不是误写成长期允许。
数据流:测试构造一个 HTTP 访问 example.com 的网络审批请求,这次没有具体命令文本,但带有网络上下文和可选决定。它把请求送进聊天界面,模拟用户按 y 接受一次。随后取出新增历史记录,用快照确认文字描述的是一次性主机放行。
调用关系:它和其他网络审批测试走同一条链路:服务端参数先转成内部请求,再由聊天界面展示和处理按键。不同点是它模拟的是 Accept,用来覆盖一次性批准这条分支。
调用图:外部调用 4 个(Char, new, assert_snapshot!, vec!)。
network_exec_approval_history_describes_canceled_host_request233–271 ↗
async fn network_exec_approval_history_describes_canceled_host_request()
作用:测试用户拒绝网络访问请求时,聊天历史能不能明确写出这个主机请求被取消了。这样用户不会误以为访问已经被允许。
数据流:测试准备一个 Socks5 TCP 访问 example.com 的审批请求,并提供“接受”或“取消”两个选择。请求进入聊天界面后,测试模拟用户按 n 拒绝。最后取出历史里的决定消息,用快照确认它表达的是取消网络主机访问。
调用关系:它由测试框架运行,使用同样的请求转换和聊天界面处理流程。它专门覆盖拒绝分支,和前两个网络审批测试一起保证接受一次、接受整场会话、拒绝三种用户选择都有正确历史文案。
调用图:外部调用 4 个(Char, new, assert_snapshot!, vec!)。
app_server_request_permissions_preserves_file_system_permissions274–320 ↗
fn app_server_request_permissions_preserves_file_system_permissions()
作用:测试单独的权限申请请求在转换时能保留文件系统读写范围、网络开关、当前工作目录和远程环境标识。简单说,就是外部请求什么权限,内部就应该准确记住什么权限。
数据流:测试先准备只读路径、可写路径和当前工作目录,并把读写路径转成应用服务器格式。然后构造一个权限申请参数,里面包含网络启用、文件读写权限、环境 ID 和申请理由。转换函数返回内部请求后,测试检查权限结构、当前目录和环境 ID 都符合预期。
调用关系:它主要检查 request_permissions_from_params。这个函数把应用服务器协议里的权限描述变成本地聊天界面能理解的权限描述;测试用断言确认转换没有改错路径,也没有丢掉环境信息。
调用图:调用 2 个内部函数(try_from, from_abs_path);外部调用 3 个(from, assert_eq!, vec!)。
exec_approval_uses_approval_id_when_present323–365 ↗
async fn exec_approval_uses_approval_id_when_present()
作用:测试审批请求同时带有 call_id 和 approval_id 时,真正提交决定要用 approval_id。这很重要,因为一个大的调用里可能有子命令,审批编号才是后端要认的编号。
数据流:测试创建聊天界面,构造一个命令审批请求,其中 call_id 是父调用,approval_id 是子命令审批号。它把请求交给界面后模拟用户按 y 同意,然后不断读取发出的应用事件。找到提交审批决定的事件后,确认里面的 ID 是 approval-subcommand,决定是接受。
调用关系:它从用户按键一路追到发给后端的操作事件。handle_exec_approval_request 负责把请求挂到界面上,chat.handle_key_event 触发提交,测试再从接收器里找 Op::ExecApproval,确保界面没有错误地使用 call_id。
调用图:调用 1 个内部函数(current_dir);外部调用 6 个(Char, new, assert!, assert_eq!, assert_matches!, vec!)。
exec_approval_decision_truncates_multiline_and_long_commands368–449 ↗
async fn exec_approval_decision_truncates_multiline_and_long_commands()
作用:测试多行命令和特别长的命令在审批历史里会被安全地缩短展示。这样历史记录不会被一大段命令刷屏,但用户仍能看出大概发生了什么。
数据流:测试先送入一个包含换行的命令审批请求,确认请求阶段仍然只用弹窗展示,并检查弹窗里能看到第一行命令。然后模拟用户按 n 拒绝,取出历史记录做快照。接着它再构造一条很长的命令,同样确认请求阶段不写历史,拒绝后检查历史里的命令摘要被截断得符合预期。
调用关系:它覆盖审批显示里比较容易出问题的边界情况:多行和超长文本。流程上仍然是请求进入 handle_exec_approval_request,界面用 render 画出来,按键由 handle_key_event 处理,最后用历史抽取和快照断言确认展示效果。
调用图:调用 1 个内部函数(current_dir);外部调用 9 个(Char, new, new, new, assert!, assert_snapshot!, format!, empty, vec!)。
tui/src/chatwidget/tests/composer_submission.rs源码 ↗
聊天输入框看起来只是打字和按回车,但实际要处理很多边角情况:本地图片、远程图片、技能提及、应用提及、权限配置、任务运行时的排队、Esc 打断后的恢复,还有不同终端里按键含义不一样。这个测试文件就是给这些行为兜底的。它会造出一个假的 ChatWidget(聊天界面组件),模拟用户输入文字、按键、插图、打开弹窗、任务开始或中断,然后检查发给后台的 Op::UserTurn(一次用户发言操作)是否正确,也检查界面历史记录里显示的内容是否正确。可以把它理解成“收银台测试”:用户把文字、图片、优惠券一起递过去,系统必须按正确顺序装袋、打印小票,并且出错时还能把东西原样退回给用户。
submission_preserves_text_elements_and_local_images14–97 ↗
async fn submission_preserves_text_elements_and_local_images()
作用:测试提交一条带本地图片占位符的消息时,文字里的特殊片段和本地图片路径不会丢。这样可以保证用户看到的“[Image #1] submit”和真正送给模型的内容是一致的。
数据流:先创建一个聊天界面和一个线程状态,再把输入框设成带图片占位符的文字,并附上一张本地图片路径。按下 Enter 后,它检查发出的用户请求里先有本地图片,再有文字;同时检查聊天历史里也保存了同样的文字、文字标记和图片路径。
调用关系:这个测试模拟最基础的“文字加本地图片提交”流程。它通过 ChatWidget 的按键处理触发提交,再从操作通道和界面事件通道里取结果,确认提交层和显示层都没有改坏数据。
调用图:调用 2 个内部函数(read_only, new);外部调用 9 个(new, new, default, new, assert!, assert_eq!, format!, panic!, vec!)。
submission_includes_configured_active_permission_profile100–169 ↗
async fn submission_includes_configured_active_permission_profile()
作用:测试用户提交消息时,会带上当前选中的权限配置。权限配置就是告诉后台这次对话能不能联网、能读写哪些文件的规则。
数据流:它先构造一个带自定义权限名称的线程状态,然后输入一段普通文字并按 Enter。最后从发出的用户请求里取出 active_permission_profile,确认它就是之前配置的那个。
调用关系:这个测试位于“会话配置进入提交请求”的链路上。它证明 ChatWidget 在 handle_thread_session 收到配置后,submit 时会把关键权限信息一起交给后台。
调用图:调用 2 个内部函数(new, new);外部调用 7 个(new, new, default, new, assert_eq!, panic!, vec!)。
submission_omits_active_permission_profile_for_legacy_snapshot172–218 ↗
async fn submission_omits_active_permission_profile_for_legacy_snapshot()
作用:测试旧格式会话里如果没有“当前激活权限配置”,提交时就不要硬塞一个。这样能兼容老数据,不会把不存在的配置误传给后台。
数据流:它准备一个 active_permission_profile 为空的线程状态,提交一条消息。然后检查发出的用户请求里这个字段仍然是 None,也就是没有值。
调用关系:这个测试和权限配置测试成对出现。前一个确认有配置时要带上,这一个确认没有配置时要保持空,避免新旧会话状态混用出错。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, new, default, new, assert_eq!, panic!)。
submission_with_remote_and_local_images_keeps_local_placeholder_numbering221–316 ↗
async fn submission_with_remote_and_local_images_keeps_local_placeholder_numbering()
作用:测试一条消息同时有远程图片和本地图片时,本地图片在文字里的编号不会被远程图片打乱。用户看到的“[Image #2]”必须继续对应那张本地图片。
数据流:它先给聊天界面放入一个远程图片网址,再在输入框里放入“[Image #2] submit mixed”和一张本地图片。按 Enter 后,它确认提交内容顺序是远程图片、本地图片、文字,并且历史记录保存了同样的占位符、本地图片和远程图片网址。
调用关系:这个测试覆盖混合图片提交场景。它连接了远程图片列表、输入框本地图片列表、用户请求组装和历史显示四个环节,确保编号映射没有被重新洗牌。
调用图:调用 2 个内部函数(read_only, new);外部调用 8 个(new, new, default, new, assert_eq!, format!, panic!, vec!)。
enter_with_only_remote_images_submits_user_turn319–383 ↗
async fn enter_with_only_remote_images_submits_user_turn()
作用:测试输入框没有文字、只有远程图片时,按 Enter 也应该提交。因为用户可能只想发一张网络图片给模型看。
数据流:它把远程图片网址放进聊天界面,确认输入框文字为空,然后按 Enter。结果应该发出只包含远程图片的用户请求,并且提交后远程图片列表被清空,历史记录里也记录这张远程图片。
调用关系:这个测试专门保护“纯图片消息”入口。它验证提交逻辑不是只看文字是否为空,而是也会看是否存在待发送的远程图片。
调用图:调用 2 个内部函数(read_only, new);外部调用 8 个(new, new, default, new, assert!, assert_eq!, panic!, vec!)。
shift_enter_with_only_remote_images_does_not_submit_user_turn386–424 ↗
async fn shift_enter_with_only_remote_images_does_not_submit_user_turn()
作用:测试只有远程图片时,Shift+Enter 不会提交。Shift+Enter 通常表示换行或特殊输入,不应该等同于发送。
数据流:它放入一个远程图片网址,然后模拟 Shift+Enter。结果是不发出用户请求,远程图片仍然留在待提交列表里。
调用关系:这个测试约束按键行为。它和普通 Enter 的测试形成对照,确认提交只发生在真正的发送按键上。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, new, default, new, assert_eq!, vec!)。
enter_with_only_remote_images_does_not_submit_when_modal_is_active427–465 ↗
async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active()
作用:测试弹窗打开时,即使按 Enter 且有远程图片,也不会提交消息。弹窗就像当前盖在界面上的对话框,按键应先由弹窗处理。
数据流:它放入远程图片,打开审核弹窗,再按 Enter。之后检查远程图片还在,且没有发出提交请求。
调用关系:这个测试保护界面焦点规则。它说明 ChatWidget 在提交前会尊重当前是否有弹窗,避免用户在弹窗里按确认时误发聊天消息。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, new, default, new, assert_eq!, vec!)。
enter_with_only_remote_images_does_not_submit_when_input_disabled468–509 ↗
async fn enter_with_only_remote_images_does_not_submit_when_input_disabled()
作用:测试输入框被禁用时,只有远程图片也不能提交。输入禁用通常表示系统暂时不允许用户发送,例如正在等待某个状态。
数据流:它放入远程图片,然后把输入框设为不可输入,并按 Enter。结果远程图片保留,没有任何提交操作发出去。
调用关系:这个测试保护输入禁用状态。它确认提交逻辑会先问输入框“现在能不能发”,而不是只根据内容直接发送。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, new, default, new, assert_eq!, vec!)。
submission_prefers_selected_duplicate_skill_path512–593 ↗
async fn submission_prefers_selected_duplicate_skill_path()
作用:测试当有两个同名技能时,提交会使用用户实际选中的那个路径。技能可以理解成可被聊天调用的外部能力说明文件。
数据流:它准备两个都叫 figma 的技能,一个来自仓库,一个来自用户目录。输入框里的 $figma 绑定到用户目录路径后提交,最后检查发出的 UserInput::Skill 只包含用户选中的路径。
调用关系:这个测试覆盖“提及绑定”比“名字匹配”更可靠的规则。它保证 ChatWidget 不会因为技能重名而选错技能文件。
调用图:调用 2 个内部函数(read_only, new);外部调用 7 个(new, new, default, new, assert_eq!, panic!, vec!)。
blocked_image_restore_preserves_mention_bindings596–648 ↗
async fn blocked_image_restore_preserves_mention_bindings()
作用:测试如果模型不支持图片,系统把被拦截的消息恢复回输入框时,不会丢掉里面的提及绑定。也就是图片发不了,但用户写的 $file 仍然保持可识别。
数据流:它构造一条带图片占位符和 $file 提及的消息,然后调用恢复函数。恢复后检查输入框文字、文字标记、本地图片路径和提及绑定都回来了,并且历史里出现“不支持图片输入”的警告。
调用关系:这个测试位于图片提交失败后的补救流程。它确认 restore_blocked_image_submission 不只是把文字塞回去,还会恢复用户选择过的提及信息。
调用图:外部调用 5 个(new, assert!, assert_eq!, format!, vec!)。
blocked_image_restore_with_remote_images_keeps_local_placeholder_mapping651–692 ↗
async fn blocked_image_restore_with_remote_images_keeps_local_placeholder_mapping()
作用:测试图片提交被拦截后恢复输入框时,远程图片存在也不会打乱本地图片占位符和路径的对应关系。
数据流:它准备两个本地图片占位符、两个本地图片路径和一个远程图片网址,然后执行被拦截消息恢复。恢复后检查输入框文字、文字标记、本地图片列表和远程图片列表都保持原样。
调用关系:这个测试关注失败恢复时的图片编号稳定性。它补充了正常提交场景,确保出错回退也不会把用户图片配错。
调用图:外部调用 4 个(new, assert_eq!, format!, vec!)。
queued_restore_with_remote_images_keeps_local_placeholder_mapping695–737 ↗
async fn queued_restore_with_remote_images_keeps_local_placeholder_mapping()
作用:测试把排队中的用户消息恢复到输入框时,本地图片占位符和远程图片都能原样回来。排队消息就是任务正在跑时,用户提前写好的下一条消息。
数据流:它构造一条包含本地图片、远程图片和文字标记的 UserMessage,然后调用恢复到输入框的函数。结果输入框文字、光标位置、文字标记、本地图片和远程图片都与原消息一致。
调用关系:这个测试覆盖队列消息编辑或中断恢复流程。它确认 restore_user_message_to_composer 是完整恢复,而不是只恢复纯文本。
调用图:外部调用 4 个(new, assert_eq!, format!, vec!)。
interrupted_turn_restore_keeps_active_mode_for_resubmission740–784 ↗
async fn interrupted_turn_restore_keeps_active_mode_for_resubmission()
作用:测试一次任务被打断后,把排队消息恢复到输入框再提交时,仍然保留之前选中的协作模式。协作模式可以理解成模型工作的不同姿态,比如“计划模式”。
数据流:它启用协作模式,选择计划模式,模拟任务运行并放入一条排队消息。打断后消息回到输入框,队列清空;再次按 Enter 时,发出的用户请求仍带着同一个协作模式。
调用关系:这个测试串起任务开始、消息排队、任务中断、恢复输入框、重新提交的完整流程。它防止中断恢复时把用户当前工作模式弄丢。
调用图:调用 2 个内部函数(new, plan_mask);外部调用 5 个(from, new, assert!, assert_eq!, panic!)。
remap_placeholders_uses_attachment_labels787–853 ↗
async fn remap_placeholders_uses_attachment_labels()
作用:测试重新编号图片占位符时,会优先使用附件自己记录的原始标签来判断对应关系。这样即使图片在文字里的出现顺序和附件列表顺序不同,也能配对正确。
数据流:它准备文字“[Image #2] before [Image #1]”,附件列表里记录 #1 和 #2 各自路径,并设置下一个编号从 3 开始。重映射后,附件得到新标签 #3、#4,文字也变成对应的新占位符。
调用关系:这个测试直接验证 remap_placeholders_for_message 的核心规则。它服务于多条消息合并或恢复时的图片编号刷新流程。
调用图:外部调用 4 个(new, assert_eq!, format!, vec!)。
remap_placeholders_uses_byte_ranges_when_placeholder_missing856–915 ↗
async fn remap_placeholders_uses_byte_ranges_when_placeholder_missing()
作用:测试如果文字标记里没有保存占位符文本,系统还能靠字节范围找到旧占位符并重新编号。字节范围就是这段文字在整条字符串里的起止位置。
数据流:它准备同样的两张图片和文字,但 TextElement 里不带 placeholder 值,只带位置范围。重映射后,文字、文字标记和附件标签仍然被正确改成新的编号。
调用关系:这个测试覆盖数据不完整时的兜底路径。它保证 remap_placeholders_for_message 不依赖单一字段,旧数据或简化数据也能处理。
调用图:外部调用 4 个(new, assert_eq!, format!, vec!)。
empty_enter_during_task_does_not_queue918–929 ↗
output_free_interrupted_turn_requests_prompt_restore932–948 ↗
async fn output_free_interrupted_turn_requests_prompt_restore()
作用:测试如果一次被取消的任务还没有产生可见输出,打断后应该恢复用户原来的提示词。这样用户可以继续编辑刚才那句话,而不是白白丢掉。
数据流:它先记录一个可恢复的用户提示词,模拟任务开始,再提交“如果无输出就恢复提示词”的打断命令。任务中断后,事件通道里应该收到 RestoreCancelledTurn,内容就是原提示词。
调用关系:这个测试覆盖取消编辑的理想恢复路径。它连接 AppCommand 的打断意图、Op::Interrupt 的发送,以及中断事件后的恢复通知。
调用图:调用 1 个内部函数(from);外部调用 2 个(assert_matches!, interrupt_and_restore_prompt_if_no_output)。
visible_output_prevents_cancelled_turn_prompt_restore951–963 ↗
async fn visible_output_prevents_cancelled_turn_prompt_restore()
作用:测试如果模型已经产生了可见回答,取消后就不要再恢复原提示词。因为这时用户已经看到结果,恢复旧输入可能反而造成混乱。
数据流:它记录一个可恢复提示词,模拟任务开始,并注入一段可见输出。随后触发打断和中断处理,最后确认没有 RestoreCancelledTurn 事件出现。
调用关系:这个测试是上一条恢复测试的反面场景。它说明恢复提示词的条件不是“只要取消就恢复”,而是“没有实际输出才恢复”。
调用图:调用 1 个内部函数(from);外部调用 2 个(assert!, interrupt_and_restore_prompt_if_no_output)。
thinking_status_keeps_cancelled_turn_prompt_restore_eligible966–977 ↗
async fn thinking_status_keeps_cancelled_turn_prompt_restore_eligible()
作用:测试只有“正在思考”这类状态文字时,仍然允许取消后恢复提示词。因为思考状态不算真正给用户的回答。
数据流:它记录提示词,模拟任务开始,再注入 reasoning delta,也就是模型内部推理/思考状态。打断并中断后,检查恢复事件仍然发出,并带回原提示词。
调用关系:这个测试细分“输出”的含义。它保证 ChatWidget 不会把思考提示误当成可见回答,从而错误阻止恢复。
调用图:调用 1 个内部函数(from);外部调用 2 个(assert_matches!, interrupt_and_restore_prompt_if_no_output)。
patch_activity_prevents_cancelled_turn_prompt_restore980–992 ↗
async fn patch_activity_prevents_cancelled_turn_prompt_restore()
作用:测试如果任务已经开始应用补丁,取消后不恢复提示词。补丁活动说明系统已经对文件做了实质动作,不能简单当作“无输出”。
数据流:它记录可恢复提示词,模拟任务开始,然后触发补丁应用开始事件。打断和中断后,它遍历事件通道,确认没有恢复提示词事件。
调用关系:这个测试保护文件修改场景。它告诉取消恢复逻辑:只要有补丁动作发生,就不能把这轮当作完全没发生过。
调用图:调用 1 个内部函数(from);外部调用 3 个(new, assert!, interrupt_and_restore_prompt_if_no_output)。
pending_steer_esc_does_not_steal_vim_insert_escape995–1021 ↗
async fn pending_steer_esc_does_not_steal_vim_insert_escape()
作用:测试在 Vim 输入模式下,第一次 Esc 应该先退出插入模式,而不是被“待发送引导消息”的中断逻辑抢走。Vim 模式是一种仿照 Vim 编辑器的键盘操作方式。
数据流:它让任务处于运行中,并放入一个 pending steer(任务运行时用户准备追加的引导消息)。开启 Vim 模式并进入插入状态后按 Esc,第一次只退出插入模式、不发中断;第二次 Esc 才触发中断并标记中断后发送 pending steer。
调用关系:这个测试处理两个按键系统抢同一个 Esc 的冲突。它确认 ChatWidget 会先尊重编辑器模式,再处理任务中断。
调用图:外部调用 5 个(Char, new, assert!, assert_eq!, panic!)。
pending_steer_interrupt_uses_remapped_binding1024–1047 ↗
async fn pending_steer_interrupt_uses_remapped_binding()
作用:测试如果用户把“中断任务”的快捷键改成 F12,那么 pending steer 的中断也必须使用 F12,而不是固定用 Esc。
数据流:它修改运行时快捷键,把 interrupt_turn 设为 F12,并让任务运行、放入 pending steer。按 Esc 不触发中断;按 F12 才发出 Op::Interrupt,并标记中断后提交 pending steer。
调用关系:这个测试覆盖可配置按键。它保证 ChatWidget、底部输入区和 pending steer 中断逻辑使用同一份 keymap(快捷键表)。
调用图:调用 1 个内部函数(defaults);外部调用 5 个(F, new, assert!, panic!, vec!)。
restore_thread_input_state_syncs_sleep_inhibitor_state1050–1079 ↗
async fn restore_thread_input_state_syncs_sleep_inhibitor_state()
作用:测试恢复线程输入状态时,防止电脑休眠的状态也会同步更新。sleep inhibitor 可以理解成“任务运行时别让电脑睡着”的开关。
数据流:它启用防休眠功能,然后恢复一个表示任务正在运行的 ThreadInputState。检查任务运行标记、sleep_inhibitor 和底部面板都变成运行中;再恢复 None,三者都变回未运行。
调用关系:这个测试覆盖线程切换或状态恢复流程。它确保界面状态、生命周期状态和系统防休眠状态不会彼此不一致。
alt_up_edits_most_recent_queued_message1082–1115 ↗
async fn alt_up_edits_most_recent_queued_message()
作用:测试按 Alt+Up 可以编辑最近一条排队消息。用户在任务运行时排了多条消息,应该能把最后一条拿回输入框修改。
数据流:它设定 Alt+Up 为编辑排队消息快捷键,让任务运行,并放入两条排队消息。按 Alt+Up 后,输入框变成第二条消息,队列里只剩第一条。
调用关系:这个测试覆盖排队消息的快捷编辑入口。它确认 refresh_pending_input_preview 后,按键处理会从队尾取出最新消息恢复到输入框。
调用图:调用 2 个内部函数(from, alt);外部调用 3 个(new, assert_eq!, vec!)。
unbound_queued_message_edit_does_not_fall_back_to_alt_up1118–1134 ↗
async fn unbound_queued_message_edit_does_not_fall_back_to_alt_up()
作用:测试如果用户没有绑定“编辑排队消息”快捷键,Alt+Up 不会偷偷变成默认行为。这样可以尊重用户明确取消快捷键的设置。
数据流:它清空编辑排队消息的快捷键配置,任务运行并放入一条排队消息。按 Alt+Up 后,输入框仍为空,队列消息仍在。
调用关系:这个测试和 Alt+Up 编辑测试互为补充。它保证快捷键系统不会在解绑后回退到隐藏默认值。
调用图:调用 1 个内部函数(from);外部调用 4 个(new, new, assert!, assert_eq!)。
shift_left_edits_most_recent_queued_message_in_apple_terminal1137–1146 ↗
async fn shift_left_edits_most_recent_queued_message_in_apple_terminal()
作用:测试在 Apple Terminal 里,Shift+Left 能用来编辑最近一条排队消息。某些终端对 Alt+Up 支持不好,所以需要替代快捷键。
数据流:它把终端信息设为 Apple Terminal,然后调用共享断言函数。共享函数会模拟排队消息并确认 Shift+Left 能把最新消息拿回输入框。
调用关系:这个测试是终端兼容性测试之一。它把具体终端信息交给通用测试逻辑,验证快捷键映射适合 Apple Terminal。
shift_left_edits_most_recent_queued_message_in_warp_terminal1149–1158 ↗
async fn shift_left_edits_most_recent_queued_message_in_warp_terminal()
作用:测试在 Warp 终端里,Shift+Left 能编辑最近排队消息。这样 Warp 用户也能用可靠的快捷键改队列内容。
数据流:它构造 Warp 终端信息,并交给共享测试函数。共享函数负责设置聊天界面、排队消息、按键并检查结果。
调用关系:这个测试属于特殊终端快捷键覆盖。它复用同一套断言,专门确认 Warp 的映射没有遗漏。
shift_left_edits_most_recent_queued_message_in_vscode_terminal1161–1170 ↗
async fn shift_left_edits_most_recent_queued_message_in_vscode_terminal()
作用:测试在 VS Code 内置终端里,Shift+Left 能编辑最近排队消息。VS Code 终端的按键表现也需要单独照顾。
数据流:它把终端名称设为 VsCode,然后运行共享的“Shift+Left 编辑队列”检查。预期是最新排队消息进入输入框,队列减少一条。
调用关系:这个测试保证 VS Code 终端走特殊快捷键路径。它和 Apple Terminal、Warp、tmux 的测试一起覆盖常见终端差异。
shift_left_edits_most_recent_queued_message_in_tmux1173–1182 ↗
async fn shift_left_edits_most_recent_queued_message_in_tmux()
作用:测试在 tmux 里,Shift+Left 能编辑最近排队消息。tmux 是终端复用器,可以把一个终端分成多个会话,按键传递常有差异。
数据流:它构造一个带 tmux 标记的终端信息,然后运行共享断言。共享断言检查 Shift+Left 是否能把最新排队消息恢复到输入框。
调用关系:这个测试覆盖终端复用器场景。它确认即使底层终端是 iTerm2,只要外面包了 tmux,也使用更兼容的 Shift+Left。
queued_message_edit_binding_mapping_covers_special_terminals_and_tmux1185–1236 ↗
fn queued_message_edit_binding_mapping_covers_special_terminals_and_tmux()
作用:测试不同终端应该使用哪个“编辑排队消息”快捷键。特殊终端和 tmux 用 Shift+Left,普通 iTerm2 用 Alt+Up。
数据流:它分别构造 Apple Terminal、Warp、VS Code、iTerm2+tmux、普通 iTerm2 的终端信息,调用映射函数并比较返回的快捷键。结果必须符合预期表。
调用关系:这个测试直接检查快捷键选择规则。前面几个实际按键测试验证行为,这里验证映射表本身。
调用图:外部调用 1 个(assert_eq!)。
enqueueing_history_prompt_multiple_times_is_stable1242–1267 ↗
async fn enqueueing_history_prompt_multiple_times_is_stable()
作用:测试从历史记录召回同一条提示词并多次排队时,每次排进去的文字都稳定一致。这样用户反复复用历史命令不会出现内容变形。
数据流:它先提交一条“repeat me”作为历史记录,然后模拟任务运行。循环三次:按 Up 召回历史,确认输入框是 repeat me,再按 Tab 排队。最后检查队列里有三条,且每条文本都一样。
调用关系:这个测试覆盖历史召回和任务排队的组合。它防止输入框在重复召回、提交、清空之间留下脏状态。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, new, assert_eq!)。
submit_user_message_ignores_inaccessible_app_mentions_from_bindings1270–1323 ↗
async fn submit_user_message_ignores_inaccessible_app_mentions_from_bindings()
作用:测试用户消息里绑定了不可访问的应用提及时,提交时不要把它当成有效 Mention 发送。不可访问应用可能只是目录里看得到,但当前账号不能用。
数据流:它启用 Apps 功能,加载一个不可访问但已启用的应用,然后提交带 $arabica-uae 绑定的消息。最后检查发出的内容只有普通文本,没有额外的 UserInput::Mention。
调用关系:这个测试保护应用提及过滤逻辑。它说明 submit_user_message 会结合连接器加载结果判断应用是否真的可用,而不是盲目信任输入框绑定。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, panic!, vec!)。
user_message_display_from_inputs_matches_flattened_user_message_shape1326–1367 ↗
fn user_message_display_from_inputs_matches_flattened_user_message_shape()
作用:测试把多个 UserInput 合成用户历史显示时,结果和“扁平化后的用户消息”形状一致。扁平化就是把分散的文字、图片、技能、提及整理成一条可显示消息。
数据流:它准备一组输入:两段文字、远程图片、本地图片、技能和应用提及。调用 user_message_display_from_inputs 后,检查显示结果只拼接文字、保留文字标记、收集图片,并与 user_message_display_from_parts 的结果相同。
调用关系:这个测试直接验证 ChatWidget 的显示转换函数。它保证后台请求里的多种输入项,最终能变成历史区可理解的用户消息。
调用图:外部调用 4 个(from, assert_eq!, user_message_display_from_inputs, vec!)。
user_message_display_from_inputs_hides_prompt_context1370–1393 ↗
fn user_message_display_from_inputs_hides_prompt_context()
作用:测试用户历史显示会隐藏 IDE 注入的上下文,只显示真正的用户请求。IDE 上下文是编辑器自动附带的信息,比如当前文件名,不一定应显示给用户当作消息正文。
数据流:它构造一段包含“Context from my IDE setup”和“My request for Codex”的原始文本,并带一个 $figma 标记。转换显示后,结果只剩“Ask $figma”,而且文字标记位置也重新对齐。
调用关系:这个测试覆盖显示层的清理规则。它确保 user_message_display_from_inputs 在渲染历史时会剥掉隐藏上下文,同时保留用户真正输入里的提及标记。
调用图:外部调用 3 个(assert_eq!, user_message_display_from_inputs, vec!)。
interrupt_restores_queued_messages_into_composer1433–1466 ↗
async fn interrupt_restores_queued_messages_into_composer()
作用:测试任务被打断时,已经排队的用户消息会回到输入框里,方便用户重新编辑或发送。否则排队内容可能在中断后消失。
数据流:它模拟任务运行,并放入两条排队消息。触发任务中断后,输入框内容变成两条消息按顺序用换行连接,队列被清空,也没有自动发出新的用户请求。
调用关系:这个测试覆盖中断后的队列恢复流程。它确认 handle_turn_interrupted 的职责是把待发内容还给用户,而不是继续悄悄发送。
调用图:调用 1 个内部函数(from);外部调用 2 个(assert!, assert_eq!)。
interrupt_prepends_queued_messages_before_existing_composer_text1469–1497 ↗
async fn interrupt_prepends_queued_messages_before_existing_composer_text()
作用:测试任务中断时,如果输入框里已经有草稿,排队消息会放在草稿前面一起恢复。这样既不丢队列,也不覆盖用户当前正在写的内容。
数据流:它让任务运行,输入框已有“current draft”,队列里有两条消息。中断后输入框变成“first queued\nsecond queued\ncurrent draft”,队列清空,没有额外提交操作。
调用关系:这个测试是队列恢复测试的扩展场景。它确认中断恢复会合并两类未发送内容,并保持排队消息在前、当前草稿在后的顺序。
调用图:调用 1 个内部函数(from);外部调用 3 个(new, assert!, assert_eq!)。
tui/src/chatwidget/tests/exec_flow.rs源码 ↗
这个文件不是真正给用户运行的功能代码,而是测试聊天窗口在各种复杂场景下的表现。聊天界面会遇到很多容易出错的情况:模型想运行命令时要不要弹审批框;命令完成后历史记录怎么写;后台终端一直没输出时状态栏怎么提示;用户输入以感叹号开头的 shell 命令时应该发给本地执行,而不是当成普通聊天;补丁审批和图片工具也要生成清楚的历史记录。这里的测试会造出假的事件,喂给 ChatWidget,然后检查它渲染出来的文字、历史单元、发给后端的操作是否符合预期。很多测试用“快照测试”,意思是把界面输出保存成标准答案,以后输出变了就能立刻发现。
exec_approval_emits_proposed_command_and_decision_history5–47 ↗
async fn exec_approval_emits_proposed_command_and_decision_history()
作用:检查当系统请求用户批准一条命令时,界面先弹出审批窗口,而不是直接把请求写进聊天历史;用户按下同意后,才写入一条简短的决定记录。
数据流:输入是一条假的执行审批请求,里面有命令、目录和理由;测试把它交给聊天组件,读取历史输出并渲染界面;最后模拟用户按 y,同意结果会变成一条历史记录,并被拿去和快照标准答案比较。
调用关系:它验证审批流程的前半段:handle_exec_approval_request 把请求放进界面状态,chat.render 显示弹窗,chat.handle_key_event 处理用户决定,drain_insert_history 收集最终写入历史的内容。
调用图:调用 1 个内部函数(current_dir);外部调用 7 个(Char, new, new, assert!, assert_chatwidget_snapshot!, empty, vec!)。
app_server_exec_approval_request_splits_shell_wrapped_command50–83 ↗
fn app_server_exec_approval_request_splits_shell_wrapped_command()
作用:检查从应用服务器来的命令字符串,如果是“shell 外壳包了一层”的形式,能被正确拆回命令数组。
数据流:输入是一段像 /bin/zsh -lc '脚本' 的命令字符串和工作目录;转换函数把字符串解析成多个参数;输出应该是 shell 路径、-lc、原始脚本这三个部分,而不是一整坨字符串。
调用关系:它保护 exec_approval_request_from_params 这个转换入口,确保后端协议里的命令到前端审批事件时不会丢失参数边界。
调用图:外部调用 2 个(assert_eq!, try_join)。
exec_approval_uses_approval_id_when_present86–128 ↗
async fn exec_approval_uses_approval_id_when_present()
作用:确认审批请求里如果有单独的 approval_id,用户批准时发回去的就是这个 id,而不是父级 call_id。
数据流:输入是一条 call_id 和 approval_id 不同的审批请求;测试模拟用户按 y;输出通道里应该出现一个 ExecApproval 操作,里面的 id 是 approval_id,决定是接受。
调用关系:它覆盖用户按键到提交操作这条链路,防止后端需要用 approval_id 对账时,前端错用了 call_id。
调用图:调用 1 个内部函数(current_dir);外部调用 6 个(Char, new, assert!, assert_eq!, assert_matches!, vec!)。
exec_approval_decision_truncates_multiline_and_long_commands131–215 ↗
async fn exec_approval_decision_truncates_multiline_and_long_commands()
作用:检查审批决定写入历史时,命令摘要不会太长,也不会把多行命令原样塞进一行历史里。
数据流:输入分别是多行命令和超长单行命令;界面先用弹窗展示审批请求,用户按 n 拒绝;输出历史里只保留被压短的单行摘要,并用快照确认格式。
调用关系:它连接审批弹窗、拒绝按键和历史摘要渲染,确保详细命令留在弹窗里,历史记录保持干净好读。
调用图:调用 1 个内部函数(current_dir);外部调用 9 个(Char, new, new, new, assert!, assert_chatwidget_snapshot!, format!, empty, vec!)。
preamble_keeps_working_status_snapshot218–245 ↗
async fn preamble_keeps_working_status_snapshot()
作用:检查模型先输出一段开场白后,界面底部的“正在工作”状态仍然能恢复显示。
数据流:输入是一次任务开始、模型流式输出一行文字、提交到历史、再完成一条 commentary 消息;测试渲染整个聊天组件;输出是快照,用来确认开场白和状态栏同时存在。
调用关系:它覆盖任务开始、消息增量、提交 tick、助手消息完成和最终渲染之间的配合,防止开场白把工作状态挤没。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_chatwidget_snapshot!, new, new)。
unified_exec_begin_restores_status_indicator_after_preamble248–262 ↗
async fn unified_exec_begin_restores_status_indicator_after_preamble()
作用:检查后台统一执行开始时,如果状态指示器之前被隐藏了,会重新显示出来。
数据流:输入是一个正在运行但状态栏被手动隐藏的聊天组件;测试触发统一执行启动;输出是状态指示器重新变为可见。
调用关系:它专门保护 begin_unified_exec_startup 对底部状态栏的修复行为,避免后台命令开始后用户看不到系统还在忙。
调用图:外部调用 1 个(assert_eq!)。
unified_exec_begin_restores_working_status_snapshot265–287 ↗
async fn unified_exec_begin_restores_working_status_snapshot()
作用:检查在已有开场白历史后,启动统一执行命令会把“正在工作”的界面状态恢复到正确样子。
数据流:输入是任务开始和一段已提交的开场白;测试启动后台命令并渲染组件;输出快照确认历史和工作状态的组合显示正确。
调用关系:它和前一个测试类似,但更关注真实渲染结果,确保 begin_unified_exec_startup 后 chat.render 的画面没坏。
调用图:外部调用 4 个(new, assert_chatwidget_snapshot!, new, new)。
exec_history_cell_shows_working_then_completed290–317 ↗
async fn exec_history_cell_shows_working_then_completed()
作用:检查普通命令从开始到成功结束时,历史记录只在结束后写入,并显示命令内容。
数据流:输入是一条 echo done 命令的开始事件和成功结束事件;开始时历史输出为空;结束后输出一条已完成的历史单元,里面包含“Ran”和命令文本。
调用关系:它验证 begin_exec 与 end_exec 的基本配合:开始先留在活动区,结束才刷进历史。
调用图:外部调用 2 个(assert!, assert_eq!)。
exec_history_cell_shows_working_then_failed320–341 ↗
async fn exec_history_cell_shows_working_then_failed()
作用:检查命令失败时也会立刻生成历史记录,并显示错误输出。
数据流:输入是一条 false 命令的开始事件和带错误文本的失败结束事件;输出是一条历史单元,包含命令和错误信息。
调用关系:它补齐成功路径之外的失败路径,保证 end_exec 不会因为退出码非零而漏掉记录。
调用图:外部调用 2 个(assert!, assert_eq!)。
exec_end_without_begin_uses_event_command344–383 ↗
async fn exec_end_without_begin_uses_event_command()
作用:检查如果收到命令结束事件时没见过对应开始事件,界面仍能用结束事件里的命令内容生成历史记录。
数据流:输入是一条只有结束信息的命令执行事件,里面带完整命令、输出、状态和工作目录;测试处理后读取历史;输出应显示 echo orphaned,而不是显示内部 call id。
调用关系:它保护 handle_exec_end 的兜底能力:即使事件顺序不完整,也尽量给用户一个可读的结果。
调用图:调用 2 个内部函数(parse_command, shlex_join);外部调用 3 个(assert!, assert_eq!, vec!)。
overlapping_exploring_exec_end_is_not_misclassified_as_orphan468–497 ↗
async fn overlapping_exploring_exec_end_is_not_misclassified_as_orphan()
作用:检查多个探索命令重叠运行时,先结束的那个不会被误判成“没有开始事件的孤儿命令”。
数据流:输入是 ls 和 cat 两个连续开始的探索命令,先结束 ls;输出历史仍为空,活动区里保留两个命令的组合展示。
调用关系:它保护探索命令分组逻辑,确保 end_exec 能认出结束的是组内成员,而不是另起一条孤立记录。
调用图:外部调用 1 个(assert!)。
exec_history_shows_unified_exec_startup_commands500–530 ↗
async fn exec_history_shows_unified_exec_startup_commands()
作用:检查统一执行启动阶段的命令,在完成后也会像普通命令一样写进历史。
数据流:输入是一条来源为 UnifiedExecStartup 的命令开始和成功结束;开始时不刷历史;结束后输出一条包含命令文本的历史单元。
调用关系:它确认 begin_exec_with_source 和 end_exec 对特殊来源命令的显示规则仍然完整。
调用图:外部调用 2 个(assert!, assert_eq!)。
exec_history_shows_unified_exec_tool_calls533–547 ↗
async fn exec_history_shows_unified_exec_tool_calls()
作用:检查统一执行里的工具调用可以被归类成“探索”展示,而不是普通输出块。
数据流:输入是一条 ls 命令,来源是统一执行启动;执行结束后读取活动区;输出应是“Explored / List ls”这种简短探索摘要。
调用关系:它验证命令来源和命令解析结果会共同影响历史样式。
调用图:外部调用 1 个(assert_eq!)。
unified_exec_unknown_end_with_active_exploring_cell_snapshot550–576 ↗
async fn unified_exec_unknown_end_with_active_exploring_cell_snapshot()
作用:检查有活动探索记录时,又出现一个未知后台命令结束,历史区和活动区最终组合长什么样。
数据流:输入是一个活动的 cat 探索命令和一个孤立 echo 后台命令的结束;测试收集历史和活动区文本;输出快照锁定两者的边界。
调用关系:它把孤立结束处理和活动探索展示放在一起测,防止两类内容互相污染。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, format!)。
unified_exec_end_after_task_complete_is_suppressed579–601 ↗
async fn unified_exec_end_after_task_complete_is_suppressed()
作用:检查任务已经结束后,迟到的统一执行结束事件不会再往聊天历史里追加内容。
数据流:输入是一个统一执行命令开始,然后任务完成,再收到命令结束;输出历史应为空。
调用关系:它保护任务生命周期边界:handle_turn_completed 之后,end_exec 对这类迟到事件要保持安静。
调用图:外部调用 1 个(assert!)。
unified_exec_interaction_after_task_complete_is_suppressed604–618 ↗
async fn unified_exec_interaction_after_task_complete_is_suppressed()
作用:检查任务完成后,后台终端的迟到交互事件不会再显示出来。
数据流:输入是任务开始、任务完成、再来一条终端交互内容;输出历史为空。
调用关系:它和迟到结束事件测试配套,覆盖 terminal_interaction 在任务完成后的静默行为。
调用图:外部调用 1 个(assert!)。
unified_exec_wait_after_final_agent_message_snapshot621–637 ↗
async fn unified_exec_wait_after_final_agent_message_snapshot()
作用:检查后台终端等待状态出现在最终回答之后时,历史记录的顺序和样式是否正确。
数据流:输入是一个回合开始、后台命令启动、空终端交互、最终助手消息、回合完成;输出是历史单元拼成的快照。
调用关系:它覆盖后台等待、最终回答和回合完成三者的交界,确保“等待后台终端”不会把最终回答显示乱。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
unified_exec_wait_before_streamed_agent_message_snapshot640–661 ↗
async fn unified_exec_wait_before_streamed_agent_message_snapshot()
作用:检查后台终端等待发生在流式助手消息之前时,最终历史显示是否稳定。
数据流:输入是后台命令启动、空终端交互、随后助手流式输出文字、回合完成;输出用快照确认历史内容。
调用关系:它和上一条形成前后顺序对照,测试 handle_agent_message_delta 与统一执行等待状态的配合。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
final_worked_for_uses_cumulative_turn_duration_snapshot664–694 ↗
async fn final_worked_for_uses_cumulative_turn_duration_snapshot()
作用:检查最终分隔提示里的“工作了多久”使用整个回合的累计耗时,而不是某个单独步骤的时间。
数据流:输入是一轮任务、一次命令执行、最终回答,以及 125000 毫秒的回合耗时;输出历史里应出现“Worked for 2m 05s”,并匹配快照。
调用关系:它验证 handle_turn_completed 给最终回答收尾时使用的是回合级 duration_ms。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
unified_exec_wait_status_header_updates_on_late_command_display697–720 ↗
async fn unified_exec_wait_status_header_updates_on_late_command_display()
作用:检查后台终端已经存在但命令显示信息较晚才补上时,状态栏能更新成“等待后台终端”并显示命令。
数据流:输入是一个手动塞入的后台进程摘要和一次空终端交互;输出是状态标题变成 Waiting for background terminal,详情显示 sleep 5。
调用关系:它验证 terminal_interaction 会查 unified_exec_processes,并把 late command display 同步到底部状态组件。
调用图:外部调用 3 个(new, assert!, assert_eq!)。
unified_exec_empty_poll_for_finished_process_does_not_show_waiting_status723–736 ↗
async fn unified_exec_empty_poll_for_finished_process_does_not_show_waiting_status()
作用:检查对已经结束或未知的后台进程做空轮询时,不会误显示“等待后台终端”。
数据流:输入是任务运行中收到一个空的终端交互,但没有对应活跃进程;输出状态仍是 Working,等待计数也为空。
调用关系:它防止 terminal_interaction 把无意义的空消息误当成后台进程仍在等待。
调用图:外部调用 2 个(assert!, assert_eq!)。
unified_exec_waiting_multiple_empty_snapshots739–765 ↗
async fn unified_exec_waiting_multiple_empty_snapshots()
作用:检查同一个后台进程连续多次没有输出时,状态栏和回合结束后的历史记录是否正确。
数据流:输入是后台命令启动和两次空终端交互;中间状态应显示等待后台终端和命令详情;回合完成后输出快照。
调用关系:它测试 unified_exec_wait_streak 这类“连续空等待”的状态累积和收尾展示。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
unified_exec_wait_status_renders_command_in_single_details_row_snapshot768–785 ↗
async fn unified_exec_wait_status_renders_command_in_single_details_row_snapshot()
作用:检查等待后台终端的状态弹窗里,长命令会放在单独一行详情里显示。
数据流:输入是一条很长的后台命令和一次空交互;测试渲染底部弹窗;输出快照确认命令没有把标题挤乱。
调用关系:它聚焦底部状态 UI 的排版,验证 render_bottom_popup 对等待状态详情行的处理。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
unified_exec_empty_then_non_empty_snapshot788–802 ↗
async fn unified_exec_empty_then_non_empty_snapshot()
作用:检查后台终端先没输出、后来有输出时,历史记录怎样从等待状态转成实际交互记录。
数据流:输入是后台命令启动、一次空交互、一次包含 ls 的交互;输出历史拼接后用快照确认。
调用关系:它验证 terminal_interaction 对空内容和非空内容的状态切换。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
unified_exec_non_empty_then_empty_snapshots805–845 ↗
async fn unified_exec_non_empty_then_empty_snapshots()
作用:检查后台终端先有输出、后来又进入空等待时,活动记录和回合完成后的记录如何显示。
数据流:输入是后台命令启动、一次 pwd 输出、一次空交互、再回合完成;测试先拍活动阶段快照,再拍完成后的组合快照。
调用关系:它覆盖“非空交互后继续等待”的较复杂路径,确保状态栏和历史单元都能正确收尾。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
view_image_tool_call_adds_history_cell848–858 ↗
async fn view_image_tool_call_adds_history_cell()
作用:检查查看本地图片的工具调用会在聊天历史里添加一条图片相关记录。
数据流:输入是一个图片路径;处理函数把它转成历史单元;输出应只有一条历史记录,并用快照确认显示内容。
调用关系:它验证 handle_view_image_tool_call 能把工具事件变成用户可见的聊天历史。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
image_generation_call_adds_history_cell861–895 ↗
async fn image_generation_call_adds_history_cell()
作用:检查图片生成工具无论成功还是失败,都能生成清楚的历史记录。
数据流:输入先是一条成功的图片生成结束事件,带提示词和保存路径;再输入一条失败事件,没有保存路径;输出分别是一条成功历史和一条失败历史。
调用关系:它覆盖 handle_image_generation_end 的两种结局,确保生成图片和失败提示都不会丢。
调用图:外部调用 3 个(assert_chatwidget_snapshot!, assert_eq!, from_file_path)。
exec_history_extends_previous_when_consecutive898–944 ↗
async fn exec_history_extends_previous_when_consecutive()
作用:检查连续的文件探索命令会合并到同一个探索历史块里,而不是每条命令都散成独立记录。
数据流:输入是一串 ls、cat、sed、cat 命令的开始和结束;每一步读取活动区文本;输出多个快照确认探索块逐步扩展。
调用关系:它重点保护探索分组逻辑:begin_exec 和 end_exec 连续调用时,会把同类读取、列目录动作串成一个清楚的过程。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
user_shell_command_renders_output_not_exploring947–972 ↗
async fn user_shell_command_renders_output_not_exploring()
作用:检查用户自己运行的 shell 命令会显示真实输出,而不是被归类成模型的“探索文件”摘要。
数据流:输入是一条来源为 UserShell 的 ls 命令和两行输出;输出是一条历史记录,快照确认里面显示文件输出。
调用关系:它区分用户命令和模型工具命令,防止同样是 ls,却因为来源不同而显示错样式。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
bang_shell_enter_while_task_running_submits_run_user_shell_command975–1019 ↗
async fn bang_shell_enter_while_task_running_submits_run_user_shell_command()
作用:检查任务正在运行时,用户在输入框输入 !echo hi 并回车,会提交“运行用户 shell 命令”的操作。
数据流:输入是一个带线程配置的聊天组件、一个正在进行的回合,以及输入框里的 !echo hi;按回车后输出通道收到 RunUserShellCommand,聊天历史也追加原始输入。
调用关系:它验证输入框、线程状态、感叹号命令识别和 op 通道之间的完整链路。
调用图:调用 2 个内部函数(read_only, new);外部调用 7 个(new, new, default, new, assert_eq!, assert_matches!, panic!)。
user_message_during_user_shell_command_is_queued_not_steered1022–1061 ↗
async fn user_message_during_user_shell_command_is_queued_not_steered()
作用:检查只有用户 shell 命令还在跑时,普通聊天消息会先排队,而不是立刻发出去干扰当前回合。
数据流:输入是一个正在运行的用户 shell 命令和用户输入 hi;按回车后不立刻发操作,而是进入队列;命令结束、助手最终消息完成、回合结束后,队列里的 hi 被提交成新的用户回合。
调用关系:它保护 input_queue 的行为,确保 shell 命令执行期间的普通消息不会被误当作“ steer 当前任务”的指令。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, new, assert!, assert_eq!, assert_matches!, panic!)。
disabled_slash_command_while_task_running_snapshot1064–1080 ↗
async fn disabled_slash_command_while_task_running_snapshot()
作用:检查任务运行中输入某些不可用的斜杠命令时,界面会给出错误提示。
数据流:输入是一个正在运行任务的聊天组件和 /model 命令;处理后历史里出现错误信息;输出用快照确认提示文字。
调用关系:它验证 dispatch_command 会根据任务状态拒绝不合适的命令,并把原因显示给用户。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
approval_modal_exec_snapshot1088–1142 ↗
async fn approval_modal_exec_snapshot() -> anyhow::Result<()>
作用:检查命令审批弹窗的完整视觉效果,包括命令、理由和“以后不要再问”之类的选项。
数据流:输入是一条带理由和执行策略建议的命令审批请求;测试渲染到固定大小的虚拟终端;输出终端内容必须包含命令,并匹配快照。
调用关系:它覆盖 handle_exec_approval_request 到真实终端渲染的路径,是审批弹窗外观的基准测试。
调用图:调用 3 个内部函数(with_options, new, current_dir);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, vec!)。
approval_modal_exec_without_reason_snapshot1147–1185 ↗
async fn approval_modal_exec_without_reason_snapshot() -> anyhow::Result<()>
作用:检查命令审批弹窗在没有理由文字时,间距和布局仍然正常。
数据流:输入是一条 reason 为空的命令审批请求;渲染固定终端;输出快照用于确认没有多余空洞或错位。
调用关系:它补充审批弹窗的无理由分支,防止 UI 只在有 reason 时好看。
调用图:调用 2 个内部函数(new, current_dir);外部调用 4 个(new, assert_chatwidget_snapshot!, new, vec!)。
approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot1190–1231 ↗
async fn approval_modal_exec_multiline_prefix_hides_execpolicy_option_snapshot() -> anyhow::Result<()>
作用:检查当建议加入执行策略的命令前缀是多行时,弹窗不会提供“以后不要再问”的策略选项。
数据流:输入是一条多行脚本形式的命令审批请求,并带 proposed_execpolicy_amendment;渲染后检查内容不包含 don't ask again;输出快照锁定弹窗样子。
调用关系:它保护审批策略的安全边界:多行命令不适合简单加入白名单,所以 UI 不应给这个选择。
调用图:调用 2 个内部函数(new, current_dir);外部调用 5 个(new, assert!, assert_chatwidget_snapshot!, new, vec!)。
approval_modal_patch_snapshot1235–1272 ↗
async fn approval_modal_patch_snapshot() -> anyhow::Result<()>
作用:检查补丁审批弹窗的外观,确认它展示的是“是否允许这些编辑”,而不是像命令一样显示 apply_patch 命令行。
数据流:输入是一个新增 README.md 的补丁审批请求,带理由和授权目录;测试渲染弹窗;输出不能包含 $ apply_patch,并匹配快照。
调用关系:它验证 handle_apply_patch_approval_request 对补丁类审批使用专门 UI,而不是复用命令审批文案。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, from, new, assert!, assert_chatwidget_snapshot!, new)。
interrupt_preserves_unified_exec_processes1275–1303 ↗
async fn interrupt_preserves_unified_exec_processes()
作用:检查回合被中断后,后台终端进程列表不会被清空,用户仍能用 /ps 查看它们。
数据流:输入是两个后台进程启动,然后回合中断;测试确认进程数量仍是两个,再调用 /ps 输出;历史里应列出这两个后台终端和命令。
调用关系:它验证 handle_turn_interrupted 不会删除 unified_exec_processes,并且 add_ps_output 还能读取这些信息。
调用图:外部调用 2 个(assert!, assert_eq!)。
interrupt_preserves_unified_exec_wait_streak_snapshot1306–1325 ↗
async fn interrupt_preserves_unified_exec_wait_streak_snapshot()
作用:检查回合中断后,后台终端的等待状态仍能保留,并在命令结束时按预期生成记录。
数据流:输入是回合开始、后台命令启动、一次空等待、回合中断、命令结束;输出历史条数和内容组合成快照。
调用关系:它保护中断场景下 unified_exec_wait_streak 的连续性,避免中断后后台命令收尾显示异常。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, format!)。
turn_complete_keeps_unified_exec_processes1328–1356 ↗
async fn turn_complete_keeps_unified_exec_processes()
作用:检查普通回合完成后,后台终端进程列表同样不会被清空。
数据流:输入是两个后台进程启动,然后回合完成;测试确认列表仍在,再调用 /ps;输出历史应能列出两个后台命令。
调用关系:它和中断测试配套,说明后台终端的生命周期可以长过一个聊天回合。
调用图:外部调用 2 个(assert!, assert_eq!)。
apply_patch_events_emit_history_cells1359–1420 ↗
apply_patch_manual_approval_adjusts_header1423–1462 ↗
apply_patch_manual_flow_snapshot1465–1508 ↗
async fn apply_patch_manual_flow_snapshot()
作用:检查手动审批补丁的完整历史显示,尤其是审批弹窗之后开始应用时写出的那条记录。
数据流:输入是一条带理由的补丁审批请求;审批请求本身不产生历史;随后补丁开始应用,输出一条历史单元并进行快照比较。
调用关系:它用快照保护手动补丁流程的最终用户可见文字。
调用图:外部调用 4 个(new, from, assert!, assert_chatwidget_snapshot!)。
apply_patch_approval_sends_op_with_call_id1511–1551 ↗
async fn apply_patch_approval_sends_op_with_call_id()
作用:检查用户批准补丁时,发给后端的 PatchApproval 操作用的是补丁调用的 call_id。
数据流:输入是一条补丁审批请求,随后模拟用户按 y;输出事件通道里应出现 PatchApproval,id 是 call-999,决定是接受。
调用关系:它验证补丁审批弹窗的按键结果能正确转成线程操作,且 id 没有误用订阅 id。
调用图:外部调用 7 个(new, Char, new, from, assert!, assert_eq!, assert_matches!)。
apply_patch_full_flow_integration_like1554–1621 ↗
async fn apply_patch_full_flow_integration_like()
作用:用接近真实集成的方式检查补丁审批从后端请求、用户批准、前端转发、再收到应用事件的整条链路。
数据流:输入是补丁审批请求;用户按 y 后产生线程级操作;测试再调用 submit_op 把操作转发到后端通道;最后模拟补丁开始和结束事件。
调用关系:它把多个小流程串起来,确认 AppEvent、Op、chat.submit_op 和补丁事件处理之间能顺畅衔接。
调用图:外部调用 7 个(new, Char, new, from, assert_eq!, assert_matches!, panic!)。
apply_patch_untrusted_shows_approval_modal1624–1672 ↗
async fn apply_patch_untrusted_shows_approval_modal() -> anyhow::Result<()>
作用:检查在需要手动审批的权限策略下,补丁请求会显示审批弹窗。
数据流:输入是 OnRequest 审批策略和一个新增 a.rs 的补丁请求;测试渲染界面缓冲区;输出画面中应包含“Would you like to make the following edits?”这个标题。
调用关系:它验证权限策略和 handle_apply_patch_approval_request 的配合,确保不受信任补丁不会静默通过。
apply_patch_request_omits_diff_summary_from_modal1675–1725 ↗
async fn apply_patch_request_omits_diff_summary_from_modal() -> anyhow::Result<()>
作用:检查补丁审批弹窗不会直接塞入详细 diff 摘要或新增行内容,避免弹窗过长、重复或泄露太多细节。
数据流:输入是一个给 README.md 新增两行的补丁审批请求;审批请求不写历史,测试渲染弹窗并收集文字;输出中不应包含 README.md (+2 -0) 或具体新增行。
调用关系:它保护补丁审批弹窗的信息取舍:弹窗只做确认,不把完整差异摘要混进去。
tui/src/chatwidget/tests/goal_validation.rs源码 ↗
这个文件是一组自动化测试。它把 ChatWidget(聊天输入和显示区域)当成一个真实用户会操作的界面来用:往输入框里填 /goal ...,按回车,按 Tab 排队,或者粘贴很长的文字。测试关注的是目标草稿事件,也就是系统应该发出“我要设置这个线程的目标”这类消息,而不是把内容提交给模型当普通问题。它还特别检查边界情况:目标刚好到长度上限、超过上限、多行目标、很大的粘贴内容、以及正在回答时排队的 /goal。辅助函数像小道具一样模拟用户动作,测试函数则检查事件队列和提交队列,确保目标内容原样保留,粘贴占位符只对应真正插入的粘贴内容,并且不会多发普通提交请求。
complete_turn_with_message5–15 ↗
fn complete_turn_with_message(chat: &mut ChatWidget, turn_id: &str, message: Option<&str>)
作用:这个辅助函数用来假装一次助手回复已经结束了。测试里需要先让聊天处于“正在回答”的状态,再用它收尾,才能检查排队输入接下来会怎么被处理。
数据流:进去的是一个聊天界面对象、一次回答的编号,以及可选的助手消息文本。它如果拿到了消息,就先把这段消息标成最终回答放进聊天里;然后通知聊天界面这一轮已经完成。出来没有返回值,但聊天界面的状态会从“这一轮进行中”变成“这一轮结束”。
调用关系:它只在 queued_goal_slash_command_emits_oversized_objective_and_stops_queue 里使用。那个测试先制造一个正在进行的回答,再排队两个输入,最后调用它触发“回答结束后处理队列”的流程。
调用图:被 1 处调用(queued_goal_slash_command_emits_oversized_objective_and_stops_queue);外部调用 1 个(format!)。
submit_composer_text17–21 ↗
fn submit_composer_text(chat: &mut ChatWidget, text: &str)
作用:这个辅助函数模拟用户在输入框里写好一段文字并提交。它让测试不用每次都重复写“填输入框、按键提交”这些步骤。
数据流:进去的是聊天界面对象和一段文本。它先把文本放进底部输入框,再调用 submit_current_composer 去模拟真正的提交按键。出来没有返回值,但聊天界面可能会发出事件,比如设置目标草稿,也可能改变输入框状态。
调用关系:它被多个 /goal 直接提交测试调用,包括长度刚好到上限、多行目标、超长目标。它把具体按键动作交给 submit_current_composer。
调用图:调用 1 个内部函数(submit_current_composer);被 3 处调用(goal_slash_command_accepts_multiline_objective_after_blank_first_line, goal_slash_command_accepts_objective_at_limit, goal_slash_command_emits_oversized_objective);外部调用 1 个(new)。
submit_current_composer23–27 ↗
fn submit_current_composer(chat: &mut ChatWidget)
作用:这个辅助函数模拟用户提交当前输入框内容。它通过发送几个键盘事件,让测试走和真实界面尽量一样的路径。
数据流:进去的是聊天界面对象。它依次给界面发送 Esc 和两次 Enter 按键事件;这些按键会让输入框退出某些编辑状态并提交内容。出来没有直接结果,但聊天界面会根据当前输入决定是发普通消息,还是发出设置目标的事件。
调用关系:submit_composer_text 会调用它来完成提交;涉及粘贴内容的测试也直接调用它,因为这些测试要先手动设置输入框和粘贴状态,再提交。
调用图:被 3 处调用(goal_slash_command_emits_only_inserted_paste_text_element, goal_slash_command_preserves_large_pasted_objective, submit_composer_text);外部调用 2 个(new, handle_key_event)。
queue_composer_text_with_tab29–33 ↗
fn queue_composer_text_with_tab(chat: &mut ChatWidget, text: &str)
作用:这个辅助函数模拟用户把一段输入放进队列,而不是马上提交。这里 Tab 键相当于“先排着,等当前回答结束再处理”。
数据流:进去的是聊天界面对象和要排队的文本。它把文本写入输入框,然后发送 Tab 按键事件。出来没有返回值,但聊天界面的输入队列会多一条待处理内容。
调用关系:它只服务于 queued_goal_slash_command_emits_oversized_objective_and_stops_queue。那个测试用它连续排队 /goal 和普通文本,检查 /goal 被处理后队列是否正确停在下一条。
调用图:被 1 处调用(queued_goal_slash_command_emits_oversized_objective_and_stops_queue);外部调用 3 个(new, new, handle_key_event)。
next_goal_objective35–49 ↗
fn next_goal_objective(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
expected_thread_id: ThreadId,
) -> String
作用:这个辅助函数从应用事件队列里找下一条“设置目标草稿”事件,并取出里面的目标文字。它让测试能专心比较目标内容,而不用每次手写取事件的代码。
数据流:进去的是一个事件接收器和期望的线程编号。它循环从接收器里拿事件,直到遇到 SetThreadGoalDraft;遇到后会确认事件里的线程编号就是期望的编号,然后返回草稿里的 objective 字段。它会消耗事件队列里的事件。
调用关系:一些测试在提交 /goal 后调用它,确认系统确实发出了目标草稿事件,并且内容没有被改坏。它不再把工作交给别的本文件函数,只依赖事件接收器取消息和断言。
调用图:外部调用 2 个(try_recv, assert_eq!)。
goal_slash_command_accepts_objective_at_limit52–74 ↗
async fn goal_slash_command_accepts_objective_at_limit()
作用:这个测试确认 /goal 后面的目标文字刚好等于允许的最大长度时,系统会接受它。这样可以防止边界判断写错,把合法内容误判成太长。
数据流:进去没有人工参数,测试自己创建聊天界面、事件接收器和线程编号。它打开 Goals 功能,构造一段长度正好到上限的目标,提交 /goal 目标。之后它读取事件,检查线程编号和目标文字完全一致,并确认没有生成普通提交请求。
调用关系:它通过 submit_composer_text 模拟用户提交。测试成功说明 /goal 命令在长度上限这个边界上会走“设置目标草稿”流程,而不是普通聊天提交流程。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 3 个(assert_eq!, format!, panic!)。
goal_slash_command_accepts_multiline_objective_after_blank_first_line77–98 ↗
async fn goal_slash_command_accepts_multiline_objective_after_blank_first_line()
作用:这个测试确认 /goal 后面即使先空几行,再写多行目标,系统也能正确提取真正的目标内容。它防止多行输入被错误清空或只取第一行。
数据流:测试创建聊天界面和线程,打开 Goals 功能,然后提交形如 /goal 后跟空行、再跟两行说明的文本。它从事件队列里取出设置目标草稿事件,确认线程正确、目标文字保留了换行内容,并确认没有普通提交请求。
调用关系:它调用 submit_composer_text 走真实提交路径。它和其他 /goal 测试一起覆盖目标文本的不同形态:单行、超长、多行和粘贴。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 3 个(assert_eq!, format!, panic!)。
goal_slash_command_emits_oversized_objective101–112 ↗
async fn goal_slash_command_emits_oversized_objective()
作用:这个测试确认目标文字即使超过最大长度,聊天界面仍会把完整内容发成目标草稿事件。这里测试的是前端不要擅自截断或拦住内容。
数据流:测试创建聊天界面、打开 Goals 功能、设置线程编号,再构造一段比上限多 1 个字符的目标。它提交 /goal 目标,然后用 next_goal_objective 取出事件里的目标,并比较是否和原文完全一样;最后确认没有普通提交请求。
调用关系:它通过 submit_composer_text 模拟提交,再通过 next_goal_objective 读取结果。它说明超长校验不应该在这个聊天输入层把内容吞掉。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert_eq!, format!)。
goal_slash_command_preserves_large_pasted_objective115–134 ↗
async fn goal_slash_command_preserves_large_pasted_objective()
作用:这个测试确认用户在 /goal 后面粘贴一大段内容时,系统不会把真实粘贴内容丢掉。界面上可能只显示一个占位提示,但后台草稿必须记住原文。
数据流:测试先把输入框设成 /goal ,再调用粘贴操作塞入一段超长文字。界面输入框里应该显示类似“粘贴内容多少字符”的占位符。提交后,测试从目标草稿里检查 pending_pastes,也就是“待附带的真实粘贴内容”,确认里面保存了原始长文本,并确认没有普通提交请求。
调用关系:它直接调用 submit_current_composer,因为粘贴前需要手动准备输入框。它依赖测试工具 next_goal_draft 取完整草稿,用来检查不只 objective,还有粘贴附件列表。
调用图:调用 2 个内部函数(new, submit_current_composer);外部调用 3 个(new, assert!, assert_eq!)。
queued_goal_slash_command_emits_oversized_objective_and_stops_queue137–154 ↗
async fn queued_goal_slash_command_emits_oversized_objective_and_stops_queue()
作用:这个测试确认当助手还在回答时,用户排队的 /goal 会在当前回答结束后被处理成目标草稿,并且不会继续把后面的普通输入也一口气提交掉。
数据流:测试先创建聊天界面并标记一轮回答开始。然后它排队一条超长 /goal,再排队一条普通的 continue,确认队列里有两条。接着它结束当前回答。之后系统应该处理第一条 /goal,发出完整目标草稿事件,并让队列还剩下一条普通输入,同时没有普通提交请求。
调用关系:它使用 queue_composer_text_with_tab 来制造队列,用 complete_turn_with_message 来触发队列处理。它验证 /goal 在队列场景里仍然是特殊命令,而且处理完会停住,不会误伤下一条输入。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 2 个(assert_eq!, format!)。
goal_slash_command_emits_only_inserted_paste_text_element157–184 ↗
async fn goal_slash_command_emits_only_inserted_paste_text_element()
作用:这个测试确认只有真正通过粘贴操作插入的那段大文本,会进入 pending_pastes。用户手打的、长得像粘贴占位符的普通文字,不能被误认为真实粘贴内容。
数据流:测试先构造一段大粘贴文本和对应的占位符文字。它在输入框里手动写入一个“看起来像占位符”的普通字符串,然后再真正粘贴同样会显示占位符的大文本。提交后,它检查目标文字里同时保留了手写占位符和插入占位符,但 pending_pastes 里只记录真正粘贴进去的那一份原文;最后确认输入框里的待粘贴记录已清空,且没有普通提交请求。
调用关系:它直接准备输入框、调用粘贴,再用 submit_current_composer 提交。它和大粘贴保存测试一起保证粘贴占位符机制不会混淆“用户打的文字”和“系统替用户藏起来的长文本”。
调用图:调用 2 个内部函数(new, submit_current_composer);外部调用 4 个(new, assert!, assert_eq!, format!)。
tui/src/chatwidget/tests/guardian.rs源码 ↗
Guardian 可以理解成一个“安全门卫”:当助手想执行命令或申请权限时,它先判断这件事能不能自动放行。这个测试文件就是检查聊天窗口有没有把门卫的判断讲明白。它会造出各种审查事件,比如正在审查、批准、拒绝、超时,再把这些事件喂给聊天组件。随后测试会看两件事:一是底部状态栏和历史记录是否显示正确,二是用户点击“批准最近被拒绝的操作”时,程序是否只提交一次真正的批准请求。文件里还会用虚拟终端渲染界面,并和快照对比。快照就像给界面拍照,防止以后改代码时不小心把警告、原因、命令摘要或并行审查状态弄坏。
auto_review_denial_event4–22 ↗
fn auto_review_denial_event() -> GuardianAssessmentEvent
作用:造一个固定的“自动审查被拒绝”事件,供多个测试重复使用。它让测试不用每次都手写一大段相同数据。
数据流:进去没有外部输入 → 它填好事件编号、目标编号、风险等级、拒绝理由、命令内容和工作目录 → 出来一个 GuardianAssessmentEvent,表示某条会把本地源码发到外部网址的命令被安全门卫拒绝了。
调用关系:它是测试用的小道具,被 auto_review_denials_popup_lists_stored_auto_review_denials 和 approving_recent_denial_emits_structured_core_op_once 调用,用来保证这两个测试用的是同一个可预测的拒绝场景。
调用图:被 2 处调用(approving_recent_denial_emits_structured_core_op_once, auto_review_denials_popup_lists_stored_auto_review_denials)。
auto_review_denials_popup_lists_stored_auto_review_denials25–35 ↗
async fn auto_review_denials_popup_lists_stored_auto_review_denials()
作用:测试聊天界面能不能把最近被自动审查拒绝的操作列在弹窗里。这样用户才知道刚才哪些危险动作被拦下了。
数据流:进去是一个新建的聊天组件和测试事件通道 → 它设置线程编号,塞入 auto_review_denial_event 造出的拒绝事件,清空历史插入消息,再打开“最近拒绝”弹窗 → 出来是渲染后的弹窗文本,并用快照确认内容没有变错。
调用关系:它由异步测试框架运行。过程中调用 ThreadId::new 生成线程编号,调用 auto_review_denial_event 准备假事件,最后交给 assert_chatwidget_snapshot! 做界面对比。
调用图:调用 2 个内部函数(new, auto_review_denial_event);外部调用 1 个(assert_chatwidget_snapshot!)。
approving_recent_denial_emits_structured_core_op_once38–61 ↗
async fn approving_recent_denial_emits_structured_core_op_once()
作用:测试用户批准一个最近被拒绝的自动审查操作时,程序只会向核心层发送一次正式批准请求。它防止重复点击造成同一个危险操作被重复提交。
数据流:进去是新聊天组件、事件接收器和一个线程编号 → 它先记录一条被拒绝事件,再调用批准函数 → 第一次会收到 SubmitThreadOp,里面带着 ApproveGuardianDeniedAction;第二次再批准同一个事件时,只产生界面历史更新,不再发核心操作。
调用关系:它由测试框架运行,使用 auto_review_denial_event 准备拒绝记录,用 assert_matches! 检查发出的事件类型和内容,用 assert! 确认通道里没有多余消息。
调用图:调用 2 个内部函数(new, auto_review_denial_event);外部调用 2 个(assert!, assert_matches!)。
guardian_denied_exec_renders_warning_and_denied_request64–125 ↗
async fn guardian_denied_exec_renders_warning_and_denied_request()
作用:测试一条 shell 命令被 Guardian 拒绝后,聊天历史里是否同时显示警告和“请求被拒绝”的记录。这样用户能明白命令为什么没执行。
数据流:进去是聊天组件、虚拟终端宽高和一条模拟的 curl 命令 → 它先送入“审查中”事件,再送入警告文本,最后送入“拒绝”事件 → 它把产生的历史行插入虚拟终端,渲染聊天界面,输出屏幕内容并和快照比对。
调用关系:它由测试框架运行。它调用终端创建函数 with_options,调用 insert_history_lines 把聊天历史写进虚拟终端,最后用 assert_chatwidget_snapshot! 确认拒绝画面和警告没有被改坏。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
guardian_approved_exec_renders_approved_request128–173 ↗
async fn guardian_approved_exec_renders_approved_request()
作用:测试一条命令被 Guardian 批准后,聊天历史是否显示“已批准”的请求摘要。它保证用户能看到某个操作已经安全放行。
数据流:进去是新聊天组件和一条模拟的删除临时文件命令 → 它送入一个状态为 Approved 的审查事件 → 然后计算界面高度,建立虚拟终端,插入历史记录并渲染,最后得到屏幕文本用于快照对比。
调用关系:它由测试框架运行,主要依赖聊天组件的 on_guardian_assessment 生成历史,再通过 with_options 和 insert_history_lines 进入虚拟终端渲染流程,最后交给 assert_chatwidget_snapshot! 检查结果。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
guardian_approved_request_permissions_renders_request_summary176–251 ↗
async fn guardian_approved_request_permissions_renders_request_summary()
作用:测试当 Guardian 审查的是“权限申请”而不是命令时,界面是否显示清楚申请原因和批准结果。比如申请写入某个报告目录,用户应该能看懂它要什么权限。
数据流:进去是聊天组件和一个权限申请动作 → 它用 from_read_write_roots 造出只允许写某个目录的权限配置,先送入“审查中”事件并检查底部状态栏文案,再送入“已批准”事件 → 最后渲染虚拟终端并用快照确认历史摘要正确。
调用关系:它由测试框架运行。它先用 assert_eq! 检查即时状态栏,再像其他界面测试一样,通过 with_options、insert_history_lines 和 assert_chatwidget_snapshot! 检查最终聊天历史。
调用图:调用 4 个内部函数(from_read_write_roots, with_options, insert_history_lines, new);外部调用 5 个(new, default, assert_chatwidget_snapshot!, assert_eq!, vec!)。
guardian_timed_out_exec_renders_warning_and_timed_out_request254–317 ↗
async fn guardian_timed_out_exec_renders_warning_and_timed_out_request()
作用:测试 Guardian 审查命令超时时,界面是否显示超时警告和超时记录。这样用户不会误以为命令已经被批准或被正常拒绝。
数据流:进去是聊天组件和一条模拟的 curl 命令 → 它先送入“审查中”事件,再送入超时警告,最后送入状态为 TimedOut 的完成事件 → 它把历史行写进虚拟终端,渲染后拿屏幕文本和快照对比。
调用关系:它由测试框架运行。它走的是聊天组件收到 Guardian 事件后的完整显示链路,并把终端渲染结果交给 assert_chatwidget_snapshot! 检查。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
app_server_guardian_review_started_sets_review_status320–358 ↗
async fn app_server_guardian_review_started_sets_review_status()
作用:测试当应用服务器通知“Guardian 审查开始”时,聊天窗口底部是否立刻显示正在审查以及正在审查的命令。它保证远端来的事件也能让用户马上看到进度。
数据流:进去是新聊天组件和一条服务器通知数据 → 它构造 ItemGuardianApprovalReviewStartedNotification,并通过 handle_server_notification 喂给聊天组件 → 出来是底部状态栏被更新,标题变成“Reviewing approval request”,详情显示待审查命令。
调用关系:它由测试框架运行,专门覆盖服务器通知入口。它不走手动构造的 GuardianAssessmentEvent,而是测试 ServerNotification 进入聊天组件后的状态栏更新,用 assert_eq! 验证文字。
调用图:外部调用 2 个(ItemGuardianApprovalReviewStarted, assert_eq!)。
app_server_guardian_review_denied_renders_denied_request_snapshot361–436 ↗
async fn app_server_guardian_review_denied_renders_denied_request_snapshot()
作用:测试服务器先通知审查开始、再通知审查被拒绝时,聊天历史是否正确显示被拒绝的请求。它保证来自应用服务器的拒绝结果不会在界面上丢失或显示错。
数据流:进去是聊天组件、事件接收器和一条模拟命令 → 它先发送 ItemGuardianApprovalReviewStartedNotification,再发送 ItemGuardianApprovalReviewCompletedNotification,完成通知里状态是 Denied,并带有高风险和拒绝理由 → 最后插入历史、渲染虚拟终端、输出屏幕内容做快照对比。
调用关系:它由测试框架运行,覆盖服务器通知到聊天历史的通路。它调用 started/completed 通知构造器,之后复用终端渲染和 assert_chatwidget_snapshot! 来验证最终界面。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 4 个(new, ItemGuardianApprovalReviewCompleted, ItemGuardianApprovalReviewStarted, assert_chatwidget_snapshot!)。
app_server_guardian_review_timed_out_renders_timed_out_request_snapshot439–517 ↗
async fn app_server_guardian_review_timed_out_renders_timed_out_request_snapshot()
作用:测试服务器通知 Guardian 审查超时时,聊天历史是否显示成“超时”而不是批准或拒绝。它让用户能分清是安全门卫没等到结果,而不是已经做出明确决定。
数据流:进去是聊天组件、事件接收器和一条模拟命令 → 它先送入服务器的“审查开始”通知,再送入“审查完成但状态为 TimedOut”的通知,并带上超时说明 → 然后把历史行写进虚拟终端,渲染界面,拿屏幕内容做快照检查。
调用关系:它由测试框架运行,和服务器拒绝测试类似,但完成通知里的状态换成 TimedOut。它通过 with_options、insert_history_lines 和 assert_chatwidget_snapshot! 检查终端最终显示。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 4 个(new, ItemGuardianApprovalReviewCompleted, ItemGuardianApprovalReviewStarted, assert_chatwidget_snapshot!)。
guardian_parallel_reviews_render_aggregate_status_snapshot520–552 ↗
async fn guardian_parallel_reviews_render_aggregate_status_snapshot()
作用:测试同时有多个 Guardian 审查进行时,底部弹窗能不能合并显示这些等待中的请求。它避免界面只显示其中一个,让用户误以为只有一个操作在等审批。
数据流:进去是聊天组件和两条不同的危险删除命令 → 它先标记任务开始,再循环送入两个 InProgress 审查事件 → 出来是底部弹窗的渲染文本,并用快照检查两个并行审查是否都被概括展示。
调用关系:它由测试框架运行。它直接调用聊天组件的 on_task_started 和 on_guardian_assessment,再用 render_bottom_popup 取出底部弹窗,最后交给 assert_chatwidget_snapshot!。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, format!)。
guardian_parallel_reviews_keep_remaining_review_visible_after_denial555–619 ↗
async fn guardian_parallel_reviews_keep_remaining_review_visible_after_denial()
作用:测试两个审查同时进行时,如果其中一个被拒绝,另一个还在审查的请求仍然留在状态栏里。它防止一个结果结束后把整个审查状态都清掉。
数据流:进去是聊天组件和两条正在审查的删除命令 → 它先送入两个 InProgress 事件,再把第一个改成 Denied → 出来是聊天组件当前状态仍显示“正在审查”,详情指向第二条还没结束的命令。
调用关系:它由测试框架运行,专门验证并行审查的状态切换。它不做终端快照,而是直接用 assert_eq! 检查聊天组件内部保存的当前状态文字。
调用图:外部调用 1 个(assert_eq!)。
tui/src/chatwidget/tests/history_replay.rs源码 ↗
这个文件测试的是 TUI 聊天组件的历史回放能力。TUI 是“终端用户界面”,也就是在命令行里显示的聊天窗口。用户可能会恢复一个旧线程,或者服务器发来一份线程快照;这时程序要把以前的用户消息、助手回复、图片、提及链接、权限设置、工作目录、运行中状态、错误重试状态等都放回正确位置。这里的每个测试都会造一个假的聊天窗口,喂给它一些“服务器事件”或“历史条目”,再检查界面输出、输入框历史、底部状态栏和配置是否符合预期。它特别关注一些容易出错的边角情况,比如仅有图片没有文字的消息、评审模式里的提示不该进输入历史、分叉线程标题不能读错来源、重连错误不该在回放时吓到用户。
resumed_initial_messages_render_history14–76 ↗
async fn resumed_initial_messages_render_history()
作用:测试恢复会话时,最开始回放的用户消息和助手消息真的会显示到聊天历史里。没有这个保证,用户重新打开线程可能会看到一片空白或漏消息。
数据流:它先创建一个假的聊天窗口和一个临时会话配置,里面带有线程编号、模型、权限、当前目录和回放文件路径。然后把一条用户消息和一条助手消息按“恢复初始消息”的方式喂给聊天窗口。最后从事件队列里取出插入到历史区的内容,拼成文字,确认里面包含用户说的“hello from user”和助手的“assistant reply”。
调用关系:这是历史回放流程的基础验收。它通过测试工具创建聊天组件,调用会话配置处理和消息回放入口,再观察聊天组件发出的 InsertHistoryCell 事件,确认下游渲染层会收到正确内容。
调用图:调用 2 个内部函数(read_only, new);外部调用 4 个(new, default, new, assert!)。
replayed_user_messages_seed_composer_history79–166 ↗
async fn replayed_user_messages_seed_composer_history()
作用:测试回放过的普通用户消息会进入输入框的历史记录,这样用户按上箭头时能翻到以前发过的话。它还检查带“提及”的消息能恢复绑定关系。
数据流:它先给底部输入区设置一份历史元数据,表示还有更多旧输入可以查。随后回放两条带 $ 提及的用户消息,比如插件和日历应用。测试先按上箭头,确认输入框依次出现最近两条回放消息,并且提及绑定还保留了真实路径。再继续按上箭头,聊天组件会向外请求更早的历史条目;测试假装返回这些条目,最后确认输入框能显示旧命令“/rename smoke-1”。
调用关系:它站在用户按方向键翻输入历史的流程上。回放消息先给底部输入框播种;当本地回放内容不够时,组件会发出 LookupMessageHistoryEntry 请求,再由 handle_history_entry_response 接收外部返回。
调用图:调用 1 个内部函数(new);外部调用 2 个(new, assert_eq!)。
replayed_review_prompt_does_not_seed_composer_history169–190 ↗
async fn replayed_review_prompt_does_not_seed_composer_history()
作用:测试进入代码评审模式时自动生成的评审提示,不会被当成用户亲手输入过的历史。这样用户按上箭头时不会翻到系统塞进去的提示语。
数据流:它创建聊天窗口,先回放一个“进入评审模式”的线程项,再回放一条看起来像用户消息的评审提示。清空历史插入事件后,它模拟用户按上箭头。结果输入框仍然是空的,说明这条评审提示没有进入可翻阅的输入历史。
调用关系:它覆盖评审模式和历史输入框之间的边界。聊天组件会接收 replay_thread_item 和 replay_user_message_text,但底部输入框不应把这种特殊提示当作普通用户输入。
调用图:外部调用 2 个(new, assert_eq!)。
replayed_user_message_preserves_text_elements_and_local_images193–267 ↗
async fn replayed_user_message_preserves_text_elements_and_local_images()
作用:测试回放用户消息时,不只保留纯文字,还要保留文字里的特殊片段信息和本地图片路径。否则含图片的旧消息会显示不完整。
数据流:它构造一条消息,文字里有“[Image #1]”占位符,并配上一段 TextElement。TextElement 可以理解为“这段文字有特殊含义”的标记。它还附上一张本地图片路径。回放后,测试从事件队列里找出 UserHistoryCell,检查里面保存的文字、特殊文字片段、本地图片路径都和输入一致,并且远程图片列表为空。
调用关系:它验证用户消息从 AppServerUserInput 输入格式进入聊天历史单元的转换过程。回放入口负责把文字和图片拆分后交给 UserHistoryCell,测试确认这个交接没有丢数据。
调用图:调用 2 个内部函数(read_only, new);外部调用 7 个(new, default, new, assert!, assert_eq!, format!, vec!)。
replayed_user_message_preserves_remote_image_urls270–337 ↗
async fn replayed_user_message_preserves_remote_image_urls()
作用:测试回放带网络图片链接的用户消息时,图片网址会被保留下来。这样旧聊天里的远程图片不会消失。
数据流:它配置一个会话,然后回放一条包含普通文字和远程图片 URL 的用户输入。接着从聊天事件里找到用户历史单元,检查文字仍然相同,本地图片列表为空,远程图片 URL 列表正好包含原来的链接。
调用关系:它检查 AppServerUserInput::Image 这类输入如何流入 UserHistoryCell。它和本地图片测试互补,确保两种图片来源不会混在一起,也不会被忽略。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, default, new, assert!, assert_eq!, vec!)。
session_configured_syncs_widget_config_permissions_and_cwd340–433 ↗
async fn session_configured_syncs_widget_config_permissions_and_cwd()
作用:测试服务器发来的会话配置会同步到聊天窗口本地配置,包括审批策略、文件权限沙盒和当前工作目录。没有这个同步,界面显示和真实执行环境可能不一致。
数据流:它先故意给聊天窗口设置一套旧权限和旧目录。然后构造服务器会话状态,里面包含新的目录、只读或受限的文件系统权限、网络限制和审批策略。调用 handle_thread_session 后,测试检查聊天配置已经改成服务器给的新值。之后它模拟用户在本地改权限,再确认本地修改可以替换会话快照里的标准权限,但有效权限仍然会结合当前线程的工作区根目录来计算。
调用关系:它位于“服务器会话状态进入本地聊天组件”的配置同步流程。handle_thread_session 是入口,权限配置对象和旧版 SandboxPolicy 是下游结果;set_permission_profile_from_session_snapshot 则验证后续本地更新能覆盖之前的会话配置。
调用图:调用 4 个内部函数(from, legacy, workspace_write, new);外部调用 4 个(default, new, assert_eq!, vec!)。
session_configured_preserves_profile_workspace_roots436–487 ↗
async fn session_configured_preserves_profile_workspace_roots()
作用:测试会话配置切换工作目录时,不会误删权限配置里本来就需要保留的工作区根目录。工作区根目录就是程序允许读写的一组项目目录。
数据流:它先设置一个旧当前目录和一个共享目录,并告诉权限系统这些都是工作区。然后服务器会话换到一个子代理目录,同时权限快照里包含子代理目录和共享目录。处理会话后,测试确认当前目录变成子代理目录;用户能看到的运行时工作区只显示服务器给的目录;但真正生效的权限仍包含会话快照里已经物化好的完整根目录集合。
调用关系:它补充测试 handle_thread_session 对工作区根目录的处理。它连接聊天配置、权限系统和服务器线程状态,防止“显示给用户的根目录”和“真正权限里的根目录”互相覆盖出错。
调用图:调用 2 个内部函数(workspace_write, new);外部调用 4 个(default, new, assert_eq!, vec!)。
session_configured_external_sandbox_keeps_external_runtime_policy490–531 ↗
async fn session_configured_external_sandbox_keeps_external_runtime_policy()
作用:测试当会话使用外部沙盒时,聊天组件会保留“外部沙盒”的运行策略,而不是强行改成普通本地沙盒。外部沙盒可以理解为隔离环境由别的系统来管。
数据流:它构造一个权限配置,表示文件限制不由本程序管理,只限制网络访问。处理会话后,测试把聊天配置里的旧版沙盒策略转换出来,确认它仍然是 ExternalSandbox,并且网络访问是受限的。同时确认有效权限配置也等于服务器给的外部权限配置。
调用关系:它检查服务器权限模型到本地 legacy sandbox policy 的投影。handle_thread_session 接收外部沙盒配置,下游配置读取接口 config_ref 应该能看到同样的外部运行策略。
调用图:调用 2 个内部函数(from, new);外部调用 3 个(default, new, assert_eq!)。
replayed_user_message_with_only_remote_images_renders_history_cell534–589 ↗
async fn replayed_user_message_with_only_remote_images_renders_history_cell()
作用:测试一条只有远程图片、没有文字的用户消息也会显示成历史单元。否则用户发过的纯图片消息可能在恢复时不见了。
数据流:它创建会话,回放一个只包含 AppServerUserInput::Image 的输入。随后从事件队列中找到 UserHistoryCell,确认消息文字为空,但远程图片 URL 列表包含原来的图片地址。
调用关系:它覆盖用户消息回放中“没有文字但有内容”的情况。回放入口不能因为文本为空就跳过这条消息,而要把图片交给历史区显示。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, default, new, assert!, assert_eq!, vec!)。
replayed_user_message_with_only_local_images_renders_history_cell592–647 ↗
async fn replayed_user_message_with_only_local_images_renders_history_cell()
作用:测试一条只有本地图片、没有文字的用户消息也会出现在历史里。这样用户从本机上传过的纯图片消息恢复时不会丢。
数据流:它创建会话,回放一个只包含本地图片路径的输入。接着读取插入历史的用户单元,确认文字为空,并且本地图片路径列表正好是输入时给的那张图片。
调用关系:它和纯远程图片测试一起保护图片消息回放逻辑。AppServerUserInput::LocalImage 经过 replay_user_message_inputs 后,应被转换成 UserHistoryCell 的 local_image_paths。
调用图:调用 2 个内部函数(read_only, new);外部调用 7 个(new, from, default, new, assert!, assert_eq!, vec!)。
forked_thread_history_line_includes_name_and_id_snapshot650–677 ↗
async fn forked_thread_history_line_includes_name_and_id_snapshot()
作用:测试当线程是从另一个有名字的线程分叉出来时,历史里会出现一行说明,并同时包含父线程名字和编号信息。快照测试用来防止这行文案以后被意外改坏。
数据流:它创建聊天窗口,准备一个固定的父线程 ID,并调用 emit_forked_thread_event,传入父线程标题。然后等待聊天组件发出历史单元,把显示内容拼成一行字符串。测试确认里面有“Thread forked from”,再和保存好的界面快照比对。
调用关系:它测试分叉线程提示的输出链路。emit_forked_thread_event 负责生成提示,事件队列把提示送到历史区,display_lines 则负责把历史单元排版成可见文字。
调用图:调用 1 个内部函数(from_string);外部调用 5 个(assert!, assert_chatwidget_snapshot!, panic!, from_secs, timeout)。
forked_thread_history_line_without_name_shows_id_once_snapshot680–706 ↗
async fn forked_thread_history_line_without_name_shows_id_once_snapshot()
作用:测试父线程没有名字时,分叉提示仍然能显示父线程 ID,而且不会重复显示得很乱。它用快照锁住最终展示效果。
数据流:它给聊天窗口设置一个临时 CODEX_HOME,也就是本地保存会话索引的目录。然后用一个固定父线程 ID 触发分叉事件,但不提供父线程标题。测试等待历史单元出现,把它排版成文字,并和快照比较。
调用关系:它验证 emit_forked_thread_event 在缺少父线程标题时的兜底显示。这里不关心查找本地标题是否成功,而是关心最终历史行的格式稳定、ID 不重复。
调用图:调用 2 个内部函数(from_string, from_absolute_path);外部调用 4 个(assert_chatwidget_snapshot!, panic!, from_secs, timeout)。
app_server_forked_thread_history_line_uses_app_server_title_snapshot709–746 ↗
async fn app_server_forked_thread_history_line_uses_app_server_title_snapshot()
作用:测试如果应用服务器已经提供了父线程标题,聊天界面应该使用这个标题,而不是去本地旧索引里找一个可能过期的名字。
数据流:它创建一个临时 CODEX_HOME,并故意写入一条本地 session_index.jsonl,里面的父线程名是“stale-local-thread”。然后触发分叉事件时传入服务器给的标题“app-server-parent-thread”。测试读取历史行,确认包含服务器标题、不包含本地旧标题,并用快照确认格式。
调用关系:它保护应用服务器和本地缓存之间的优先级。emit_forked_thread_event 接到服务器标题后,应直接使用它,不把工作交给本地会话索引查询逻辑。
调用图:调用 2 个内部函数(from_string, from_absolute_path);外部调用 7 个(assert!, assert_chatwidget_snapshot!, format!, panic!, write, from_secs, timeout)。
app_server_forked_thread_history_line_without_app_server_name_ignores_local_snapshot749–788 ↗
async fn app_server_forked_thread_history_line_without_app_server_name_ignores_local_snapshot()
作用:测试应用服务器没有给父线程标题时,聊天界面也不会偷偷读取本地旧索引里的标题。这样可以避免把不属于当前服务器上下文的旧名字显示出来。
数据流:它同样在临时 CODEX_HOME 写入一条本地旧线程名。随后触发分叉事件,但不传服务器标题。读取历史行后,测试确认里面不包含本地旧标题,并和快照比较最终展示。
调用关系:它延续上一个测试的边界:应用服务器路径下的分叉标题不应依赖本地 CODEX_HOME。emit_forked_thread_event 只能用明确传入的信息和 ID 生成历史提示。
调用图:调用 2 个内部函数(from_string, from_absolute_path);外部调用 7 个(assert!, assert_chatwidget_snapshot!, format!, panic!, write, from_secs, timeout)。
thread_snapshot_replay_preserves_agent_message_during_review_mode791–807 ↗
async fn thread_snapshot_replay_preserves_agent_message_during_review_mode()
作用:测试在线程快照回放中,即使当前处于评审模式,助手消息也仍然会被显示。否则恢复评审会话时可能漏掉助手已经说过的话。
数据流:它先回放进入评审模式的状态,并清掉因此产生的历史事件。然后按“线程快照”方式回放一条助手消息。最后取出新插入的历史内容,确认只插入了一条,并且里面包含“Review progress update”。
调用关系:它覆盖评审模式和线程快照回放的交叉场景。replay_entered_review_mode 设置上下文,replay_agent_message 再走普通助手消息回放路径,历史区必须照常接收。
调用图:外部调用 2 个(assert!, assert_eq!)。
replayed_retryable_app_server_error_keeps_turn_running810–853 ↗
async fn replayed_retryable_app_server_error_keeps_turn_running()
作用:测试回放一个“会重试”的服务器错误时,不会把当前任务当成失败结束。比如网络流暂时断开、马上要重连,界面应该继续显示正在工作。
数据流:它先回放一个 TurnStarted 通知,让聊天窗口进入某个回合正在运行的状态。然后回放一个 Error 通知,内容是“Reconnecting... 1/5”,并标记 will_retry 为 true。测试确认没有新增历史错误单元,底部任务仍然运行,状态栏标题还是“Working”,没有额外详情。
调用关系:它测试 handle_server_notification 对 TurnStarted 和 Error 的组合处理。对于带重试标记的错误,回放路径不应把它交给普通错误展示逻辑,而应保持运行状态。
调用图:外部调用 5 个(Error, TurnStarted, new, assert!, assert_eq!)。
replayed_thread_closed_notification_does_not_exit_tui856–867 ↗
async fn replayed_thread_closed_notification_does_not_exit_tui()
作用:测试回放历史里的“线程已关闭”通知时,不会让整个终端界面退出。历史回放只是还原过去,不应该触发现在的关闭动作。
数据流:它把 ThreadClosed 通知按线程快照回放的方式交给聊天窗口。随后立刻检查事件队列,确认没有任何退出或其他事件发出。
调用关系:它限定 handle_server_notification 在回放模式下的副作用。ThreadClosed 在实时场景可能有意义,但在重放旧状态时必须被当成历史信息处理,而不是控制当前 TUI 生命周期。
调用图:外部调用 2 个(ThreadClosed, assert_matches!)。
replayed_reasoning_item_hides_raw_reasoning_when_disabled870–915 ↗
async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled()
作用:测试当用户关闭“显示原始推理”选项时,回放推理项只显示摘要,不显示原始推理内容。原始推理是模型的内部思考文字,用户可能不希望看到。
数据流:它先把 show_raw_agent_reasoning 设为 false,并配置一个普通会话。然后回放一个 Reasoning 项,里面既有摘要“Summary only”,也有原始内容“Raw reasoning”。测试取出插入的历史单元,确认渲染结果不是空的,但不包含“Raw reasoning”。
调用关系:它测试 replay_thread_item 对 Reasoning 项的显示策略。配置项 show_raw_agent_reasoning 决定下游 transcript_lines 是否包含原始内容。
调用图:调用 2 个内部函数(read_only, new);外部调用 4 个(new, assert!, panic!, vec!)。
replayed_reasoning_item_shows_raw_reasoning_when_enabled918–962 ↗
async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled()
作用:测试当用户打开“显示原始推理”选项时,回放推理项会显示原始推理内容。这样高级用户可以看到更完整的历史信息。
数据流:它把 show_raw_agent_reasoning 设为 true,配置会话,然后回放一个带摘要和原始内容的 Reasoning 项。测试读取插入的历史单元并转成文字,确认其中包含“Raw reasoning”。
调用关系:它和隐藏原始推理的测试成对出现,验证同一个 replay_thread_item 路径会根据配置走不同显示分支。
调用图:调用 2 个内部函数(read_only, new);外部调用 4 个(new, assert!, panic!, vec!)。
replayed_in_progress_mcp_tool_call_stays_active965–990 ↗
async fn replayed_in_progress_mcp_tool_call_stays_active()
作用:测试回放一个还在进行中的 MCP 工具调用时,它会留在“活动中”的区域,而不是被写成一条已完成但没结果的历史。MCP 可以理解为模型调用外部工具的一套协议。
数据流:它回放一个 McpToolCall 项,状态是 InProgress,参数里写着要等待。随后确认没有插入历史单元;再读取聊天窗口当前活动内容,确认里面有“Calling”,并且没有“工具调用完成但没有结果”这类错误文案。
调用关系:它保护工具调用状态机。replay_thread_item 收到进行中的工具项时,应更新活动显示,而不是交给完成态历史渲染。
live_reasoning_summary_is_not_rendered_twice_when_item_completes993–1047 ↗
async fn live_reasoning_summary_is_not_rendered_twice_when_item_completes()
作用:测试实时收到推理摘要增量后,等完整推理项完成时,同一段摘要不会在历史里显示两遍。否则用户会看到重复内容。
数据流:它先发送 TurnStarted,让回合开始。然后发送 ReasoningSummaryTextDelta,模拟服务器流式追加“Summary only”。接着发送 ItemCompleted,里面的完整 Reasoning 项也包含同样摘要。测试取出最终历史单元,统计“Summary only”只出现一次。
调用关系:它覆盖实时流式事件和完成事件的衔接。handle_server_notification 先处理摘要增量,再处理 ItemCompleted,完成态渲染需要知道哪些内容已经显示过,避免重复。
调用图:外部调用 7 个(ItemCompleted, ReasoningSummaryTextDelta, TurnStarted, new, assert_eq!, panic!, vec!)。
thread_snapshot_replayed_turn_started_marks_task_running1050–1062 ↗
async fn thread_snapshot_replayed_turn_started_marks_task_running()
作用:测试线程快照里回放到“回合开始”事件时,底部状态会显示任务正在运行。这样恢复一个还没完成的线程时,界面不会误以为它空闲。
数据流:它通过辅助函数按线程快照方式回放 TurnStarted。清掉历史插入事件后,检查底部输入区处于任务运行状态,并且状态栏标题是“Working”。
调用关系:它验证 replay_turn_started 进入 handle_server_notification 后对底部状态栏的影响。这个测试专注状态,不关心具体历史文本。
调用图:外部调用 2 个(assert!, assert_eq!)。
replayed_in_progress_turn_marks_task_running1065–1089 ↗
async fn replayed_in_progress_turn_marks_task_running()
作用:测试直接回放一整个仍在进行中的 turn,也会把聊天界面标记为正在工作。turn 可以理解为用户一次请求和助手一次处理过程。
数据流:它调用 replay_thread_turns,传入一个状态为 InProgress 的 AppServerTurn。测试确认没有插入历史单元,但底部任务运行标记为 true,状态栏显示“Working”。
调用关系:它覆盖另一条恢复路径:不是单个 TurnStarted 通知,而是整批 turn 快照。replay_thread_turns 必须和通知回放一样更新运行状态。
调用图:外部调用 3 个(assert!, assert_eq!, vec!)。
replayed_stream_error_does_not_set_retry_status_or_status_indicator1092–1111 ↗
async fn replayed_stream_error_does_not_set_retry_status_or_status_indicator()
作用:测试回放历史里的流错误时,不会设置当前界面的重试状态或状态提示。历史里的“曾经重连”不应该影响现在的状态栏。
数据流:它先把当前状态标题设成“Idle”。然后用带回放标记的方式处理一个流错误,内容是“Reconnecting... 2/5”。测试确认没有历史单元,当前状态标题仍是“Idle”,重试状态为空,底部状态组件也不存在。
调用关系:它限定 handle_stream_error_with_replay 在回放模式下的行为。实时流错误可能会改变状态栏,但回放旧错误时应被静默忽略。
调用图:外部调用 2 个(assert!, assert_eq!)。
thread_snapshot_replayed_stream_recovery_restores_previous_status_header1114–1137 ↗
async fn thread_snapshot_replayed_stream_recovery_restores_previous_status_header()
作用:测试线程快照回放中,如果先遇到重连错误、之后又收到助手消息增量,状态栏会恢复到之前的“Working”。这防止界面一直卡在重连提示上。
数据流:它先按线程快照回放 TurnStarted,让状态变成工作中。然后回放一次流错误,再回放一段助手消息增量“hello”,表示流恢复了。最后检查底部状态栏标题回到“Working”,详情为空,内部重试状态也被清掉。
调用关系:它测试回放场景下的错误恢复链路。replay_turn_started 建立原状态,handle_stream_error_with_replay 暂时处理重连,replay_agent_message_delta 代表恢复并触发状态复原。
调用图:外部调用 2 个(assert!, assert_eq!)。
stream_recovery_restores_previous_status_header1140–1159 ↗
async fn stream_recovery_restores_previous_status_header()
作用:测试实时运行时发生流错误后,只要又收到助手消息增量,状态栏也会从重连提示恢复到“Working”。这是上一个测试的实时版本。
数据流:它先用 handle_turn_started 开启一个正在运行的回合。然后调用 handle_stream_error 模拟重连错误,再调用 handle_agent_message_delta 模拟服务器继续发来内容。最后确认底部状态栏标题是“Working”,详情为空,重试状态被清除。
调用关系:它覆盖非回放的正常运行流程。handle_turn_started、handle_stream_error 和 handle_agent_message_delta 按真实事件顺序协作,保证状态栏不会停留在过期的错误状态。
调用图:外部调用 2 个(assert!, assert_eq!)。
tui/src/chatwidget/tests/mcp_startup.rs源码 ↗
这个文件不是产品功能本身,而是一组自动化检查。它把 ChatWidget 当成一个真实聊天窗口来喂事件:某个 MCP 服务器开始启动、启动失败、启动完成,或者启动过程因为服务端消息太慢而被强制收尾。测试会看三类结果:历史区有没有插入正确的警告文字,底部状态栏是不是还显示“任务运行中”,以及状态标题是不是从“正在启动 MCP”正确切回“Working”或“Reviewing approval request”。它还特别检查一些容易出错的边角:同一个失败警告不能重复刷屏;别的会话线程发来的状态要忽略;旧一轮启动结束后,迟到的旧消息不能污染下一轮;没有预先声明服务器时,运行时冒出来的服务器也要能重新激活启动流程。整体上,这个文件像一套“验收清单”,防止用户界面在 MCP 启动异常时给出误导信息。
notify_mcp_status4–14 ↗
fn notify_mcp_status(chat: &mut ChatWidget, name: &str, status: McpServerStartupState)
作用:这是测试里的小帮手,用来模拟“某个 MCP 服务器状态更新了”的通知。测试不需要手写一大段通知对象,只要给服务器名和状态就行。
数据流:输入是一个可修改的聊天窗口、服务器名字、启动状态 → 它组装成一条带有固定线程号的 MCP 状态通知 → 交给聊天窗口的通知处理入口,聊天窗口内部状态和界面输出可能因此改变。
调用关系:很多测试都会用它来制造“开始启动”或“已就绪”这类普通状态。它把细节交给 McpServerStatusUpdated 构造通知,再交给 handle_server_notification 走真实处理路径。
调用图:被 15 处调用(app_server_mcp_startup_after_lag_can_settle_without_starting_updates, app_server_mcp_startup_after_lag_preserves_partial_terminal_only_round, app_server_mcp_startup_failure_renders_warning_history, app_server_mcp_startup_lag_settles_startup_and_ignores_late_updates, app_server_mcp_startup_next_round_after_lag_can_settle_without_starting_updates, app_server_mcp_startup_next_round_discards_stale_terminal_updates, app_server_mcp_startup_next_round_keeps_terminal_statuses_after_starting, app_server_mcp_startup_next_round_with_empty_expected_servers_reactivates, mcp_startup_complete_does_not_clear_running_task, mcp_startup_complete_preserves_review_status (+5 more));外部调用 2 个(McpServerStatusUpdated, handle_server_notification)。
notify_mcp_status_error16–26 ↗
fn notify_mcp_status_error(chat: &mut ChatWidget, name: &str, error: &str)
作用:这是另一个测试帮手,专门模拟“某个 MCP 服务器启动失败并带有错误文字”的通知。它让失败场景写起来更短,也保证所有失败通知格式一致。
数据流:输入是聊天窗口、服务器名字、错误说明 → 它把状态固定为 Failed,并把错误说明塞进通知 → 交给聊天窗口处理,之后可能出现警告历史、启动流程收尾或状态栏变化。
调用关系:失败相关测试大量调用它。它和 notify_mcp_status 配合,一个负责普通状态,一个负责失败状态,最后都走 handle_server_notification 这条真实入口。
调用图:被 11 处调用(app_server_mcp_startup_after_lag_can_settle_without_starting_updates, app_server_mcp_startup_after_lag_includes_runtime_servers_with_expected_set, app_server_mcp_startup_after_lag_preserves_partial_terminal_only_round, app_server_mcp_startup_failure_renders_warning_history, app_server_mcp_startup_lag_settles_startup_and_ignores_late_updates, app_server_mcp_startup_next_round_after_lag_can_settle_without_starting_updates, app_server_mcp_startup_next_round_discards_stale_terminal_updates, app_server_mcp_startup_next_round_keeps_terminal_statuses_after_starting, app_server_mcp_startup_next_round_with_empty_expected_servers_reactivates, mcp_startup_dedupes_same_round_duplicate_failure_warning (+1 more));外部调用 2 个(McpServerStatusUpdated, handle_server_notification)。
mcp_startup_ignores_status_for_other_thread29–67 ↗
async fn mcp_startup_ignores_status_for_other_thread()
作用:这个测试确认:如果 MCP 状态通知属于另一个聊天线程,当前聊天窗口必须忽略它。否则一个后台会话的失败,可能会把当前会话的状态栏弄乱。
数据流:先创建聊天窗口,并让当前窗口处在一个重试错误状态 → 再伪造另一个线程的 MCP 启动和失败通知 → 最后检查历史区没有新增内容、底部没有开始跑任务、MCP 启动状态为空,原来的状态标题和重试提示都没变。
调用关系:它直接构造 McpServerStatusUpdated 通知,而不是用固定线程号的帮手,因为这里重点就是测试“线程号不同”。断言负责确认 ChatWidget 的通知处理没有误收别人的消息。
调用图:调用 1 个内部函数(new);外部调用 4 个(McpServerStatusUpdated, assert!, assert_eq!, matches!)。
mcp_startup_dedupes_same_round_duplicate_failure_warning70–103 ↗
async fn mcp_startup_dedupes_same_round_duplicate_failure_warning()
作用:这个测试确认:同一轮启动里,同一个 MCP 服务器重复报同一个失败,只显示一次警告。这样用户不会被重复错误刷屏。
数据流:先声明预期会启动 alpha 和 beta 两个服务器 → alpha 开始后连续两次报同样失败 → 读取历史输出,应该只有一条 alpha 失败警告 → beta 就绪后再读取输出,应该只有一条总结,说明 alpha 失败导致启动不完整。
调用关系:它用 notify_mcp_status 和 notify_mcp_status_error 生成状态变化。这个测试站在“用户会看到什么”的角度,检查失败警告和最终总结之间的配合。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 1 个(assert_eq!)。
mcp_startup_header_booting_snapshot106–122 ↗
async fn mcp_startup_header_booting_snapshot()
作用:这个测试用截图方式确认:MCP 服务器正在启动时,聊天界面的标题栏长得符合预期。截图测试适合抓 UI 排版和文字变化。
数据流:创建聊天窗口 → 发送 alpha 正在启动的通知 → 计算界面高度并把组件画到测试终端里 → 把终端内容和保存好的快照对比,确认画面一致。
调用关系:它调用 notify_mcp_status 进入启动状态,再使用测试终端渲染 ChatWidget。最后 assert_chatwidget_snapshot! 负责把当前画面和标准答案比较。
调用图:调用 1 个内部函数(notify_mcp_status);外部调用 3 个(assert_chatwidget_snapshot!, new, new)。
mcp_startup_complete_does_not_clear_running_task125–140 ↗
async fn mcp_startup_complete_does_not_clear_running_task()
作用:这个测试确认:如果聊天本身已经有一轮工作正在跑,MCP 启动完成不能把“正在工作”的状态误清掉。否则用户会以为任务结束了,其实模型还在处理。
数据流:先模拟一轮聊天任务开始,让底部显示运行中 → 再声明一个 MCP 服务器并让它从启动变为就绪 → 最后检查底部仍然是运行中,状态标题仍然是 Working。
调用关系:它把聊天任务开始和 MCP 启动完成叠在一起测试。notify_mcp_status 只推进 MCP 状态,断言保证它没有错误覆盖更重要的聊天运行状态。
调用图:调用 1 个内部函数(notify_mcp_status);外部调用 2 个(assert!, assert_eq!)。
turn_start_preserves_active_mcp_startup_header143–159 ↗
async fn turn_start_preserves_active_mcp_startup_header()
作用:这个测试确认:当 MCP 仍在启动时,即使新的聊天回合开始,界面也应该继续告诉用户正在启动哪个 MCP 服务器。这样用户能看懂当前卡点在哪里。
数据流:先设置预期服务器 schaltwerk → 发送正在启动通知 → 再模拟聊天回合开始 → 检查底部任务运行中,但标题仍是 Booting MCP server: schaltwerk → 等 MCP 就绪后,标题才切回 Working。
调用关系:它验证聊天回合开始事件和 MCP 启动状态之间的优先级。notify_mcp_status 负责制造 MCP 状态,handle_turn_started 负责制造聊天任务状态。
调用图:调用 1 个内部函数(notify_mcp_status);外部调用 2 个(assert!, assert_eq!)。
turn_start_replaces_idle_completed_mcp_startup_header162–179 ↗
async fn turn_start_replaces_idle_completed_mcp_startup_header()
作用:这个测试确认:如果 MCP 已经启动完成,而且界面没有别的任务在跑,那么新的聊天回合开始时,标题应该切成 Working。也就是说旧的“启动 MCP”标题不能一直挂着。
数据流:先让 schaltwerk 从启动到就绪,底部此时不再运行任务,但标题还停留在启动提示 → 再模拟聊天回合开始 → 检查底部变为运行中,标题更新为 Working。
调用关系:它和 turn_start_preserves_active_mcp_startup_header 形成对照:一个测试“还没完成时保留启动标题”,这个测试“已经完成后让聊天工作标题接管”。
调用图:调用 1 个内部函数(notify_mcp_status);外部调用 2 个(assert!, assert_eq!)。
app_server_mcp_startup_failure_renders_warning_history182–250 ↗
async fn app_server_mcp_startup_failure_renders_warning_history()
作用:这个测试确认:MCP 启动失败时,历史区会显示清楚的警告,并且最终总结也能正确渲染到终端画面里。它不只看文字内容,也看实际 UI 呈现。
数据流:设置 alpha 和 beta 为预期服务器 → alpha 启动后失败,检查只出现失败警告且启动流程还没结束 → beta 启动并就绪后,检查出现启动不完整总结且底部停止运行 → 再把这些历史行插入测试终端,渲染整个聊天窗口并做快照对比。
调用关系:它调用两个通知帮手制造失败和完成流程,还调用 insert_history_lines 把历史内容放进终端。最后用截图断言确认警告历史在真实终端布局里没有跑偏。
调用图:调用 5 个内部函数(notify_mcp_status, notify_mcp_status_error, with_options, insert_history_lines, new);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, assert_eq!)。
mcp_startup_failure_restores_running_status_header253–279 ↗
async fn mcp_startup_failure_restores_running_status_header()
作用:这个测试确认:聊天任务正在运行时,如果 MCP 启动中有服务器失败,MCP 流程收尾后状态标题要回到 Working。失败提示可以出现,但不能盖住正在工作的状态。
数据流:先开始一轮聊天任务,再让 alpha 和 beta 都进入启动中 → alpha 失败,beta 就绪 → 清空历史输出后检查底部仍运行中、状态指示可见、标题恢复为 Working。
调用关系:它测试 MCP 失败收尾和聊天任务运行状态的衔接。notify_mcp_status_error 触发失败,notify_mcp_status 触发其他服务器完成,断言确认 ChatWidget 选对最终标题。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 2 个(assert!, assert_eq!)。
mcp_startup_complete_preserves_review_status282–326 ↗
async fn mcp_startup_complete_preserves_review_status()
作用:这个测试确认:如果界面正在等待用户审批一个危险命令,MCP 启动完成后不能把审批提示覆盖掉。审批提示比普通工作状态更需要用户注意。
数据流:先开始聊天回合并让 alpha 正在启动 → 再模拟一个 Guardian 审批事件,Guardian 可以理解成安全检查器,要求用户确认命令 → alpha 就绪后,检查底部仍运行中,标题是 Reviewing approval request,详情里还是那条待审批命令。
调用关系:它把 MCP 启动、聊天运行、安全审批三种状态叠在一起。notify_mcp_status 推动 MCP 完成,on_guardian_assessment 设置审批状态,断言保证审批状态优先保留。
调用图:调用 1 个内部函数(notify_mcp_status);外部调用 2 个(assert!, assert_eq!)。
app_server_mcp_startup_lag_settles_startup_and_ignores_late_updates329–365 ↗
async fn app_server_mcp_startup_lag_settles_startup_and_ignores_late_updates()
作用:这个测试确认:如果服务端启动消息拖太久,系统可以主动结束这一轮 MCP 启动,并且之后迟到的旧消息不会重新把界面弄成运行中。
数据流:先让 alpha 失败、beta 停在启动中 → 调用 finish_mcp_startup_after_lag,表示等太久了要收尾 → 检查历史里同时有启动被中断、beta 未完成、alpha 失败总结,底部停止运行 → 再发送 beta 的迟到启动和就绪通知,检查没有新历史,底部也不再运行。
调用关系:它使用通知帮手制造“半吊子启动”,再直接调用 ChatWidget 的超时收尾方法。后半段专门验证 handle_server_notification 对迟到更新的过滤。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 1 个(assert!)。
app_server_mcp_startup_after_lag_can_settle_without_starting_updates368–396 ↗
async fn app_server_mcp_startup_after_lag_can_settle_without_starting_updates()
作用:这个测试确认:即使先发生了“等待太久”的收尾,后面只收到失败和就绪通知、没有收到启动中通知,也能正确完成一轮 MCP 状态结算。
数据流:先设置 alpha 和 beta 为预期服务器并立即调用超时收尾 → alpha 随后报失败,历史区出现失败警告,底部变为运行中 → beta 随后就绪,历史区出现 alpha 失败总结,底部停止运行。
调用关系:它测试一种消息顺序很怪的情况:收尾信号先到,服务器状态后到。notify_mcp_status_error 和 notify_mcp_status 继续走正常通知入口,用断言确认系统仍能自洽。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 2 个(assert!, assert_eq!)。
app_server_mcp_startup_after_lag_preserves_partial_terminal_only_round399–437 ↗
async fn app_server_mcp_startup_after_lag_preserves_partial_terminal_only_round()
作用:这个测试确认:一次因超时而只在终端留下部分信息的 MCP 启动轮次,不会被后续重复失败立刻污染,但在合适的下一次收尾时仍能给出完整总结。
数据流:先让 alpha 失败、beta 启动中,并清掉已输出的历史 → 超时收尾后底部停止运行 → 再次收到 alpha 相同失败时不应输出新内容 → 再次超时收尾后,beta 就绪触发总结,历史里应该包含 alpha 失败和启动不完整总结。
调用关系:它专门覆盖“终端里已有部分记录,但内部轮次又被推进”的复杂情况。通知帮手负责送旧消息和完成消息,finish_mcp_startup_after_lag 负责切换轮次边界。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 1 个(assert!)。
app_server_mcp_startup_next_round_discards_stale_terminal_updates440–481 ↗
async fn app_server_mcp_startup_next_round_discards_stale_terminal_updates()
作用:这个测试确认:上一轮超时结束后,迟到的旧失败消息会被丢掉;但真正的新一轮服务器状态仍然可以重新启动流程。这样旧账不会误伤新账。
数据流:先让 alpha 失败、beta 启动中,然后超时收尾 → 之后 alpha 又发来一条带 stale 字样的旧失败,应该没有输出 → beta 又发启动也不应重新运行 → alpha 发就绪后,底部开始运行,表示新一轮被识别 → beta 就绪后没有失败总结,底部停止运行。
调用关系:它检查旧消息过滤和新轮次识别之间的边界。notify_mcp_status_error 模拟迟到旧失败,notify_mcp_status 模拟后续状态,断言观察 ChatWidget 是否正确判断哪些消息该算数。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 1 个(assert!)。
app_server_mcp_startup_next_round_keeps_terminal_statuses_after_starting484–518 ↗
async fn app_server_mcp_startup_next_round_keeps_terminal_statuses_after_starting()
作用:这个测试确认:如果超时收尾后,下一轮真的从 Starting 状态开始,那么这轮里的失败和就绪都应该被正常记录。也就是说,不是所有超时后的消息都该被忽略。
数据流:先调用超时收尾 → alpha 发 Starting,暂时没有历史 → alpha 失败,历史出现失败警告 → beta 发 Starting,底部运行中 → beta 就绪,历史出现 alpha 失败总结,底部停止运行。
调用关系:它和丢弃旧消息的测试形成对照:只要新一轮有明确 Starting 开头,后续状态就要保留。两个通知帮手共同模拟完整的新一轮。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 2 个(assert!, assert_eq!)。
app_server_mcp_startup_next_round_with_empty_expected_servers_reactivates521–544 ↗
async fn app_server_mcp_startup_next_round_with_empty_expected_servers_reactivates()
作用:这个测试确认:即使最初没有预期要启动的 MCP 服务器,运行时突然出现的服务器状态也能重新激活启动流程。否则动态注册的服务器失败时用户可能完全看不到提示。
数据流:先把预期服务器设为空,并手动结束一个空启动轮次 → runtime 服务器发 Starting,底部变为运行中但没有历史 → runtime 随后失败,历史里同时出现失败原因和启动不完整总结,底部停止运行。
调用关系:它覆盖“没有预设名单”的场景。notify_mcp_status 让运行时服务器进入流程,notify_mcp_status_error 触发失败总结,测试确认 ChatWidget 不依赖固定名单才能工作。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 2 个(new, assert!)。
app_server_mcp_startup_after_lag_includes_runtime_servers_with_expected_set547–573 ↗
async fn app_server_mcp_startup_after_lag_includes_runtime_servers_with_expected_set()
作用:这个测试确认:即使已经有预期服务器名单,运行时冒出来并失败的额外服务器也要被纳入最终总结。用户应该知道所有失败,而不只是预设名单里的失败。
数据流:先只声明 alpha 是预期服务器 → runtime 直接报失败,历史区出现 runtime 失败警告,底部进入运行中 → 调用超时收尾后,历史总结里也必须包含 runtime 失败,底部停止运行。
调用关系:它只用 notify_mcp_status_error 制造额外服务器失败,再用 finish_mcp_startup_after_lag 收尾。重点是验证总结收集失败名单时不会漏掉运行时服务器。
调用图:调用 1 个内部函数(notify_mcp_status_error);外部调用 1 个(assert!)。
app_server_mcp_startup_next_round_after_lag_can_settle_without_starting_updates576–625 ↗
async fn app_server_mcp_startup_next_round_after_lag_can_settle_without_starting_updates()
作用:这个测试确认:上一轮超时结束并丢弃旧失败后,下一轮即使没有 Starting 通知,也能在合适时机把失败和就绪合并成最终总结。它覆盖的是非常乱序的服务端消息。
数据流:先制造 alpha 失败、beta 启动中,并超时收尾 → 再收到一条旧 alpha 失败,被确认没有输出 → 再次超时收尾 → 新的 alpha 失败先被暂存、不立刻输出且底部不运行 → beta 就绪后,历史里出现 alpha 失败和启动不完整总结,底部仍停止运行。
调用关系:它把 notify_mcp_status、notify_mcp_status_error 和 finish_mcp_startup_after_lag 多次交错使用,模拟服务端消息延迟、重复、缺少 Starting 的极端情况。测试目标是确保 ChatWidget 的轮次判断和最终汇总仍然稳定。
调用图:调用 2 个内部函数(notify_mcp_status, notify_mcp_status_error);外部调用 1 个(assert!)。
tui/src/chatwidget/tests/permissions.rs源码 ↗
这个文件像一套“安全门禁演练”。聊天界面允许用户选择不同权限:只读、可写工作区、全权限、是否需要人工批准、是否让系统自动审查等。这里的测试会造出一个假的 ChatWidget,打开底部弹窗,模拟用户按上下键、回车、Esc,然后检查两件事:屏幕上写的内容对不对,以及程序发出的 AppEvent 事件对不对。它还特别照顾 Windows 沙箱场景:比如需要管理员权限时要提示,组织只允许某种沙箱时不能给用户不合规的选项。文件里有两个小助手:一个生成“工作区可写但多一个可写目录”的权限配置,另一个生成 Windows 沙箱要求配置。其余大多是测试用例,保证权限 UI 的显示、导航、历史记录和确认流程都不会悄悄退化。
app_server_workspace_write_profile12–49 ↗
fn app_server_workspace_write_profile(extra_root: AbsolutePathBuf) -> PermissionProfile
作用:这个辅助函数造出一个“工作区可写”的权限配置,并额外允许写入一个指定目录。测试用它来模拟真实服务端可能下发的、更复杂一点的权限清单。
数据流:输入一个额外目录路径 → 它组装出网络受限、根目录只读、项目目录和临时目录可写、额外目录也可写的权限配置 → 返回这个 PermissionProfile,不改动外部状态。
调用关系:它不是测试入口,而是被 preset_matching_accepts_workspace_write_with_extra_roots 和 permissions_selection_marks_auto_review_current_with_custom_workspace_write_details 调用,用来验证界面不要因为多了可写目录就认错当前权限模式。
调用图:被 2 处调用(permissions_selection_marks_auto_review_current_with_custom_workspace_write_details, preset_matching_accepts_workspace_write_with_extra_roots);外部调用 1 个(vec!)。
windows_sandbox_requirements_stack51–68 ↗
fn windows_sandbox_requirements_stack(
allowed_sandbox_implementations: Vec<WindowsSandboxModeToml>,
) -> ConfigLayerStack
作用:这个辅助函数造出一套 Windows 沙箱要求配置,比如只允许管理员沙箱,或同时允许非管理员沙箱。测试用它来模拟组织策略对用户选择的限制。
数据流:输入允许的 Windows 沙箱模式列表 → 它把这些模式放进配置要求对象,再转成运行时会用的 ConfigLayerStack → 返回一套可塞进 chat.config 的配置层。
调用关系:它被多个 Windows 沙箱相关测试调用。那些测试先用它改造配置,再打开提示弹窗或启动流程,检查界面是否按组织要求隐藏或显示某些按钮。
调用图:调用 1 个内部函数(new);被 5 处调用(required_windows_sandbox_setup_defers_configured_initial_prompt, startup_windows_sandbox_prompt_blocks_disallowed_unelevated_fallback, windows_sandbox_required_enable_prompt_reopens_on_cancel_when_unelevated_allowed, windows_sandbox_required_enable_prompt_snapshot, windows_sandbox_required_fallback_prompt_snapshot);外部调用 4 个(default, new, try_from, default)。
approvals_selection_popup_snapshot71–85 ↗
async fn approvals_selection_popup_snapshot()
作用:这个测试检查“审批模式选择”弹窗的整体外观。快照测试就像给界面拍照片,以后界面文字或布局变了会被发现。
数据流:先创建一个假的聊天界面 → 关闭 GuardianApproval 功能并打开审批弹窗 → 把底部弹窗渲染成文字 → 和保存好的快照比对,Windows 下使用单独快照。
调用关系:它由测试框架运行,主要覆盖 chat.open_approvals_popup 和 render_bottom_popup 这条显示路径,不再把工作交给本文件其他函数。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, with_settings!)。
profile_permissions_selection_popup_snapshot88–105 ↗
async fn profile_permissions_selection_popup_snapshot()
作用:这个测试检查“权限配置档选择”弹窗在使用内置工作区权限时长什么样。它保证显式权限配置模式下,当前配置能正确显示出来。
数据流:创建聊天界面 → 打开显式权限配置模式 → 把当前权限设成内置 workspace 配置 → 打开权限弹窗并渲染 → 与快照比较。
调用关系:它由测试框架直接运行,依赖权限快照和内置 workspace_write 配置来搭好场景,然后验证 ChatWidget 的权限弹窗输出。
调用图:调用 3 个内部函数(new, active, workspace_write);外部调用 1 个(assert_chatwidget_snapshot!)。
profile_permissions_selection_popup_with_custom_profiles_snapshot108–135 ↗
async fn profile_permissions_selection_popup_with_custom_profiles_snapshot()
作用:这个测试检查有自定义权限配置档时,权限弹窗是否把它们显示出来。它防止自定义配置的名称和说明在界面上丢失。
数据流:创建聊天界面 → 打开显式权限配置模式 → 塞入两个自定义权限档摘要 → 把当前活动档设为 locked-down → 打开弹窗并做快照比对。
调用关系:它由测试框架运行,主要验证 ChatWidget 读取 chat.config.custom_permission_profiles 后渲染菜单的能力。
调用图:调用 3 个内部函数(new, active, workspace_write);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
profile_permissions_selection_emits_named_profile_event_only138–167 ↗
async fn profile_permissions_selection_emits_named_profile_event_only()
作用:这个测试确认用户在配置档模式下按回车时,只发出“选择某个权限档”的事件,而不是乱发其他更新。这样上层逻辑能明确知道用户选了哪个档。
数据流:创建聊天界面和事件接收器 → 设置当前为 :workspace 权限档 → 打开弹窗并按回车 → 从事件队列取出事件 → 检查只有一个 SelectPermissionProfile,且带着正确的审批策略、审查人和显示标签。
调用关系:它由测试框架运行,模拟用户在权限弹窗里确认当前项,验证 ChatWidget.handle_key_event 最终发出的 AppEvent。
调用图:调用 3 个内部函数(new, active, workspace_write);外部调用 4 个(from, assert!, assert_eq!, from_fn)。
profile_permissions_selection_emits_active_custom_profile170–199 ↗
async fn profile_permissions_selection_emits_active_custom_profile()
作用:这个测试确认当前活动的是自定义权限档时,按回车会发出自定义档本身,而不是被误当成内置档。这样用户选择的企业或个人配置不会被替换掉。
数据流:创建聊天界面 → 加入 locked-down 自定义权限档 → 设置它为当前活动档 → 打开弹窗并按回车 → 收集事件 → 检查事件里的 profile_id 和显示文字都是 locked-down,且没有内置审批策略覆盖。
调用关系:它由测试框架运行,覆盖自定义权限档选择路径,重点检查 SelectPermissionProfile 事件内容。
调用图:调用 3 个内部函数(new, active, workspace_write);外部调用 5 个(from, assert!, assert_eq!, from_fn, vec!)。
profile_permissions_selection_emits_auto_review_mode_event202–232 ↗
async fn profile_permissions_selection_emits_auto_review_mode_event()
作用:这个测试确认用户从“请求批准”往下选到“自动帮我批准/审查”时,会发出正确事件。它保证键盘导航后选择的不是原来的那一项。
数据流:创建聊天界面 → 设置当前 workspace 权限档 → 打开权限弹窗 → 按一次下键再回车 → 读取事件 → 检查发出的 SelectPermissionProfile 使用 AutoReview,并显示为 Approve for me。
调用关系:它由测试框架运行,验证 open_permissions_popup 后的键盘移动和确认逻辑。
调用图:调用 3 个内部函数(new, active, workspace_write);外部调用 4 个(from, assert!, assert_eq!, from_fn)。
profile_permissions_full_access_opens_confirmation235–262 ↗
async fn profile_permissions_full_access_opens_confirmation()
作用:这个测试确认选择“全权限”不会立刻生效,而是先打开确认窗口。全权限风险高,所以必须多一道确认门。
数据流:创建聊天界面 → 打开显式权限模式并关闭相关保护功能提示隐藏 → 打开权限弹窗 → 用上键选到 Full Access 并回车 → 读取事件 → 检查事件是 OpenFullAccessConfirmation,且带着全权限预设和返回权限菜单的标记。
调用关系:它由测试框架运行,检查 ChatWidget 在危险权限选择时把流程交给确认弹窗,而不是直接提交权限变更。
调用图:外部调用 4 个(from, assert!, assert_eq!, from_fn)。
approvals_selection_popup_snapshot_windows_degraded_sandbox267–289 ↗
async fn approvals_selection_popup_snapshot_windows_degraded_sandbox()
作用:这个 Windows 专用测试检查当只能使用非管理员沙箱时,审批弹窗是否说清楚这是降级沙箱。它防止用户误以为自己正在使用完整的默认沙箱。
数据流:创建聊天界面 → 开启 WindowsSandbox 功能但关闭 Elevated 能力 → 打开审批弹窗 → 渲染文本 → 检查里面包含非管理员沙箱标签、设置提示和说明文字。
调用关系:它只在 Windows 测试中运行,覆盖 Windows 沙箱功能标志影响审批弹窗文案的路径。
调用图:外部调用 1 个(assert!)。
preset_matching_accepts_workspace_write_with_extra_roots292–318 ↗
async fn preset_matching_accepts_workspace_write_with_extra_roots()
作用:这个测试确认“工作区可写”预设能接受带额外可写目录的真实权限配置。否则界面可能把本来属于“请求批准”的状态显示成未知或不匹配。
数据流:找到 auto 预设 → 用 app_server_workspace_write_profile 生成带额外目录的当前权限 → 给出当前工作目录 → 检查 OnRequest 能匹配该预设,同时 Never 不应匹配。
调用关系:它调用 app_server_workspace_write_profile 搭场景,然后测试 ChatWidget::preset_matches_current 的判断规则。
调用图:调用 1 个内部函数(app_server_workspace_write_profile);外部调用 1 个(assert!)。
preset_matching_does_not_treat_non_cwd_writable_profile_as_read_only321–357 ↗
async fn preset_matching_does_not_treat_non_cwd_writable_profile_as_read_only()
作用:这个测试确认只要权限配置里有任何可写目录,就不能被误判为“只读”。这能避免界面低估模型实际能改文件的能力。
数据流:构造一个根目录只读、另一个非当前目录可写的权限配置 → 找到 read-only 预设 → 调用匹配判断 → 期望结果为不匹配。
调用关系:它由测试框架运行,直接验证 ChatWidget::preset_matches_current 对只读预设的安全判断。
full_access_confirmation_popup_snapshot360–373 ↗
async fn full_access_confirmation_popup_snapshot()
作用:这个测试检查“启用全权限?”确认弹窗的外观。它保证危险操作的提示文案和布局不会被无意改坏。
数据流:创建聊天界面 → 找到 full-access 预设 → 打开全权限确认弹窗 → 渲染底部弹窗 → 与快照比较。
调用关系:它由测试框架运行,覆盖 chat.open_full_access_confirmation 的显示结果。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
windows_auto_mode_prompt_requests_enabling_sandbox_feature377–395 ↗
async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature()
作用:这个 Windows 专用测试检查选择自动模式时,如果需要启用沙箱,提示里是否说明需要管理员权限并提供非管理员备选。它保证用户知道为什么被拦住,以及还能怎么继续。
数据流:创建聊天界面 → 找到 auto 预设 → 打开 Windows 沙箱启用提示 → 渲染弹窗 → 检查文字包含 Administrator permissions 和 Use non-admin sandbox。
调用关系:它由 Windows 测试运行,覆盖 chat.open_windows_sandbox_enable_prompt 的普通提示文案。
调用图:外部调用 1 个(assert!)。
startup_prompts_for_windows_sandbox_when_agent_requested399–424 ↗
async fn startup_prompts_for_windows_sandbox_when_agent_requested()
作用:这个 Windows 专用测试确认启动时如果需要代理沙箱但功能没启用,界面会主动提示用户设置。它防止程序静默进入不符合预期的沙箱状态。
数据流:创建聊天界面 → 关闭 Windows 沙箱及管理员沙箱能力 → 调用启动提示检查 → 渲染弹窗 → 检查包含管理员权限、设置默认沙箱、非管理员沙箱和退出选项。
调用关系:它由 Windows 测试运行,覆盖 chat.maybe_prompt_windows_sandbox_enable 在启动触发时的行为。
调用图:外部调用 1 个(assert!)。
startup_windows_sandbox_prompt_blocks_disallowed_unelevated_fallback428–447 ↗
async fn startup_windows_sandbox_prompt_blocks_disallowed_unelevated_fallback()
作用:这个 Windows 专用测试确认当组织只允许默认管理员沙箱时,启动提示不能展示“使用非管理员沙箱”的退路。它保证界面尊重组织安全策略。
数据流:创建聊天界面 → 关闭沙箱功能 → 用 windows_sandbox_requirements_stack 设置只允许 Elevated → 触发启动提示 → 检查弹窗说明组织要求默认沙箱,并且不包含非管理员选项。
调用关系:它调用 windows_sandbox_requirements_stack 伪造组织要求,再测试 maybe_prompt_windows_sandbox_enable 的提示过滤。
调用图:调用 1 个内部函数(windows_sandbox_requirements_stack);外部调用 2 个(assert!, vec!)。
windows_sandbox_required_enable_prompt_snapshot450–466 ↗
async fn windows_sandbox_required_enable_prompt_snapshot()
作用:这个测试检查“必须启用 Windows 默认沙箱”提示的完整外观。它用快照锁住组织强制要求场景下的文案。
数据流:创建聊天界面 → 设置配置层只允许 Elevated 沙箱 → 找到 auto 预设 → 打开 Windows 沙箱启用提示 → 渲染并与快照比较。
调用关系:它调用 windows_sandbox_requirements_stack 搭配置,再覆盖 open_windows_sandbox_enable_prompt 的强制要求分支。
调用图:调用 1 个内部函数(windows_sandbox_requirements_stack);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
windows_sandbox_required_enable_prompt_reopens_on_cancel_when_unelevated_allowed469–489 ↗
async fn windows_sandbox_required_enable_prompt_reopens_on_cancel_when_unelevated_allowed()
作用:这个测试确认当当前要求管理员沙箱、同时允许非管理员兜底时,用户按 Esc 取消后会重新打开合适的提示。它防止取消动作让用户绕过必要设置。
数据流:创建聊天界面和事件接收器 → 设置当前模式为 Elevated,配置允许 Elevated 和 Unelevated → 打开启用提示 → 按 Esc → 检查事件队列里出现重新打开 Windows 沙箱启用提示的事件。
调用关系:它调用 windows_sandbox_requirements_stack 设置规则,然后测试弹窗取消键如何通过 AppEvent 把流程拉回去。
调用图:调用 1 个内部函数(windows_sandbox_requirements_stack);外部调用 3 个(new, assert!, vec!)。
required_windows_sandbox_setup_defers_configured_initial_prompt492–549 ↗
async fn required_windows_sandbox_setup_defers_configured_initial_prompt()
作用:这个测试确认如果启动时还必须完成 Windows 沙箱设置,预先配置的用户初始消息不会立刻发给模型。这样可以避免模型在安全环境未准备好时开始执行。
数据流:创建聊天界面、事件接收器和操作接收器 → 设置需要沙箱并放入一条初始用户消息 → 模拟会话配置完成 → 检查初始消息仍被暂存且没有 UserTurn 操作发出 → 切到非管理员沙箱后提交待发消息 → 检查这时才发出包含原始文本的 UserTurn。
调用关系:它调用 windows_sandbox_requirements_stack 搭安全要求,经过 handle_thread_session 和 submit_initial_user_message_if_pending,验证启动流程和沙箱设置流程的先后顺序。
调用图:调用 3 个内部函数(workspace_write, new, windows_sandbox_requirements_stack);外部调用 6 个(new, new, assert!, assert_eq!, panic!, vec!)。
windows_sandbox_required_fallback_prompt_snapshot552–566 ↗
async fn windows_sandbox_required_fallback_prompt_snapshot()
作用:这个测试检查 Windows 沙箱强制要求下的兜底提示长什么样。快照能保证这类安全提示的文字不会被无意改掉。
数据流:创建聊天界面 → 配置只允许 Elevated 沙箱 → 找到 auto 预设 → 打开 Windows 沙箱兜底提示 → 渲染弹窗并做快照比对。
调用关系:它调用 windows_sandbox_requirements_stack 准备限制条件,然后验证 open_windows_sandbox_fallback_prompt 的显示结果。
调用图:调用 1 个内部函数(windows_sandbox_requirements_stack);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
startup_does_not_prompt_for_windows_sandbox_when_not_requested570–581 ↗
async fn startup_does_not_prompt_for_windows_sandbox_when_not_requested()
作用:这个 Windows 专用测试确认如果启动流程没有要求马上显示沙箱提示,就不要弹窗打扰用户。它防止无关的启动提示突然出现。
数据流:创建聊天界面 → 关闭 Windows 沙箱功能 → 调用 maybe_prompt_windows_sandbox_enable 且传入 show_now=false → 检查底部区域没有任何弹窗或模态框。
调用关系:它由 Windows 测试运行,覆盖启动提示函数的“不展示”分支。
调用图:外部调用 1 个(assert!)。
approvals_popup_shows_disabled_presets584–619 ↗
async fn approvals_popup_shows_disabled_presets()
作用:这个测试确认被配置规则禁用的审批预设仍会显示出来,并标明禁用原因。这样用户能看见选项为什么不可用,而不是困惑于它消失了。
数据流:创建聊天界面 → 把审批策略限制成只允许 OnRequest,其他值返回一段错误说明 → 打开审批弹窗 → 用虚拟终端渲染完整界面 → 检查屏幕文字包含 disabled 标记和禁用原因。
调用关系:它由测试框架运行,测试配置约束 Constrained 如何影响 open_approvals_popup 的显示。
permissions_selection_emits_history_cell_when_selection_changes720–744 ↗
async fn permissions_selection_emits_history_cell_when_selection_changes()
作用:这个测试确认用户改了权限后,聊天历史里会插入一条说明记录。这样用户之后能看到权限什么时候被改过。
数据流:创建聊天界面 → 隐藏会干扰测试的警告 → 打开权限弹窗 → 下移并回车选择另一项 → 从事件队列收集历史单元 → 检查恰好一条,并包含 Permissions updated to。
调用关系:它由测试框架运行,验证权限选择完成后 ChatWidget 会发出 InsertHistoryCell 这类历史记录事件。
调用图:外部调用 3 个(from, assert!, assert_eq!)。
permissions_selection_history_snapshot_after_mode_switch747–769 ↗
async fn permissions_selection_history_snapshot_after_mode_switch()
作用:这个测试检查普通权限模式切换后,插入到聊天历史里的那条说明长什么样。它用快照固定用户能看到的历史文案。
数据流:创建聊天界面 → 设置功能和警告状态 → 打开权限弹窗 → 移动到另一个权限模式并回车 → 收集一条历史记录 → 把它渲染成单行文字并与快照比较。
调用关系:它由测试框架运行,覆盖权限弹窗选择后生成历史单元的显示路径。
调用图:外部调用 3 个(from, assert_chatwidget_snapshot!, assert_eq!)。
permissions_selection_history_snapshot_full_access_to_default772–812 ↗
async fn permissions_selection_history_snapshot_full_access_to_default()
作用:这个测试检查从“全权限”切回默认权限时,历史记录显示是否正确。它保证从危险权限降回安全模式时,用户也能看到清楚记录。
数据流:创建聊天界面 → 先把当前权限设成 Never 审批和 Disabled 沙箱,也就是全权限状态 → 打开权限弹窗并选回默认项 → 收集历史记录 → 按平台使用对应快照比对。
调用关系:它由测试框架运行,验证权限状态从全权限变回默认时的历史记录生成和渲染。
调用图:外部调用 4 个(from, assert_chatwidget_snapshot!, assert_eq!, with_settings!)。
permissions_selection_emits_history_cell_when_current_is_selected815–846 ↗
async fn permissions_selection_emits_history_cell_when_current_is_selected()
作用:这个测试确认即使用户选择的就是当前权限,也仍然会插入一条权限更新历史。这样用户的确认动作不会悄悄没有反馈。
数据流:创建聊天界面 → 设置当前为 OnRequest 加 workspace_write → 打开权限弹窗 → 直接按回车 → 收集历史单元 → 检查有一条,并包含权限更新文字。
调用关系:它由测试框架运行,覆盖“选择当前项”这个容易被当成无操作的分支。
调用图:调用 1 个内部函数(workspace_write);外部调用 3 个(from, assert!, assert_eq!)。
permissions_selection_hides_auto_review_when_feature_disabled849–866 ↗
async fn permissions_selection_hides_auto_review_when_feature_disabled()
作用:这个测试确认 GuardianApproval 功能关闭时,权限弹窗里不显示“Approve for me”。这避免用户看到一个当前版本不支持或不该开放的选项。
数据流:创建聊天界面 → 关闭 GuardianApproval 功能并隐藏全权限警告 → 打开权限弹窗 → 渲染弹窗 → 检查文本里没有 Approve for me。
调用关系:它由测试框架运行,验证功能开关如何控制权限菜单里的自动审查选项。
调用图:外部调用 1 个(assert!)。
permissions_selection_hides_auto_review_when_feature_disabled_even_if_auto_review_is_active869–897 ↗
async fn permissions_selection_hides_auto_review_when_feature_disabled_even_if_auto_review_is_active()
作用:这个测试确认哪怕当前配置里审查人已经是 AutoReview,只要功能开关关闭,界面仍不显示“Approve for me”。它保证功能开关优先于旧状态。
数据流:创建聊天界面 → 关闭 GuardianApproval → 手动把当前审查人设为 AutoReview,并设置 workspace_write 权限 → 打开权限弹窗 → 检查渲染文本不包含 Approve for me。
调用关系:它由测试框架运行,覆盖“旧会话状态和当前功能开关冲突”时的菜单显示规则。
调用图:调用 1 个内部函数(workspace_write);外部调用 1 个(assert!)。
permissions_selection_marks_auto_review_current_after_session_configured900–943 ↗
async fn permissions_selection_marks_auto_review_current_after_session_configured()
作用:这个测试确认会话配置同步后,如果当前审查人是 AutoReview,权限弹窗会把“Approve for me”标成当前项。它防止 UI 和真实会话状态不同步。
数据流:创建聊天界面 → 开启 GuardianApproval → 模拟收到 ThreadSessionState,其中审批为 OnRequest、审查人为 AutoReview、权限为 workspace_write → 打开权限弹窗 → 检查 Approve for me 后面带 current 标记。
调用关系:它由测试框架运行,通过 handle_thread_session 模拟后端会话状态,再验证 open_permissions_popup 的当前项识别。
调用图:调用 2 个内部函数(workspace_write, new);外部调用 3 个(new, new, assert!)。
permissions_selection_marks_auto_review_current_with_custom_workspace_write_details946–993 ↗
async fn permissions_selection_marks_auto_review_current_with_custom_workspace_write_details()
作用:这个测试确认即使 workspace_write 权限里多了一些额外可写目录,自动审查模式仍能被识别为当前项。它避免 UI 因权限细节略有不同就显示错状态。
数据流:创建聊天界面 → 开启 GuardianApproval → 用 app_server_workspace_write_profile 生成带额外目录的权限 → 模拟会话配置为 AutoReview 加这个权限 → 打开权限弹窗 → 检查 Approve for me 显示为 current。
调用关系:它调用 app_server_workspace_write_profile 准备更真实的权限配置,再测试会话同步和预设匹配的配合。
调用图:调用 2 个内部函数(new, app_server_workspace_write_profile);外部调用 3 个(new, new, assert!)。
permissions_selection_can_disable_auto_review996–1033 ↗
async fn permissions_selection_can_disable_auto_review()
作用:这个测试确认用户可以从“自动帮我审查”切回人工审批。它还检查这个切换不会顺手改功能开关,只改变审查人。
数据流:创建聊天界面 → 开启 GuardianApproval,当前权限设为 workspace_write → 打开权限弹窗 → 用上键选到 Ask for approval 并回车 → 收集事件 → 检查有 UpdateApprovalsReviewer(User),且没有 UpdateFeatureFlags。
调用关系:它由测试框架运行,验证权限菜单选择如何发出审查人更新事件,而不是修改全局功能标志。
调用图:调用 1 个内部函数(workspace_write);外部调用 3 个(from, assert!, from_fn)。
permissions_selection_sends_approvals_reviewer_in_override_turn_context1036–1115 ↗
async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context()
作用:这个测试确认选择“Approve for me”时,发给核心引擎的 OverrideTurnContext 里也带上 AutoReview。否则界面看起来选了自动审查,但模型实际上下文不会变。
数据流:创建聊天界面 → 开启 GuardianApproval,当前为人工 OnRequest 加 workspace_write → 打开弹窗并确认当前项选中 → 下移到 Approve for me 后回车 → 从事件里找 OverrideTurnContext → 检查它包含 OnRequest、AutoReview、workspace_write 和活动权限档;再检查还发出了 UpdateActivePermissionProfile。
调用关系:它由测试框架运行,覆盖从用户键盘选择到 AppEvent::CodexOp 的完整传递链,确保审批审查人信息被送到核心操作里。
调用图:调用 1 个内部函数(workspace_write);外部调用 4 个(from, assert!, assert_eq!, from_fn)。
permissions_full_access_history_cell_emitted_only_after_confirmation1118–1183 ↗
async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
作用:这个测试确认选择全权限时,历史记录只能在用户确认之后出现。它防止界面先写下“已启用全权限”,但用户其实还没同意。
数据流:创建聊天界面 → 打开权限弹窗并选到全权限 → 收集事件,找出打开确认弹窗的事件,同时记录确认前是否已有历史单元 → 打开确认弹窗并按回车 → 再收集历史单元 → 检查总共只有一条,且内容是权限更新到 Full Access。
调用关系:它由测试框架运行,串起 open_permissions_popup、OpenFullAccessConfirmation、open_full_access_confirmation 和最终确认,验证危险权限流程的历史记录时机。
调用图:外部调用 5 个(from, new, assert!, assert_eq!, cfg!)。
tui/src/chatwidget/tests/plan_mode.rs源码 ↗
这个文件像一份“验收清单”。它不实现聊天功能,而是模拟用户在终端聊天界面里打字、按回车、按 Esc、切换模式、选择弹窗,然后检查界面和发出的事件对不对。重点是 Plan 模式:用户输入里出现独立的“plan”时要不要提醒;计划完成后是否弹出“开始实现计划”的选择框;切换推理强度时是只影响 Plan 模式,还是同时影响全局默认设置;正在运行任务、回放旧消息、排队消息、限流提示等特殊情况会不会误弹窗。它还检查发送给后端的用户消息是否带上正确的协作模式、人格设置和插件提及。简单说,这个文件保证 Plan 模式像交通信号灯一样在各种路况下都亮对灯。
plan_mode_nudge_matches_only_standalone_plain_text_keyword5–12 ↗
fn plan_mode_nudge_matches_only_standalone_plain_text_keyword()
作用:检查“plan”提醒只认独立出现的 plan,不会把 plane 或 planning 误判成计划提示。
数据流:输入是一组短文本 → 测试把它们交给关键词判断函数 → 输出是一组真假断言,确认只有真正独立的 plan 会被识别。
调用关系:这是 Plan 提醒逻辑最底层的文字匹配测试,给后面的界面提醒测试打基础。
调用图:外部调用 1 个(assert!)。
plan_mode_nudge_shows_only_for_eligible_default_mode_drafts15–35 ↗
plan_mode_nudge_hides_while_task_or_modal_is_active38–60 ↗
plan_mode_nudge_dismissal_is_scoped_to_current_thread63–87 ↗
plan_mode_nudge_shift_tab_uses_existing_mode_cycle_path90–100 ↗
async fn plan_mode_nudge_shift_tab_uses_existing_mode_cycle_path()
作用:检查在 Plan 提醒出现时按 Shift+Tab,会走已有的模式切换路径,切到 Plan 模式。
数据流:输入含 plan 的草稿并显示提醒 → 模拟按 BackTab,也就是 Shift+Tab → 刷新后检查当前模式变成 Plan,提醒消失。
调用关系:它确认提醒里的快捷操作不是另起一套逻辑,而是复用聊天界面已有的协作模式轮换机制。
调用图:外部调用 4 个(from, new, assert!, assert_eq!)。
plan_mode_nudge_snapshot103–112 ↗
async fn plan_mode_nudge_snapshot()
作用:保存并比对正常宽度下 Plan 提醒弹窗的样子。
数据流:创建聊天界面,设置上下文用量,输入含 plan 的草稿 → 渲染底部弹窗 → 和快照文件比较。
调用关系:它属于界面外观测试,用快照防止提示文字、排版或上下文用量显示被意外改掉。
调用图:外部调用 2 个(new, assert_chatwidget_snapshot!)。
plan_mode_nudge_narrow_snapshot115–124 ↗
async fn plan_mode_nudge_narrow_snapshot()
作用:检查窄屏幕下 Plan 提醒弹窗仍然排版正确。
数据流:创建聊天界面并触发 Plan 提醒 → 用较小宽度渲染底部弹窗 → 和窄屏快照比较。
调用关系:它补充正常宽度快照,防止终端窗口较窄时提示框变形。
调用图:外部调用 2 个(new, assert_chatwidget_snapshot!)。
plan_implementation_popup_snapshot127–134 ↗
async fn plan_implementation_popup_snapshot()
作用:检查计划完成后,“是否开始实现计划”的弹窗长什么样。
数据流:创建聊天界面,记录一份已完成的计划,再打开实现计划提示 → 渲染底部弹窗 → 和快照比较。
调用关系:它锁定 Plan 完成后的关键选择界面,确保用户看到的选项稳定。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
plan_implementation_popup_context_usage_snapshot137–147 ↗
async fn plan_implementation_popup_context_usage_snapshot()
作用:检查上下文快满时,实现计划弹窗是否正确显示上下文用量。
数据流:创建聊天界面,设置 90% 上下文用量,放入已完成计划并打开弹窗 → 渲染 → 和快照比较。
调用关系:它关注弹窗里的风险提示,让用户知道继续当前上下文还是开新上下文。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
plan_implementation_popup_no_selected_snapshot150–158 ↗
async fn plan_implementation_popup_no_selected_snapshot()
作用:检查实现计划弹窗在没有选中可用项时的显示效果。
数据流:打开实现计划弹窗后按下方向键移动选择 → 渲染底部弹窗 → 和快照比较。
调用关系:它验证键盘导航后的弹窗状态,避免选中高亮或禁用项显示异常。
调用图:外部调用 2 个(from, assert_chatwidget_snapshot!)。
plan_implementation_popup_yes_emits_submit_message_event161–180 ↗
async fn plan_implementation_popup_yes_emits_submit_message_event()
作用:检查用户在实现计划弹窗里选“是”时,会发出一条用默认模式开始编码的提交事件。
数据流:打开弹窗 → 按 Enter → 从事件通道取出事件 → 检查事件文字是固定的实现计划消息,协作模式是 Default。
调用关系:它连接弹窗选择和真正提交消息的流程,保证用户确认后会进入编码执行阶段。
调用图:外部调用 3 个(from, assert_eq!, panic!)。
plan_implementation_popup_clear_context_emits_clear_submit_event183–204 ↗
async fn plan_implementation_popup_clear_context_emits_clear_submit_event()
作用:检查用户选择“清空上下文再实现”时,会提交一条带原计划的新消息。
数据流:先保存计划内容并打开弹窗 → 移到第二个选项后按 Enter → 读取事件 → 确认事件要求清空界面并把计划作为新上下文的输入。
调用关系:它验证“fresh context(新上下文)”路线,保证计划不会丢失,而是被带到新线程里继续执行。
调用图:外部调用 3 个(from, assert_eq!, panic!)。
plan_implementation_clear_context_requires_default_mode_and_plan207–264 ↗
async fn plan_implementation_clear_context_requires_default_mode_and_plan()
作用:检查“清空上下文再实现”这个选项必须同时有默认模式和有效计划才可用。
数据流:构造不同参数:没有默认模式、没有计划、空白计划、有计划、有上下文用量标签 → 生成选择项 → 检查禁用原因、描述和动作是否正确。
调用关系:它直接测试 plan_implementation::selection_view_params,保证弹窗选项的可用性规则清楚可靠。
调用图:调用 2 个内部函数(selection_view_params, default_mode_mask);外部调用 2 个(assert!, assert_eq!)。
submit_user_message_with_mode_sets_coding_collaboration_mode267–290 ↗
async fn submit_user_message_with_mode_sets_coding_collaboration_mode()
作用:检查用指定模式提交消息时,后端收到的用户回合带着默认编码模式。
数据流:创建聊天界面并启用协作模式 → 生成默认模式配置 → 调用带模式提交 → 从操作通道读取 UserTurn → 确认 collaboration_mode 是 Default。
调用关系:它验证弹窗或其他入口调用 submit_user_message_with_mode 后,真正发给后端的模式不会丢。
调用图:调用 2 个内部函数(new, default_mode_mask);外部调用 1 个(panic!)。
reasoning_selection_in_plan_mode_opens_scope_prompt_event293–317 ↗
async fn reasoning_selection_in_plan_mode_opens_scope_prompt_event()
作用:检查在 Plan 模式里改推理强度时,会先询问这个改动影响范围。
数据流:进入 Plan 模式,设置当前推理强度,打开模型推理选择弹窗并选择不同项 → 读取事件 → 确认发出 OpenPlanReasoningScopePrompt。
调用关系:它保证 Plan 模式有专门的范围确认,不会把 Plan 的设置误改成全局设置。
调用图:调用 2 个内部函数(new, plan_mask);外部调用 2 个(from, assert_matches!)。
reasoning_selection_in_plan_mode_without_effort_change_does_not_open_scope_prompt_event320–350 ↗
async fn reasoning_selection_in_plan_mode_without_effort_change_does_not_open_scope_prompt_event()
作用:检查在 Plan 模式中如果没有实际改变推理强度,就不弹范围确认。
数据流:设置 Plan 模式和中等推理强度 → 打开推理弹窗直接确认当前项 → 收集事件 → 确认只有模型和推理更新事件,没有范围确认事件。
调用关系:它防止无意义的确认弹窗打扰用户,同时保证正常的模型更新事件仍发出。
调用图:调用 2 个内部函数(new, plan_mask);外部调用 3 个(from, assert!, from_fn)。
reasoning_selection_in_plan_mode_matching_plan_effort_but_different_global_opens_scope_prompt353–381 ↗
async fn reasoning_selection_in_plan_mode_matching_plan_effort_but_different_global_opens_scope_prompt()
作用:检查 Plan 当前有效推理强度和全局不同的时候,确认 Plan 当前项仍会询问作用范围。
数据流:让全局推理是 High,而 Plan 内置有效值是 Medium → 在 Plan 模式打开推理弹窗并确认 → 读取事件 → 确认弹出带 Medium 的范围选择。
调用关系:它覆盖一个容易出错的边界:不能因为用户点了当前 Plan 值,就偷偷改写全局默认值。
调用图:调用 2 个内部函数(new, plan_mask);外部调用 2 个(from, assert_matches!)。
reasoning_shortcut_in_plan_mode_updates_plan_override_without_prompt_or_persist384–428 ↗
async fn reasoning_shortcut_in_plan_mode_updates_plan_override_without_prompt_or_persist()
作用:检查 Plan 模式里的快捷键只临时更新 Plan 推理覆盖值,不弹确认,也不保存到全局配置。
数据流:进入 Plan 模式并设置全局推理 → 按 Alt+. 快捷键 → 收集事件 → 确认只出现 UpdatePlanModeReasoningEffort,没有范围弹窗、持久化或全局推理更新。
调用关系:它验证快捷键路径和弹窗路径的区别:快捷键是快速改当前 Plan 覆盖值,不做更重的保存动作。
调用图:调用 2 个内部函数(new, plan_mask);外部调用 4 个(Char, new, assert!, from_fn)。
plan_mode_reasoning_override_is_marked_current_in_reasoning_popup431–451 ↗
reasoning_selection_in_plan_mode_model_switch_does_not_open_scope_prompt_event454–482 ↗
plan_reasoning_scope_popup_all_modes_persists_global_and_plan_override485–515 ↗
plan_mode_prompt_notification_uses_dedicated_type_name518–533 ↗
fn plan_mode_prompt_notification_uses_dedicated_type_name()
作用:检查 Plan 模式提示通知使用专门的通知类型名。
数据流:构造 PlanModePrompt 通知 → 用自定义通知白名单测试允许和拒绝 → 检查显示文字。
调用关系:它保证外部通知过滤可以单独控制 Plan 模式提示,而不会和审批通知等其他类型混淆。
调用图:外部调用 2 个(assert!, assert_eq!)。
open_plan_implementation_prompt_sets_pending_notification536–547 ↗
async fn open_plan_implementation_prompt_sets_pending_notification()
作用:检查打开实现计划弹窗时,会设置一个待发送的 Plan 模式通知。
数据流:配置只允许 plan-mode-prompt 通知 → 打开实现计划弹窗 → 检查 pending_notification 的标题是实现计划弹窗标题。
调用关系:它把界面弹窗和系统通知连起来,确保用户在终端外也能被提醒。
调用图:外部调用 3 个(assert_matches!, Custom, vec!)。
open_plan_reasoning_scope_prompt_sets_pending_notification550–561 ↗
async fn open_plan_reasoning_scope_prompt_sets_pending_notification()
作用:检查打开 Plan 推理范围弹窗时,也会设置 Plan 模式通知。
数据流:配置通知白名单 → 打开推理范围弹窗 → 检查待通知标题是推理范围标题。
调用关系:它覆盖另一种 Plan 提示入口,保证两类 Plan 弹窗都能触发同一类通知。
调用图:外部调用 3 个(assert_matches!, Custom, vec!)。
agent_turn_complete_does_not_override_pending_plan_mode_prompt_notification564–576 ↗
async fn agent_turn_complete_does_not_override_pending_plan_mode_prompt_notification()
作用:检查普通“助手回复完成”通知不会覆盖已经等待发送的 Plan 弹窗通知。
数据流:先打开实现计划弹窗设置待通知 → 再发助手回合完成通知 → 检查待通知仍是 PlanModePrompt。
调用关系:它保护通知优先级,避免重要的 Plan 选择提示被普通完成提示挤掉。
调用图:外部调用 1 个(assert_matches!)。
request_user_input_notification_overrides_pending_agent_turn_complete_notification579–607 ↗
async fn request_user_input_notification_overrides_pending_agent_turn_complete_notification()
作用:检查需要用户输入的提示可以覆盖普通助手完成通知。
数据流:先设置助手完成通知 → 再模拟工具请求用户回答“Reasoning scope” → 检查待通知变成 PlanModePrompt,标题来自问题头。
调用关系:它说明真正需要用户操作的通知优先级更高,能打断普通完成提醒。
调用图:外部调用 2 个(assert_matches!, vec!)。
handle_request_user_input_sets_pending_notification610–637 ↗
async fn handle_request_user_input_sets_pending_notification()
作用:检查处理“请求用户输入”时会设置对应的 Plan 模式通知。
数据流:配置允许 Plan 提示通知 → 传入一个带选项的问题 → 检查 pending_notification 使用问题标题。
调用关系:它验证服务端或工具发来的交互请求能转成界面和通知层都认识的提示。
调用图:外部调用 3 个(assert_matches!, Custom, vec!)。
plan_reasoning_scope_popup_mentions_selected_reasoning640–654 ↗
async fn plan_reasoning_scope_popup_mentions_selected_reasoning()
作用:检查 Plan 推理范围弹窗会清楚写出用户刚选的推理强度和当前 Plan 覆盖值。
数据流:设置 Plan 覆盖为 Low,再打开要应用 Medium 的范围弹窗 → 渲染文本 → 检查包含说明、两个选项和当前覆盖值提示。
调用关系:它保证弹窗文案足够清楚,让用户知道自己到底在改什么。
调用图:外部调用 1 个(assert!)。
plan_reasoning_scope_popup_mentions_built_in_plan_default_when_no_override657–666 ↗
async fn plan_reasoning_scope_popup_mentions_built_in_plan_default_when_no_override()
作用:检查没有用户自定义 Plan 覆盖时,弹窗会说明当前使用的是内置 Plan 默认值。
数据流:不设置 Plan 覆盖,打开 Medium 推理范围弹窗 → 渲染文本 → 检查出现 built-in Plan default。
调用关系:它补充上一条测试,覆盖没有覆盖值时的说明文字。
调用图:外部调用 1 个(assert!)。
plan_reasoning_scope_popup_plan_only_does_not_update_all_modes_reasoning669–689 ↗
submit_user_message_with_mode_errors_when_mode_changes_during_running_turn692–717 ↗
async fn submit_user_message_with_mode_errors_when_mode_changes_during_running_turn()
作用:检查任务正在运行时,如果提交消息想切换到不同协作模式,会被拒绝并显示错误。
数据流:先进入 Plan 模式并标记任务运行中 → 尝试用 Default 模式提交 → 检查模式没变、没有提交操作、历史里出现不能切换模式的错误。
调用关系:它保护运行中的回合,避免同一轮对话中途换模式造成后端状态混乱。
调用图:调用 3 个内部函数(new, default_mask, mask_for_kind);外部调用 3 个(assert!, assert_eq!, assert_matches!)。
submit_user_message_with_mode_allows_same_mode_during_running_turn742–769 ↗
async fn submit_user_message_with_mode_allows_same_mode_during_running_turn()
作用:检查任务运行中如果继续使用同一个协作模式提交,是允许的。
数据流:进入 Plan 模式并标记任务运行中 → 用同一个 Plan mask 提交继续规划消息 → 读取 UserTurn → 确认带 Plan 模式且没有排队。
调用关系:它和“运行中禁止换模式”测试配对,说明禁止的是换模式,不是所有运行中输入。
调用图:调用 2 个内部函数(new, mask_for_kind);外部调用 3 个(assert!, assert_eq!, panic!)。
submit_user_message_with_mode_submits_when_plan_stream_is_not_active772–799 ↗
async fn submit_user_message_with_mode_submits_when_plan_stream_is_not_active()
作用:检查没有正在流式输出计划时,可以从 Plan 模式提交到默认模式。
数据流:进入 Plan 模式但不启动任务 → 用 Default 模式提交实现计划消息 → 检查当前模式切到默认并发出对应 UserTurn。
调用关系:它验证从“计划完成”转到“执行编码”的正常入口。
调用图:调用 3 个内部函数(new, default_mask, mask_for_kind);外部调用 3 个(assert!, assert_eq!, panic!)。
plan_implementation_popup_skips_replayed_turn_complete802–833 ↗
async fn plan_implementation_popup_skips_replayed_turn_complete()
作用:检查恢复旧会话回放完成时,不会弹出实现计划提示。
数据流:进入 Plan 模式 → 回放一个已完成的助手消息 → 渲染底部弹窗 → 确认没有实现计划标题。
调用关系:它防止打开历史会话时被旧消息触发新弹窗。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 2 个(assert!, vec!)。
plan_implementation_popup_shows_once_when_replay_precedes_live_turn_complete836–904 ↗
async fn plan_implementation_popup_shows_once_when_replay_precedes_live_turn_complete()
作用:检查先回放旧消息再收到真实新完成时,计划实现弹窗只出现一次。
数据流:先产生计划内容,再回放旧完成回合并确认不弹窗 → 模拟一次实时助手完成并确认弹窗出现 → 按 Esc 关闭 → 再模拟第二次完成并确认不重复出现。
调用关系:它覆盖恢复会话后的真实使用场景,确保新计划会提示,但不会反复骚扰。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 3 个(new, assert!, vec!)。
plan_implementation_popup_skips_when_messages_queued907–927 ↗
async fn plan_implementation_popup_skips_when_messages_queued()
作用:检查已有排队用户消息时,计划实现弹窗不会出现。
数据流:进入 Plan 模式,标记任务运行并排入一条用户消息 → 任务完成 → 渲染弹窗 → 确认没有实现计划提示。
调用关系:它保证排队消息优先继续执行,避免弹窗打断用户已经安排好的下一步。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert!)。
plan_implementation_popup_skips_without_proposed_plan930–954 ↗
async fn plan_implementation_popup_skips_without_proposed_plan()
作用:检查只有内部计划状态更新、但没有实际输出计划文本时,不弹实现提示。
数据流:任务开始后发送结构化计划更新,但不发送计划正文完成事件 → 任务完成 → 检查弹窗不出现。
调用关系:它区分“内部进度”与“用户可采纳的计划文本”,只有后者才触发实现提示。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 2 个(assert!, vec!)。
plan_implementation_popup_shows_after_proposed_plan_output957–976 ↗
async fn plan_implementation_popup_shows_after_proposed_plan_output()
作用:检查真正输出并完成了一份计划后,任务结束会弹出实现计划提示。
数据流:任务开始 → 接收计划增量和计划完成文本 → 任务完成 → 渲染弹窗 → 确认包含实现计划标题。
调用关系:这是计划到实现的主路径测试,确认核心体验可用。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert!)。
plan_implementation_popup_skips_when_steer_follows_proposed_plan979–1019 ↗
async fn plan_implementation_popup_skips_when_steer_follows_proposed_plan()
作用:检查计划后用户又追问或引导了一句,当前回合完成时不会把旧计划拿出来要求实现。
数据流:先保存计划 → 用户输入 Please continue 并提交 → 模拟该用户消息完成,再结束任务 → 渲染弹窗 → 确认没有实现计划提示。
调用关系:它防止旧计划在用户已经改变方向后误触发弹窗。
调用图:调用 2 个内部函数(new, mask_for_kind);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
plan_implementation_popup_shows_after_new_plan_follows_steer1022–1066 ↗
async fn plan_implementation_popup_shows_after_new_plan_follows_steer()
作用:检查用户引导之后如果又产生了新计划,任务完成仍会弹实现提示。
数据流:先有初始计划,再提交 Please revise → 模拟用户消息完成后保存修订计划 → 任务完成 → 检查弹窗出现。
调用关系:它说明弹窗看的是“用户最近引导之后的新计划”,不是简单地禁用所有后续提示。
调用图:调用 2 个内部函数(new, mask_for_kind);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
plan_implementation_popup_skips_when_rate_limit_prompt_pending1069–1099 ↗
async fn plan_implementation_popup_skips_when_rate_limit_prompt_pending()
作用:检查限流提示待显示时,计划实现弹窗会让位。
数据流:进入 Plan 模式并模拟接近限流 → 任务完成 → 渲染弹窗 → 确认显示限流提示,不显示实现计划标题。
调用关系:它保证更紧急的账户或额度提醒优先显示。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 2 个(assert!, vec!)。
plan_completion_restores_status_indicator_after_streaming_plan_output1102–1123 ↗
async fn plan_completion_restores_status_indicator_after_streaming_plan_output()
作用:检查计划流式输出结束后,底部状态指示器会恢复显示。
数据流:任务开始时状态指示器可见 → 收到计划增量并提交刷新后指示器隐藏 → 收到计划完成后检查指示器重新可见,同时任务仍在运行。
调用关系:它验证 Plan 输出流和底部状态栏的配合,防止界面一直像“没在工作”。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert_eq!)。
submit_user_message_queues_while_compaction_turn_is_running1126–1202 ↗
async fn submit_user_message_queues_while_compaction_turn_is_running()
作用:检查压缩上下文的回合正在运行时,用户消息会先尝试发送,失败后排队,等压缩结束再发送。
数据流:模拟一个 compact 回合开始 → 提交用户消息 → 先看到一条 UserTurn 和 pending steer → 模拟后端返回“compact 不能被引导”错误 → 消息移入队列 → compact 完成后再次发出 UserTurn。
调用关系:它覆盖上下文压缩这种特殊后台回合,保证用户输入不会丢,只是换个时机提交。
调用图:调用 2 个内部函数(new, from);外部调用 6 个(TurnCompleted, TurnStarted, new, assert!, assert_eq!, panic!)。
submit_user_message_emits_structured_plugin_mentions_from_bindings1205–1271 ↗
async fn submit_user_message_emits_structured_plugin_mentions_from_bindings()
作用:检查用户输入里的插件提及会被发送成结构化的 Mention 项,而不只是普通文字。
数据流:创建带线程状态的聊天界面并启用插件 → 配置一个 Sample Plugin → 提交含 $sample 和绑定信息的用户消息 → 检查 UserTurn 里同时有文本项和插件 Mention 项。
调用关系:它连接输入框的提及绑定和后端协议,保证插件调用信息不会在提交时丢失。
调用图:调用 2 个内部函数(read_only, new);外部调用 6 个(new, default, new, assert_eq!, panic!, vec!)。
enter_submits_when_plan_stream_is_not_active1274–1295 ↗
collab_mode_shift_tab_cycles_only_when_idle1298–1314 ↗
async fn collab_mode_shift_tab_cycles_only_when_idle()
作用:检查 Shift+Tab 只在空闲时轮换协作模式,任务运行时不会切换。
数据流:创建聊天界面 → 连按两次 BackTab,看到活动模式在 Plan 和 Default 间切换但当前配置对象不变 → 任务开始后再按,确认模式不变。
调用关系:它验证模式快捷键的安全边界:空闲可切,运行中不可切。
调用图:外部调用 2 个(from, assert_eq!)。
mode_switch_surfaces_model_change_notification_when_effective_model_changes1317–1353 ↗
async fn mode_switch_surfaces_model_change_notification_when_effective_model_changes()
作用:检查切换协作模式导致实际模型改变时,会在历史里显示模型变更通知。
数据流:记录默认模型 → 把 Plan 模式模型改成 gpt-5.4-mini 并切过去 → 读取历史通知 → 再切回默认模式并检查默认模型通知。
调用关系:它保证模式切换带来的模型变化对用户透明,不是悄悄发生。
调用图:调用 2 个内部函数(default_mask, mask_for_kind);外部调用 2 个(assert!, format!)。
mode_switch_surfaces_reasoning_change_notification_when_model_stays_same1356–1374 ↗
plan_slash_command_switches_to_plan_mode1377–1392 ↗
async fn plan_slash_command_switches_to_plan_mode()
作用:检查输入 /plan 命令会切到 Plan 模式,但不会提交普通应用事件。
数据流:启用协作模式并记录当前协作配置 → 分发 Plan 斜杠命令 → 清空事件通道并确认只有历史插入事件 → 检查活动模式为 Plan。
调用关系:它验证命令入口和快捷键入口一样能切换 Plan 模式。
调用图:外部调用 2 个(assert!, assert_eq!)。
plan_slash_command_with_args_submits_prompt_in_plan_mode1395–1440 ↗
async fn plan_slash_command_with_args_submits_prompt_in_plan_mode()
作用:检查 /plan 后面带文字时,会去掉命令本身,把剩余文字作为 Plan 模式消息提交。
数据流:配置线程状态并启用协作模式 → 输入“/plan build the plan”后按 Enter → 读取 UserTurn → 检查提交文本是“build the plan”,活动模式是 Plan。
调用关系:它覆盖斜杠命令的快捷提交用法,保证用户一条命令即可切模式并发起规划。
调用图:调用 2 个内部函数(read_only, new);外部调用 5 个(from, default, new, assert_eq!, panic!)。
collaboration_modes_defaults_to_code_on_startup1443–1454 ↗
async fn collaboration_modes_defaults_to_code_on_startup()
作用:检查启用协作模式后,启动时默认仍是编码用的 Default 模式。
数据流:用命令行覆盖打开 collaboration_modes 功能创建聊天界面 → 检查活动模式是 Default,当前模型是测试环境解析出的默认模型。
调用关系:它调用 make_startup_chat_with_cli_overrides,验证应用刚启动时的默认落点。
调用图:调用 1 个内部函数(make_startup_chat_with_cli_overrides);外部调用 2 个(assert_eq!, vec!)。
vim_mode_default_disabled_starts_composer_in_insert_mode1457–1460 ↗
async fn vim_mode_default_disabled_starts_composer_in_insert_mode()
作用:检查默认不启用 Vim 模式时,输入框不是 Vim 键位模式。
数据流:不加覆盖创建启动聊天界面 → 检查 composer_is_vim_enabled 为 false。
调用关系:它用同一个启动辅助函数,覆盖输入框默认配置。
调用图:调用 1 个内部函数(make_startup_chat_with_cli_overrides);外部调用 2 个(new, assert!)。
vim_mode_default_enabled_starts_composer_in_normal_mode1463–1475 ↗
async fn vim_mode_default_enabled_starts_composer_in_normal_mode()
作用:检查配置默认启用 Vim 模式时,输入框从普通模式开始,按 x 不会直接输入字符。
数据流:用配置覆盖启用 tui.vim_mode_default → 创建聊天界面 → 检查 Vim 开启且输入为空 → 按 x → 确认输入仍为空。
调用关系:它验证启动配置会影响输入框键盘模式,和 Plan 测试共用聊天界面启动路径。
调用图:调用 1 个内部函数(make_startup_chat_with_cli_overrides);外部调用 5 个(Char, new, assert!, assert_eq!, vec!)。
make_startup_chat_with_cli_overrides1477–1512 ↗
async fn make_startup_chat_with_cli_overrides(
cli_overrides: Vec<(String, TomlValue)>,
) -> ChatWidget
作用:这是测试用的启动辅助函数,用给定命令行配置覆盖创建一个接近真实启动状态的 ChatWidget。
数据流:输入是一组配置覆盖键值 → 创建临时 codex_home,构建配置,解析测试模型,准备遥测、事件发送器、模型目录等初始化参数 → 输出一个新的 ChatWidget。
调用关系:它被启动相关测试调用,负责把重复的配置和初始化步骤集中起来,避免每个测试都手写一遍。
调用图:调用 3 个内部函数(new, new, test_dummy);被 3 处调用(collaboration_modes_defaults_to_code_on_startup, vim_mode_default_disabled_starts_composer_in_insert_mode, vim_mode_default_enabled_starts_composer_in_normal_mode);外部调用 4 个(new, new, default, new_with_app_event)。
set_model_updates_active_collaboration_mask1515–1526 ↗
async fn set_model_updates_active_collaboration_mask()
作用:检查在 Plan 模式下改模型,会同步更新当前活动协作模式的模型信息。
数据流:进入 Plan 模式 → 调用 set_model 改成 gpt-5.4-mini → 检查当前模型已更新,活动模式仍是 Plan。
调用关系:它验证全局模型设置和当前协作模式 mask 之间不会脱节。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert_eq!)。
set_reasoning_effort_updates_active_collaboration_mask1529–1543 ↗
async fn set_reasoning_effort_updates_active_collaboration_mask()
作用:检查在 Plan 模式下改全局推理设置时,当前有效推理仍按 Plan 模式默认值计算。
数据流:进入 Plan 模式 → 调用 set_reasoning_effort(None) → 检查当前推理是 Medium,模式仍是 Plan。
调用关系:它确认 Plan 模式的有效推理值由模式规则维护,而不是简单照搬全局输入。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert_eq!)。
set_reasoning_effort_does_not_override_active_plan_override1546–1561 ↗
async fn set_reasoning_effort_does_not_override_active_plan_override()
作用:检查已有 Plan 专属推理覆盖时,改全局推理不会覆盖它。
数据流:先设置 Plan 推理覆盖为 High 并进入 Plan 模式 → 把全局推理改成 Low → 检查当前有效推理仍是 High。
调用关系:它保护用户专门为 Plan 模式设置的偏好,不被全局设置误伤。
调用图:调用 1 个内部函数(mask_for_kind);外部调用 1 个(assert_eq!)。
collab_mode_is_sent_after_enabling1564–1586 ↗
collab_mode_applies_default_preset1589–1613 ↗
async fn collab_mode_applies_default_preset()
作用:检查即使没有显式启用功能开关,提交消息也会套用默认协作模式预设。
数据流:创建线程 → 输入 hello 按 Enter → 检查 UserTurn 带 Default 协作模式和 Pragmatic 人格 → 再检查界面当前模式也是 Default。
调用关系:它保证默认预设是一条稳定的基础路径,不依赖用户主动切换。
调用图:调用 1 个内部函数(new);外部调用 4 个(from, new, assert_eq!, panic!)。
user_turn_includes_personality_from_config1616–1633 ↗
plan_update_renders_history_cell1636–1666 ↗
tui/src/chatwidget/tests/popups_and_settings.rs源码 ↗
这个文件不写正式功能,而是模拟用户在终端聊天界面里打开各种弹窗、按方向键、回车、空格、粘贴文字,然后检查画面文字和发出的内部事件是否正确。它覆盖很多容易出错的地方:插件市场加载、安装、卸载、搜索;应用列表刷新失败时怎么兜底;实验功能开关是否保存;记忆设置是否确认;模型和推理强度选择是否正确。这里的“snapshot(快照)”可以理解成给界面拍一张标准照片,以后改代码时再对比照片,发现界面意外变化就报警。没有这些测试,很多细小但影响体验的问题,比如按钮提示错、加载状态提前消失、禁用插件还能被安装,都会很难及时发现。
experimental_mode_plan_is_ignored_on_startup14–58 ↗
async fn experimental_mode_plan_is_ignored_on_startup()
作用:检查启动时即使配置里写了实验性的 plan 模式,聊天界面也不会自动切到这个模式。这样可以避免用户一打开程序就进入不该默认启用的协作模式。
数据流:测试先造一个临时配置目录,把相关功能开关和 tui.experimental_mode 写成 plan → 用这些配置创建 ChatWidget → 最后读取当前协作模式和模型,确认模式仍是默认值,模型仍是解析后的测试模型。
调用关系:这是由异步测试框架直接运行的启动行为测试。它通过 ConfigBuilder、ChatWidgetInit 和 ChatWidget::new_with_app_event 搭出一个接近真实启动的聊天控件,然后用断言验证初始化结果。
调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 6 个(new, new, assert_eq!, default, new_with_app_event, vec!)。
plugins_popup_loading_state_snapshot61–73 ↗
async fn plugins_popup_loading_state_snapshot()
作用:检查用户打开 /plugins 时,如果插件市场数据还没回来,弹窗会显示“正在加载”。这能防止界面空白或误导用户以为没有插件。
数据流:测试创建聊天控件并打开插件功能 → 调用 add_plugins_output 打开插件弹窗 → 渲染底部弹窗文字 → 确认里面有加载提示,并和保存的界面快照对比。
调用关系:测试框架运行它时,它主要驱动 ChatWidget 的插件弹窗入口,再交给渲染辅助函数生成文本。快照断言负责发现界面文案或布局的意外变化。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
marketplace_upgrade_loading_popup_snapshot76–93 ↗
async fn marketplace_upgrade_loading_popup_snapshot()
作用:检查升级某个插件市场时,加载弹窗会明确告诉用户正在升级哪个市场。这样用户不会以为程序卡住了。
数据流:测试创建聊天控件并启用插件 → 打开名为 debug 的市场升级加载弹窗 → 渲染弹窗并筛出包含 Upgrading 的行 → 用快照确认提示文字符合预期。
调用关系:它直接调用聊天控件的 open_marketplace_upgrade_loading_popup,模拟后台升级开始后的界面状态。结果由渲染函数和 insta 快照检查。
调用图:外部调用 1 个(assert_snapshot!)。
marketplace_upgrade_failure_includes_backend_messages_snapshot96–128 ↗
async fn marketplace_upgrade_failure_includes_backend_messages_snapshot()
作用:检查插件市场升级失败时,后端返回的具体错误信息会出现在历史消息里。这样用户能知道是认证失败、清单无效,还是别的问题。
数据流:测试给聊天控件喂入一个升级结果,里面包含两个市场的错误 → 从事件接收器里取出插入历史记录的内容 → 把多行内容合并成字符串 → 用快照确认错误摘要包含每个市场的后端消息。
调用关系:它模拟 AppEvent 处理链中“市场升级完成”的回调。ChatWidget 负责把失败结果转成人能读懂的历史消息,测试从事件通道里把这条消息捞出来检查。
调用图:外部调用 3 个(new, assert_snapshot!, vec!)。
hooks_popup_shows_list_diagnostics131–152 ↗
async fn hooks_popup_shows_list_diagnostics()
作用:检查 hooks 弹窗能显示配置警告和错误。hooks 是“某些时机自动触发的小脚本或规则”,配置错了必须让用户看见。
数据流:测试创建一个 hooks 列表响应,里面放入 warning 和 error → 调用 on_hooks_loaded 交给聊天控件 → 渲染底部弹窗并标准化路径 → 用快照确认诊断信息展示出来。
调用关系:它模拟 hooks 列表加载完成后的 UI 更新。ChatWidget 接收后端结果,底部弹窗负责展示,快照断言负责锁住最终画面。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
plugins_popup_snapshot_shows_all_marketplaces_and_sorts_installed_then_name155–214 ↗
async fn plugins_popup_snapshot_shows_all_marketplaces_and_sorts_installed_then_name()
作用:检查 /plugins 会显示所有市场里的插件,并且先显示已安装插件,再按名字排序。这样列表不会漏掉非官方市场,也更好找。
数据流:测试构造官方市场和仓库市场的插件数据 → 打开已加载的插件弹窗 → 检查隐藏仓库插件也出现 → 再比较几行的位置,确认排序是已安装优先、再按名称。
调用关系:它通过测试数据生成器模拟插件后端响应。ChatWidget 接收响应后交给插件弹窗排序和渲染,测试用文字位置来验证排序规则。
调用图:外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
plugins_popup_truncates_long_descriptions_in_list_rows217–260 ↗
async fn plugins_popup_truncates_long_descriptions_in_list_rows()
作用:检查插件列表里的长描述会被截断,而不是换行撑乱列表。这样窄终端里界面仍然整齐。
数据流:测试准备一个短描述插件和一个超长描述插件 → 加载插件数据并打开弹窗 → 用较窄宽度渲染 → 找到长描述那一行,确认它以省略号截断,完整长句没有出现。
调用关系:它关注插件列表渲染层的表现。数据由测试生成器提供,ChatWidget 更新状态,render_bottom_popup 输出最终文本。
调用图:外部调用 3 个(assert!, assert_snapshot!, vec!)。
plugins_popup_add_marketplace_tab_opens_prompt_and_submits_source263–316 ↗
async fn plugins_popup_add_marketplace_tab_opens_prompt_and_submits_source()
作用:检查“添加插件市场”标签页的完整流程:打开提示框、输入来源、提交后发出加载和抓取事件。这样用户添加 Git 仓库或本地市场时不会卡在半路。
数据流:测试先加载空的官方市场 → 连按右键切到添加市场标签 → 按回车要求打开输入框 → 粘贴 owner/repo 并回车 → 从事件通道确认先发打开加载弹窗事件,再发真正抓取市场的事件,且来源和当前目录正确。
调用关系:它模拟用户键盘操作。ChatWidget 负责把按键转成 AppEvent,后台任务将来会根据 FetchMarketplaceAdd 去执行真正添加。
调用图:外部调用 5 个(from, assert!, assert_eq!, panic!, vec!)。
plugins_popup_upgrades_user_configured_git_marketplace_from_marketplace_tab319–388 ↗
async fn plugins_popup_upgrades_user_configured_git_marketplace_from_marketplace_tab()
作用:检查用户自己配置的 Git 插件市场能在市场标签页里升级,而且连续按快捷键不会重复发起升级。这样能避免重复任务和混乱提示。
数据流:测试把配置层伪造成含有 repo 市场 → 加载官方市场和 repo 市场插件 → 切到 repo 标签页 → 按两次 Ctrl+U → 读取事件,确认只发出一次打开升级加载和一次抓取升级请求。
调用关系:它把配置系统、插件弹窗和事件通道串起来。ChatWidget 判断当前标签页是否可升级,并把动作交给 AppEvent 让外部后台处理。
调用图:外部调用 8 个(Char, from, new, assert!, assert_eq!, default, panic!, vec!)。
marketplace_add_success_refreshes_to_new_marketplace_tab391–472 ↗
async fn marketplace_add_success_refreshes_to_new_marketplace_tab()
作用:检查添加插件市场成功后,插件弹窗会刷新并自动切到新市场标签页。这样用户马上能看到刚添加的市场和里面的插件。
数据流:测试先打开普通插件弹窗 → 显示添加市场加载状态 → 喂入添加成功结果 → 再喂入刷新后的插件市场列表 → 渲染弹窗,确认成功提示、升级/移除快捷键和新插件都出现;关闭再重开后,成功提示不再重复显示。
调用关系:它模拟添加市场后的前后台接力:添加完成后 ChatWidget 记录成功状态,随后 on_plugins_loaded 用新列表刷新界面。测试还确认这个一次性提示不会污染后续打开。
调用图:外部调用 5 个(from, assert!, assert_chatwidget_snapshot!, default, vec!)。
plugins_popup_removes_user_configured_marketplace_flow475–584 ↗
async fn plugins_popup_removes_user_configured_marketplace_flow()
作用:检查移除用户配置的插件市场的完整流程:确认、加载、完成后刷新列表。这样用户不会误删,也能看到删除后的真实结果。
数据流:测试配置一个 repo 市场并加载插件 → 切到该市场标签 → 按 Ctrl+R 打开确认框 → 回车确认后检查移除相关事件 → 模拟加载中和后端移除成功 → 再加载刷新后的插件列表,确认被移除市场和插件不再显示。
调用关系:它覆盖从 UI 确认到 AppEvent 请求再到完成回调的整条链。ChatWidget 负责状态切换,外部后台按 FetchMarketplaceRemove 去实际删除。
调用图:外部调用 9 个(Char, from, new, assert!, assert_chatwidget_snapshot!, assert_eq!, default, panic!, vec!)。
plugin_detail_popup_snapshot_shows_install_actions_and_capability_summaries587–628 ↗
async fn plugin_detail_popup_snapshot_shows_install_actions_and_capability_summaries()
作用:检查未安装插件的详情页会展示安装操作,以及技能、hooks、关联应用、MCP 服务等能力摘要。这样用户安装前能知道插件会做什么。
数据流:测试加载一个 Figma 插件摘要 → 打开插件列表 → 喂入该插件详情 → 渲染详情弹窗 → 用快照确认安装按钮和能力说明都在正确位置。
调用关系:它模拟插件详情加载完成后的界面。列表状态先由 on_plugins_loaded 建立,详情状态由 on_plugin_detail_loaded 注入,最终由底部弹窗渲染。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
plugin_detail_popup_hides_disclosure_for_installed_plugins631–676 ↗
async fn plugin_detail_popup_hides_disclosure_for_installed_plugins()
作用:检查已经安装的插件详情页不会再显示安装前的数据共享提示。这样避免用户看到不合时宜的安装披露信息。
数据流:测试准备一个已安装的 Figma 插件 → 加载列表和详情 → 渲染弹窗 → 确认数据共享披露文案不存在,并用快照检查整体详情页。
调用关系:它和未安装插件详情测试形成对照。ChatWidget 根据插件 installed 状态选择不同详情页文案。
调用图:外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
plugins_popup_remote_row_opens_remote_detail679–732 ↗
async fn plugins_popup_remote_row_opens_remote_detail()
作用:检查远程插件市场里的插件行按回车后,会请求远程详情,而不是本地文件详情。远程插件没有本地路径,所以必须用市场名和插件名定位。
数据流:测试构造一个没有 path 的远程市场和 Calendar 插件 → 打开列表并确认该行可查看 → 按回车 → 从事件通道确认打开详情加载事件和 FetchPluginDetail 参数,其中 marketplace_path 为空、remote_marketplace_name 正确。
调用关系:它验证插件列表行到详情请求的转接逻辑。ChatWidget 把用户选择的远程插件转成 AppEvent,后台再根据这些参数去读取详情。
调用图:外部调用 5 个(from, assert!, assert_eq!, panic!, vec!)。
plugin_detail_remote_install_uses_remote_location735–807 ↗
async fn plugin_detail_remote_install_uses_remote_location()
作用:检查在远程插件详情页安装插件时,请求里会带远程市场名。这样后台知道去哪个远程位置安装。
数据流:测试加载一个未安装的远程 Linear 插件和详情 → 渲染确认有安装按钮 → 选择安装操作并回车 → 检查事件中 location 是 Remote,市场名、插件名和显示名都正确。
调用关系:它接在远程详情页之后,验证安装动作的事件参数。ChatWidget 只发事件,真正安装由收到 FetchPluginInstall 的后台完成。
plugin_detail_remote_uninstall_uses_remote_plugin_id810–875 ↗
async fn plugin_detail_remote_uninstall_uses_remote_plugin_id()
作用:检查卸载远程插件时使用远程插件自己的 ID。这样不会因为显示名或本地别名不同而卸错。
数据流:测试加载一个已安装的远程 Linear 插件 → 打开详情 → 选择卸载动作 → 从事件通道确认 OpenPluginUninstallLoading 和 FetchPluginUninstall 里的 plugin_id 是远程 ID。
调用关系:它验证远程插件卸载路径。ChatWidget 从详情数据里取出远程身份标识,然后把它交给 AppEvent。
调用图:外部调用 5 个(from, new, assert_eq!, panic!, vec!)。
plugin_detail_remote_without_remote_id_disables_uninstall_action878–937 ↗
async fn plugin_detail_remote_without_remote_id_disables_uninstall_action()
作用:检查远程插件如果没有提供可卸载身份,就禁用卸载操作。这样系统不会发出无法执行或可能错误的卸载请求。
数据流:测试构造一个来源标为远程、已安装但 ID 不符合远程卸载要求的插件 → 加载详情 → 渲染弹窗 → 确认出现“没有卸载身份”的提示,且不会出现正常卸载按钮,也没有事件发出。
调用关系:它验证详情页的保护逻辑。ChatWidget 在渲染操作项时识别缺失身份,直接显示禁用说明,不把活交给后台。
plugin_detail_admin_disabled_plugin_blocks_install940–982 ↗
plugins_popup_admin_disabled_installed_plugin_has_no_toggle_hint985–1023 ↗
async fn plugins_popup_admin_disabled_installed_plugin_has_no_toggle_hint()
作用:检查已安装但被管理员禁用的插件,在列表里不会提示用户用空格启用或禁用。这样用户不会看到做不到的操作。
数据流:测试加载一个已安装且管理员禁用的插件 → 渲染列表,确认只有查看详情提示,没有 Space 切换提示 → 按空格后再次渲染,确认没有事件、画面不变。
调用关系:它验证插件列表中的按键提示和按键处理一致。ChatWidget 既不显示切换入口,也不响应空格切换。
调用图:外部调用 5 个(Char, new, assert!, assert_eq!, vec!)。
plugin_detail_error_popup_skips_disabled_row_numbering1026–1051 ↗
async fn plugin_detail_error_popup_skips_disabled_row_numbering()
作用:检查插件详情加载失败时,错误弹窗的行号和可选项不会因为禁用行而乱掉。这样错误页看起来仍然清楚。
数据流:测试加载一个插件列表 → 对详情加载回调传入错误字符串 → 渲染底部弹窗 → 用快照确认错误页的布局和编号正确。
调用关系:它模拟 FetchPluginDetail 失败后的展示路径。ChatWidget 把错误转成详情错误视图,快照负责锁定界面。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
plugins_popup_refresh_preserves_selected_row_position1054–1132 ↗
async fn plugins_popup_refresh_preserves_selected_row_position()
作用:检查插件列表刷新后,会尽量保留用户当前选中的行位置。这样列表更新时光标不会突然跳回顶部。
数据流:测试先加载 Notion 和 Slack,并把选择移动到 Slack → 再加载一个插入了 Airtable 的新列表 → 渲染弹窗 → 确认选中位置仍是第二行,所以现在选中 Notion,同时新插件也显示出来。
调用关系:它验证 on_plugins_loaded 刷新状态时的选择保持策略。ChatWidget 不是按插件身份锁定,而是保留列表中的位置。
plugins_popup_refreshes_installed_counts_after_install1135–1201 ↗
plugins_popup_space_toggles_installed_plugin_from_list1204–1262 ↗
async fn plugins_popup_space_toggles_installed_plugin_from_list()
作用:检查在插件列表里对已安装插件按空格,会发出启用/禁用事件,并在结果回来后保持选中那一行。这样用户能快速开关插件。
数据流:测试加载两个已安装插件 → 移到 Drive → 按空格 → 检查事件里 plugin_id 是 plugin-drive、enabled 是 false → 模拟设置成功回调 → 渲染确认 Drive 仍被选中且变成未勾选。
调用关系:它验证列表快捷键到 SetPluginEnabled 事件的路径。ChatWidget 发事件给外部执行,on_plugin_enabled_set 再把结果反映回 UI。
调用图:外部调用 7 个(Char, from, new, assert!, assert_eq!, panic!, vec!)。
plugins_popup_space_on_uninstalled_row_does_not_start_search1265–1303 ↗
async fn plugins_popup_space_on_uninstalled_row_does_not_start_search()
作用:检查在未安装插件行按空格不会误触发搜索或其他动作。这样空格键行为不会让用户困惑。
数据流:测试加载两个未安装插件 → 记录按空格前的弹窗 → 按空格 → 确认没有事件发出,按键后的弹窗和之前完全一样。
调用关系:它验证插件列表的按键过滤。ChatWidget 只有在插件已安装且可切换时才把空格当作开关。
调用图:外部调用 5 个(Char, new, assert!, assert_eq!, vec!)。
plugins_popup_space_with_active_search_does_not_toggle_installed_plugin1306–1343 ↗
async fn plugins_popup_space_with_active_search_does_not_toggle_installed_plugin()
作用:检查插件搜索框里已经有输入时,空格不会去切换当前插件。这样用户输入搜索词时不会误改插件状态。
数据流:测试加载已安装插件 → 选中 Drive → 输入搜索查询 dr → 按空格 → 确认事件通道没有收到启用/禁用事件。
调用关系:它验证搜索输入状态优先于列表快捷键。ChatWidget 在搜索活跃时把按键留给搜索框,而不是插件行操作。
plugins_popup_search_filters_visible_rows_snapshot1346–1391 ↗
async fn plugins_popup_search_filters_visible_rows_snapshot()
作用:检查插件搜索会过滤可见行,只显示匹配的插件。这样插件多时用户能快速找到目标。
数据流:测试加载 Calendar、Slack、Drive → 输入搜索 sla → 渲染弹窗 → 用快照确认界面,并额外检查 Calendar 和 Drive 不再出现。
调用关系:它测试插件弹窗内部搜索功能。输入由键盘事件模拟,ChatWidget 更新搜索字符串,渲染层根据字符串筛选行。
调用图:外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
plugins_popup_installed_tab_filters_rows_and_clears_search1394–1438 ↗
async fn plugins_popup_installed_tab_filters_rows_and_clears_search()
作用:检查切到“已安装”标签页时,只显示已安装插件,并清空之前的搜索词。这样标签页不会被旧搜索条件干扰。
数据流:测试加载一个已安装 Calendar 和一个未安装 Slack → 输入搜索 sla → 按右键切到已安装标签 → 渲染确认只剩 Calendar,标题显示已安装视图,并且搜索词 sla 不见了。
调用关系:它验证标签切换和搜索状态的配合。ChatWidget 在切换 tab 时重置搜索,弹窗按当前 tab 重新筛选。
plugins_popup_openai_curated_tab_omits_marketplace_in_rows1441–1485 ↗
async fn plugins_popup_openai_curated_tab_omits_marketplace_in_rows()
作用:检查官方精选市场标签页里,插件行不会重复显示市场名称。因为整个标签已经说明来源了,重复文字会显得啰嗦。
数据流:测试加载官方市场插件和仓库市场插件 → 切到 OpenAI Curated 标签 → 渲染弹窗 → 确认只显示官方插件,不显示仓库插件,也不显示“ChatGPT Marketplace ·”这种市场前缀。
调用关系:它验证插件弹窗的市场标签页视图。ChatWidget 根据当前标签选择插件集合,并让行文案适配这个上下文。
plugins_popup_refresh_preserves_duplicate_marketplace_tab_by_path1488–1549 ↗
async fn plugins_popup_refresh_preserves_duplicate_marketplace_tab_by_path()
作用:检查两个市场重名时,刷新后能靠路径保留用户选中的那个标签。这样不会从第二个同名市场跳到第一个。
数据流:测试构造两个名字都叫 duplicate、路径不同的市场 → 切到第二个市场标签 → 用同一响应刷新 → 渲染确认标题是第二个 duplicate,列表里是 Repo Plugin 而不是 Home Plugin。
调用关系:它验证市场标签身份识别。ChatWidget 不能只看市场名,还要用 path 区分同名市场。
plugins_popup_search_no_matches_and_backspace_restores_results1552–1605 ↗
async fn plugins_popup_search_no_matches_and_backspace_restores_results()
作用:检查搜索没有结果时会显示“无匹配”,退格清空后列表会恢复。这样搜索体验完整、可逆。
数据流:测试加载 Calendar 和 Slack → 输入 zzz → 渲染确认显示查询词和无匹配提示 → 连按三次退格 → 再渲染确认两个插件回来,无匹配提示消失。
调用关系:它覆盖搜索输入、空结果状态和退格恢复。ChatWidget 维护搜索字符串,渲染层根据字符串决定显示列表或空状态。
apps_popup_stays_loading_until_final_snapshot_updates1608–1699 ↗
async fn apps_popup_stays_loading_until_final_snapshot_updates()
作用:检查应用弹窗在只收到部分应用列表时继续显示加载,直到最终完整列表到达。这样用户不会把半截数据误认为完整结果。
数据流:测试启用 Apps 功能和连接器 → 先喂入一个非最终快照 → 打开 /apps 并确认仍是加载状态 → 再喂入最终快照,包含 Notion 和 Linear → 渲染确认已安装数量和新应用出现。
调用关系:它测试连接器刷新流程。ChatWidget 的 on_connectors_loaded 区分 partial 和 final,底部应用弹窗只在完整数据到达后展示正式列表。
调用图:外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
apps_notification_update_excludes_inaccessible_apps_from_mentions1702–1770 ↗
async fn apps_notification_update_excludes_inaccessible_apps_from_mentions()
作用:检查输入 $ 触发应用提及时,不可访问的目录应用不会出现在候选里。这样用户不会选到自己不能用的应用。
数据流:测试启用应用功能并把输入框内容设为 $ → 喂入一个可访问 Google Drive 和一个不可访问 Arabica → 确认内部部分快照确实包含不可访问项 → 渲染弹窗,确认只显示 Google Drive。
调用关系:它验证提及弹窗和连接器快照的过滤关系。ChatWidget 可以缓存完整数据,但底部提及候选只展示可访问应用。
调用图:外部调用 4 个(new, assert!, assert_matches!, vec!)。
apps_refresh_failure_keeps_existing_full_snapshot1773–1859 ↗
async fn apps_refresh_failure_keeps_existing_full_snapshot()
作用:检查应用刷新失败时,如果之前已有完整列表,就继续使用旧完整列表。这样一次网络失败不会让应用列表消失。
数据流:测试先喂入 Notion 和 Linear 的完整快照 → 再喂入部分快照 → 最终刷新返回错误 → 确认缓存仍是原来的完整快照 → 打开应用弹窗,确认仍按旧数据显示。
调用关系:它测试连接器缓存的容错策略。ChatWidget 在 final 错误时不覆盖已有 Ready 缓存,从而让 UI 保持可用。
调用图:外部调用 3 个(assert!, assert_matches!, vec!)。
apps_popup_preserves_selected_app_across_refresh1862–1979 ↗
async fn apps_popup_preserves_selected_app_across_refresh()
作用:检查应用列表刷新后,用户选中的应用会尽量保持为同一个应用。这样列表插入新项时选择不会乱跳。
数据流:测试先加载 Notion 和 Slack → 打开弹窗并选中 Slack → 再加载包含 Airtable、Notion、Slack 的新完整列表 → 渲染确认 Slack 仍然被选中,而不是重置到 Notion。
调用关系:它验证应用弹窗的选择保持策略。ChatWidget 按应用身份而不是单纯位置恢复选中项。
apps_refresh_failure_with_cached_snapshot_triggers_pending_force_refetch1982–2023 ↗
async fn apps_refresh_failure_with_cached_snapshot_triggers_pending_force_refetch()
作用:检查应用刷新失败且有待执行的强制刷新时,会保持旧缓存并重新标记还有刷新在路上。这样失败后仍能继续补拉数据。
数据流:测试手动设置已有 Ready 缓存、刷新正在进行、还有强制刷新待处理 → 喂入最终错误 → 检查 prefetch_in_flight 仍为真,pending 标记被消费,缓存仍是旧应用。
调用关系:它测试连接器刷新状态机里的边角情况。ChatWidget 在失败后决定是否继续发起下一轮预取,而不是简单停止。
调用图:外部调用 4 个(assert!, assert_matches!, Ready, vec!)。
apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads2026–2127 ↗
async fn apps_popup_keeps_existing_full_snapshot_while_partial_refresh_loads()
作用:检查已有完整应用列表时,新一轮部分刷新不会立刻替换 UI。这样用户看到的是稳定的旧完整列表,而不是半截新列表。
数据流:测试先喂入完整快照并打开应用弹窗 → 再喂入一个非最终部分快照,其中有隐藏应用 → 确认缓存仍是旧完整快照 → 渲染确认旧计数还在,隐藏应用没有出现。
调用关系:它验证 partial refresh 的展示策略。ChatWidget 可以记录部分数据,但应用弹窗在最终结果前继续用旧 Ready 缓存。
调用图:外部调用 3 个(assert!, assert_matches!, vec!)。
apps_refresh_failure_without_full_snapshot_falls_back_to_installed_apps2130–2186 ↗
async fn apps_refresh_failure_without_full_snapshot_falls_back_to_installed_apps()
作用:检查如果没有旧完整列表,最终刷新失败时会退回到已收到的已安装应用。这样至少能显示用户已有的应用,而不是一直加载。
数据流:测试先喂入一个非最终快照 → 打开应用弹窗确认仍加载 → 再喂入最终错误 → 确认缓存变成包含一个应用的 Ready 状态 → 渲染确认按 1/1 可用应用展示。
调用关系:它测试没有完整缓存时的兜底分支。ChatWidget 把部分结果升级成可展示缓存,避免错误后 UI 无内容。
调用图:外部调用 3 个(assert!, assert_matches!, vec!)。
apps_popup_shows_disabled_status_for_installed_but_disabled_apps2189–2229 ↗
async fn apps_popup_shows_disabled_status_for_installed_but_disabled_apps()
作用:检查已安装但被禁用的应用会显示 Disabled 状态,并提示可以进入页面启用或禁用。这样用户能明白应用装了但当前不可用。
数据流:测试加载一个可访问但 is_enabled 为 false 的 Notion → 打开应用弹窗 → 渲染确认选中描述里同时有 Installed、Disabled 和 enable/disable 提示。
调用关系:它验证应用行说明文案。ChatWidget 根据 is_accessible 和 is_enabled 组合出用户能理解的状态。
apps_refresh_preserves_toggled_enabled_state2232–2300 ↗
async fn apps_refresh_preserves_toggled_enabled_state()
作用:检查用户刚切换过应用启用状态后,刷新数据不会马上把这个本地状态覆盖掉。这样后台返回旧值时界面不会反跳。
数据流:测试先加载启用的 Notion → 调用 update_connector_enabled 把它本地改成禁用 → 再喂入后端仍说启用的完整快照 → 检查缓存里 Notion 仍是禁用 → 打开弹窗确认 Disabled 文案还在。
调用关系:它测试本地乐观更新和刷新数据的合并。ChatWidget 保留用户刚做的开关选择,避免被旧快照冲掉。
调用图:外部调用 3 个(assert!, assert_matches!, vec!)。
apps_popup_for_not_installed_app_uses_install_only_selected_description2303–2343 ↗
async fn apps_popup_for_not_installed_app_uses_install_only_selected_description()
作用:检查未安装应用的说明只提示去安装,不提示启用/禁用。这样操作建议符合应用当前状态。
数据流:测试加载一个不可访问的 Linear,表示还没安装或不可用 → 打开应用弹窗 → 渲染确认说明是“可以安装”,并确认没有 enable/disable 文案。
调用关系:它验证应用弹窗对未安装状态的文案分支。ChatWidget 用 is_accessible 判断用户现在是否已有该应用。
experimental_features_popup_snapshot2346–2372 ↗
async fn experimental_features_popup_snapshot()
作用:检查实验功能弹窗的整体样子。实验功能是还没完全稳定的开关,需要清楚显示名称、说明和当前是否启用。
数据流:测试手工创建两个实验功能项 → 用 ExperimentalFeaturesView 显示在底部面板 → 渲染弹窗 → 用快照确认布局、文案和勾选状态。
调用关系:它不经过命令入口,而是直接把视图塞进 bottom_pane。ExperimentalFeaturesView 负责展示,快照负责锁定视觉结果。
调用图:调用 2 个内部函数(new, defaults);外部调用 3 个(new, assert_chatwidget_snapshot!, vec!)。
experimental_features_toggle_saves_on_exit2375–2413 ↗
async fn experimental_features_toggle_saves_on_exit()
作用:检查实验功能弹窗里按空格只是临时切换,按回车退出时才发保存事件。这样用户可以先调整,再统一确认。
数据流:测试创建一个未启用的 JsRepl 开关 → 打开弹窗 → 按空格后确认没有事件 → 再按回车 → 从事件通道找到 UpdateFeatureFlags,确认它把 JsRepl 改成 true。
调用关系:它验证 ExperimentalFeaturesView 的保存时机。按键先由 ChatWidget 转给当前视图,视图在确认时通过 AppEvent 发出更新。
调用图:调用 2 个内部函数(new, defaults);外部调用 6 个(new, Char, new, assert!, assert_eq!, vec!)。
experimental_popup_omits_stable_guardian_approval2416–2433 ↗
async fn experimental_popup_omits_stable_guardian_approval()
作用:检查已经是稳定阶段的 GuardianApproval 不会出现在实验功能弹窗。实验弹窗应该只放还在实验阶段的东西。
数据流:测试先从功能元数据里确认 GuardianApproval 的阶段是 Stable → 打开实验功能弹窗 → 渲染后确认里面没有 Auto-review 文案。
调用关系:它验证 ChatWidget 打开实验弹窗时的筛选规则。功能清单 FEATURES 提供阶段信息,弹窗只展示合适的条目。
调用图:外部调用 2 个(assert!, assert_eq!)。
multi_agent_enable_prompt_snapshot2436–2443 ↗
async fn multi_agent_enable_prompt_snapshot()
作用:检查启用多代理功能的提示弹窗长什么样。多代理功能涉及下次会话生效,需要给用户明确说明。
数据流:测试打开 multi-agent 启用提示 → 渲染底部弹窗 → 用快照确认文案和按钮布局。
调用关系:它直接调用 ChatWidget 的 open_multi_agent_enable_prompt。底部面板显示确认视图,快照检查最终界面。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
multi_agent_enable_prompt_updates_feature_and_emits_notice2446–2462 ↗
async fn multi_agent_enable_prompt_updates_feature_and_emits_notice()
作用:检查用户确认启用多代理后,会发出功能开关更新,并插入一条提示说明下次会话生效。这样用户知道设置不是立刻改变当前会话。
数据流:测试打开启用提示并按回车 → 从事件通道确认 UpdateFeatureFlags 把 Collab 设为 true → 再取出历史消息单元,确认里面写着子代理将在下次会话启用。
调用关系:它验证确认弹窗到配置更新和用户通知的双重输出。ChatWidget 发事件,历史消息作为可见反馈返回给界面。
调用图:外部调用 4 个(from, assert!, assert_matches!, panic!)。
memories_enable_prompt_snapshot2465–2473 ↗
async fn memories_enable_prompt_snapshot()
作用:检查记忆功能未启用时,打开记忆入口会显示启用提示。记忆功能会影响系统是否记住用户偏好,所以需要明确确认。
数据流:测试先把 MemoryTool 功能关掉 → 打开 memories 弹窗 → 渲染底部弹窗 → 用快照确认启用提示。
调用关系:它验证 ChatWidget 根据功能开关选择不同记忆视图。未启用时显示引导,而不是直接设置页。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
memories_enable_prompt_updates_feature_without_notice2476–2491 ↗
async fn memories_enable_prompt_updates_feature_without_notice()
作用:检查确认启用记忆功能时,只发功能开关更新,不提前显示成功通知。这样只有持久化真正成功后才应给用户成功反馈。
数据流:测试关闭 MemoryTool → 打开启用提示并按回车 → 确认事件是 UpdateFeatureFlags 把 MemoryTool 设为 true → 再确认没有额外成功通知事件。
调用关系:它验证记忆启用流程比多代理更克制。ChatWidget 只把更新请求交给外部配置保存流程,不擅自插入成功消息。
调用图:外部调用 3 个(from, assert!, assert_matches!)。
memories_settings_popup_snapshot2494–2504 ↗
async fn memories_settings_popup_snapshot()
作用:检查记忆功能已启用时,记忆设置弹窗的标准样子。这里用户可以控制是否使用记忆、是否生成记忆。
数据流:测试启用 MemoryTool,并把 use_memories 设为 true、generate_memories 设为 false → 打开记忆弹窗 → 渲染并去掉终端链接标记 → 用快照确认设置页。
调用关系:它验证 ChatWidget 打开的是设置视图而不是启用提示。渲染辅助函数用于生成可比对的文本。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
memories_reset_confirmation_snapshot2507–2520 ↗
async fn memories_reset_confirmation_snapshot()
作用:检查在记忆设置里选择重置时,会出现二次确认弹窗。重置记忆是破坏性操作,不能一按就执行。
数据流:测试打开记忆设置 → 连按向下移动到重置项 → 按回车进入确认 → 渲染弹窗 → 用快照确认确认页文案和选项。
调用关系:它模拟用户在设置页里的导航。ChatWidget 把按键交给当前视图,视图切换到重置确认状态。
调用图:外部调用 2 个(from, assert_chatwidget_snapshot!)。
memories_settings_toggle_saves_on_enter2523–2541 ↗
async fn memories_settings_toggle_saves_on_enter()
作用:检查用户切换记忆生成选项后,按回车会发出保存记忆设置事件。这样配置更改能交给外部保存。
数据流:测试打开记忆设置 → 移到 generate_memories 项并按空格切换 → 按回车 → 确认事件是 UpdateMemorySettings,use_memories 为 true,generate_memories 为 true。
调用关系:它验证记忆设置视图的保存路径。按键由 ChatWidget 转发,视图在确认时发 AppEvent。
调用图:外部调用 3 个(Char, from, assert_matches!)。
memories_reset_confirmation_sends_event_on_confirm2544–2557 ↗
async fn memories_reset_confirmation_sends_event_on_confirm()
作用:检查重置记忆二次确认后,会发出 ResetMemories 事件。这样真正删除记忆的工作可以由外部流程处理。
数据流:测试打开记忆设置 → 移到重置项 → 回车进入确认 → 再回车确认 → 从事件通道确认收到 ResetMemories。
调用关系:它验证危险操作的最终确认出口。ChatWidget 不直接清除记忆,而是通过 AppEvent 通知负责持久化的部分。
调用图:外部调用 2 个(from, assert_matches!)。
model_selection_popup_snapshot2560–2567 ↗
async fn model_selection_popup_snapshot()
作用:检查模型选择弹窗的界面快照。模型决定 AI 使用哪套能力,所以选择界面必须清楚。
数据流:测试创建指定模型的聊天控件并设置 thread_id → 打开模型弹窗 → 渲染底部弹窗 → 用快照确认模型列表和当前选择。
调用关系:它直接测试 ChatWidget 的 open_model_popup。thread_id 让场景更接近已有会话,快照锁定选择器表现。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_chatwidget_snapshot!)。
personality_selection_popup_snapshot2570–2577 ↗
async fn personality_selection_popup_snapshot()
作用:检查人格选择弹窗的界面快照。人格会影响模型回答风格,所以弹窗要让用户看得懂当前选项。
数据流:测试用支持人格的模型创建聊天控件并设置 thread_id → 打开人格弹窗 → 渲染并快照比对。
调用关系:它验证 ChatWidget 的 open_personality_popup。模型能力决定弹窗内容,渲染结果由快照检查。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_chatwidget_snapshot!)。
model_picker_hides_show_in_picker_false_models_from_cache2589–2628 ↗
async fn model_picker_hides_show_in_picker_false_models_from_cache()
作用:检查模型选择器会隐藏标记为不应显示的模型。这样内部或不可选模型不会出现在用户面前。
数据流:测试构造一个可见模型和一个 show_in_picker 为 false 的隐藏模型 → 打开带这些预设的模型弹窗 → 渲染后确认可见模型出现、隐藏模型不出现,并做快照比对。
调用关系:它验证模型目录到模型弹窗的过滤。ChatWidget 的 open_model_popup_with_presets 接收缓存数据,然后选择器按 show_in_picker 筛选。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
server_overloaded_error_does_not_switch_models2631–2660 ↗
async fn server_overloaded_error_does_not_switch_models()
作用:检查服务器过载错误不会触发自动切换模型。过载是临时问题,不应该改变用户选择的模型。
数据流:测试创建聊天控件并设定模型 → 清空已有事件 → 调用 handle_error 模拟 ServerOverloaded → 检查普通事件和操作事件里都没有把模型改成别的值。
调用关系:它测试错误处理逻辑和模型状态之间的边界。handle_error 可能处理很多错误,但这个测试保证过载错误不把工作交给模型切换流程。
调用图:外部调用 2 个(assert!, assert_eq!)。
model_reasoning_selection_popup_snapshot2663–2681 ↗
async fn model_reasoning_selection_popup_snapshot()
作用:检查推理强度选择弹窗的界面,包括自定义强度选项。推理强度可以理解成模型思考得浅还是深。
数据流:测试启用账号状态并把当前推理强度设为 High → 取出 gpt-5.4 预设并插入一个自定义 max 选项 → 打开推理弹窗 → 渲染并快照比对。
调用关系:它验证 ChatWidget 的 open_reasoning_popup。模型预设提供可选强度,弹窗负责展示。
调用图:外部调用 2 个(Custom, assert_chatwidget_snapshot!)。
model_reasoning_selection_popup_applies_custom_effort2684–2716 ↗
async fn model_reasoning_selection_popup_applies_custom_effort()
作用:检查用户在推理弹窗里选择自定义强度后,会同时更新当前强度并持久化模型选择。这样当前会话和下次配置都一致。
数据流:测试准备 custom max 强度并打开推理弹窗 → 按下移动到该选项并回车 → 收集事件 → 确认先有 UpdateReasoningEffort,再有 PersistModelSelection,二者都带 custom max。
调用关系:它验证推理选择视图的确认输出。ChatWidget 接收按键后由视图发出即时更新和保存事件。
调用图:外部调用 4 个(from, Custom, assert_eq!, from_fn)。
model_reasoning_selection_popup_extra_high_warning_snapshot2719–2730 ↗
async fn model_reasoning_selection_popup_extra_high_warning_snapshot()
作用:检查选择 Extra High 推理强度时弹窗会显示对应警告。更高推理可能更慢或更耗资源,需要提醒用户。
数据流:测试设置当前强度为 XHigh → 用 gpt-5.2 的模型预设打开推理弹窗 → 渲染并用快照确认警告文案。
调用关系:它测试推理弹窗针对特殊强度的额外说明。ChatWidget 根据当前 effort 和模型预设生成视图。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
assert_reasoning_shortcuts_update_effort2732–2767 ↗
async fn assert_reasoning_shortcuts_update_effort(
key_events: [KeyEvent; 2],
expected_effort: ReasoningEffortConfig,
expect_model_update: bool,
)
作用:这是一个测试辅助函数,用来复用“按快捷键后推理强度是否改变”的检查。它避免上调和下调两个测试写重复代码。
数据流:它接收两个按键、期望推理强度、以及是否期望模型更新 → 对每个按键都创建新聊天控件并设置当前强度 → 发送按键 → 收集事件,确认有正确的 UpdateReasoningEffort,按需要检查 UpdateModel,并确认没有持久化事件。
调用关系:它不是独立业务入口,而是被 reasoning_up_shortcuts_raise_reasoning_effort 和 reasoning_down_shortcuts_lower_reasoning_effort 调用。它把共同断言集中起来,让两个测试只关心方向和期望值。
调用图:调用 1 个内部函数(new);被 2 处调用(reasoning_down_shortcuts_lower_reasoning_effort, reasoning_up_shortcuts_raise_reasoning_effort);外部调用 2 个(assert!, from_fn)。
reasoning_up_shortcuts_raise_reasoning_effort2770–2780 ↗
async fn reasoning_up_shortcuts_raise_reasoning_effort()
作用:检查提高推理强度的快捷键能把 Medium 提升到 High。这样用户不用打开弹窗也能快速调整模型思考深度。
数据流:测试把 Alt+. 和 Shift+Up 这两个按键交给辅助函数 → 期望结果是 High,并期望同时触发模型更新 → 辅助函数逐个创建控件、按键、查事件。
调用关系:它调用 assert_reasoning_shortcuts_update_effort 完成核心检查。这个测试只定义“上调”的快捷键集合和预期。
调用图:调用 1 个内部函数(assert_reasoning_shortcuts_update_effort);外部调用 2 个(Char, new)。
reasoning_down_shortcuts_lower_reasoning_effort2783–2793 ↗
async fn reasoning_down_shortcuts_lower_reasoning_effort()
作用:检查降低推理强度的快捷键能把 Medium 降到 Low。这样用户可以快速换成更省时的回答方式。
数据流:测试把 Alt+, 和 Shift+Down 交给辅助函数 → 期望结果是 Low,并且不要求模型更新事件 → 辅助函数完成按键和事件断言。
调用关系:它调用 assert_reasoning_shortcuts_update_effort。和上调测试配成一组,覆盖两个方向的快捷键。
调用图:调用 1 个内部函数(assert_reasoning_shortcuts_update_effort);外部调用 2 个(Char, new)。
reasoning_shortcut_clears_armed_quit_shortcut2796–2814 ↗
async fn reasoning_shortcut_clears_armed_quit_shortcut()
作用:检查按推理快捷键时,会取消已经准备好的退出快捷键,不会误退出程序。这样连续快捷键操作更安全。
数据流:测试先设置当前推理强度,再把退出快捷键 arm 起来,也就是进入“再按一次就退出”的状态 → 按 Alt+. → 检查退出提示隐藏、退出状态清空,并确认没有 Exit 事件。
调用关系:它验证 ChatWidget 的快捷键优先级。推理快捷键处理后会清理退出提示,避免把同一次操作误判成退出。
调用图:调用 2 个内部函数(new, ctrl);外部调用 4 个(Char, new, assert!, from_fn)。
reasoning_shortcut_is_ignored_with_model_popup_open2817–2838 ↗
async fn reasoning_shortcut_is_ignored_with_model_popup_open()
作用:检查模型弹窗打开时,推理快捷键不会生效。这样弹窗里的按键上下文不会被全局快捷键打断。
数据流:测试打开模型选择弹窗 → 按 Alt+. → 收集事件 → 确认没有 UpdateReasoningEffort,也没有 PersistModelSelection。
调用关系:它验证 ChatWidget 对活动弹窗的保护。当前有模型弹窗时,推理快捷键不会交给全局处理流程。
reasoning_popup_shows_extra_high_with_space2841–2858 ↗
async fn reasoning_popup_shows_extra_high_with_space()
作用:检查推理弹窗里 Extra High 的显示有空格,不会写成 Extrahigh。这个小细节影响用户阅读。
数据流:测试打开 gpt-5.4 的推理弹窗 → 用较宽宽度渲染 → 确认包含“Extra high”,并确认不包含错误拼法“Extrahigh”。
调用关系:它测试推理强度显示名称的格式。ChatWidget 生成弹窗,渲染文本后由断言检查拼写。
调用图:外部调用 1 个(assert!)。
single_reasoning_option_skips_selection2861–2905 ↗
async fn single_reasoning_option_skips_selection()
作用:检查某个模型只有一个推理选项时,不弹出选择框,而是自动应用。这样用户不会被没有选择意义的弹窗打扰。
数据流:测试构造一个只支持 High 的模型预设 → 调用 open_reasoning_popup → 渲染确认没有“Select Reasoning Level” → 收集事件,确认 High 被自动设置。
调用关系:它验证 ChatWidget 打开推理选择时的快捷分支。选项只有一个时,控件直接发 UpdateReasoningEffort,不创建弹窗。
feedback_selection_popup_snapshot2908–2916 ↗
async fn feedback_selection_popup_snapshot()
作用:检查通过反馈命令打开的反馈分类选择弹窗。反馈分类决定用户接下来提交 bug、好结果还是其他反馈。
数据流:测试派发 Feedback 斜杠命令 → 渲染底部弹窗 → 用快照确认分类选择界面。
调用关系:它通过 dispatch_command 模拟真实斜杠命令入口。ChatWidget 把命令转成反馈选择视图。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
feedback_upload_consent_popup_snapshot2919–2937 ↗
async fn feedback_upload_consent_popup_snapshot()
作用:检查反馈上传前的同意弹窗,尤其是包含诊断信息和日志文件说明时的样子。上传反馈可能带日志,所以必须让用户确认。
数据流:测试直接构造反馈上传同意视图,分类为 Bug,并带 rollout 文件名、Windows 沙盒日志标记和代理诊断 → 显示该视图 → 渲染并快照比对。
调用关系:它测试 bottom_pane 的 feedback_upload_consent_params 生成的视图。ChatWidget 只是承载这个选择视图,快照检查最终文案。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_chatwidget_snapshot!, feedback_upload_consent_params, vec!)。
feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename2940–2958 ↗
async fn feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename()
作用:检查“好结果”反馈的上传同意弹窗也会包含连通性诊断相关信息。这样不同反馈类型都能正确说明将上传什么。
数据流:测试构造 GoodResult 类型的反馈同意视图,带 rollout 文件名和代理诊断,但不包含 Windows 沙盒日志 → 渲染弹窗 → 用快照确认内容。
调用关系:它和 bug 反馈同意弹窗测试相互补充。二者都调用 feedback_upload_consent_params,只是输入分类和日志选项不同。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_chatwidget_snapshot!, feedback_upload_consent_params, vec!)。
reasoning_popup_escape_returns_to_model_popup2961–2977 ↗
async fn reasoning_popup_escape_returns_to_model_popup()
作用:检查从模型弹窗进入推理弹窗后,按 Esc 会回到模型弹窗,而不是直接关闭全部。这样多层选择流程更符合用户预期。
数据流:测试先打开模型弹窗 → 再打开某模型的推理弹窗 → 渲染确认当前是推理选择 → 按 Esc → 再渲染确认回到 Select Model,且推理标题消失。
调用关系:它验证弹窗栈或返回关系。ChatWidget 需要记住推理弹窗是从模型弹窗打开的,所以 Esc 时返回上一层。
tui/src/chatwidget/tests/review_mode.rs源码 ↗
这份文件不是产品功能本身,而是给产品功能装上的“安全网”。它模拟用户在终端聊天界面里的各种操作:进入代码审查、提交审查追问、模型还在回答时继续输入、打断任务、恢复草稿、打开审查弹窗、选择分支或自定义审查提示等。测试会创建一个假的 ChatWidget(聊天组件),再往里塞键盘事件、服务器通知和用户消息,然后检查界面历史、输入框内容、待发送队列、发给后台的操作是否符合预期。这里特别关心两类容易出错的事:一是消息顺序,像排队取号一样不能乱;二是用户输入不能丢,图片、提及绑定、文本标记都要跟着一起恢复。
interrupted_turn_restores_queued_messages_with_images_and_elements6–100 ↗
async fn interrupted_turn_restores_queued_messages_with_images_and_elements()
作用:检查一次任务被打断后,已经排队的用户消息会回到输入框里,而且图片占位符会重新编号。这样用户不会因为中断而丢掉带图片的草稿。
数据流:测试先准备两条排队消息和一条正在输入框里的消息,每条都有图片路径和文本标记;然后模拟任务中断;最后检查输入框变成三段合并文本,图片编号从 1 到 3 连续排列,图片路径也按顺序保留下来。
调用关系:这是测试框架直接运行的用例。它主要驱动聊天组件的中断处理逻辑,并用断言检查输入框和图片列表是否被正确重建。
调用图:外部调用 5 个(from, new, assert_eq!, format!, vec!)。
entered_review_mode_uses_request_hint104–113 ↗
async fn entered_review_mode_uses_request_hint()
作用:检查进入代码审查模式时,界面会显示调用方传进来的审查说明。比如用户说审查 feature branch,横幅就应该这样写。
数据流:测试创建聊天组件,传入“feature branch”作为审查提示;组件写入一条历史横幅;测试读取这条横幅并确认文字正确,同时确认内部状态已经标记为审查模式。
调用关系:测试运行器调用它。它围绕进入审查模式的处理函数展开,验证这个处理函数会更新界面历史和 review 状态。
调用图:外部调用 2 个(assert!, assert_eq!)。
entered_review_mode_defaults_to_current_changes_banner117–126 ↗
async fn entered_review_mode_defaults_to_current_changes_banner()
作用:检查进入审查模式时,如果目标是当前改动,界面横幅会明确显示“current changes”。
数据流:测试把“current changes”传给进入审查模式的逻辑;组件生成审查开始横幅;测试取出最后一条历史并比对文字,再确认审查模式已开启。
调用关系:这是进入审查模式的另一个场景测试,和上一条一起确保不同审查提示都会正确出现在用户眼前。
调用图:外部调用 2 个(assert!, assert_eq!)。
live_review_prompt_item_is_not_rendered129–144 ↗
async fn live_review_prompt_item_is_not_rendered()
作用:检查审查模式里的内部提示词不会显示给用户。这个提示词是发给模型的“工作指令”,不应该污染聊天历史。
数据流:测试先进入审查模式并确认只出现开始横幅;然后模拟一条名为 review-prompt 的用户消息完成;最后确认界面没有新增历史内容。
调用关系:测试框架调用它。它验证完成用户消息的渲染逻辑会识别审查内部提示,并把它隐藏起来。
调用图:外部调用 2 个(assert!, assert_eq!)。
live_app_server_review_prompt_item_is_not_rendered147–196 ↗
async fn live_app_server_review_prompt_item_is_not_rendered()
作用:检查从应用服务器协议来的审查启动和内部审查提示,也不会把不该显示的提示词渲染出来。
数据流:测试先模拟服务器发来“进入审查模式”的开始通知,界面显示一条审查横幅;再模拟这个条目完成,确认没有重复显示;最后模拟 review-prompt 用户消息完成,确认仍然没有新增历史。
调用关系:它测试聊天组件处理服务器通知的路径,覆盖 ItemStarted 和 ItemCompleted 两类通知,确保实时服务端事件和本地辅助函数行为一致。
调用图:外部调用 5 个(ItemCompleted, ItemStarted, assert!, assert_eq!, vec!)。
steer_rejection_queues_review_follow_up_before_existing_queued_messages199–286 ↗
async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages()
作用:检查审查中的追问如果被后台拒绝为“不能插话”,会被放回队列最前面,而不是排到已有草稿后面。
数据流:测试先启动一个审查回合,再放入一条已有排队消息;用户连续提交两条审查追问,组件先尝试把它们作为运行中插话发出;后台返回“审查回合不可插话”的错误后,测试确认这两条追问被转回普通队列,并排在旧草稿前;审查结束后,它们会先合并提交,旧草稿再提交。
调用关系:它串起提交、错误处理、退出审查和回合完成几个步骤,验证输入队列在失败重试时的优先级规则。
调用图:调用 2 个内部函数(new, from);外部调用 3 个(assert!, assert_eq!, panic!)。
esc_with_review_queued_steers_shows_warning_and_does_not_interrupt289–309 ↗
async fn esc_with_review_queued_steers_shows_warning_and_does_not_interrupt()
作用:检查审查模式下还有待处理追问时按 Esc,不会直接打断审查,而是提示用户这样做不合适。
数据流:测试创建一个运行中的审查回合,并手动放入一条待插话消息;用户按 Esc;测试确认没有发送中断操作,待插话仍在队列里,并且界面出现警告提示。
调用关系:它测试键盘事件处理里的特殊分支:普通运行中 Esc 可能打断任务,但审查追问排队时要先保护审查流程。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, assert_eq!)。
live_agent_message_renders_during_review_mode312–328 ↗
async fn live_agent_message_renders_during_review_mode()
作用:检查审查模式下,模型正常发来的进度消息仍然会显示。隐藏的只是内部提示词,不是所有消息。
数据流:测试进入审查模式后清空已有横幅事件;再模拟助手消息完成;最后确认历史里插入了一条包含“Review progress update”的消息。
调用关系:它补充验证审查模式的渲染规则:用户看不到内部 review-prompt,但能看到助手真正说的话。
调用图:外部调用 2 个(assert!, assert_eq!)。
review_restores_context_window_indicator332–358 ↗
async fn review_restores_context_window_indicator()
作用:检查退出审查模式后,底部的上下文窗口百分比会恢复到审查前的值。上下文窗口可以理解为模型还能记住多少内容的容量提示。
数据流:测试先设置审查前的 token 数量并得到 30%;进入审查后设置另一组 token 数量并显示 97%;退出审查后,测试确认百分比回到 30%,审查状态也关闭。
调用关系:它测试审查模式对底部状态栏的临时覆盖行为,确保退出时不会把审查期间的容量显示错误地留在普通聊天里。
调用图:外部调用 2 个(assert!, assert_eq!)。
restore_thread_input_state_restores_pending_steers_without_downgrading_them361–410 ↗
async fn restore_thread_input_state_restores_pending_steers_without_downgrading_them()
作用:检查恢复线程输入状态时,待插话消息仍然保持“待插话”身份,不会被误降级成普通排队草稿。
数据流:测试构造一个保存过的输入状态,里面有待插话、已拒绝插话和普通排队消息;恢复到聊天组件后,确认已拒绝插话进入普通发送队列前面,而真正的待插话仍留在 pending_steers,并保留用于匹配的比较信息。
调用关系:它测试状态恢复函数,场景通常发生在切换线程或重新载入会话时,保证恢复后的输入队列语义不变。
调用图:调用 1 个内部函数(from);外部调用 2 个(new, assert_eq!)。
steer_enter_queues_while_plan_stream_is_active413–437 ↗
async fn steer_enter_queues_while_plan_stream_is_active()
作用:检查计划模式正在流式输出时,用户按 Enter 提交的内容会作为普通排队消息,而不是插话。流式输出就是内容还在一段段出现。
数据流:测试打开协作模式功能,切到 Plan 模式并模拟计划内容正在输出;用户在输入框写入一条消息并按 Enter;最后确认它进入 queued_user_messages,没有进入 pending_steers,也没有立刻发给后台。
调用关系:它覆盖键盘提交逻辑和协作模式的配合,说明计划流还在活动时,系统选择排队等待,而不是打断或插话。
调用图:调用 2 个内部函数(new, mask_for_kind);外部调用 4 个(new, new, assert!, assert_eq!)。
steer_enter_uses_pending_steers_while_turn_is_running_without_streaming440–472 ↗
async fn steer_enter_uses_pending_steers_while_turn_is_running_without_streaming()
作用:检查任务运行中但没有流式输出时,用户按 Enter 会作为“插话”立即提交,而不是普通排队。
数据流:测试启动一个正在运行的任务;用户输入一句话并按 Enter;组件把它放进 pending_steers,并发出一次用户回合操作;当服务器确认这条用户消息完成后,pending_steers 被清掉,历史里显示这条消息。
调用关系:它测试运行中追加输入的常规路径:键盘事件产生插话提交,服务器完成消息后再把等待中的预览转成正式历史。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
steer_enter_uses_pending_steers_while_final_answer_stream_is_active475–513 ↗
async fn steer_enter_uses_pending_steers_while_final_answer_stream_is_active()
作用:检查模型最终答案还在输出时,用户按 Enter 也会作为待插话提交,避免界面进入“卡住但不发送”的状态。
数据流:测试启动任务并模拟最终答案流正在输出;用户提交一条后续消息;组件把它记录为 pending_steers 并发送给后台;当用户消息完成通知回来后,待插话被清掉并显示到历史里。
调用关系:它覆盖一个容易出错的时间窗口:模型还在吐最终答案时用户又发消息。这个测试保证提交路径仍然有效。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
failed_pending_steer_submit_does_not_add_pending_preview516–532 ↗
item_completed_only_pops_front_pending_steer535–576 ↗
async fn item_completed_only_pops_front_pending_steer()
作用:检查完成通知只会移除队首匹配的待插话,不会因为别的用户消息完成就乱删队列。
数据流:测试先放入 first 和 second 两条待插话;模拟一条 unrelated 的用户消息完成,队列不变;再模拟 first 完成,队列才弹出 first,只剩 second。
调用关系:它测试服务器完成消息和 pending_steers 队列之间的匹配规则,保证队列像排队窗口一样只从正确的前端推进。
调用图:外部调用 2 个(assert!, assert_eq!)。
item_completed_pops_pending_steer_with_local_image_and_text_elements579–660 ↗
async fn item_completed_pops_pending_steer_with_local_image_and_text_elements()
作用:检查带本地图片和文本标记的待插话,在服务器确认完成后也能正确移除,并把原始图片和标记保存到历史里。
数据流:测试临时写入一张小 PNG 图片,把它作为用户消息附件提交;组件把消息放入 pending_steers,并记录文字和图片数量作为匹配钥匙;随后模拟服务器返回图片输入和文本输入完成;最后确认待插话清空,历史单元里仍有原文字、文本标记和本地图片路径。
调用关系:它覆盖比纯文本更复杂的插话路径,确保提交、匹配和渲染历史时不会丢掉附件信息。
调用图:调用 1 个内部函数(new);外部调用 6 个(new, assert!, assert_eq!, panic!, write, vec!)。
steer_enter_during_final_stream_preserves_follow_up_prompts_in_order663–745 ↗
async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order()
作用:检查最终答案流输出期间连续提交多条追问时,顺序不会乱。
数据流:测试让模型最终答案保持流式输出;用户依次提交 first follow-up 和 second follow-up;组件按顺序放入 pending_steers,并分别发出两次提交;随后服务器依次确认完成,测试确认历史也按相同顺序出现。
调用关系:它测试多条待插话的队列行为,是单条插话测试的加强版,防止并发时先后顺序被打乱。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
manual_interrupt_restores_pending_steers_to_composer748–790 ↗
async fn manual_interrupt_restores_pending_steers_to_composer()
作用:检查手动打断任务后,已经提交但还没完成的待插话会回到输入框,方便用户继续编辑或重新发送。
数据流:测试在最终答案流期间提交一条待插话,并确认它已发出;随后模拟任务被中断;组件清空 pending_steers,把那条文本放回输入框,而且不再额外发送提交操作,也不把它插入历史。
调用关系:它测试中断恢复路径,确保“还没正式完成的用户输入”不会变成历史,也不会消失。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_draft793–870 ↗
async fn esc_interrupt_sends_all_pending_steers_immediately_and_keeps_existing_draft()
作用:检查用户按 Esc 打断时,如果有多条待插话,系统会在中断后把它们合并发送,同时保留用户正在编辑的草稿和已有队列。
数据流:测试先提交两条待插话,再放入一条普通排队草稿,并让输入框里还有“still editing”;用户按 Esc 后发送中断操作;中断处理完成后,组件把两条待插话合并成一条提交,输入框仍是原来的编辑内容,普通草稿仍在队列里。
调用关系:它串起键盘 Esc、中断操作和中断后的自动补交逻辑,验证不同种类的输入不会互相覆盖。
调用图:调用 2 个内部函数(new, from);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
esc_with_pending_steers_overrides_agent_command_interrupt_behavior873–892 ↗
async fn esc_with_pending_steers_overrides_agent_command_interrupt_behavior()
作用:检查输入框里看起来像 /agent 命令时,如果已有待插话,Esc 仍优先用于中断并提交待插话流程,而不是按命令编辑规则处理。
数据流:测试先提交一条待插话;再把输入框改成“/agent ”并按 Esc;组件发出中断操作,同时保留输入框里的命令文本。
调用关系:它测试 Esc 键在多个功能都可能响应时的优先级:待插话中断流程比 agent 命令的特殊行为更重要。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert_eq!, panic!)。
manual_interrupt_restores_pending_steer_mention_bindings_to_composer895–935 ↗
async fn manual_interrupt_restores_pending_steer_mention_bindings_to_composer()
作用:检查待插话被中断恢复到输入框时,像 $figma 这样的提及绑定也会一起回来。提及绑定就是把文本里的名字连接到具体文件或能力。
数据流:测试输入一条带 $figma 的消息,并附上对应绑定信息;提交后确认发给后台的文本元素正确;随后模拟中断;最后确认输入框文字恢复,提及绑定也能从输入框取回,没有额外提交。
调用关系:它扩展了中断恢复测试,专门防止恢复时只保留文字、丢掉隐藏绑定数据。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert_eq!, panic!, vec!)。
manual_interrupt_restores_pending_steers_before_queued_messages938–978 ↗
async fn manual_interrupt_restores_pending_steers_before_queued_messages()
作用:检查中断后恢复到输入框时,待插话会排在普通排队草稿前面。
数据流:测试先提交一条待插话,再放入一条普通排队草稿;中断发生后,pending_steers 和 queued_user_messages 都被清空,但输入框里按“待插话在前、草稿在后”的顺序合并成两行。
调用关系:它测试中断恢复时的合并顺序,保证用户看到的重新编辑内容仍符合原来的优先级。
调用图:调用 2 个内部函数(new, from);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
ctrl_c_shutdown_works_with_caps_lock981–987 ↗
async fn ctrl_c_shutdown_works_with_caps_lock()
作用:检查 Ctrl-C 即使用大写 C 也能触发关闭。这样 Caps Lock 打开时快捷键也不会失效。
数据流:测试向聊天组件发送 Ctrl + 大写 C 键盘事件;组件往应用事件通道发出 ShutdownFirst 退出事件;测试确认收到这个退出事件。
调用关系:它测试键盘处理的大小写兼容性,关注的是入口按键到应用退出事件的转换。
调用图:外部调用 3 个(Char, new, assert_matches!)。
ctrl_c_interrupts_without_arming_quit_when_double_press_disabled990–1005 ↗
async fn ctrl_c_interrupts_without_arming_quit_when_double_press_disabled()
作用:检查任务运行时按 Ctrl-C 会中断任务,但不会进入“再按一次退出”的提示状态。
数据流:测试把底部状态设成任务运行中;连续两次发送 Ctrl-C;每次都确认发出了中断操作,没有发退出事件,也没有显示退出快捷键提示。
调用关系:它测试运行中 Ctrl-C 的行为边界:此处 Ctrl-C 是打断,不是退出,也不显示双击退出提示。
调用图:外部调用 4 个(Char, new, assert!, assert_matches!)。
ctrl_c_cleared_prompt_is_recoverable_via_history1008–1036 ↗
async fn ctrl_c_cleared_prompt_is_recoverable_via_history()
作用:检查 Ctrl-C 清空输入框后,用户还能通过历史上箭头把草稿找回来,包括图片附件。
数据流:测试先输入草稿并附上一张图片,输入框末尾出现图片占位符;按 Ctrl-C 后输入框清空且没有发后台操作;再按上箭头,草稿文字和图片占位符恢复,图片路径也能取回。
调用关系:它测试草稿历史功能和 Ctrl-C 清空功能的配合,保证清空不是永久删除。
调用图:外部调用 6 个(Char, new, from, assert!, assert_eq!, assert_matches!)。
review_popup_custom_prompt_action_sends_event1041–1063 ↗
review_commit_picker_shows_subjects_without_timestamps1067–1124 ↗
async fn review_commit_picker_shows_subjects_without_timestamps()
作用:检查审查用的提交选择器只显示提交标题,不显示“几分钟前”这类时间信息。
数据流:测试打开审查弹窗,再用两条假提交记录显示提交选择器;把界面渲染到一个虚拟终端缓冲区;最后检查输出里有两个提交标题,但没有 ago、second、minute、hour、day 等时间词。
调用关系:它测试渲染层的显示内容,借助虚拟终端读取最终画面,防止提交选择器泄露或展示不需要的时间字段。
调用图:外部调用 6 个(new, assert!, empty, new, show_review_commit_picker_with_entries, vec!)。
custom_prompt_submit_sends_review_op1129–1150 ↗
async fn custom_prompt_submit_sends_review_op()
作用:检查自定义审查说明输入后按 Enter,会发送真正的审查操作,而且会去掉前后空格。
数据流:测试打开自定义审查输入界面,粘贴一段前后带空格的文字,再按 Enter;事件通道收到 Op::Review,目标是 Custom,instructions 里只保留修剪后的“please audit dependencies”。
调用关系:它测试自定义审查表单的提交路径,从用户输入到应用事件,保证后台收到的是干净的审查指令。
调用图:外部调用 3 个(new, assert_eq!, panic!)。
custom_prompt_enter_empty_does_not_send1154–1163 ↗
interrupt_exec_marks_failed_snapshot1168–1187 ↗
async fn interrupt_exec_marks_failed_snapshot()
作用:检查正在运行的命令被中断时,界面会把命令历史标成失败,而不是继续显示转圈。
数据流:测试先模拟一个长时间运行的命令,让界面出现活动命令单元;随后模拟回合中断;组件把活动命令单元收尾并插入历史;测试把结果文本和快照对比,确认有红色失败标记。
调用关系:它测试执行命令单元在中断时的收尾渲染,使用快照测试固定用户可见的文字样子。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
interrupted_turn_error_message_snapshot1192–1208 ↗
async fn interrupted_turn_error_message_snapshot()
作用:检查普通中断任务后,界面会显示一条友好的提示,告诉用户可以说明想让模型怎么改。
数据流:测试先模拟一个回合正在运行;再模拟回合被中断;组件往历史里插入提示消息;测试取最后一条历史并和快照比较。
调用关系:它测试普通中断后的用户提示文案,保证提示不会被无意改坏。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
interrupted_turn_after_goal_budget_limited_uses_budget_message_snapshot1211–1274 ↗
async fn interrupted_turn_after_goal_budget_limited_uses_budget_message_snapshot()
作用:检查如果任务因为目标的 token 预算受限而停止,最终显示的是预算相关提示,而不是普通中断提示。token 可以理解为模型处理文字时的计量单位。
数据流:测试打开目标功能,模拟服务器发来回合开始、目标状态变成 BudgetLimited、回合以 Interrupted 完成;组件根据目标状态插入预算受限消息;测试把最后一条历史和快照比较。
调用关系:它测试服务器通知驱动的完整路径,确保目标预算状态会影响中断完成时显示给用户的说明。
调用图:外部调用 5 个(new, assert_chatwidget_snapshot!, ThreadGoalUpdated, TurnCompleted, TurnStarted)。
direct_budget_limited_turn_uses_budget_message_snapshot1277–1286 ↗
async fn direct_budget_limited_turn_uses_budget_message_snapshot()
作用:检查直接收到预算受限回合处理时,也会显示预算受限提示。
数据流:测试先启动一个回合,再调用预算受限处理;组件插入对应消息;测试读取最后一条历史并做快照比对。
调用关系:它覆盖预算受限的简化入口,和服务器通知版本一起保证不同来源的预算停止都显示同类文案。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
budget_limited_turn_restores_queued_input_without_submitting1289–1305 ↗
async fn budget_limited_turn_restores_queued_input_without_submitting()
作用:检查预算受限停止后,已经排队的用户输入会回到输入框,但不会自动发送。
数据流:测试先放入一条排队消息并刷新预览;模拟回合开始后预算受限结束;最后确认队列清空、输入框恢复这条文字,同时后台没有收到新的提交操作。
调用关系:它测试预算停止时的输入保护逻辑,避免系统在模型已经预算受限时又自动发下一条。
调用图:调用 1 个内部函数(from);外部调用 2 个(assert!, assert_eq!)。
interrupted_turn_pending_steers_message_snapshot1311–1330 ↗
async fn interrupted_turn_pending_steers_message_snapshot()
作用:检查为了提交待插话而中断模型时,界面显示的是说明性提示,而不是普通错误提示。
数据流:测试预先放入一条 pending steer,并设置“中断后提交待插话”的标记;启动并中断回合;组件插入一条“Model interrupted to submit steer instructions.”相关信息;测试找到这条信息并做快照比对。
调用关系:它测试中断原因会影响提示文案,区分主动为插话让路和普通失败中断。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_chatwidget_snapshot!)。
enter_submits_steer_while_review_is_running1404–1441 ↗
async fn enter_submits_steer_while_review_is_running()
作用:检查审查正在运行时,用户按 Enter 提交追问会先作为待插话发给后台。
数据流:测试启动一个回合并进入审查模式;用户输入一句“审查运行中提交的插话”并按 Enter;组件不放入普通队列,而是加入 pending_steers,并向后台发送 UserTurn。
调用关系:它测试审查运行中的正常追问入口,和后面的“审查不可插话被拒绝”测试配合覆盖成功尝试和失败回退。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
review_queues_user_messages_snapshot1444–1477 ↗
async fn review_queues_user_messages_snapshot()
作用:检查审查运行时提交追问被拒绝后,界面会以正确方式显示排队状态。
数据流:测试启动审查回合,提交一条追问;随后模拟后台返回“审查回合不能插话”的错误;组件把消息转入队列并更新底部界面;测试把整个底部区域渲染到虚拟终端并用快照确认显示效果。
调用关系:它是审查追问失败回退的界面快照测试,关注用户最终看到的排队提示是否稳定。
调用图:调用 4 个内部函数(new, from, with_options, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
tui/src/chatwidget/tests/side.rs源码 ↗
侧边对话可以理解成主聊天旁边临时开的“小纸条”:用户想问个岔开的问题,但不想打断主线程。这个测试文件就是检查这张“小纸条”是否守规矩。它会造出一个假的聊天窗口,往里面输入命令,再看程序吐出的界面事件和后台操作是否正确。重点包括:侧边对话不能重命名;某些斜杠命令在侧边对话里要被拦住;代码审查运行时不能再开侧边对话;/side 和 /btw 可以带问题开启侧边对话,但不能把消息提交到主线程;像“!echo hello”这种看起来像 shell 命令的内容,在这里也必须当普通文字发送。文件还用快照测试,也就是把渲染出来的界面画面保存成标准答案,以防以后有人不小心改坏提示文案或状态栏。
forked_thread_history_line_without_name_shows_id_once_snapshot7–28 ↗
async fn forked_thread_history_line_without_name_shows_id_once_snapshot()
作用:测试从主线程分叉出侧边对话时,如果没有可显示的父标题,历史记录里应该只显示一次线程 ID。这样用户能知道来源,但界面不会重复啰嗦。
数据流:测试先创建一个假的聊天窗口和事件接收器,再把一串固定文字转成 ThreadId(线程 ID,可以理解成对话的身份证)。它触发“分叉线程”事件后,在最多两秒内等待历史记录插入事件。拿到这条历史记录后,把多行显示内容合成一段文字,最后用快照比对它是不是预期样子。
调用关系:这是一个异步测试,由测试运行器启动。它会调用 from_string 把文字变成线程 ID,用 timeout 防止测试无限等下去,最后交给 assert_chatwidget_snapshot! 做界面文字的标准答案比对。
调用图:调用 1 个内部函数(from_string);外部调用 4 个(assert_chatwidget_snapshot!, panic!, from_secs, timeout)。
suppressed_interrupted_turn_notice_skips_history_warning31–49 ↗
async fn suppressed_interrupted_turn_notice_skips_history_warning()
作用:测试当聊天窗口被设置为“不显示打断提示”时,用户打断一次回答后,历史记录里不会出现提醒文案。这样侧边对话里不会冒出多余的警告。
数据流:测试先创建聊天窗口,给它一个新的线程 ID,然后把打断提示模式设为 Suppress(抑制,也就是别显示)。接着模拟任务开始、模型输出了一点内容、随后被用户打断。最后它取出已插入的历史记录,确认里面没有两种常见的打断提示文字。
调用关系:这是测试侧边对话打断行为的一环。它用 ThreadId::new 准备对话身份,通过聊天窗口自己的 on_task_started、on_agent_message_delta 和 on_interrupted_turn 推动状态变化,最后用 assert! 验证没有不该出现的提示。
assert_side_rename_rejected51–70 ↗
fn assert_side_rename_rejected(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
op_rx: &mut tokio::sync::mpsc::UnboundedReceiver<Op>,
)
作用:这是一个测试辅助函数,用来统一检查“侧边对话不能重命名”这件事。两个重命名相关测试都会用它,避免重复写同一套检查代码。
数据流:函数接收两个通道:一个收界面事件,一个收后台操作。它先从界面事件里取出一条消息,确认这是插入历史记录,并且显示了“侧边对话是临时的,不能重命名”的错误提示。之后它继续检查:界面事件没有第二条,后台操作通道也没有真正发出重命名请求。
调用关系:它被 slash_rename_is_rejected_for_side_threads 和 slash_rename_with_args_is_rejected_for_side_threads 调用。它自己主要使用 try_recv 立刻查看通道里有没有消息,用 assert! 和 panic! 把不符合预期的情况变成测试失败。
调用图:被 2 处调用(slash_rename_is_rejected_for_side_threads, slash_rename_with_args_is_rejected_for_side_threads);外部调用 3 个(try_recv, assert!, panic!)。
slash_rename_is_rejected_for_side_threads73–81 ↗
async fn slash_rename_is_rejected_for_side_threads()
作用:测试用户在侧边对话里直接输入 /rename 时会被拒绝。因为侧边对话是临时的,不应该像正式主线程那样改名字。
数据流:测试先创建聊天窗口、界面事件通道和后台操作通道。它设置好“不能重命名”的提示文案,然后派发 Rename 斜杠命令。最后把检查工作交给 assert_side_rename_rejected,确认只显示错误、不产生重命名操作。
调用关系:这是重命名拦截规则的一个入口测试。它触发聊天窗口的命令分发流程,然后调用 assert_side_rename_rejected 做细节验收。
调用图:调用 1 个内部函数(assert_side_rename_rejected)。
slash_rename_with_args_is_rejected_for_side_threads84–92 ↗
async fn slash_rename_with_args_is_rejected_for_side_threads()
作用:测试用户在侧边对话里输入带参数的 /rename,比如想改成某个名字,也同样会被拒绝。这样不会因为多传了名字就绕过限制。
数据流:测试创建聊天窗口和两个接收通道,设置侧边对话的重命名拒绝文案。然后它派发带参数的 Rename 命令,参数是“investigate”。执行后,它调用 assert_side_rename_rejected,确认界面只出现拒绝提示,后台没有收到重命名操作。
调用关系:它覆盖的是“带参数命令”的分支。测试本身负责触发 dispatch_command_with_args,后续验证复用 assert_side_rename_rejected。
调用图:调用 1 个内部函数(assert_side_rename_rejected);外部调用 1 个(new)。
slash_commands_without_side_flag_are_rejected_for_side_threads95–118 ↗
async fn slash_commands_without_side_flag_are_rejected_for_side_threads()
作用:测试普通斜杠命令如果没有声明自己能在侧边对话里用,就会被拦下。这里用 /review 做例子,防止用户在临时侧边对话里启动不合适的功能。
数据流:测试先创建聊天窗口,并把“侧边对话正在进行”设为真。接着派发 Review 命令。然后它从界面事件通道读取一条历史记录,确认里面告诉用户 /review 在侧边对话不可用,并提示先按 Ctrl+C 回到主线程。最后确认没有更多界面事件,也没有后台 review 操作。
调用关系:它直接推动聊天窗口的命令分发流程,用通道结果检查拦截是否发生。遇到错误事件类型或多余操作时,assert! 或 panic! 会让测试失败。
slash_side_is_rejected_for_side_threads121–147 ↗
async fn slash_side_is_rejected_for_side_threads()
作用:测试已经在侧边对话里时,不能再输入 /side 开第二层侧边对话。这样避免对话像套娃一样越开越深,用户也不容易迷路。
数据流:测试创建聊天窗口,标记当前已经是侧边对话,然后派发 Side 命令。它读取界面事件,确认显示的是“/side 在侧边对话不可用,请先 Ctrl+C 回主线程”的提示。最后检查没有后续事件,也没有启动侧边对话的后台操作。
调用关系:它验证命令分发里的嵌套侧边对话保护。整个测试只期待一个错误提示,任何额外事件或操作都会被 assert! 判为失败。
slash_side_is_rejected_during_review_mode150–174 ↗
async fn slash_side_is_rejected_during_review_mode()
作用:测试代码审查正在运行时,/side 命令会被拒绝。这样避免审查流程和侧边对话同时抢同一个聊天界面状态。
数据流:测试创建聊天窗口,把 review.is_review_mode 设为 true,表示当前处于代码审查模式。然后派发 Side 命令。它读取一条界面历史记录,确认提示用户“代码审查运行时 /side 不可用”,并确认没有多余事件或启动侧边对话的操作。
调用关系:它覆盖 /side 的另一个限制条件:不是因为已经在侧边对话里,而是因为审查模式正在占用流程。测试通过 assert! 和 panic! 检查命令是否被干净地拦住。
slash_btw_is_rejected_during_review_mode177–201 ↗
async fn slash_btw_is_rejected_during_review_mode()
作用:测试代码审查运行时,/btw 命令也会被拒绝。/btw 和 /side 都是开侧边对话的入口,所以限制必须一致。
数据流:测试创建聊天窗口,将代码审查模式打开,然后派发 Btw 命令。它从事件通道中取出错误提示,确认文案说明“代码审查运行时 /btw 不可用”。最后它检查没有额外界面事件,也没有后台侧边对话操作。
调用关系:它和 slash_side_is_rejected_during_review_mode 是一组镜像测试,分别覆盖两个命令入口。它主要验证聊天窗口的命令分发在审查模式下不会漏放 /btw。
slash_btw_is_rejected_before_the_session_starts204–227 ↗
async fn slash_btw_is_rejected_before_the_session_starts()
作用:测试会话还没真正开始时,/btw 不能使用。因为侧边对话需要依附一个已有主线程,没有主线程就没有可分叉的来源。
数据流:测试创建一个还没有线程 ID、也没有开始任务的聊天窗口,然后直接派发 Btw 命令。它读取界面事件,确认提示“会话开始前 /btw 不可用”。之后检查没有更多事件,也没有任何开启侧边对话的后台操作。
调用关系:它覆盖 /btw 的前置条件检查。命令分发收到请求后应该先发现主会话不存在,然后只发出错误提示,不再继续走启动侧边对话的流程。
submit_user_message_as_plain_user_turn_does_not_run_shell_commands230–248 ↗
async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands()
作用:测试把用户输入当作普通聊天消息提交时,即使内容像 shell 命令,也不会真的当命令执行。这里的“shell 命令”就是终端里那种操作系统命令,比如 !echo hello。
数据流:测试创建聊天窗口并给它一个线程 ID。然后调用 submit_user_message_as_plain_user_turn,传入“!echo hello”。接着它从后台操作通道取出下一条提交操作,确认这只是一个 UserTurn(用户发言),内容原样是普通文本,没有图片、没有额外文本元素。如果不是这样就失败。
调用关系:它验证侧边对话提交文字时绕开命令执行路径。测试通过 next_submit_op 观察聊天窗口发出的后台操作,再用 assert_eq! 精确比较提交内容。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, panic!)。
slash_side_without_args_starts_empty_side_conversation251–273 ↗
async fn slash_side_without_args_starts_empty_side_conversation()
作用:测试用户只输入 /side、没有附带问题时,会开启一个空的侧边对话。它不应该把任何消息提交到主线程。
数据流:测试创建聊天窗口,准备一个父线程 ID,并模拟主任务已经开始。它把输入框文字设成“/side”,再模拟按下回车。结果应该是界面事件通道收到 StartSide,里面带着正确的父线程 ID,但 user_message 是 None。后台操作通道应该为空,输入队列也应该没有排队消息。
调用关系:它模拟真实键盘提交路径:先设置输入框,再用 handle_key_event 处理回车。测试用 assert_matches! 检查 StartSide 事件,用 assert! 确认没有误向父线程提交操作。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert!, assert_matches!)。
slash_btw_without_args_starts_empty_side_conversation276–298 ↗
async fn slash_btw_without_args_starts_empty_side_conversation()
作用:测试用户只输入 /btw、没有附带问题时,也会开启一个空的侧边对话。它和 /side 是同类入口,行为应该一致。
数据流:测试创建聊天窗口,设置父线程 ID,模拟主任务开始。它把输入框设成“/btw”,再模拟用户按回车。随后它检查事件通道里出现 StartSide,父线程 ID 正确,user_message 为空;同时确认后台没有提交操作,输入队列也没有残留消息。
调用关系:它走的是和 /side 空参数测试相同的键盘输入流程,只是命令换成 /btw。这样可以确保两个入口不会因为实现分支不同而表现不一致。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert!, assert_matches!)。
slash_side_requests_forked_side_question_while_task_running301–347 ↗
async fn slash_side_requests_forked_side_question_while_task_running()
作用:测试主任务运行中输入“/side 加问题”时,会请求开启一个带问题的侧边对话,而不是把这个问题发到主线程。它还检查界面底部提示是否正常显示。
数据流:测试创建聊天窗口,设置父线程 ID、状态栏模型名,并模拟任务正在运行。它把输入框设成“/side explore the codebase”,再按回车。事件通道应该收到 StartSide,里面带正确父线程 ID 和一条 UserMessage,文本是“explore the codebase”,没有图片和额外绑定。后台操作通道应为空。之后测试用假终端渲染聊天窗口,把画面拿去做快照比对。
调用关系:它既测试命令分发,也测试渲染结果。前半段用 assert_matches! 确认 StartSide 事件,后半段用 TestBackend 假装一个终端,再用 assert_chatwidget_snapshot! 检查底部区域有没有被正确画出来。
调用图:调用 1 个内部函数(new);外部调用 8 个(new, new, new, assert!, assert_chatwidget_snapshot!, assert_matches!, new, vec!)。
slash_btw_requests_forked_side_question_while_task_running350–382 ↗
async fn slash_btw_requests_forked_side_question_while_task_running()
作用:测试主任务运行中输入“/btw 加问题”时,会开启带问题的侧边对话,并且不影响主线程。它确认 /btw 和 /side 在带问题场景下行为一致。
数据流:测试创建聊天窗口,设置父线程 ID,并模拟主任务开始。它把输入框文字设为“/btw explore the codebase”,然后按回车。结果应该是事件通道收到 StartSide,包含正确的父线程 ID 和文本为“explore the codebase”的用户消息。后台操作通道必须为空,表示没有把这句话发给主线程。
调用关系:它是 /side 带问题测试的对应版本,但不额外检查界面快照。它主要通过 assert_matches! 验证事件内容,通过 assert! 验证父线程没有收到提交操作。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert!, assert_matches!)。
side_context_label_preserves_status_line_snapshot385–405 ↗
async fn side_context_label_preserves_status_line_snapshot()
作用:测试侧边对话的上下文标签显示出来时,不会把原本的状态栏信息挤坏或丢掉。状态栏可以理解成界面底部显示当前模型、状态等信息的小牌子。
数据流:测试创建聊天窗口,关闭欢迎横幅,设置状态栏内容为“model-name”,并刷新状态栏。然后它标记当前是侧边对话,设置上下文标签“Side from main thread · Ctrl+C to return”。接着用一个假的 80 列终端渲染界面,并把渲染结果和快照标准答案比较。
调用关系:它关注的是显示层,而不是后台操作。测试通过 Terminal 和 TestBackend 造出假终端环境,再调用聊天窗口 render,最后用 assert_chatwidget_snapshot! 保证状态栏和侧边标签的组合画面对得上。
调用图:外部调用 4 个(new, assert_chatwidget_snapshot!, new, vec!)。
side_context_label_shows_parent_status_snapshot408–423 ↗
async fn side_context_label_shows_parent_status_snapshot()
作用:测试侧边对话底部能显示来自主线程的状态提示,比如主线程需要用户输入。这样用户待在侧边对话里时,也不会忘记主线发生了什么。
数据流:测试创建聊天窗口,关闭欢迎横幅,打开侧边对话状态,并设置一段包含“main needs input”的上下文标签。然后它用假的 80 列终端渲染整个聊天窗口,最后把渲染出来的画面和快照标准答案比较。
调用关系:它同样是界面快照测试。它不发命令、不查后台操作,只设置聊天窗口状态后调用 render,再由 assert_chatwidget_snapshot! 检查侧边对话上下文提示是否按预期显示。
调用图:外部调用 3 个(new, assert_chatwidget_snapshot!, new)。
tui/src/chatwidget/tests/slash_commands.rs源码 ↗
这个文件像一份“操作说明书的验收清单”。它不实现正式功能,而是假装用户在终端聊天界面里按键、粘贴文字、输入斜杠命令,然后检查界面发出的事件、排队的消息、弹出的菜单和保存的状态是否正确。这里特别关心一些容易出错的场景:任务还在跑时输入下一条消息,命令需要等上一轮结束再执行;/goal 要保留大段粘贴、图片和提及;/usage 要等输出流结束后再插入历史;/copy 要拿到最后一次有效的助手回复;/pets 要根据终端是否支持图片给出不同反应。可以把它理解成一群机器人测试员,反复模拟真实用户乱按、连续输入、取消菜单、切换模式,确保聊天窗口不会丢消息、不会重复提交、不会把错误结果写进历史。
force_pet_image_support6–10 ↗
fn force_pet_image_support(chat: &mut ChatWidget)
作用:把测试里的聊天窗口强行设成“支持宠物图片”。这样后面的 /pets 测试不用依赖真实终端能力。
数据流:输入一个可修改的 ChatWidget → 把它的宠物图片能力改成支持 Kitty 图片协议 → 聊天窗口之后会像运行在支持图片的终端里一样。
调用关系:它是宠物命令测试的准备步骤,被 slash_pets_opens_picker 和 slash_pets_with_arg_selects_named_pet 调用,让这些测试能稳定打开宠物选择或选择指定宠物。
调用图:被 2 处调用(slash_pets_opens_picker, slash_pets_with_arg_selects_named_pet);外部调用 2 个(set_pet_image_support_for_tests, Supported)。
force_tmux_pet_image_unsupported12–16 ↗
fn force_tmux_pet_image_unsupported(chat: &mut ChatWidget)
作用:把测试窗口伪装成在 tmux 里,宠物图片不可用。tmux 是一种终端复用工具,有时会挡住图片协议。
数据流:输入 ChatWidget → 写入“不支持,原因是 tmux”的测试状态 → 后续 /pets 会走警告或禁用分支。
调用关系:它被多个宠物禁用和警告测试使用,用来确认即使图片不可用,禁用宠物命令仍然能执行,而选择宠物会被拦住。
调用图:被 4 处调用(slash_pet_hide_disables_pets_even_on_unsupported_terminal, slash_pets_disable_disables_pets_even_on_unsupported_terminal, slash_pets_on_unsupported_terminal_warns_without_picker, slash_pets_with_arg_on_unsupported_terminal_warns_without_selection);外部调用 2 个(set_pet_image_support_for_tests, Unsupported)。
force_terminal_pet_image_unsupported18–22 ↗
fn force_terminal_pet_image_unsupported(chat: &mut ChatWidget)
作用:把测试窗口设成普通终端不支持图片宠物。这样能检查提示文案是否告诉用户终端能力不够。
数据流:输入 ChatWidget → 写入“不支持,原因是终端”的状态 → /pets 后续应显示终端不支持的说明。
调用关系:它只服务于 slash_pets_on_unsupported_terminal_shows_terminal_warning,帮助测试普通终端不支持图片时的提示。
调用图:被 1 处调用(slash_pets_on_unsupported_terminal_shows_terminal_warning);外部调用 2 个(set_pet_image_support_for_tests, Unsupported)。
force_old_iterm2_pet_image_unsupported24–28 ↗
fn force_old_iterm2_pet_image_unsupported(chat: &mut ChatWidget)
作用:把测试窗口设成 iTerm2 版本太旧,不能显示宠物图片。iTerm2 是 macOS 上常见终端软件。
数据流:输入 ChatWidget → 写入“不支持,原因是 iTerm2 太旧”的状态 → 后续 /pets 应提示升级。
调用关系:它被 slash_pets_on_old_iterm2_shows_upgrade_warning 调用,专门检查旧 iTerm2 的升级提示。
调用图:被 1 处调用(slash_pets_on_old_iterm2_shows_upgrade_warning);外部调用 2 个(set_pet_image_support_for_tests, Unsupported)。
fast_tier_command30–36 ↗
fn fast_tier_command() -> ServiceTierCommand
作用:造出一个代表“快速模式”的服务档位命令。服务档位就是向后端请求不同速度或用量策略的选项。
数据流:不读取外部输入 → 组装 fast 档位的 id、名称和说明 → 返回一个 ServiceTierCommand 供测试直接触发。
调用关系:它被快速模式相关测试复用,避免每个测试都手写同一份 fast 命令数据。
调用图:被 3 处调用(fast_slash_command_updates_and_persists_local_service_tier, user_turn_carries_service_tier_after_fast_toggle, user_turn_sends_standard_override_after_fast_is_turned_off)。
complete_turn_with_message38–48 ↗
fn complete_turn_with_message(chat: &mut ChatWidget, turn_id: &str, message: Option<&str>)
作用:帮测试快速模拟一轮助手回复结束。可选地先塞入一条最终回复,再通知聊天窗口这一轮完成。
数据流:输入 ChatWidget、轮次 id 和可选消息 → 如果有消息就追加最终助手消息,再发出轮次完成信号 → 聊天状态进入“上一轮结束,可以处理后续队列”的状态。
调用关系:许多排队命令测试都靠它推进流程;它让 queued_* 测试能验证上一轮结束后,下一条命令或消息是否被正确取出执行。
调用图:被 19 处调用(active_goal_without_follow_up_suppresses_agent_turn_complete_notification, agent_turn_complete_notification_does_not_reuse_stale_copy_source, assert_cancelled_queued_menu_drains_next_input, queued_bang_shell_dispatches_after_active_turn, queued_bang_shell_waits_for_user_shell_completion_before_next_input, queued_bare_rename_drains_next_input_after_name_update, queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_input, queued_fast_slash_applies_before_next_queued_message, queued_follow_up_suppresses_agent_turn_complete_notification, queued_goal_slash_command_preserves_large_paste (+9 more));外部调用 1 个(format!)。
submit_composer_text50–54 ↗
fn submit_composer_text(chat: &mut ChatWidget, text: &str)
作用:把一段文字放进输入框并模拟用户提交。它让测试不用重复写设置输入框和按键的步骤。
数据流:输入 ChatWidget 和文本 → 设置底部输入框内容 → 调用 submit_current_composer 模拟提交 → 结果可能是发消息、执行斜杠命令或显示错误。
调用关系:大量命令测试用它作为统一入口;它把真正的按键动作交给 submit_current_composer。
调用图:调用 1 个内部函数(submit_current_composer);被 21 处调用(bare_slash_command_is_available_from_local_recall_after_dispatch, goal_control_slash_command_without_thread_shows_full_usage, goal_control_slash_commands_emit_goal_events, goal_edit_slash_command_opens_goal_editor, goal_slash_command_with_extra_os_emits_set_goal_event, inline_slash_command_is_available_from_local_recall_after_dispatch, no_op_stub_slash_command_is_available_from_local_recall, queued_goal_slash_command_emits_set_goal_event_after_thread_starts, queued_goal_slash_command_preserves_current_draft_metadata, restored_queued_goal_slash_command_emits_set_goal_event (+11 more));外部调用 1 个(new)。
submit_current_composer56–60 ↗
fn submit_current_composer(chat: &mut ChatWidget)
作用:模拟用户用键盘提交当前输入框内容。这里按 Esc 和 Enter,是为了走真实界面的提交路径。
数据流:输入 ChatWidget → 连续发送几个按键事件 → 聊天窗口根据当前输入决定提交、确认或执行命令。
调用关系:submit_composer_text 和一个大粘贴恢复测试会调用它,用来复用真实键盘处理逻辑,而不是绕过界面。
调用图:被 2 处调用(queued_goal_slash_command_restores_large_paste_for_edit, submit_composer_text);外部调用 2 个(new, handle_key_event)。
queue_composer_text_with_tab62–66 ↗
fn queue_composer_text_with_tab(chat: &mut ChatWidget, text: &str)
作用:把文字放进输入框,然后按 Tab,把它当成“下一步要排队的输入”。
数据流:输入 ChatWidget 和文本 → 设置输入框 → 发送 Tab 键事件 → 如果当前有活跃任务,这段输入会进入队列。
调用关系:它被许多 queued_* 测试调用,专门检查忙碌时输入的斜杠命令、普通消息或 shell 命令会不会按顺序排队。
调用图:被 13 处调用(assert_cancelled_queued_menu_drains_next_input, queued_bang_shell_dispatches_after_active_turn, queued_bang_shell_waits_for_user_shell_completion_before_next_input, queued_bare_rename_drains_next_input_after_name_update, queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_input, queued_fast_slash_applies_before_next_queued_message, queued_inline_rename_does_not_drain_again_before_turn_started, queued_menu_slash_keeps_agent_turn_complete_notification, queued_slash_compact_dispatches_after_active_turn, queued_slash_menu_selection_drains_next_input (+3 more));外部调用 3 个(new, new, handle_key_event)。
queue_goal_with_large_paste68–73 ↗
fn queue_goal_with_large_paste(chat: &mut ChatWidget, paste: String)
作用:模拟在 /goal 后面粘贴一大段超长内容并排队。这个场景容易丢失粘贴内容,所以单独封装。
数据流:输入 ChatWidget 和大段粘贴文本 → 输入 /goal 前缀 → 调用粘贴处理 → 按 Tab 排队 → 队列里保留大粘贴的占位和原文。
调用关系:它被 /goal 大粘贴相关测试使用,帮助确认排队、编辑和中断时都不会把大段文字弄混。
调用图:被 3 处调用(interrupt_disambiguates_same_sized_goal_pastes, queued_goal_slash_command_preserves_large_paste, queued_goal_slash_command_restores_large_paste_for_edit);外部调用 4 个(new, new, handle_key_event, handle_paste)。
recall_latest_after_clearing75–80 ↗
fn recall_latest_after_clearing(chat: &mut ChatWidget) -> String
作用:清空输入框后按上箭头,取回最近一条本地历史输入。用来检查命令是否能被用户再次召回。
数据流:输入 ChatWidget → 清空输入框 → 发送上箭头 → 读取输入框当前文本并返回。
调用关系:多个测试在提交命令后调用它,确认有效命令或用法错误命令是否进入了本地输入历史。
调用图:外部调用 4 个(new, new, new, handle_key_event)。
dispatch_usage_and_expect_refresh82–88 ↗
fn dispatch_usage_and_expect_refresh(
chat: &mut ChatWidget,
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
) -> u64
作用:触发 /usage daily,并立刻确认界面请求刷新用量数据。/usage 用来查看 token 活动,token 可以理解成模型计费和上下文的文字单位。
数据流:输入 ChatWidget 和事件接收器 → 分发 /usage daily → 从事件队列里取刷新请求 → 返回请求 id。
调用关系:它把触发和检查合在一起,被一系列用量刷新测试调用;内部把取事件的细节交给 expect_token_activity_refresh。
调用图:调用 1 个内部函数(expect_token_activity_refresh);被 11 处调用(account_state_change_discards_pending_token_activity_refresh, clearing_pending_token_activity_refreshes_discards_late_result, completed_token_activity_refresh_retries_after_plan_item_completion, completed_token_activity_refresh_returns_one_history_cell, completed_token_activity_refresh_waits_for_active_history_cell, completed_token_activity_refresh_waits_for_active_hook, completed_token_activity_refresh_waits_for_active_stream, completed_token_activity_refresh_waits_for_queued_stream_consolidation, pending_token_activity_refresh_keeps_composer_visible_in_short_viewport, pending_token_activity_refresh_renders_above_composer_snapshot (+1 more));外部调用 2 个(new, dispatch_command_with_args)。
expect_token_activity_refresh90–95 ↗
fn expect_token_activity_refresh(rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>) -> u64
作用:从事件队列里取出“刷新 token 活动”的请求,并返回它的编号。拿不到就直接让测试失败。
数据流:输入事件接收器 → 尝试取一个事件 → 如果是 RefreshTokenActivity 就返回 request_id,否则报错。
调用关系:它被 dispatch_usage_and_expect_refresh 和重复刷新测试调用,是检查 /usage 是否真的发起后台查询的关卡。
调用图:被 2 处调用(dispatch_usage_and_expect_refresh, repeated_token_activity_refreshes_keep_only_latest_card);外部调用 2 个(try_recv, panic!)。
next_add_to_history_event97–110 ↗
fn next_add_to_history_event(rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>) -> String
作用:不断从事件队列里找下一条“追加到消息历史”的事件。它帮助测试确认哪些文字被记录进历史。
数据流:输入事件接收器 → 跳过无关事件 → 找到 AppendMessageHistoryEntry 就返回其中的文本;队列空或关闭就报错。
调用关系:很多测试在提交消息、shell 命令或复制历史后调用它,用来验证历史记录没有漏写或写错。
调用图:外部调用 2 个(try_recv, panic!)。
service_tier_commands_lowercase_catalog_names113–139 ↗
async fn service_tier_commands_lowercase_catalog_names()
作用:检查服务档位命令显示时会把模型目录里的名称转成小写。这样即使目录写 Fast,用户看到和输入的还是 fast。
数据流:创建聊天窗口和模型预设 → 把 fast 档位名称改成大写 → 请求当前模型的服务档位命令 → 断言返回名称是小写且说明不变。
调用关系:测试框架直接运行它;它关注 ChatWidget 从 ModelCatalog 生成 ServiceTierCommand 的行为。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_eq!, new, vec!)。
slash_compact_eagerly_queues_follow_up_before_turn_start142–167 ↗
async fn slash_compact_eagerly_queues_follow_up_before_turn_start()
作用:检查 /compact 刚发出但还没真正开始轮次时,用户马上输入的后续消息会排队,而不是被当成转向指令乱塞进去。
数据流:创建聊天窗口 → 执行 /compact → 确认发出 Compact 操作 → 输入后续文字并提交 → 检查它进入用户消息队列且没有立刻发核心操作。
调用关系:测试框架直接运行它;它验证 /compact 命令和输入队列之间的衔接。
调用图:外部调用 6 个(new, new, assert!, assert_eq!, assert_matches!, panic!)。
queued_slash_compact_dispatches_after_active_turn170–197 ↗
async fn queued_slash_compact_dispatches_after_active_turn()
作用:检查忙碌时排队的 /compact,会在当前轮完成后真正发出压缩操作。
数据流:模拟有活跃轮次 → 用 Tab 排队 /compact → 完成当前轮 → 从事件中确认出现 Compact 操作。
调用关系:它使用 queue_composer_text_with_tab 排队,用 complete_turn_with_message 推进到轮次结束,验证队列出队后的命令解析。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 4 个(assert!, assert_eq!, assert_matches!, from_fn)。
queued_slash_review_with_args_dispatches_after_active_turn200–218 ↗
async fn queued_slash_review_with_args_dispatches_after_active_turn()
作用:检查忙碌时输入的 /review 加说明,会在当前轮结束后带着说明执行代码审查。
数据流:活跃轮次中排队 /review check regressions → 完成轮次 → 从核心操作队列取出 Review,并检查自定义说明没丢。
调用关系:测试框架运行它;它依赖排队助手和完成轮次助手来覆盖延迟执行命令的路径。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 2 个(assert_eq!, panic!)。
queued_slash_review_with_args_restores_for_edit221–233 ↗
async fn queued_slash_review_with_args_restores_for_edit()
作用:检查排队的 /review 命令可以被用户按 Alt+上箭头取回编辑。
数据流:活跃轮次中排队 /review 命令 → 发送编辑召回按键 → 读取输入框 → 应恢复完整原文。
调用关系:它使用 queue_composer_text_with_tab 构造队列,验证输入队列和编辑召回功能配合正确。
调用图:调用 2 个内部函数(new, queue_composer_text_with_tab);外部调用 2 个(new, assert_eq!)。
queued_bang_shell_dispatches_after_active_turn236–262 ↗
async fn queued_bang_shell_dispatches_after_active_turn()
作用:检查忙碌时排队的 !shell 命令,会在当前轮结束后作为用户 shell 命令执行。
数据流:活跃轮次中排队 !echo hi → 确认队列动作是 RunShell → 完成轮次 → 核心收到 echo hi,历史也记录原命令。
调用关系:它使用排队和完成助手,验证感叹号命令和普通斜杠命令一样遵守队列顺序。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 4 个(assert!, assert_eq!, assert_matches!, panic!)。
queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_input265–299 ↗
async fn queued_empty_bang_shell_reports_help_when_dequeued_and_drains_next_input()
作用:检查只输入一个感叹号时,系统会在轮次结束后显示 shell 帮助,并继续处理下一条排队消息。
数据流:排队空 shell 命令和后一条普通消息 → 完成当前轮 → 历史里出现帮助文字 → 下一条普通消息被提交。
调用关系:它验证队列不会因为无效 shell 命令卡死,后续输入仍会被 drain,也就是继续取出处理。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 3 个(assert!, assert_eq!, panic!)。
queued_bang_shell_waits_for_user_shell_completion_before_next_input302–338 ↗
async fn queued_bang_shell_waits_for_user_shell_completion_before_next_input()
作用:检查 shell 命令执行期间,下一条用户消息不会抢跑,必须等 shell 完成后再发。
数据流:排队 shell 和普通消息 → 当前轮结束后先发 shell 操作 → 模拟 shell 开始和结束 → 然后普通消息才被提交。
调用关系:它覆盖用户 shell 命令的等待机制,防止后续消息和 shell 输出顺序混乱。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 3 个(assert!, assert_eq!, panic!)。
queued_bare_rename_drains_next_input_after_name_update413–461 ↗
async fn queued_bare_rename_drains_next_input_after_name_update()
作用:检查排队的空 /rename 会先打开命名弹窗,等线程名真的更新后,再处理下一条消息。
数据流:排队 /rename 和普通消息 → 轮次结束后出现命名弹窗 → 输入新名字并确认 → 模拟服务器通知名字已更新 → 后续消息提交。
调用关系:它覆盖需要用户交互又要等服务器确认的命令,防止 rename 后队列提前或永远不动。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 7 个(new, ThreadNameUpdated, assert!, assert_eq!, assert_matches!, panic!, from_fn)。
queued_inline_rename_does_not_drain_again_before_turn_started464–544 ↗
async fn queued_inline_rename_does_not_drain_again_before_turn_started()
作用:检查带名字的 /rename 执行后,只会推进一条后续消息,不能在新轮次开始前重复 drain 队列。
数据流:排队 /rename 名字和两条消息 → 当前轮结束后改名并提交第一条 → 保存和恢复输入状态 → 名字更新通知到来时不再抢发第二条 → 等第二轮完成后才发第二条。
调用关系:它验证队列状态里的 user_turn_pending_start 标记,避免重命名通知触发重复提交。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 6 个(ThreadNameUpdated, assert!, assert_eq!, assert_matches!, panic!, from_fn)。
queued_unknown_slash_reports_error_when_dequeued547–569 ↗
async fn queued_unknown_slash_reports_error_when_dequeued()
作用:检查未知斜杠命令如果是在忙碌时排队的,错误提示要等它出队时才显示。
数据流:活跃轮次中排队 /does-not-exist → 当前历史暂时没有错误 → 完成轮次 → 历史里出现未知命令错误,队列清空。
调用关系:它验证延迟解析的错误时机,和 queue_composer_text_with_tab、complete_turn_with_message 配合。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 1 个(assert!)。
ctrl_d_quits_without_prompt572–577 ↗
async fn ctrl_d_quits_without_prompt()
作用:检查按 Ctrl+D 会直接请求退出应用。Ctrl+D 常被终端用户当作结束输入的快捷键。
数据流:创建聊天窗口 → 发送 Ctrl+D 按键 → 事件队列应出现 ShutdownFirst 退出事件。
调用关系:测试框架直接运行它;它覆盖全局快捷键退出路径。
调用图:外部调用 3 个(Char, new, assert_matches!)。
ctrl_d_with_modal_open_does_not_quit580–587 ↗
async fn ctrl_d_with_modal_open_does_not_quit()
作用:检查有弹窗打开时 Ctrl+D 不会退出,避免用户在菜单里误触导致整个程序关闭。
数据流:创建窗口并打开审批弹窗 → 发送 Ctrl+D → 事件队列保持为空。
调用关系:它和 ctrl_d_quits_without_prompt 形成对照,证明快捷键会尊重当前界面状态。
调用图:外部调用 3 个(Char, new, assert_matches!)。
slash_init_does_not_depend_on_loaded_instruction_sources590–599 ↗
async fn slash_init_does_not_depend_on_loaded_instruction_sources()
作用:检查 /init 不依赖已经加载的项目说明文件列表。即使有说明源路径,也应能正常排队。
数据流:设置 instruction_source_paths → 提交 /init → 检查它进入队列、没有立刻写历史,并能从本地历史召回。
调用关系:它使用 submit_composer_text 和 recall_latest_after_clearing,验证 /init 的提交和历史行为。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 3 个(assert!, assert_eq!, vec!)。
bare_slash_command_is_available_from_local_recall_after_dispatch602–610 ↗
async fn bare_slash_command_is_available_from_local_recall_after_dispatch()
作用:检查像 /diff 这种不带参数的命令执行后,用户可以按上箭头重新找回它。
数据流:提交 /diff → 清掉历史插入事件 → 按上箭头 → 输入框恢复 /diff。
调用关系:它用 submit_composer_text 走真实提交路径,关注本地输入历史。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(new, assert_eq!)。
inline_slash_command_is_available_from_local_recall_after_dispatch613–621 ↗
async fn inline_slash_command_is_available_from_local_recall_after_dispatch()
作用:检查带参数的斜杠命令执行后,也能完整从本地历史召回。
数据流:提交 /rename Better title → 清事件 → 按上箭头 → 输入框恢复完整命令和参数。
调用关系:它和 bare_slash_command_is_available_from_local_recall_after_dispatch 对照,覆盖带参数情况。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(new, assert_eq!)。
goal_slash_command_with_extra_os_emits_set_goal_event624–647 ↗
async fn goal_slash_command_with_extra_os_emits_set_goal_event()
作用:检查 /goal 的命令名即使多打了一串 o,只要被识别为 goal 命令,仍会设置目标草稿。
数据流:开启 Goals 功能并设置线程 id → 提交长拼写的 /goooooal 命令 → 事件里应出现 SetThreadGoalDraft,内容保留参数文本。
调用关系:它测试目标功能的命令解析,并确认不会向核心提交普通用户轮次。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert_eq!, panic!)。
goal_slash_command_uses_plain_text_for_mentions650–673 ↗
async fn goal_slash_command_uses_plain_text_for_mentions()
作用:检查 /goal 里的提及内容按用户看到的普通文字保存,而不是被改成内部链接格式。
数据流:设置带 $figma 提及绑定的输入框 → 提交 /goal → 取出目标草稿 → objective 仍是 use $figma for the mockup。
调用关系:它验证 goal 命令和提及系统的边界:目标文本保留原样,提交事件不走核心对话。
调用图:调用 1 个内部函数(new);外部调用 4 个(new, new, assert_eq!, vec!)。
goal_slash_command_emits_attached_images676–711 ↗
bare_goal_slash_command_drains_pending_submission_state714–733 ↗
goal_control_slash_commands_emit_goal_events736–776 ↗
async fn goal_control_slash_commands_emit_goal_events()
作用:检查 /goal clear、pause、resume 会分别发出清除目标或改变目标状态的事件。
数据流:对三种命令逐个创建窗口并提交 → clear 产生 ClearThreadGoal,pause/resume 产生 SetThreadGoalStatus → 线程 id 和状态都要匹配。
调用关系:它集中覆盖目标控制命令,是 /goal 子命令行为的表格化测试。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert_eq!, panic!)。
goal_control_slash_command_without_thread_shows_full_usage779–791 ↗
async fn goal_control_slash_command_without_thread_shows_full_usage()
作用:检查还没有会话线程时执行 /goal pause,会显示完整用法和“会话必须先开始”的提示。
数据流:开启 Goals 但不设置线程 id → 提交 /goal pause → 历史里出现一条用法说明快照。
调用关系:它验证目标控制命令在缺少线程上下文时不会静默失败,而是给用户明确说明。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert_eq!, assert_snapshot!)。
goal_edit_slash_command_opens_goal_editor794–812 ↗
async fn goal_edit_slash_command_opens_goal_editor()
作用:检查 /goal edit 会打开目标编辑器,不管当前有没有线程 id。
数据流:分别用有线程和无线程两种状态提交 /goal edit → 事件里出现 OpenThreadGoalEditor,并携带对应的线程 id 选项。
调用关系:它覆盖目标编辑入口,确认这个命令不会误发核心对话操作。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert_eq!, panic!)。
queued_goal_slash_command_emits_set_goal_event_after_thread_starts815–831 ↗
async fn queued_goal_slash_command_emits_set_goal_event_after_thread_starts()
作用:检查还没有线程时提交 /goal 会先排队,等线程 id 出现后再设置目标。
数据流:提交 /goal 文本 → 它进入队列且不发核心操作 → 后来设置线程 id 并尝试发送队列 → 收到目标草稿事件。
调用关系:它验证 /goal 需要线程上下文时的延迟执行逻辑。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert_eq!, assert_matches!)。
queued_goal_slash_command_preserves_large_paste834–852 ↗
async fn queued_goal_slash_command_preserves_large_paste()
作用:检查忙碌时排队的 /goal 大粘贴,在出队后仍保留完整粘贴原文。
数据流:活跃轮次中排队超长粘贴的 /goal → 完成轮次 → 草稿里 pending_pastes 包含原文,objective 里含占位符。
调用关系:它使用 queue_goal_with_large_paste 和 complete_turn_with_message,验证大文本不会在队列中被截断。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_goal_with_large_paste);外部调用 2 个(assert!, assert_eq!)。
queued_goal_slash_command_restores_large_paste_for_edit855–874 ↗
async fn queued_goal_slash_command_restores_large_paste_for_edit()
作用:检查排队的大粘贴 /goal 被取回编辑时,粘贴原文也能恢复。
数据流:排队大粘贴 /goal → Alt+上箭头恢复到输入框 → 检查 pending_pastes 原文 → 完成轮次并重新提交 → 草稿仍含原粘贴。
调用关系:它连接了队列编辑、粘贴保存和目标草稿生成三段流程。
调用图:调用 4 个内部函数(new, complete_turn_with_message, queue_goal_with_large_paste, submit_current_composer);外部调用 2 个(new, assert_eq!)。
interrupt_disambiguates_same_sized_goal_pastes877–897 ↗
async fn interrupt_disambiguates_same_sized_goal_pastes()
作用:检查两段长度相同但内容不同的大粘贴,在中断和历史召回后不会被混淆。
数据流:排队第一段大粘贴 → 当前输入框再粘贴第二段同长度文本 → 中断轮次 → 召回并删除一个字符 → 剩余 pending paste 应是第二段。
调用关系:它测试大粘贴用唯一占位识别,而不是只靠长度猜测。
调用图:调用 1 个内部函数(queue_goal_with_large_paste);外部调用 4 个(new, new, assert!, assert_eq!)。
queued_goal_slash_command_preserves_current_draft_metadata900–935 ↗
async fn queued_goal_slash_command_preserves_current_draft_metadata()
作用:检查排队的 /goal 出队时,不会破坏用户当前正在输入的草稿、图片和远程 URL。
数据流:先排队一个 /goal → 当前输入框换成另一个带图片的草稿 → 设置线程并发送队列 → 目标事件发出后,当前草稿和附件仍保持原样。
调用关系:它验证处理队列时会保存并恢复当前输入框状态,避免用户正在写的内容被覆盖。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 5 个(from, assert_eq!, assert_matches!, format!, vec!)。
restored_queued_goal_slash_command_emits_set_goal_event938–958 ↗
async fn restored_queued_goal_slash_command_emits_set_goal_event()
作用:检查保存再恢复的排队 /goal 命令,仍能在线程开始后发出目标事件。
数据流:提交 /goal 并捕获输入状态 → 创建新聊天窗口并恢复该状态 → 设置线程 id 并发送队列 → 收到目标草稿事件。
调用关系:它覆盖跨窗口或恢复会话场景,确认排队输入可被序列化后继续执行。
调用图:调用 2 个内部函数(new, submit_composer_text)。
merged_history_record_preserves_raw_text_and_rebased_elements961–999 ↗
fn merged_history_record_preserves_raw_text_and_rebased_elements()
作用:检查多条用户消息合并成历史记录时,原始文字和文字元素的位置会正确重算。
数据流:准备一条带提及的消息和一条内部提示 → 合并消息与历史覆盖记录 → 断言历史文本用换行拼接,元素范围按新位置移动。
调用关系:测试框架直接运行它;它验证 merge_user_messages_with_history_record 的历史展示数据不会错位。
调用图:调用 1 个内部函数(from);外部调用 3 个(new, assert_eq!, vec!)。
merged_history_record_remaps_override_image_placeholders1002–1072 ↗
fn merged_history_record_remaps_override_image_placeholders()
作用:检查合并多条带图片的消息时,重复的图片占位符会被重新编号,历史覆盖文本也同步更新。
数据流:准备两条都叫 [Image #1] 的图片消息 → 合并 → 第二张变成 [Image #2],消息正文、图片列表和历史记录里的元素范围都匹配。
调用关系:它专门覆盖图片占位冲突,防止历史里两张图看起来像同一张。
调用图:外部调用 4 个(new, assert_eq!, format!, vec!)。
interrupted_merged_message_history_encodes_mentions_once1075–1130 ↗
slash_rename_prefills_existing_thread_name1133–1148 ↗
async fn slash_rename_prefills_existing_thread_name()
作用:检查 /rename 弹窗会预填当前线程名,方便用户直接修改。
数据流:设置当前线程名 → 分发 /rename → 渲染弹窗并做快照 → 按 Enter → 发出 SetThreadName,名称就是原来的标题。
调用关系:它测试重命名弹窗和当前线程状态的连接。
调用图:外部调用 3 个(new, assert_chatwidget_snapshot!, assert_matches!)。
slash_rename_without_existing_thread_name_starts_empty1151–1163 ↗
async fn slash_rename_without_existing_thread_name_starts_empty()
作用:检查没有线程名时 /rename 弹窗从空白开始,直接回车不会提交空名字。
数据流:不设置线程名 → 分发 /rename → 弹窗显示命名提示 → 按 Enter → 事件队列没有改名请求。
调用关系:它和预填测试形成对照,验证空标题不能被误提交。
调用图:外部调用 3 个(new, assert!, assert_matches!)。
usage_error_slash_command_is_available_from_local_recall1166–1184 ↗
async fn usage_error_slash_command_is_available_from_local_recall()
作用:检查 /raw 参数写错后显示用法错误,但这条输入仍能从本地历史召回。
数据流:提交 /raw maybe → 输入框清空并显示 Usage → 清空后召回 → 得到 /raw maybe。
调用关系:它验证“认识这个命令但参数错了”的情况和完全未知命令的历史策略不同。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
signed_out_usage_command_reports_chatgpt_login_requirement1187–1203 ↗
async fn signed_out_usage_command_reports_chatgpt_login_requirement()
作用:检查未登录时执行 /usage,会提示需要用 ChatGPT 登录。
数据流:未设置认证 → 提交 /usage → 历史里出现登录要求提示 → 命令可被召回。
调用关系:它覆盖 /usage 的权限门槛,防止用户看到无意义的后台错误。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
signed_out_usage_command_with_args_reports_chatgpt_login_requirement1206–1222 ↗
async fn signed_out_usage_command_with_args_reports_chatgpt_login_requirement()
作用:检查未登录时执行带参数的 /usage weekly,也会提示需要登录。
数据流:未登录状态提交 /usage weekly → 历史文本包含登录要求 → 本地历史可召回原命令。
调用关系:它补充 /usage 带视图参数时的未登录路径。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
usage_command_with_invalid_view_reports_usage_snapshot1225–1238 ↗
async fn usage_command_with_invalid_view_reports_usage_snapshot()
作用:检查已登录但 /usage 视图参数无效时,会显示正确用法。
数据流:设置 ChatGPT 认证 → 提交 /usage monthly → 历史里出现用法说明快照 → 原命令可召回。
调用关系:它验证 /usage 参数校验,避免错误参数发起无效后台请求。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
usage_command_runs_with_backend_auth_without_chatgpt_account_flag1241–1252 ↗
async fn usage_command_runs_with_backend_auth_without_chatgpt_account_flag()
作用:检查即使没有 ChatGPT 账号标记,只要有 Codex 后端认证,/usage 也能运行。
数据流:更新账号状态为无 ChatGPT 账号但有后端认证 → 分发 /usage daily → 事件里出现刷新 token 活动请求。
调用关系:它测试认证判断逻辑,防止只看一个账号标志导致误拒绝。
调用图:外部调用 3 个(new, assert!, assert_matches!)。
usage_command_runs_with_backend_auth_from_widget_init1255–1267 ↗
async fn usage_command_runs_with_backend_auth_from_widget_init()
作用:检查聊天窗口初始化时带有后端认证,也能直接使用 /usage。
数据流:用后端认证参数创建窗口 → 分发 /usage daily → 收到刷新 token 活动事件,并确认账号标志状态正确。
调用关系:它和运行中更新账号状态的测试互补,覆盖初始化路径。
调用图:外部调用 3 个(new, assert!, assert_matches!)。
clearing_pending_token_activity_refreshes_discards_late_result1270–1294 ↗
async fn clearing_pending_token_activity_refreshes_discards_late_result()
作用:检查清除待处理的 /usage 刷新后,迟到的结果会被丢弃。
数据流:触发 /usage 并拿到请求 id → 确认加载卡片存在 → 清除待处理刷新 → 用同一 id 完成刷新 → 返回 false,表示不接受旧结果。
调用关系:它使用 dispatch_usage_and_expect_refresh,验证请求 id 能防止旧数据覆盖新界面。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 2 个(assert!, assert_eq!)。
account_state_change_discards_pending_token_activity_refresh1297–1321 ↗
async fn account_state_change_discards_pending_token_activity_refresh()
作用:检查账号切换会丢掉正在加载的 /usage 结果,避免旧账号数据显示到新账号下。
数据流:触发 /usage → 更新账号状态为新账号 → 待处理卡片消失 → 旧请求完成时被拒绝。
调用关系:它测试账号状态和 token 活动刷新之间的安全隔离。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 1 个(assert!)。
pending_token_activity_refresh_renders_above_composer_snapshot1324–1343 ↗
async fn pending_token_activity_refresh_renders_above_composer_snapshot()
作用:检查 /usage 加载卡片会显示在输入框上方,视觉布局符合预期。
数据流:触发 /usage → 创建虚拟终端并渲染 ChatWidget → 对屏幕内容做快照比较。
调用关系:它使用 VT100Backend 这种模拟终端,验证界面渲染而不是真实网络请求。
调用图:调用 3 个内部函数(dispatch_usage_and_expect_refresh, with_options, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
completed_token_activity_refresh_returns_one_history_cell1346–1369 ↗
async fn completed_token_activity_refresh_returns_one_history_cell()
作用:检查 /usage 刷新完成后,会生成一条可插入历史的结果卡片。
数据流:触发 /usage → 用错误结果完成刷新 → 取出 completed token activity output → 内容包含命令和错误提示,待处理状态被清空。
调用关系:它验证刷新完成后的数据从“加载中”转成“历史单元”。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 2 个(assert!, assert_eq!)。
completed_token_activity_refresh_waits_for_active_stream1372–1399 ↗
async fn completed_token_activity_refresh_waits_for_active_stream()
作用:检查助手正在流式输出时,/usage 结果不会马上插入历史,而是等输出结束。
数据流:触发 /usage → 模拟助手输出 partial response → 完成用量刷新 → 结果保留待插入 → finalize_turn 后发出提交待处理用量输出事件。
调用关系:它防止 /usage 卡片插到助手回答中间,保持聊天记录顺序清楚。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 2 个(assert!, assert_eq!)。
completed_token_activity_refresh_waits_for_queued_stream_consolidation1402–1422 ↗
async fn completed_token_activity_refresh_waits_for_queued_stream_consolidation()
作用:检查助手输出虽然结束但还在整理合并时,/usage 结果也要继续等待。
数据流:触发 /usage → 模拟部分回复并进入 stream consolidation → 完成用量刷新 → 插入被阻塞 → 合并完成后阻塞解除。
调用关系:它覆盖流式输出收尾阶段,避免历史单元插入太早。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 1 个(assert!)。
completed_token_activity_refresh_waits_for_active_history_cell1425–1445 ↗
async fn completed_token_activity_refresh_waits_for_active_history_cell()
作用:检查当前还有活跃历史单元时,/usage 结果会等它先 flush。
数据流:触发 /usage → 手动设置 active_cell → 完成刷新 → 插入被阻塞 → flush active_cell 后先插入它,再提交用量输出。
调用关系:它验证历史写入顺序,避免两个正在生成的历史卡片互相穿插。
调用图:调用 2 个内部函数(dispatch_usage_and_expect_refresh, new);外部调用 4 个(new, assert!, assert_matches!, vec!)。
completed_token_activity_refresh_waits_for_active_hook1448–1487 ↗
async fn completed_token_activity_refresh_waits_for_active_hook()
作用:检查有 hook 正在运行时,/usage 结果要等 hook 输出结束。hook 是自动触发的小脚本或检查步骤。
数据流:模拟 hook 开始 → 触发并完成 /usage → 插入被阻塞 → 模拟 hook 完成并产生输出 → 先插入 hook 历史,再提交用量输出。
调用关系:它覆盖工具钩子和用量卡片的排队关系。
调用图:调用 1 个内部函数(dispatch_usage_and_expect_refresh);外部调用 4 个(new, assert!, assert_matches!, vec!)。
completed_token_activity_refresh_retries_after_plan_item_completion1490–1515 ↗
async fn completed_token_activity_refresh_retries_after_plan_item_completion()
作用:检查计划项输出完成后,会再次尝试提交等待中的 /usage 结果。
数据流:触发 /usage → 设置计划流控制器为活跃 → 完成用量刷新 → 计划项完成 → 事件队列出现提交待处理用量输出的请求。
调用关系:它验证 plan 输出这种特殊流也会解除 /usage 插入阻塞。
调用图:调用 2 个内部函数(dispatch_usage_and_expect_refresh, new);外部调用 1 个(assert!)。
pending_token_activity_refresh_keeps_composer_visible_in_short_viewport1518–1544 ↗
async fn pending_token_activity_refresh_keeps_composer_visible_in_short_viewport()
作用:检查窗口很矮时,即使上方有 /usage 加载卡片和大量输出,输入框也不能被挤没。
数据流:设置很多活跃输出 → 触发 /usage → 用 8 行高虚拟终端渲染 → 屏幕仍包含输入提示。
调用关系:它关注布局底线:用户始终要看得到输入框。
调用图:调用 4 个内部函数(dispatch_usage_and_expect_refresh, with_options, new, new);外部调用 5 个(new, from, new, assert!, repeat_n)。
repeated_token_activity_refreshes_keep_only_latest_card1547–1569 ↗
async fn repeated_token_activity_refreshes_keep_only_latest_card()
作用:检查连续执行多次 /usage 时,界面只保留最新一次卡片,旧结果回来也不生效。
数据流:触发 daily 刷新并拿 id → 再触发 weekly 刷新拿新 id → 当前卡片显示 weekly → 第一个请求完成被拒绝,第二个被接受。
调用关系:它使用 dispatch_usage_and_expect_refresh 和 expect_token_activity_refresh,验证请求新旧淘汰机制。
调用图:调用 2 个内部函数(dispatch_usage_and_expect_refresh, expect_token_activity_refresh);外部调用 3 个(new, assert!, assert_eq!)。
unrecognized_slash_command_is_not_added_to_local_recall1572–1589 ↗
async fn unrecognized_slash_command_is_not_added_to_local_recall()
作用:检查完全不认识的斜杠命令不会进入本地历史召回。
数据流:提交 /does-not-exist → 显示未知命令错误,输入框仍保留原文 → 清空后召回得到空字符串。
调用关系:它和参数错误、禁用命令测试形成对照,说明只有可识别命令才进历史。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
no_op_stub_slash_command_is_available_from_local_recall1612–1628 ↗
async fn no_op_stub_slash_command_is_available_from_local_recall()
作用:检查尚未真正实现的占位命令,也会显示提示并进入本地历史。
数据流:提交 /debug-m-drop → 历史显示 Memory maintenance 提示 → 召回得到原命令。
调用关系:它覆盖 stub 命令,保证用户能看到明确说明而不是无反应。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
slash_quit_requests_exit1631–1637 ↗
async fn slash_quit_requests_exit()
作用:检查 /quit 会请求退出程序。
数据流:分发 Quit 命令 → 事件队列出现 Exit(ShutdownFirst)。
调用关系:测试框架直接运行它;它验证斜杠退出命令和应用事件的连接。
调用图:外部调用 1 个(assert_matches!)。
slash_logout_requests_app_server_logout1640–1646 ↗
async fn slash_logout_requests_app_server_logout()
作用:检查 /logout 会请求应用服务器执行登出。
数据流:分发 Logout 命令 → 事件队列出现 Logout。
调用关系:它测试账号退出命令不会误走核心对话通道。
调用图:外部调用 1 个(assert_matches!)。
slash_copy_state_tracks_turn_complete_final_reply1649–1658 ↗
async fn slash_copy_state_tracks_turn_complete_final_reply()
作用:检查一轮完成后,/copy 可用的最后助手回复会更新为最终回答。
数据流:模拟一轮完成并带最终文本 → 读取 last_agent_markdown_text → 应等于最终回复。
调用关系:它使用 complete_turn_with_message,验证复制来源的基础更新。
调用图:调用 1 个内部函数(complete_turn_with_message);外部调用 1 个(assert_eq!)。
slash_copy_state_tracks_plan_item_completion1661–1684 ↗
async fn slash_copy_state_tracks_plan_item_completion()
作用:检查如果助手输出的是计划项,/copy 的来源也会更新为计划文本。
数据流:模拟服务器发来 Plan item completed → 完成轮次 → last_agent_markdown_text 和完成通知都使用计划文本。
调用关系:它覆盖非普通消息的助手输出,确保 /copy 不只认 final answer。
调用图:外部调用 4 个(ItemCompleted, new, assert_eq!, assert_matches!)。
slash_copy_reports_when_no_agent_response_exists1687–1700 ↗
async fn slash_copy_reports_when_no_agent_response_exists()
作用:检查没有任何助手回复时执行 /copy,会给出友好提示。
数据流:分发 Copy 命令 → 历史插入一条信息 → 内容说明没有可复制的助手回复。
调用关系:它验证复制命令的空状态提示。
调用图:外部调用 3 个(assert!, assert_chatwidget_snapshot!, assert_eq!)。
ctrl_o_copy_reports_when_no_agent_response_exists1703–1715 ↗
async fn ctrl_o_copy_reports_when_no_agent_response_exists()
作用:检查复制快捷键 Ctrl+O 在没有助手回复时,也显示同样的提示。
数据流:发送 Ctrl+O → 历史插入一条信息 → 内容包含没有可复制回复。
调用关系:它和 /copy 命令测试对照,确认快捷键走同一类行为。
调用图:外部调用 4 个(Char, new, assert!, assert_eq!)。
keymap_capture_can_capture_current_copy_shortcut1718–1747 ↗
async fn keymap_capture_can_capture_current_copy_shortcut()
作用:检查正在录入快捷键时,按当前复制快捷键不会触发复制,而是被记录为配置项。
数据流:打开 keymap capture → 按 Ctrl+O → 事件里出现 KeymapCaptured,内容是 composer.submit 和 ctrl-o → 历史没有复制提示。
调用关系:它验证快捷键捕获模式优先于普通快捷键执行。
调用图:调用 1 个内部函数(defaults);外部调用 5 个(Char, new, assert!, assert_eq!, panic!)。
slash_keymap_capture_can_capture_app_shortcuts1750–1778 ↗
async fn slash_keymap_capture_can_capture_app_shortcuts()
作用:检查快捷键捕获模式能捕获全局快捷键,比如 Ctrl+T、Ctrl+L、Ctrl+G。
数据流:循环打开捕获模式 → 发送对应 Ctrl 组合键 → 每次都收到 KeymapCaptured,记录正确的按键字符串。
调用关系:它扩展 keymap capture 测试到应用级快捷键,防止这些快捷键在捕获时产生副作用。
调用图:调用 1 个内部函数(defaults);外部调用 4 个(Char, new, assert_eq!, panic!)。
slash_keymap_debug_opens_keypress_inspector1781–1797 ↗
slash_keymap_debug_can_inspect_app_shortcuts1800–1824 ↗
slash_keymap_invalid_args_show_usage1827–1844 ↗
async fn slash_keymap_invalid_args_show_usage()
作用:检查 /keymap 参数错误时会显示用法说明,并可从历史召回。
数据流:提交 /keymap nope → 历史显示 Usage: /keymap [debug] → 召回得到原命令 → 没有核心操作。
调用关系:它验证 keymap 子命令参数校验。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
copy_shortcut_can_be_remapped1847–1871 ↗
async fn copy_shortcut_can_be_remapped()
作用:检查复制快捷键可以从默认 Ctrl+O 改成别的键,比如 Ctrl+X。
数据流:修改配置里的 copy 绑定并应用 → 按旧 Ctrl+O 没反应 → 按新 Ctrl+X 触发复制空状态提示。
调用关系:它验证运行时 keymap 更新会真正改变 ChatWidget 的快捷键行为。
调用图:调用 1 个内部函数(from_config);外部调用 6 个(Char, new, assert!, assert_eq!, KeybindingSpec, One)。
slash_copy_stores_clipboard_lease_and_preserves_it_on_failure1874–1905 ↗
async fn slash_copy_stores_clipboard_lease_and_preserves_it_on_failure()
作用:检查复制成功时保存剪贴板租约,后续复制失败也不会丢掉旧租约。剪贴板租约可以理解成保持剪贴板内容有效的句柄。
数据流:设置最后助手文本 → 用成功回调复制,保存 lease 并显示成功 → 再用失败回调复制 → 显示失败但 lease 仍存在。
调用关系:它测试 copy_last_agent_markdown_with 的成功和失败分支,保证剪贴板状态稳妥。
调用图:外部调用 2 个(assert!, assert_eq!)。
slash_copy_state_is_preserved_during_running_task1908–1918 ↗
async fn slash_copy_state_is_preserved_during_running_task()
作用:检查新任务开始后,上一条已完成助手回复仍然可供 /copy 使用。
数据流:完成一轮并设置上一条回复 → 标记任务开始 → last_agent_markdown_text 仍是上一条回复。
调用关系:它防止运行中任务把复制来源提前清空。
调用图:调用 1 个内部函数(complete_turn_with_message);外部调用 1 个(assert_eq!)。
slash_copy_uses_agent_message_item_when_turn_complete_omits_final_text1921–1943 ↗
async fn slash_copy_uses_agent_message_item_when_turn_complete_omits_final_text()
作用:检查轮次完成事件没有最终文本时,/copy 会退回使用已经收到的助手消息项。
数据流:开始轮次 → 接收一条助手消息但 phase 为空 → 完成轮次且没有 final 文本 → last_agent_markdown_text 使用这条消息。
调用关系:它兼容旧式服务器事件,避免 /copy 因缺少 final 字段失效。
调用图:外部调用 2 个(assert_eq!, assert_matches!)。
agent_turn_complete_notification_does_not_reuse_stale_copy_source1946–1958 ↗
async fn agent_turn_complete_notification_does_not_reuse_stale_copy_source()
作用:检查新一轮没有回复文本时,完成通知不会错误复用上一轮的回复。
数据流:先完成一轮有 Previous reply → 清空通知 → 再完成 turn-2 但没有消息 → 新通知 response 为空。
调用关系:它验证通知内容和复制来源不能混用旧数据。
调用图:调用 1 个内部函数(complete_turn_with_message);外部调用 1 个(assert_matches!)。
active_goal_without_follow_up_suppresses_agent_turn_complete_notification1961–1987 ↗
async fn active_goal_without_follow_up_suppresses_agent_turn_complete_notification()
作用:检查有活跃目标且没有后续用户输入时,助手轮次完成通知会被压住不弹。
数据流:开启 Goals 并接收活跃目标更新 → 完成一轮有回复 → pending_notification 仍为空。
调用关系:它测试目标模式下通知策略,避免自动目标流程频繁打扰用户。
调用图:调用 1 个内部函数(complete_turn_with_message);外部调用 2 个(ThreadGoalUpdated, assert_matches!)。
queued_follow_up_suppresses_agent_turn_complete_notification1990–2001 ↗
async fn queued_follow_up_suppresses_agent_turn_complete_notification()
作用:检查如果已经排了后续用户消息,当前助手完成通知不会弹出。
数据流:活跃轮次中排队 Continue → 完成当前轮 → 后续消息被提交,通知为空。
调用关系:它验证自动继续对话时不显示“已完成”通知。
调用图:调用 2 个内部函数(new, complete_turn_with_message);外部调用 2 个(assert!, assert_matches!)。
slash_copy_uses_latest_surviving_response_after_rollback2021–2048 ↗
async fn slash_copy_uses_latest_surviving_response_after_rollback()
作用:检查回滚对话后,/copy 使用仍然保留下来的最新助手回复。
数据流:回放两轮用户和助手消息 → 当前复制来源是第二轮回复 → 截断到一轮 → 复制来源变成第一轮回复,并复制它。
调用关系:它测试对话回滚和复制历史栈的配合。
调用图:外部调用 1 个(assert_eq!)。
slash_copy_reports_when_rewind_exceeds_retained_copy_history2051–2074 ↗
async fn slash_copy_reports_when_rewind_exceeds_retained_copy_history()
作用:检查回滚超过保留的复制历史范围时,/copy 会说明无法复制那条旧回复。
数据流:回放一轮助手回复 → 截断到 0 个用户轮次 → 执行 /copy → 历史提示只保留最近 32 条响应。
调用关系:它验证复制历史有上限时的错误提示。
调用图:外部调用 1 个(assert!)。
slash_exit_requests_exit2077–2083 ↗
async fn slash_exit_requests_exit()
作用:检查 /exit 和 /quit 一样会请求退出程序。
数据流:分发 Exit 命令 → 事件队列出现 ShutdownFirst 退出事件。
调用关系:它覆盖另一个退出命令别名。
调用图:外部调用 1 个(assert_matches!)。
slash_stop_submits_background_terminal_cleanup2086–2099 ↗
async fn slash_stop_submits_background_terminal_cleanup()
作用:检查 /stop 会要求清理所有后台终端,并在历史里给用户确认。
数据流:分发 Stop 命令 → 核心操作队列收到 CleanBackgroundTerminals → 历史显示正在停止后台终端。
调用关系:它验证停止后台任务命令会同时通知核心和用户界面。
调用图:外部调用 3 个(assert!, assert_eq!, assert_matches!)。
slash_clear_requests_ui_clear_when_idle2102–2108 ↗
async fn slash_clear_requests_ui_clear_when_idle()
作用:检查空闲时 /clear 会请求清空界面。
数据流:分发 Clear 命令 → 事件队列出现 ClearUi。
调用关系:它覆盖清屏命令的正常路径。
调用图:外部调用 1 个(assert_matches!)。
slash_clear_after_ctrl_c_keeps_stashed_draft_recallable2111–2139 ↗
async fn slash_clear_after_ctrl_c_keeps_stashed_draft_recallable()
作用:检查 Ctrl+C 暂存的草稿,在执行 /clear 后仍然能用历史召回。
数据流:提交 ok 到历史 → 输入草稿并按 Ctrl+C 暂存 → 执行 /clear → 按上箭头先召回草稿,再召回 ok。
调用关系:它验证清屏不会破坏底部输入历史。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 5 个(Char, new, new, assert_eq!, assert_matches!)。
slash_clear_is_disabled_while_task_running2142–2160 ↗
slash_archive_is_disabled_while_task_running2163–2181 ↗
slash_memory_drop_reports_stubbed_feature2184–2201 ↗
slash_mcp_requests_inventory_via_app_server2204–2220 ↗
async fn slash_mcp_requests_inventory_via_app_server()
作用:检查 /mcp 会通过应用服务器请求 MCP 工具清单。MCP 是让模型连接外部工具的一套协议。
数据流:设置线程 id → 分发 Mcp → 活跃区域显示 Loading MCP inventory → 事件请求 ToolsAndAuthOnly 清单并带线程 id。
调用关系:它验证 /mcp 走应用事件通道,而不是核心对话操作。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert!, assert_matches!)。
slash_mcp_verbose_requests_full_inventory_via_app_server2223–2239 ↗
async fn slash_mcp_verbose_requests_full_inventory_via_app_server()
作用:检查 /mcp verbose 会请求更完整的 MCP 清单。
数据流:设置线程 id → 提交 /mcp verbose → 显示加载状态 → 事件请求 Full 详情。
调用关系:它补充 /mcp 的 verbose 参数路径。
调用图:调用 2 个内部函数(new, submit_composer_text);外部调用 2 个(assert!, assert_matches!)。
slash_mcp_invalid_args_show_usage2242–2259 ↗
async fn slash_mcp_invalid_args_show_usage()
作用:检查 /mcp 参数不对时会显示用法说明,并可召回。
数据流:提交 /mcp full → 历史显示 Usage: /mcp [verbose] → 召回原命令 → 核心操作为空。
调用关系:它验证 MCP 命令参数校验。
调用图:调用 1 个内部函数(submit_composer_text);外部调用 2 个(assert!, assert_eq!)。
slash_memory_update_reports_stubbed_feature2274–2291 ↗
slash_resume_opens_picker2294–2300 ↗
async fn slash_resume_opens_picker()
作用:检查 /resume 不带参数时会打开会话恢复选择器。
数据流:分发 Resume → 事件队列出现 OpenResumePicker。
调用关系:它验证恢复会话入口会交给外层应用打开选择器。
调用图:外部调用 1 个(assert_matches!)。
slash_import_opens_claude_code_import_picker2303–2312 ↗
async fn slash_import_opens_claude_code_import_picker()
作用:检查 /import 会打开外部代理配置迁移入口,比如从 Claude Code 导入。
数据流:分发 Import → 事件队列出现 OpenExternalAgentConfigMigration。
调用关系:它测试导入命令和应用级迁移界面的连接。
调用图:外部调用 1 个(assert_matches!)。
slash_archive_confirmation_requests_current_thread_archive2315–2330 ↗
async fn slash_archive_confirmation_requests_current_thread_archive()
作用:检查 /archive 会先弹确认框,用户确认后才归档当前线程。
数据流:分发 Archive → 底部出现确认弹窗且无事件 → 选择确认并回车 → 发出 ArchiveCurrentThread。
调用关系:它验证危险操作有确认步骤。
调用图:外部调用 4 个(from, assert!, assert_chatwidget_snapshot!, assert_matches!)。
slash_delete_confirmation_requests_current_thread_delete2333–2348 ↗
async fn slash_delete_confirmation_requests_current_thread_delete()
作用:检查 /delete 会先弹确认框,用户确认后才删除当前线程。
数据流:分发 Delete → 底部出现确认弹窗 → 选择确认回车 → 发出 DeleteCurrentThread。
调用关系:它和归档确认测试类似,覆盖删除命令。
调用图:外部调用 4 个(from, assert!, assert_chatwidget_snapshot!, assert_matches!)。
slash_resume_with_arg_requests_named_session2351–2366 ↗
async fn slash_resume_with_arg_requests_named_session()
作用:检查 /resume 后面带名字或 id 时,会直接请求恢复指定会话,而不是打开选择器。
数据流:输入 /resume my-saved-thread 并回车 → 事件队列出现 ResumeSessionByIdOrName,值是 my-saved-thread → 核心操作为空。
调用关系:它补充 /resume 的带参数路径。
调用图:外部调用 3 个(from, new, assert_matches!)。
slash_pets_opens_picker2370–2381 ↗
async fn slash_pets_opens_picker()
作用:检查终端支持图片时,/pets 会打开宠物选择器。
数据流:强制宠物图片支持 → 分发 Pets → 底部有活动视图 → 弹窗快照匹配宠物选择器。
调用关系:它调用 force_pet_image_support,覆盖宠物功能的正常入口。
调用图:调用 1 个内部函数(force_pet_image_support);外部调用 3 个(assert!, assert_chatwidget_snapshot!, assert_matches!)。
slash_pets_with_arg_selects_named_pet2385–2398 ↗
async fn slash_pets_with_arg_selects_named_pet()
作用:检查 /pets chefito 会直接选择名为 chefito 的宠物。
数据流:强制图片支持 → 输入 /pets chefito 回车 → 事件队列出现 PetSelected,pet_id 是 chefito。
调用关系:它调用 force_pet_image_support,测试带参数选择宠物路径。
调用图:调用 1 个内部函数(force_pet_image_support);外部调用 3 个(from, new, assert_matches!)。
slash_pets_disable_disables_pets_even_on_unsupported_terminal2402–2413 ↗
async fn slash_pets_disable_disables_pets_even_on_unsupported_terminal()
作用:检查即使终端不支持显示宠物,/pets disable 也能禁用宠物。
数据流:强制 tmux 不支持 → 输入 /pets disable → 事件队列出现 PetDisabled,且没有额外事件或核心操作。
调用关系:它调用 force_tmux_pet_image_unsupported,确保禁用命令不被图片能力检查拦住。
调用图:调用 1 个内部函数(force_tmux_pet_image_unsupported);外部调用 3 个(from, new, assert_matches!)。
slash_pet_hide_disables_pets_even_on_unsupported_terminal2417–2428 ↗
async fn slash_pet_hide_disables_pets_even_on_unsupported_terminal()
作用:检查旧命令 /pet hide 在不支持终端上也能禁用宠物。
数据流:强制 tmux 不支持 → 输入 /pet hide → 收到 PetDisabled → 无额外事件。
调用关系:它和 /pets disable 测试一起覆盖禁用宠物的别名路径。
调用图:调用 1 个内部函数(force_tmux_pet_image_unsupported);外部调用 3 个(from, new, assert_matches!)。
slash_pets_on_unsupported_terminal_warns_without_picker2432–2447 ↗
async fn slash_pets_on_unsupported_terminal_warns_without_picker()
作用:检查 tmux 里执行 /pets 会显示警告,而不是打开选择器。
数据流:强制 tmux 不支持 → 分发 Pets → 底部没有活动视图 → 历史提示宠物在 tmux 中禁用并建议离开 tmux。
调用关系:它验证不支持环境下的用户提示。
调用图:调用 1 个内部函数(force_tmux_pet_image_unsupported);外部调用 1 个(assert!)。
slash_pets_with_arg_on_unsupported_terminal_warns_without_selection2451–2468 ↗
async fn slash_pets_with_arg_on_unsupported_terminal_warns_without_selection()
作用:检查不支持终端上即使指定宠物名,也不会选择宠物,只显示警告。
数据流:强制 tmux 不支持 → 输入 /pets chefito → 历史提示 tmux 不支持 → 没有 PetSelected 或核心操作。
调用关系:它补充带参数宠物命令在不支持环境下的拦截。
调用图:调用 1 个内部函数(force_tmux_pet_image_unsupported);外部调用 4 个(from, new, assert!, assert_matches!)。
slash_pets_on_unsupported_terminal_shows_terminal_warning2472–2487 ↗
async fn slash_pets_on_unsupported_terminal_shows_terminal_warning()
作用:检查普通终端不支持图片时,/pets 的提示会说明需要 Kitty graphics 或 Sixel 支持。
数据流:强制终端不支持 → 分发 Pets → 历史里出现终端不支持和所需图片能力说明。
调用关系:它调用 force_terminal_pet_image_unsupported,覆盖非 tmux 的不支持原因。
调用图:调用 1 个内部函数(force_terminal_pet_image_unsupported);外部调用 1 个(assert!)。
slash_pets_on_old_iterm2_shows_upgrade_warning2491–2506 ↗
async fn slash_pets_on_old_iterm2_shows_upgrade_warning()
作用:检查 iTerm2 太旧时,/pets 会提示升级到新版。
数据流:强制旧 iTerm2 不支持 → 分发 Pets → 历史包含需要 iTerm2 3.6 或更新版本的提示。
调用关系:它调用 force_old_iterm2_pet_image_unsupported,覆盖版本过旧的专门文案。
调用图:调用 1 个内部函数(force_old_iterm2_pet_image_unsupported);外部调用 1 个(assert!)。
slash_fork_requests_current_fork2509–2515 ↗
async fn slash_fork_requests_current_fork()
作用:检查 /fork 会请求从当前会话分叉出一个新会话。
数据流:分发 Fork → 事件队列出现 ForkCurrentSession。
调用关系:它验证会话分叉命令只发应用事件。
调用图:外部调用 1 个(assert_matches!)。
slash_app_requests_desktop_handoff2518–2531 ↗
async fn slash_app_requests_desktop_handoff()
作用:检查 /app 在已有线程 id 时,会请求在桌面应用里打开当前线程。
数据流:设置线程 id → 分发 App → 事件队列出现 OpenDesktopThread,并携带同一个线程 id。
调用关系:它测试终端到桌面应用的交接入口。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_matches!)。
slash_app_without_thread_id_shows_starting_error2534–2545 ↗
async fn slash_app_without_thread_id_shows_starting_error()
作用:检查线程还没开始时执行 /app,会显示当前不能打开桌面的错误提示。
数据流:无线程 id → 分发 App → 历史插入一条启动阶段错误提示快照。
调用关系:它和有线程 id 的 /app 测试对照,验证缺少上下文时的提示。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, assert_eq!)。
slash_rollout_displays_current_path2548–2562 ↗
async fn slash_rollout_displays_current_path()
作用:检查 /rollout 会显示当前 rollout 日志文件路径。rollout 可以理解成一次会话回放或记录文件。
数据流:设置 current_rollout_path → 分发 Rollout → 历史里出现该路径。
调用关系:它验证调试信息命令能把当前记录路径展示给用户。
调用图:外部调用 3 个(from, assert!, assert_eq!)。
slash_rollout_handles_missing_path2565–2581 ↗
async fn slash_rollout_handles_missing_path()
作用:检查没有 rollout 路径时,/rollout 会说明不可用,而不是崩溃。
数据流:不设置 current_rollout_path → 分发 Rollout → 历史显示 not available。
调用关系:它覆盖 rollout 信息缺失的空状态。
调用图:外部调用 2 个(assert!, assert_eq!)。
fast_slash_command_updates_and_persists_local_service_tier2584–2614 ↗
async fn fast_slash_command_updates_and_persists_local_service_tier()
作用:检查触发 fast 服务档位命令会更新当前上下文,并请求持久保存选择。
数据流:准备支持 fast 的模型目录并启用 FastMode → 分发 fast 档位 → 事件里有 OverrideTurnContext 和 PersistServiceTierSelection → 核心操作队列为空。
调用关系:它调用 fast_tier_command,验证快速模式命令的两件事:影响下一轮请求,并保存用户选择。
调用图:调用 1 个内部函数(fast_tier_command);外部调用 3 个(assert!, assert_matches!, from_fn)。
fast_keybinding_toggle_uses_same_events_as_fast_slash_command2617–2647 ↗
async fn fast_keybinding_toggle_uses_same_events_as_fast_slash_command()
作用:检查快捷键切换快速模式时,发出的事件和 /fast 命令一致。
数据流:准备模型目录并启用 FastMode → 调用 toggle_fast_mode_from_ui → 事件里同样出现服务档位覆盖和持久化。
调用关系:它和 fast_slash_command_updates_and_persists_local_service_tier 对照,保证两种入口共用行为。
调用图:外部调用 3 个(assert!, assert_matches!, from_fn)。
fast_keybinding_toggle_requires_feature_and_idle_surface2650–2662 ↗
async fn fast_keybinding_toggle_requires_feature_and_idle_surface()
作用:检查快速模式快捷键只有在功能开启且界面空闲时才可用。
数据流:先关闭 FastMode → can_toggle 返回 false → 开启后返回 true → 设置任务运行中后又返回 false。
调用关系:它验证快捷键可用性判断,防止忙碌时切换影响正在运行的任务。
调用图:外部调用 1 个(assert!)。
user_turn_carries_service_tier_after_fast_toggle2665–2687 ↗
async fn user_turn_carries_service_tier_after_fast_toggle()
作用:检查打开 fast 后,下一次用户消息会带上 fast 服务档位提交。
数据流:设置线程、认证、模型目录和 FastMode → 触发 fast 命令 → 输入 hello 并提交 → 核心收到 UserTurn,service_tier 是 fast。
调用关系:它验证服务档位不是只改界面,还会真正进入发送给后端的用户轮次。
调用图:调用 2 个内部函数(new, fast_tier_command);外部调用 4 个(from, new, panic!, from_fn)。
model_switch_recomputes_catalog_default_service_tier2690–2728 ↗
queued_fast_slash_applies_before_next_queued_message2731–2770 ↗
async fn queued_fast_slash_applies_before_next_queued_message()
作用:检查忙碌时排队的 /fast,会在下一条排队消息发送前先生效。
数据流:活跃轮次中排队 /fast 和普通消息 → 完成轮次 → 事件先更新 fast 服务档位 → 后续 UserTurn 带 fast 档位和消息文本。
调用关系:它验证队列里命令和消息的顺序很重要,/fast 必须先改变上下文再发消息。
调用图:调用 3 个内部函数(new, complete_turn_with_message, queue_composer_text_with_tab);外部调用 4 个(assert!, assert_eq!, panic!, from_fn)。
user_turn_sends_standard_override_after_fast_is_turned_off2773–2816 ↗
async fn user_turn_sends_standard_override_after_fast_is_turned_off()
作用:检查 fast 打开后再关闭,下一次用户消息会带回标准服务档位覆盖。
数据流:触发 fast 打开 → 再触发同一命令关闭 → 事件里持久化默认档位 → 提交 hello → UserTurn 的 service_tier 是默认值。
调用关系:它调用 fast_tier_command,覆盖快速模式从开到关的完整切换。
调用图:调用 2 个内部函数(new, fast_tier_command);外部调用 5 个(from, new, assert!, panic!, from_fn)。
raw_slash_command_toggles_and_accepts_on_off_args2819–2848 ↗
raw_slash_command_reports_usage_for_invalid_arg2851–2867 ↗
compact_queues_user_messages_snapshot2870–2900 ↗
async fn compact_queues_user_messages_snapshot()
作用:检查 compact 轮次不能被普通消息 steer 时,界面会把用户消息排队并正确显示。
数据流:模拟活跃 compact 场景 → 提交用户消息 → 模拟错误 ActiveTurnNotSteerable → 用虚拟终端渲染 → 快照确认队列提示和布局。
调用关系:它覆盖 /compact 相关的可视化队列提示,确保用户知道消息已排队而不是丢了。
调用图:调用 4 个内部函数(new, from, with_options, new);外部调用 2 个(new, assert_chatwidget_snapshot!)。
tui/src/chatwidget/tests/status_command_tests.rs源码 ↗
这个文件不是正式功能代码,而是一组自动化测试。它像一份验收清单,检查用户在终端聊天界面输入 /status 后会发生什么。重点有几件事:第一,状态信息要马上出现在历史记录里,不能先显示“正在刷新额度”这种一闪而过的脏内容;第二,如果当前是 ChatGPT 登录,会额外发出“刷新额度”的请求;第三,刷新回来的额度要被缓存,下一次 /status 能用上新数据;第四,非 ChatGPT 会话不该乱请求额度刷新。它还检查模型默认推理设置、指令来源文件,以及两个 /status 同时刷新时不会互相串号。可以把它理解成给 /status 命令装了几个“防摔测试”,保证界面展示稳定、信息准确。
status_command_renders_immediately_and_refreshes_rate_limits_for_chatgpt_auth5–28 ↗
async fn status_command_renders_immediately_and_refreshes_rate_limits_for_chatgpt_auth()
作用:这个测试确认:在 ChatGPT 登录状态下执行 /status,会先立刻显示状态,再发起额度刷新请求。它还特意检查历史记录里不要出现“正在刷新额度”这种临时文字。
数据流:测试先创建一个聊天窗口和事件接收器,再把它设置成 ChatGPT 登录。然后它发出 /status 命令,读取第一个事件,要求它必须是插入一条状态历史记录;接着读取第二个事件,要求它必须是刷新额度请求。最后它检查请求编号是 0,并确认显示出来的文字不包含临时刷新提示。
调用关系:它模拟用户第一次输入 /status 的场景。测试过程主要依靠聊天窗口发出的 AppEvent 事件来判断结果,并用 assert、panic 和 assert_eq 这类测试断言来确认行为符合预期。
调用图:外部调用 3 个(assert!, panic!, assert_eq!)。
status_command_refresh_updates_cached_limits_for_future_status_outputs31–63 ↗
async fn status_command_refresh_updates_cached_limits_for_future_status_outputs()
作用:这个测试确认:额度刷新完成后,新额度会被记住,之后再执行 /status 时会显示刷新后的额度。它防止界面一直显示旧的额度信息。
数据流:测试先建立 ChatGPT 登录的聊天窗口,执行一次 /status,并取出对应的刷新请求编号。然后它模拟系统收到了一个额度快照,比如已用 92%,再通知聊天窗口这个刷新请求完成。清掉中间产生的历史事件后,它再次执行 /status,并检查新显示的文字里有“8% left”,说明缓存已经更新。
调用关系:它接在额度刷新流程之后,验证刷新结果会影响下一次状态输出。它调用聊天窗口的额度快照接收入口和刷新完成入口,再通过事件接收器观察下一次 /status 的展示内容。
status_command_renders_immediately_without_rate_limit_refresh66–77 ↗
async fn status_command_renders_immediately_without_rate_limit_refresh()
作用:这个测试确认:不是 ChatGPT 登录的会话执行 /status 时,只需要显示状态,不应该去刷新 ChatGPT 额度。它避免无关会话发出多余请求。
数据流:测试创建一个普通聊天窗口,不设置 ChatGPT 登录。它执行 /status 后,先确认马上收到一条插入历史记录的事件。然后它继续把剩余事件读完,检查里面没有任何刷新额度请求。
调用关系:它覆盖的是“普通会话”的分支,和 ChatGPT 登录分支形成对照。它用 assert_matches 检查第一个事件类型,再用 assert 确认后续没有多余的 RefreshRateLimits 事件。
调用图:外部调用 2 个(assert!, assert_matches!)。
status_command_uses_catalog_default_reasoning_when_config_empty80–96 ↗
async fn status_command_uses_catalog_default_reasoning_when_config_empty()
作用:这个测试确认:如果配置里没有写模型的推理强度,/status 会使用模型目录里的默认值来显示。它防止状态页把缺省配置显示成空白或错误信息。
数据流:测试创建一个指定模型为 gpt-5.4 的聊天窗口,然后故意把配置里的 model_reasoning_effort 清空。执行 /status 后,它读取显示出来的状态文字,并检查里面是否写着这个模型的默认设置:reasoning medium 和 summaries auto。
调用关系:它验证 /status 输出会结合当前模型信息和模型目录默认值。测试只观察最终渲染出来的历史记录文字,用 assert 和 panic 保证结果符合预期。
status_command_renders_instruction_sources_from_thread_session99–119 ↗
async fn status_command_renders_instruction_sources_from_thread_session()
作用:这个测试确认:/status 会显示当前会话真正使用的指令来源文件,比如 AGENTS.md。它防止界面还显示旧的“没有指令文件”信息。
数据流:测试先创建聊天窗口,再把 instruction_source_paths 设置成当前目录下的 AGENTS.md。执行 /status 后,它读取状态输出文字,检查里面出现了 Agents.md,同时确认没有出现“Agents.md <none>”这种互相矛盾的旧内容。
调用关系:它关注的是聊天会话从服务端或线程状态拿到的指令来源。测试通过直接设置聊天窗口里的指令路径,再观察 /status 的渲染结果,确保显示内容来自最新会话状态。
status_command_overlapping_refreshes_update_matching_cells_only122–164 ↗
async fn status_command_overlapping_refreshes_update_matching_cells_only()
作用:这个测试确认:连续执行两次 /status、产生两个额度刷新请求时,每个刷新结果只更新自己对应的状态输出,不会互相串。它防止异步刷新先后顺序不同导致界面改错内容。
数据流:测试先设置 ChatGPT 登录,执行第一次 /status,拿到第一个刷新请求编号。接着马上执行第二次 /status,拿到第二个请求编号,并确认两个编号不同、第二次显示里也没有临时刷新提示。然后它先结束第一个刷新,检查仍然还有一个状态输出在等待刷新;再提供额度快照并结束第二个刷新,最后确认等待列表清空。
调用关系:它模拟两个刷新请求重叠的复杂场景。测试用请求编号把每次 /status 和对应刷新绑在一起,通过检查 refreshing_status_outputs 的数量变化,确认聊天窗口内部没有把两个刷新任务混在一起。
调用图:外部调用 4 个(assert!, assert_ne!, panic!, assert_eq!)。
tui/src/chatwidget/tests/usage.rs源码 ↗
这里不是正式功能代码,而是一组自动化测试。它像反复演练柜台办业务:先造一个假的聊天窗口,模拟用户输入 /usage、按上下键和回车,再检查界面弹窗长什么样、系统发出了什么事件。重点覆盖“有重置次数、没有次数、还在加载、领取成功、领取失败、重复领取、切换账号、弹窗被别的浮层盖住”等情况。文件里还用快照测试,也就是把界面渲染结果保存成标准答案,之后改代码时如果界面变了,测试会提醒开发者确认是不是故意改的。几个小辅助函数负责包装后端返回、记录弹窗文字、按 Esc 关闭弹窗、制造一个遮挡用的测试浮层。
usage_command_can_recheck_reset_availability_after_cached_zero_snapshot30–48 ↗
async fn usage_command_can_recheck_reset_availability_after_cached_zero_snapshot()
作用:测试之前缓存显示“没有重置次数”时,Usage 菜单仍然允许用户重新检查。这样用户不会被旧结果卡住。
数据流:测试先让聊天窗口记住可用次数为 0;打开 Usage 菜单后,比对“不显示可用重置次数”的弹窗快照;然后按向下和回车选择重新检查入口,结果应该发出打开重置额度流程的事件。
调用关系:由测试运行器执行。它通过键盘事件走真实菜单选择路径,最后用事件接收端确认 ChatWidget 把用户带到重新检查流程。
调用图:外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, assert_matches!)。
usage_command_can_check_reset_availability_before_startup_refresh_finishes_snapshot51–65 ↗
async fn usage_command_can_check_reset_availability_before_startup_refresh_finishes_snapshot()
作用:测试启动时的额度检查还没返回结果时,用户仍能从 Usage 菜单主动检查。它防止界面因为后台请求未完成而变成死等。
数据流:先启动一次额度检查但不返回结果;打开 Usage 菜单并保存快照;再模拟用户选择检查重置额度,最终应收到 OpenRateLimitResetCredits 事件。
调用关系:由测试运行器调用。它覆盖后台刷新未完成的中间状态,确认菜单仍会把工作交给打开重置额度流程的事件。
调用图:外部调用 3 个(new, assert_chatwidget_snapshot!, assert_matches!)。
usage_command_disables_reset_for_workspace_accounts68–78 ↗
async fn usage_command_disables_reset_for_workspace_accounts()
作用:测试工作区或企业类账号不会显示或进入个人账号的重置额度流程。这样可以避免给不适用的账号提供错误操作。
数据流:测试把聊天窗口设成已登录且套餐类型为 Business;打开 Usage 菜单后按向下和回车;最后检查收到的是打开普通用量活动页,而不是打开重置额度页。
调用关系:由测试运行器执行。它验证 ChatWidget 根据账号套餐类型改变菜单行为,不把业务交给重置额度流程。
调用图:外部调用 2 个(new, assert_matches!)。
rate_limit_reset_popup_states_snapshot98–173 ↗
async fn rate_limit_reset_popup_states_snapshot()
作用:测试重置额度弹窗在各种状态下显示是否正确,包括加载、可用、没有额度、加载失败、领取中、领取失败和领取成功。它像给整个弹窗状态机拍一组标准照片。
数据流:测试不断让聊天窗口进入不同状态,每次用 record_popup 记录当前弹窗;中间用 dismiss_popup 关闭旧弹窗;对成功、失败和不同后端结果分别模拟返回;最后把所有画面拼起来做快照比对。
调用关系:由测试运行器执行。它是这个文件里覆盖面最广的界面测试,调用 record_popup 和 dismiss_popup 两个辅助函数,把 ChatWidget 的多个重置相关完成函数串起来验证。
调用图:调用 2 个内部函数(dismiss_popup, record_popup);外部调用 3 个(new, assert!, assert_chatwidget_snapshot!)。
rate_limit_reset_confirmation_selects_cancel_by_default176–188 ↗
rate_limit_reset_confirmation_can_use_reset191–207 ↗
async fn rate_limit_reset_confirmation_can_use_reset()
作用:测试用户明确选择“使用重置”后,会真正发出领取请求。它还检查请求带了一个合法的唯一编号,避免重复操作混乱。
数据流:先模拟有 1 次可用重置;按向上切到使用选项,再按回车;事件通道应收到 ConsumeRateLimitResetCredit,并且里面的 idempotency_key 能解析成 UUID。
调用关系:由测试运行器执行。它从确认弹窗走到应用事件,确认 ChatWidget 会把领取动作交给外层应用去执行。
调用图:外部调用 3 个(new, assert!, assert_matches!)。
rate_limit_reset_retry_reuses_idempotency_key210–226 ↗
async fn rate_limit_reset_retry_reuses_idempotency_key()
作用:测试领取请求失败后重试时,会复用同一个幂等键。幂等键就是“同一笔操作的编号”,防止网络抖动导致重复扣次数。
数据流:先让聊天窗口进入领取中状态;再模拟后端返回失败,并带入 stable-redeem-id;用户按回车重试后,发出的新领取事件仍应使用这个相同编号。
调用关系:由测试运行器执行。它验证失败弹窗里的重试按钮不会新造一笔业务,而是继续同一笔领取请求。
调用图:外部调用 3 个(new, assert!, assert_matches!)。
no_credit_outcome_allows_reset_availability_recheck229–251 ↗
async fn no_credit_outcome_allows_reset_availability_recheck()
作用:测试如果领取结果说“没有额度”,之后 Usage 菜单还能重新检查可用性。这样用户可以刷新状态,而不是永远停在旧判断上。
数据流:先模拟启动时看到有 1 次额度;再模拟领取返回 NoCredit;关闭弹窗后打开 Usage 菜单,选择重置相关入口;最后应收到 OpenRateLimitResetCredits 事件。
调用关系:由测试运行器执行。它使用 dismiss_popup 关闭错误结果,再通过菜单路径确认 ChatWidget 允许重新进入检查流程。
调用图:调用 1 个内部函数(dismiss_popup);外部调用 3 个(new, assert!, assert_matches!)。
rate_limit_reset_redemption_cannot_be_dismissed_while_in_flight254–277 ↗
async fn rate_limit_reset_redemption_cannot_be_dismissed_while_in_flight()
作用:测试正在领取重置额度时,普通 Esc 关闭不了弹窗。因为这时请求还在路上,随便关掉会让用户不知道操作是否完成。
数据流:先显示“正在使用重置”的弹窗;调用 dismiss_popup 尝试关闭,画面仍应包含 Using a reset;领取成功后进入刷新状态,再尝试关闭也仍显示 Refreshing;等刷新完成后,Esc 才能真正关闭。
调用关系:由测试运行器执行。它调用 dismiss_popup 模拟用户按 Esc,验证 ChatWidget 在请求进行中会保护关键弹窗。
调用图:调用 1 个内部函数(dismiss_popup);外部调用 1 个(assert!)。
rate_limit_reset_redemption_allows_ctrl_c_to_quit_while_in_flight280–288 ↗
async fn rate_limit_reset_redemption_allows_ctrl_c_to_quit_while_in_flight()
作用:测试虽然领取中的弹窗不能普通关闭,但 Ctrl+C 仍然能退出程序。也就是说,防误关不等于强行拦住用户退出。
数据流:先进入领取中状态;模拟按下 Ctrl+C;事件通道应收到退出事件,同时弹窗画面仍保留“Using a reset...”的状态。
调用关系:由测试运行器执行。它通过 ChatWidget 的键盘处理确认全局退出快捷键优先于弹窗锁定。
调用图:外部调用 4 个(Char, new, assert!, assert_matches!)。
already_redeemed_is_an_idempotent_success291–309 ↗
async fn already_redeemed_is_an_idempotent_success()
作用:测试后端说“这笔已经领取过”时,界面把它当成成功处理。因为同一个幂等键重试时,已经成功过就不该显示失败。
数据流:先进入领取中状态;用 finish_reset_consume_outcome 模拟 AlreadyRedeemed;随后刷新剩余次数为 0;最后弹窗文字应说明用量已重置且剩余 0 次。
调用关系:由测试运行器执行。它依赖辅助函数包装后端结果,验证 ChatWidget 对重复成功的处理和真正成功一致。
调用图:外部调用 1 个(assert!)。
failed_post_consume_refresh_does_not_keep_stale_reset_count312–338 ↗
async fn failed_post_consume_refresh_does_not_keep_stale_reset_count()
作用:测试领取成功后,如果刷新剩余额度失败,界面不会继续显示旧的额度数字。这样不会误导用户以为还有之前那 2 次。
数据流:先记录启动时有 2 次额度;模拟成功使用一次重置;再让后续刷新失败;关闭弹窗后打开 Usage 菜单,画面应提示重新检查,而不应写着还有 2 次可用。
调用关系:由测试运行器执行。它调用 dismiss_popup 后重新走 Usage 菜单,确认 ChatWidget 在刷新失败时清掉不可靠的缓存。
调用图:调用 1 个内部函数(dismiss_popup);外部调用 1 个(assert!)。
account_change_invalidates_pending_reset_requests341–356 ↗
async fn account_change_invalidates_pending_reset_requests()
作用:测试账号状态变化会让正在等待的重置额度请求失效。这样旧账号的查询结果不会跑到新账号界面里。
数据流:先打开重置额度加载弹窗并拿到请求编号;随后把账号状态更新成未登录;再把旧请求的成功结果送回来,函数应返回 false,弹窗也应消失。
调用关系:由测试运行器执行。它直接调用账号更新和请求完成接口,验证 ChatWidget 用请求编号和账号状态挡住过期结果。
调用图:外部调用 1 个(assert!)。
clearing_pending_reset_hint_preserves_in_flight_redemption359–378 ↗
async fn clearing_pending_reset_hint_preserves_in_flight_redemption()
作用:测试清掉启动提示时,不会误伤正在进行的领取操作。启动提示和实际领取是两件事,不能互相打断。
数据流:先开启一个领取中的请求;同时启动并完成一次启动提示检查;调用 clear_pending_rate_limit_reset_hint 清除提示;最后领取请求仍能正常以 Reset 结果完成。
调用关系:由测试运行器执行。它把提示刷新和领取流程交错在一起,确认 ChatWidget 分开保存两类状态。
调用图:外部调用 1 个(assert!)。
rate_limit_reset_load_result_updates_popup_beneath_overlay381–400 ↗
async fn rate_limit_reset_load_result_updates_popup_beneath_overlay()
作用:测试当重置弹窗被另一个浮层盖住时,后台加载结果仍会更新被盖住的弹窗。用户关掉上层浮层后,能看到最新结果。
数据流:先显示重置额度加载弹窗;再用 show_usage_test_overlay 放一个测试浮层盖在上面;加载完成后确认当前可见的还是测试浮层;按 Esc 关闭浮层后,下面的弹窗应显示有 2 次重置可用。
调用关系:由测试运行器执行。它调用 show_usage_test_overlay 制造遮挡场景,验证 ChatWidget 的底部面板可以更新非当前最上层视图。
调用图:调用 1 个内部函数(show_usage_test_overlay);外部调用 3 个(new, assert!, assert_eq!)。
rate_limit_reset_success_updates_popup_beneath_overlay403–428 ↗
async fn rate_limit_reset_success_updates_popup_beneath_overlay()
作用:测试领取成功和刷新剩余次数时,即使弹窗被遮住,底下的弹窗也会更新成成功状态。这样用户回到弹窗时不会看到旧进度。
数据流:先进入领取中弹窗;盖上测试浮层;模拟领取成功并刷新剩余次数为 1;确认当前仍是测试浮层;关闭浮层后,底下弹窗应显示“已重置,还剩 1 次”。
调用关系:由测试运行器执行。它用 show_usage_test_overlay 覆盖界面,再通过领取完成和刷新完成接口验证隐藏弹窗也能接收状态更新。
调用图:调用 1 个内部函数(show_usage_test_overlay);外部调用 3 个(new, assert!, assert_eq!)。
account_change_dismisses_reset_popup_beneath_overlay431–448 ↗
async fn account_change_dismisses_reset_popup_beneath_overlay()
作用:测试账号变化时,即使重置弹窗被别的浮层盖住,底下那个旧账号弹窗也会被清掉。这样不会在关闭浮层后看到过期账号的信息。
数据流:先显示重置额度加载弹窗;再盖上测试浮层;随后把账号改成未登录;当前可见的仍是测试浮层;按 Esc 关掉浮层后,底部不应再有任何弹窗。
调用关系:由测试运行器执行。它调用 show_usage_test_overlay 制造覆盖,再验证账号更新会清理下面的重置视图。
调用图:调用 1 个内部函数(show_usage_test_overlay);外部调用 3 个(new, assert!, assert_eq!)。
startup_check_shows_available_reset_hint_snapshot451–467 ↗
async fn startup_check_shows_available_reset_hint_snapshot()
作用:测试程序启动时如果发现有可用重置次数,会生成一条提示。这个提示让用户不用主动打开菜单也能知道自己可以重置限流。
数据流:先设为已登录;启动重置额度检查;返回有 2 次可用;取出 pending_rate_limit_reset_hint 的显示文字并做快照比对。
调用关系:由测试运行器执行。它验证启动检查完成后,ChatWidget 会把结果变成一段等待展示的提示文本。
调用图:外部调用 2 个(assert!, assert_chatwidget_snapshot!)。
startup_reset_hint_waits_for_active_output_snapshot470–498 ↗
async fn startup_reset_hint_waits_for_active_output_snapshot()
作用:测试如果界面正在显示一段活跃输出,启动时的重置提示会先等一等,不会插队打断当前内容。等当前输出结束后,提示才进入历史记录。
数据流:先启动额度检查;再手动放入一个活跃输出单元;返回有 2 次重置后,检查用量历史插入被阻塞,事件通道没有立即插入历史;随后 flush_active_cell 结束当前输出,才收到插入历史和提交待展示用量输出的事件。
调用关系:由测试运行器执行。它创建 PlainHistoryCell 模拟正在输出的内容,验证 ChatWidget 在活跃输出、待显示提示和历史插入事件之间的排队关系。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, assert!, assert_chatwidget_snapshot!, assert_matches!, vec!)。
opening_rate_limit_reset_flow_invalidates_in_flight_startup_hint501–513 ↗
async fn opening_rate_limit_reset_flow_invalidates_in_flight_startup_hint()
作用:测试用户手动打开重置流程后,之前还没完成的启动提示检查会失效。这样不会在用户已经进入流程后又弹出一条重复提示。
数据流:先开始启动检查并拿到请求编号;再显示重置额度加载弹窗;随后把启动检查结果送回,函数应返回 false,待显示提示也应为空。
调用关系:由测试运行器执行。它验证 ChatWidget 在进入正式重置流程时,会取消旧的启动提示请求。
调用图:外部调用 1 个(assert!)。
starting_rate_limit_reset_redemption_clears_deferred_startup_hint516–529 ↗
async fn starting_rate_limit_reset_redemption_clears_deferred_startup_hint()
作用:测试一旦开始真正领取重置额度,之前排队等待显示的启动提示会被清掉。用户已经在操作了,就不需要再提示“你有额度”。
数据流:先完成启动检查并生成一个待显示提示;确认提示存在;随后显示领取中的弹窗;结果待显示提示应变成空。
调用关系:由测试运行器执行。它验证 ChatWidget 在领取流程开始时会清理提醒状态,避免重复信息。
调用图:外部调用 1 个(assert!)。
startup_check_omits_reset_hint_when_none_are_available532–542 ↗
async fn startup_check_omits_reset_hint_when_none_are_available()
作用:测试启动检查发现没有可用重置次数时,不会显示提示。没有可操作内容就不打扰用户。
数据流:先设为已登录并启动检查;返回 available_count 为 0;结果 pending_rate_limit_reset_hint 应为空。
调用关系:由测试运行器执行。它覆盖启动提示的“无额度”分支,确认 ChatWidget 不生成无意义提示。
调用图:外部调用 1 个(assert!)。
startup_check_omits_reset_hint_for_workspace_accounts545–557 ↗
async fn startup_check_omits_reset_hint_for_workspace_accounts()
作用:测试工作区账号即使后端返回有重置次数,也不会显示个人重置提示,也不会缓存这个额度。这样不同账号类型的规则不会混在一起。
数据流:先设为已登录并把套餐改为 Business;启动检查后返回有 2 次可用;结果提示为空,available_rate_limit_reset_credits 也保持 None。
调用关系:由测试运行器执行。它验证 ChatWidget 根据 plan_type 过滤启动提示和额度缓存。
调用图:外部调用 2 个(assert!, assert_eq!)。
consume_response559–563 ↗
fn consume_response(
outcome: ConsumeAccountRateLimitResetCreditOutcome,
) -> ConsumeAccountRateLimitResetCreditResponse
作用:这是一个小包装函数,把“领取重置额度的结果”包成后端响应对象。测试里用它少写重复代码。
数据流:输入一个 ConsumeAccountRateLimitResetCreditOutcome,也就是后端说领取结果是什么;函数把它放进 ConsumeAccountRateLimitResetCreditResponse;输出这个响应对象,不改动外部状态。
调用关系:它不直接由测试运行器调用,而是被 finish_reset_consume_outcome 使用,作为构造模拟后端返回的最小零件。
调用图:被 1 处调用(finish_reset_consume_outcome)。
finish_reset_consume_outcome565–576 ↗
fn finish_reset_consume_outcome(
chat: &mut ChatWidget,
request_id: u64,
idempotency_key: &str,
outcome: ConsumeAccountRateLimitResetCreditOutcome,
) -> bool
作用:这是测试用的便捷函数,用来模拟“领取重置额度请求完成,并返回某种业务结果”。它让各个测试不用每次手动拼响应结构。
数据流:输入聊天窗口、请求编号、幂等键和领取结果;它先调用 consume_response 包装结果,再调用 chat.finish_rate_limit_reset_consume;输出布尔值,表示这个结果是否被当前聊天窗口接受并处理。
调用关系:多个测试用它来制造 Reset、NoCredit、AlreadyRedeemed 等结果。它把测试场景交给 ChatWidget 的正式完成函数处理。
调用图:调用 1 个内部函数(consume_response);外部调用 1 个(finish_rate_limit_reset_consume)。
record_popup578–580 ↗
fn record_popup(chat: &ChatWidget, states: &mut Vec<String>)
作用:这个辅助函数把当前底部弹窗的文字记录下来。它用于一次测试里收集多种弹窗状态,最后统一做快照比较。
数据流:输入聊天窗口和一个字符串列表;它渲染当前底部弹窗,宽度固定为 80;然后把渲染出的文字追加到列表里,没有返回值。
调用关系:它被 rate_limit_reset_popup_states_snapshot 调用,负责把每一步弹窗画面收集成一串可比较的结果。
调用图:被 1 处调用(rate_limit_reset_popup_states_snapshot)。
dismiss_popup582–584 ↗
fn dismiss_popup(chat: &mut ChatWidget)
作用:这个辅助函数模拟用户按 Esc 关闭弹窗。测试里用它表达“用户想把当前弹窗关掉”。
数据流:输入一个可修改的聊天窗口;它构造一个 Esc 键盘事件并交给 chat.handle_key_event;输出没有直接返回值,但聊天窗口的弹窗状态可能被改变。
调用关系:它被多个测试调用,尤其用于检查哪些弹窗能关、哪些请求进行中的弹窗不能关。它把关闭动作交给 ChatWidget 的真实键盘处理逻辑。
调用图:被 4 处调用(failed_post_consume_refresh_does_not_keep_stale_reset_count, no_credit_outcome_allows_reset_availability_recheck, rate_limit_reset_popup_states_snapshot, rate_limit_reset_redemption_cannot_be_dismissed_while_in_flight);外部调用 2 个(new, handle_key_event)。
show_usage_test_overlay586–597 ↗
fn show_usage_test_overlay(chat: &mut ChatWidget)
作用:这个辅助函数在底部面板上放一个测试用浮层,用来模拟“真正的重置弹窗被别的界面盖住”。它帮助测试隐藏弹窗是否仍会更新或被清理。
数据流:输入一个可修改的聊天窗口;它构造一个带固定 view_id、标题和一个 Close 选项的选择视图;然后让 bottom_pane 显示这个视图,改变当前可见的底部界面。
调用关系:它被三个覆盖浮层相关测试调用。那些测试先用它盖住重置弹窗,再检查加载结果、领取成功或账号变化是否正确影响下面的弹窗。
调用图:被 3 处调用(account_change_dismisses_reset_popup_beneath_overlay, rate_limit_reset_load_result_updates_popup_beneath_overlay, rate_limit_reset_success_updates_popup_beneath_overlay);外部调用 2 个(default, vec!)。
聊天组件渲染和状态界面
本组聚焦如何通过记录单元格、状态行、布局、终端标题和配置错误渲染来呈现聊天组件状态。
tui/src/chatwidget/tests/config_errors_tests.rs源码 ↗
这个测试模拟了一个用户在文字终端界面里看到配置保存失败的场景。错误内容很长,而且里面套着多层原因,比如“保存默认模型失败”“批量写配置失败”“某个配置值不被支持”。这种信息如果不处理好,在窄窗口里很容易挤成一团、断行奇怪,用户就看不懂。测试先创建一个聊天窗口,再手动塞入这条错误消息。然后它造了一个假的 VT100 终端后端。VT100 可以理解成一种老式终端的显示规则,这里用来在测试里模拟真实终端屏幕。接着测试把聊天历史写进这个假终端,最后用快照断言对比屏幕内容。快照断言就是把“应该长什么样”的结果保存下来,以后每次测试都拿新结果去比,防止界面显示被无意改坏。
chained_config_error_wraps_in_history_snapshot4–25 ↗
async fn chained_config_error_wraps_in_history_snapshot()
作用:这个测试函数确认一条很长的、层层嵌套的配置错误,在聊天历史区域里能按预期显示。有人改动错误展示、历史插入或终端渲染时,它可以及时发现显示结果变了。
数据流:输入是测试里写死的一条长错误消息,以及一个宽 56、高 8 的模拟终端窗口。函数先创建聊天组件,把错误消息加入聊天历史;再从接收端取出要插入历史区的行;然后把这些行写进模拟终端;最后读取终端屏幕内容,做路径归一化后和已保存的快照比较。出来的结果不是业务数据,而是测试通过或失败:通过表示屏幕显示和预期一致,失败表示界面输出发生了变化。
调用关系:它是 Tokio 异步测试,测试框架会自动运行它。它先通过测试辅助函数创建聊天组件;创建终端时会调用 VT100Backend::new 和 Terminal::with_options;拿到历史行后把活交给 insert_history_lines 写进终端;最后交给 assert_chatwidget_snapshot! 这个快照断言宏判断画面是否符合预期。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 2 个(assert_chatwidget_snapshot!, new)。
tui/src/chatwidget/tests/status_and_layout.rs源码 ↗
这个文件像一份“聊天界面体检清单”。它不实现真正功能,而是模拟用户、服务器和终端窗口的各种情况,然后检查 ChatWidget 这个聊天组件有没有把信息显示对。测试覆盖很多容易出错的细节:上下文 token 用量、速率限制和余额提示、模型和推理模式状态栏、终端标题、小窗口布局、流式回答收尾、执行命令时的状态块、审批弹窗、宠物图片占位、工作区限额弹窗、目标状态、运行钩子提示等。很多测试还会用快照测试,也就是把终端画面保存成标准答案,以后改代码时逐字对比。这样做能保证用户看到的界面稳定、清楚,不会因为内部逻辑调整而出现闪烁、重复提示或错误提示。
enable_test_ambient_pet10–15 ↗
fn enable_test_ambient_pet(chat: &mut ChatWidget)
作用:给测试里的聊天窗口强行打开“环境宠物”图片支持。这样后面的测试不用依赖真实终端能力,也能稳定检查宠物布局。
数据流:输入一个可修改的 ChatWidget → 把宠物图片协议设置成测试可用,并安装一个不会播放动画的测试宠物 → 聊天窗口之后会像真的支持宠物一样参与布局计算。
调用关系:它是多个宠物布局测试的准备步骤;这些测试先调用它,再检查绘制位置、文字宽度预留和通知遮挡等行为。
调用图:被 5 处调用(ambient_pet_draw_uses_terminal_screen_area_not_short_inline_viewport, ambient_pet_hides_notification_text_overlay, ambient_pet_reduces_stream_width_and_composer_text_width, ambient_pet_reserves_history_wrap_width, ambient_pet_screen_bottom_anchor_uses_terminal_bottom);外部调用 3 个(install_test_ambient_pet_for_tests, set_pet_image_support_for_tests, Supported)。
token_count_none_resets_context_indicator19–33 ↗
async fn token_count_none_resets_context_indicator()
作用:确认收到“没有 token 用量”的更新时,底部上下文用量提示会被清空。没有这个行为,界面可能一直显示过期的用量。
数据流:先给聊天窗口塞入一次有用量的数据 → 确认显示 30% → 再传入 None → 期望底部窗格不再显示上下文百分比。
调用关系:它直接验证 token 更新处理函数对底部状态栏的影响,是上下文用量显示测试的一部分。
调用图:外部调用 1 个(assert_eq!)。
app_server_cyber_policy_error_renders_dedicated_notice36–51 ↗
async fn app_server_cyber_policy_error_renders_dedicated_notice()
作用:检查服务器报告网络安全策略风险时,界面显示专门的安全提示,而不是普通错误文本。
数据流:输入一个聊天窗口、一个备用错误消息和 CyberPolicy 错误类型 → 错误处理把专门说明插入历史区 → 测试读取历史内容,确认包含安全说明且不包含备用消息。
调用关系:它验证错误处理流程中针对特殊错误类型的分支,确保用户看到的是可理解的风险说明。
调用图:外部调用 2 个(assert!, assert_eq!)。
app_server_model_verification_renders_warning54–69 ↗
async fn app_server_model_verification_renders_warning()
作用:确认模型验证发现网络安全相关标记时,会显示清楚的警告和帮助链接。
数据流:输入模型验证结果列表 → 聊天窗口插入一条警告历史 → 输出内容应包含风险、多重标记、安全检查和网址。
调用关系:它覆盖服务器模型验证通知到用户提示的路径,防止重要安全提示丢失。
调用图:外部调用 3 个(assert!, assert_eq!, vec!)。
context_indicator_shows_used_tokens_when_window_unknown72–98 ↗
async fn context_indicator_shows_used_tokens_when_window_unknown()
作用:检查模型上下文窗口大小未知时,界面退而显示已经用了多少 token。这样用户至少知道消耗量。
数据流:把模型窗口设为未知,只提供总 token 数 → token 处理不计算百分比 → 底部窗格显示已用 token 数。
调用关系:它补充了上下文用量显示逻辑,验证没有窗口上限时的备用显示方式。
调用图:外部调用 2 个(assert_eq!, default)。
token_usage_update_uses_runtime_context_window101–145 ↗
async fn token_usage_update_uses_runtime_context_window()
作用:确认状态栏和 /status 使用运行时收到的上下文窗口,而不是配置里旧的窗口值。
数据流:配置里放一个 1M 窗口,再传入运行时 950K 窗口 → 状态项和状态输出都应显示 950K → 不应出现配置里的 1M。
调用关系:它验证 token 用量事件会更新聊天界面当前认知,并影响状态栏和状态输出。
调用图:外部调用 2 个(assert!, assert_eq!)。
status_line_git_summary_items_render_values148–169 ↗
async fn status_line_git_summary_items_render_values()
作用:检查状态栏能正确显示拉取请求编号和分支变更统计。
数据流:给聊天窗口塞入 PR 编号、增加行数和删除行数 → 请求对应状态栏项 → 得到“PR #20252”和“+143 -22”。
调用关系:它验证 Git 摘要数据进入状态栏渲染的最后一步。
调用图:外部调用 1 个(assert_eq!)。
raw_output_status_line_value_only_shows_when_enabled172–186 ↗
async fn raw_output_status_line_value_only_shows_when_enabled()
作用:确认“原始输出模式”只有打开后才在状态栏出现。这样普通用户不会看到无关标记。
数据流:先读取状态栏 RawOutput 项 → 返回 None → 打开原始输出模式后再读取 → 返回“raw output”。
调用关系:它检查模式开关和状态栏显示之间的连接。
调用图:外部调用 1 个(assert_eq!)。
status_line_branch_changes_render_no_changes189–203 ↗
async fn status_line_branch_changes_render_no_changes()
作用:检查分支没有增删行时,状态栏显示“No changes”,而不是空白或“+0 -0”。
数据流:输入 0 增加、0 删除的分支统计 → 请求 BranchChanges 状态项 → 输出友好的“No changes”。
调用关系:它属于 Git 状态栏格式化测试,覆盖零变更这个边界情况。
调用图:外部调用 1 个(assert_eq!)。
stale_status_line_git_summary_update_is_ignored206–227 ↗
raw_output_mode_can_change_without_inserting_notice230–250 ↗
async fn raw_output_mode_can_change_without_inserting_notice()
作用:检查原始输出模式可以静默切换,也可以带通知切换。两种入口的用户体验不同。
数据流:静默打开 raw output → 历史区没有新消息 → 用带通知的方法关闭 → 历史区出现恢复富文本渲染的说明。
调用关系:它比较两个模式切换函数,确保只有通知版本会打扰用户。
调用图:外部调用 1 个(assert!)。
flush_answer_stream_keeps_default_reflow_for_plain_text_tail253–298 ↗
async fn flush_answer_stream_keeps_default_reflow_for_plain_text_tail()
作用:确认普通纯文本流式回答结束时,按默认方式插入历史并合并消息。
数据流:构造一个流控制器并推入普通文本 → 刷新回答流 → 事件队列里应有历史插入和一次合并,且不要求强制重排。
调用关系:它验证流式回答结束流程中,普通文本尾巴走轻量路径。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert!, assert_eq!)。
flush_answer_stream_requests_scrollback_reflow_for_live_table_tail301–355 ↗
async fn flush_answer_stream_requests_scrollback_reflow_for_live_table_tail()
作用:确认流式回答尾部是 Markdown 表格时,会等最终表格渲染后再进入历史。这样表格不会先显示成半成品。
数据流:推入表格文本造成 live tail → 刷新回答流 → 不先插入历史,而是把待渲染内容交给合并事件并要求重排。
调用关系:它和纯文本测试相对,验证表格这种复杂尾巴需要更谨慎的收尾流程。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert!, assert_eq!)。
completed_plan_table_tail_skips_provisional_history_insert358–404 ↗
configured_pet_load_is_deferred_until_after_construction408–452 ↗
async fn configured_pet_load_is_deferred_until_after_construction()
作用:确认配置里的宠物不会在 ChatWidget 构造过程中立刻启用,而是构造完后异步加载。
数据流:准备带宠物配置的初始化数据和测试宠物包 → 创建 ChatWidget → 起初宠物未启用 → 随后事件队列收到宠物加载完成事件。
调用关系:它验证启动初始化和异步资源加载的边界,避免构造函数做太重的事。
调用图:调用 3 个内部函数(new, new, test_dummy);外部调用 8 个(new, new, assert!, assert_matches!, write_test_pack, from_secs, timeout, new_with_app_event)。
prefetch_rate_limits_is_gated_on_chatgpt_auth_provider455–468 ↗
async fn prefetch_rate_limits_is_gated_on_chatgpt_auth_provider()
作用:检查预取速率限制只在 ChatGPT 账号认证且模型提供方需要 OpenAI 认证时发生。
数据流:先无认证检查 → 不预取 → 设置 ChatGPT 认证 → 可预取 → 关闭提供方认证要求或已预取后 → 不再预取。
调用关系:它验证限额信息预加载的开关条件,避免无意义请求。
调用图:外部调用 1 个(assert!)。
rate_limit_warnings_emit_thresholds471–500 ↗
async fn rate_limit_warnings_emit_thresholds()
作用:确认用量超过 75% 和 95% 时,只为每个限额发出合适的一次提醒。
数据流:连续喂入不同主限额和副限额使用百分比 → 状态对象记录已提醒过的门槛 → 输出四条预期警告。
调用关系:它测试 RateLimitWarningState 的去重和门槛逻辑,是限额提醒可靠性的核心测试。
调用图:外部调用 3 个(new, assert_eq!, default)。
test_rate_limit_warnings_monthly503–520 ↗
async fn test_rate_limit_warnings_monthly()
作用:检查月度窗口的限额提醒文字能正确叫“monthly”。
数据流:给副限额传入 75% 使用量和约一个月的分钟数 → 输出一条月度限额剩余不足 25% 的提醒。
调用关系:它补充时间窗口标签映射,确保长周期限额文字不混乱。
调用图:外部调用 3 个(new, assert_eq!, default)。
rate_limit_duration_labels_only_render_supported_windows523–530 ↗
fn rate_limit_duration_labels_only_render_supported_windows()
作用:检查只有支持的限额窗口会被翻译成日常标签,比如 daily、annual。
数据流:输入 2 小时、1 天、1 年的分钟数 → 2 小时返回 None,其他返回对应标签。
调用关系:它直接测试限额窗口标签函数,被限额状态栏和提醒文案间接依赖。
调用图:外部调用 1 个(assert_eq!)。
test_rate_limit_warnings_use_generic_fallback_labels533–552 ↗
async fn test_rate_limit_warnings_use_generic_fallback_labels()
作用:确认没有窗口时间时,限额提醒会使用通用名称,不会显示奇怪空值。
数据流:主副限额都传 75% 使用量但不传窗口 → 输出“secondary usage limit”和“usage limit”两条通用提醒。
调用关系:它覆盖限额数据不完整时的文案兜底。
调用图:外部调用 2 个(assert_eq!, default)。
test_rate_limit_warnings_use_secondary_fallback_for_unsupported_window555–569 ↗
async fn test_rate_limit_warnings_use_secondary_fallback_for_unsupported_window()
作用:确认副限额窗口不受支持时,提醒文案会退回“secondary usage limit”。
数据流:输入副限额 75% 和一个不支持的 2 小时窗口 → 输出一条通用副限额提醒。
调用关系:它验证窗口标签失败时不会阻断提醒生成。
调用图:外部调用 2 个(assert_eq!, default)。
status_line_uses_secondary_fallback_for_unsupported_window572–594 ↗
async fn status_line_uses_secondary_fallback_for_unsupported_window()
作用:检查状态栏遇到不支持的副限额窗口时,显示通用副限额剩余量。
数据流:给聊天窗口一份只有副限额的快照 → 状态栏 WeeklyLimit 项返回“secondary usage 50% left”。
调用关系:它连接限额快照缓存和状态栏渲染,覆盖不标准窗口。
调用图:外部调用 1 个(assert_eq!)。
status_line_legacy_limit_items_prefer_matching_windows597–627 ↗
async fn status_line_legacy_limit_items_prefer_matching_windows()
作用:确认旧的 5 小时和每周状态项会优先匹配真正对应的窗口。
数据流:传入主限额为周限额、副限额为 5 小时限额 → 5h 项显示副限额,weekly 项显示主限额。
调用关系:它保护旧配置项的兼容性,避免主副限额位置变化导致显示错位。
调用图:外部调用 1 个(assert_eq!)。
status_line_shows_secondary_non_weekly_when_primary_is_weekly630–660 ↗
async fn status_line_shows_secondary_non_weekly_when_primary_is_weekly()
作用:检查主限额是周限额、副限额是月限额时,状态栏能同时显示两者。
数据流:传入 weekly 主限额和 monthly 副限额 → FiveHourLimit 位置显示 monthly,WeeklyLimit 显示 weekly。
调用关系:它测试旧状态栏槽位在新限额种类下的退让显示。
调用图:外部调用 1 个(assert_eq!)。
status_line_five_hour_item_omits_weekly_only_limit663–689 ↗
async fn status_line_five_hour_item_omits_weekly_only_limit()
作用:确认只有周限额时,5 小时状态项不会硬显示周限额。
数据流:输入一份仅含 weekly 主限额的快照 → FiveHourLimit 返回 None,WeeklyLimit 返回 weekly 剩余量。
调用关系:它防止一个限额被错误重复显示在两个槽位里。
调用图:外部调用 1 个(assert_eq!)。
status_line_single_monthly_primary_omits_weekly_limit_item692–718 ↗
async fn status_line_single_monthly_primary_omits_weekly_limit_item()
作用:确认只有月度主限额时,它显示在主要限额位置,而每周项留空。
数据流:传入 monthly 主限额 → FiveHourLimit 返回 monthly 65% left → WeeklyLimit 返回 None。
调用关系:它验证单个非周限额在旧状态栏项里的合理落位。
调用图:外部调用 1 个(assert_eq!)。
status_line_secondary_only_non_weekly_limit_omits_primary_limit_item721–747 ↗
async fn status_line_secondary_only_non_weekly_limit_omits_primary_limit_item()
作用:确认只有非周副限额时,不把它冒充成主要 5 小时限额。
数据流:传入仅含 monthly 副限额的快照 → FiveHourLimit 为空,WeeklyLimit 显示 monthly 剩余量。
调用关系:它覆盖副限额单独存在时的状态栏显示规则。
调用图:外部调用 1 个(assert_eq!)。
rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers750–804 ↗
async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers()
作用:确认后续限额快照缺少余额信息时,不会把之前的余额清掉。
数据流:先传入带 credits 的快照 → 缓存余额 17.5 → 再传入只有限额窗口的快照 → 输出缓存仍保留余额且更新窗口用量。
调用关系:它验证限额缓存合并逻辑,避免响应头不全导致余额闪没。
调用图:外部调用 2 个(assert!, assert_eq!)。
rolling_rate_limit_snapshot_preserves_prior_individual_limit807–838 ↗
async fn rolling_rate_limit_snapshot_preserves_prior_individual_limit()
作用:确认滚动限额更新不会抹掉之前的个人月度限额信息。
数据流:先传入带 individual_limit 的快照 → 再传滚动快照 → 缓存仍保留格式化后的个人限额;普通快照再来时可清除它。
调用关系:它区分滚动更新和完整更新,保护月度限额显示。
调用图:外部调用 2 个(assert!, assert_eq!)。
rate_limit_snapshot_updates_and_retains_plan_type841–903 ↗
async fn rate_limit_snapshot_updates_and_retains_plan_type()
作用:检查套餐类型会随限额快照更新,并在后续缺失时保留上一次值。
数据流:依次传入 Plus、Pro、无 plan_type 的快照 → chat.plan_type 先变 Plus,再变 Pro,最后仍是 Pro。
调用关系:它保证套餐信息不会因为某次响应没带字段就丢失。
调用图:外部调用 1 个(assert_eq!)。
rate_limit_snapshots_keep_separate_entries_per_limit_id906–962 ↗
async fn rate_limit_snapshots_keep_separate_entries_per_limit_id()
作用:确认不同 limit_id 的限额快照分开缓存,互不覆盖。
数据流:传入 codex 和 codex_other 两份快照 → 缓存表里有两个条目 → 各自保留自己的用量和余额。
调用关系:它保护多限额场景,避免不同产品或模型的限额混在一起。
调用图:外部调用 2 个(assert!, assert_eq!)。
rate_limit_switch_prompt_skips_when_on_lower_cost_model965–975 ↗
async fn rate_limit_switch_prompt_skips_when_on_lower_cost_model()
作用:确认已经在低成本模型上时,不再弹出建议切换模型的提示。
数据流:用 nudge 目标模型创建聊天窗口并模拟高用量 → 提示状态仍为 Idle。
调用关系:它验证模型切换提醒不会在用户已经切好的情况下重复出现。
调用图:外部调用 1 个(assert!)。
rate_limit_switch_prompt_skips_non_codex_limit978–1001 ↗
async fn rate_limit_switch_prompt_skips_non_codex_limit()
作用:确认非 codex 限额触顶时,不触发 codex 模型切换提示。
数据流:传入 limit_id 为 codex_other 的高用量快照 → rate_limit_switch_prompt 保持 Idle。
调用关系:它防止别的限额误触发 Codex 专用提示。
调用图:外部调用 1 个(assert!)。
rate_limit_switch_prompt_shows_once_per_session1004–1024 ↗
async fn rate_limit_switch_prompt_shows_once_per_session()
作用:确认限额模型切换提示一个会话只显示一次。
数据流:第一次高用量后显示提示 → 再次更高用量 → 状态仍为 Shown,不重新弹。
调用关系:它测试提示状态机的去重,避免反复打扰用户。
调用图:外部调用 1 个(assert!)。
rate_limit_switch_prompt_defers_until_task_complete1041–1058 ↗
async fn rate_limit_switch_prompt_defers_until_task_complete()
作用:确认任务运行中不会立刻弹出限额切换提示,而是等任务结束后再显示。
数据流:任务运行时传入高用量 → 提示变 Pending → 标记任务停止并检查 pending → 提示变 Shown。
调用关系:它保护正在执行任务时的界面稳定,避免弹窗打断流程。
调用图:外部调用 1 个(assert!)。
rate_limit_switch_prompt_popup_snapshot1061–1070 ↗
async fn rate_limit_switch_prompt_popup_snapshot()
作用:用快照固定限额模型切换弹窗的实际外观。
数据流:模拟高用量并显示提示 → 渲染底部弹窗为文本 → 与保存的快照比对。
调用关系:它检查提示状态机之后的视觉结果,防止弹窗文案或布局意外变化。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
workspace_member_credits_depleted_prompts_and_sends_credits1073–1089 ↗
async fn workspace_member_credits_depleted_prompts_and_sends_credits()
作用:确认工作区成员余额耗尽时,会提示联系 owner,并在用户按 y 后发送“加余额”请求事件。
数据流:设置限额类型为成员 credits 耗尽 → 触发限额错误 → 渲染弹窗 → 用户按 y → 事件队列输出 Credits 类型。
调用关系:它使用 next_send_add_credits_nudge_email_event 读取事件,验证弹窗到后台请求的完整链路。
调用图:调用 1 个内部函数(next_send_add_credits_nudge_email_event);外部调用 4 个(Char, new, assert_chatwidget_snapshot!, assert_eq!)。
workspace_member_usage_limit_prompts_and_sends_usage_limit1092–1108 ↗
async fn workspace_member_usage_limit_prompts_and_sends_usage_limit()
作用:确认工作区成员达到用量上限时,按 y 会发送“申请提高用量上限”的请求。
数据流:设置成员 usage limit 达到 → 触发限额错误 → 弹窗出现 → 用户确认 → 事件队列输出 UsageLimit 类型。
调用关系:它和余额耗尽测试成对,区分两种 owner 通知的请求类型。
调用图:调用 1 个内部函数(next_send_add_credits_nudge_email_event);外部调用 4 个(Char, new, assert_chatwidget_snapshot!, assert_eq!)。
header_rate_limit_snapshot_preserves_member_limit_type_for_error_prompt1111–1137 ↗
async fn header_rate_limit_snapshot_preserves_member_limit_type_for_error_prompt()
作用:确认错误前到来的响应头快照即使缺少限额类型,也不会抹掉之前的成员限额分类。
数据流:先传成员 usage limit 类型 → 再传缺少类型的 header 快照 → 触发错误 → 弹窗仍显示申请提高限制,并发送 UsageLimit。
调用关系:它保护真实请求失败时的事件顺序,避免关键分类被后来的不完整快照覆盖。
调用图:调用 1 个内部函数(next_send_add_credits_nudge_email_event);外部调用 4 个(Char, new, assert!, assert_eq!)。
usage_limit_error_remaps_stale_member_credits_state_to_usage_limit_prompt1140–1159 ↗
async fn usage_limit_error_remaps_stale_member_credits_state_to_usage_limit_prompt()
作用:确认如果旧状态说余额耗尽,但实际错误是用量上限,界面会改成用量上限提示。
数据流:先存成员 credits 耗尽 → 触发 UsageLimit 错误 → 弹窗显示申请提高限制 → 用户确认后发送 UsageLimit。
调用关系:它验证错误类型比旧缓存更可信,防止提示错方向。
调用图:调用 1 个内部函数(next_send_add_credits_nudge_email_event);外部调用 4 个(Char, new, assert!, assert_eq!)。
workspace_owner_limit_states_do_not_prompt_for_owner_nudge1162–1187 ↗
async fn workspace_owner_limit_states_do_not_prompt_for_owner_nudge()
作用:确认工作区 owner 自己遇到限额问题时,不显示“通知 owner”的弹窗。
数据流:遍历 owner credits、owner usage、普通 rate limit 几种状态 → 触发错误 → 弹窗不含 workspace owner 文案,事件队列没有通知或刷新事件。
调用关系:它使用 assert_no_owner_nudge_or_rate_limit_refresh 来保证不误发 owner 通知。
调用图:调用 1 个内部函数(assert_no_owner_nudge_or_rate_limit_refresh);外部调用 1 个(assert!)。
workspace_owner_limit_states_render_state_specific_messages1190–1224 ↗
async fn workspace_owner_limit_states_render_state_specific_messages()
作用:检查 owner 不同限额状态会显示对应的明确说明。
数据流:分别模拟 owner 余额耗尽和 owner 用量上限 → 读取历史区文本 → 确认包含各自专门文案并做快照。
调用关系:它验证不弹 owner 请求时,仍然给用户正确的错误解释。
调用图:外部调用 3 个(new, assert!, assert_chatwidget_snapshot!)。
missing_rate_limit_reached_type_does_not_prompt_or_refresh1227–1238 ↗
async fn missing_rate_limit_reached_type_does_not_prompt_or_refresh()
作用:确认缺少限额分类时,不冒然显示 owner 通知弹窗或刷新限额。
数据流:传入没有 reached_type 的满额快照 → 触发 UsageLimit 错误 → 弹窗不含 owner 文案,事件队列也没有相关动作。
调用关系:它保护未知状态下的保守行为。
调用图:调用 1 个内部函数(assert_no_owner_nudge_or_rate_limit_refresh);外部调用 1 个(assert!)。
workspace_owner_nudge_default_no_dismisses_without_sending1241–1254 ↗
async fn workspace_owner_nudge_default_no_dismisses_without_sending()
作用:确认 owner 通知弹窗默认选项是“不发送”,按回车会关闭而不发请求。
数据流:显示成员 credits 耗尽弹窗 → 用户按 Enter → 事件队列没有通知 owner 或刷新限额。
调用关系:它验证弹窗默认操作不会误发邮件。
调用图:调用 1 个内部函数(assert_no_owner_nudge_or_rate_limit_refresh);外部调用 1 个(new)。
workspace_owner_nudge_reappears_after_dismissing_no1257–1279 ↗
async fn workspace_owner_nudge_reappears_after_dismissing_no()
作用:确认用户选择不通知 owner 后,下次同类错误还可以再次出现提示。
数据流:第一次显示弹窗并按 Enter 关闭 → 确认无发送 → 再触发错误 → 弹窗再次包含申请提高限制文案。
调用关系:它说明“这次不发”不是永久隐藏。
调用图:调用 1 个内部函数(assert_no_owner_nudge_or_rate_limit_refresh);外部调用 2 个(new, assert!)。
workspace_owner_credits_nudge_completion_renders_feedback1282–1315 ↗
async fn workspace_owner_credits_nudge_completion_renders_feedback()
作用:检查通知 owner 加余额请求完成后,成功、冷却中、失败三种结果都有合适反馈。
数据流:分别启动并完成 Credits 请求 → 读取历史输出 → 确认包含“已通知”“最近已通知”“无法通知”等文案并做快照。
调用关系:它覆盖后台邮件请求返回后对用户的反馈显示。
调用图:外部调用 3 个(new, assert!, assert_chatwidget_snapshot!)。
workspace_owner_usage_limit_nudge_completion_renders_feedback1318–1351 ↗
async fn workspace_owner_usage_limit_nudge_completion_renders_feedback()
作用:检查申请提高用量上限请求完成后,三种结果都有对应反馈。
数据流:分别完成 UsageLimit 请求为成功、冷却、失败 → 历史区显示对应说明 → 与快照比对。
调用关系:它和 credits 完成反馈测试对应,确保两类请求文案不会混用。
调用图:外部调用 3 个(new, assert!, assert_chatwidget_snapshot!)。
next_send_add_credits_nudge_email_event1353–1362 ↗
fn next_send_add_credits_nudge_email_event(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
) -> AddCreditsNudgeCreditType
作用:从测试事件队列里找出“发送 owner 通知邮件”的事件,并返回它的类型。
数据流:输入事件接收器 → 不断尝试取事件 → 找到 SendAddCreditsNudgeEmail 就返回 credit_type;找不到就让测试失败。
调用关系:多个 owner 通知测试用它确认用户按 y 后真的发出了正确事件。
调用图:被 4 处调用(header_rate_limit_snapshot_preserves_member_limit_type_for_error_prompt, usage_limit_error_remaps_stale_member_credits_state_to_usage_limit_prompt, workspace_member_credits_depleted_prompts_and_sends_credits, workspace_member_usage_limit_prompts_and_sends_usage_limit);外部调用 2 个(try_recv, panic!)。
assert_no_owner_nudge_or_rate_limit_refresh1364–1376 ↗
fn assert_no_owner_nudge_or_rate_limit_refresh(
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
)
作用:检查事件队列里没有 owner 通知或限额刷新事件。
数据流:输入事件接收器 → 取出当前所有事件 → 如果发现 SendAddCreditsNudgeEmail 或 RefreshRateLimits 就让测试失败。
调用关系:它被多个“不应该通知 owner”的测试复用,作为防误触发的断言工具。
调用图:被 4 处调用(missing_rate_limit_reached_type_does_not_prompt_or_refresh, workspace_owner_limit_states_do_not_prompt_for_owner_nudge, workspace_owner_nudge_default_no_dismisses_without_sending, workspace_owner_nudge_reappears_after_dismissing_no);外部调用 2 个(try_recv, assert!)。
streaming_final_answer_keeps_task_running_state1379–1408 ↗
async fn streaming_final_answer_keeps_task_running_state()
作用:确认最终回答正在流式输出时,任务仍算运行中,用户可以排队下一条消息,也可以 Ctrl-C 中断。
数据流:启动任务并流入最终回答 → 提交一条排队输入 → 不发送新 turn → 再按 Ctrl-C → 输出 Interrupt 操作并隐藏退出提示。
调用关系:它验证流式最终回答期间的输入队列和中断行为。
调用图:调用 1 个内部函数(new);外部调用 7 个(Char, new, new, assert!, assert_eq!, assert_matches!, panic!)。
ctrl_c_interrupt_pauses_active_goal_turn1411–1447 ↗
async fn ctrl_c_interrupt_pauses_active_goal_turn()
作用:确认有活动目标时按 Ctrl-C 中断,会同时把目标状态设为暂停。
数据流:启用 Goals 并设置活动目标 → 启动任务 → 按 Ctrl-C → 操作队列收到 Interrupt,事件队列收到 SetThreadGoalStatus Paused。
调用关系:它连接键盘中断、任务控制和目标系统状态更新。
调用图:调用 2 个内部函数(new, test_thread_goal);外部调用 5 个(Char, new, ThreadGoalUpdated, assert_matches!, panic!)。
idle_commit_ticks_do_not_restore_status_without_commentary_completion1450–1466 ↗
async fn idle_commit_ticks_do_not_restore_status_without_commentary_completion()
作用:确认流式最终回答隐藏状态条后,空闲提交 tick 不会把状态条又闪回来。
数据流:启动任务 → 流入最终回答并提交 → 状态指示器隐藏但任务仍运行 → 再来一次提交 tick → 状态仍隐藏。
调用关系:它防止界面抖动,是流式状态条行为的回归测试。
调用图:外部调用 1 个(assert_eq!)。
final_answer_completion_restores_status_indicator_for_pending_steer1469–1525 ↗
async fn final_answer_completion_restores_status_indicator_for_pending_steer()
作用:确认用户在最终回答流式期间追加引导消息后,最终回答完成会恢复状态条。
数据流:流入多段最终回答 → 状态条隐藏 → 用户提交 steer → 完成 assistant 消息 → 状态条恢复;再完成 user 消息 → pending steer 清空。
调用关系:它覆盖流式回答、用户插话和任务状态显示三者的配合。
调用图:调用 1 个内部函数(new);外部调用 5 个(new, new, assert!, assert_eq!, panic!)。
commentary_completion_restores_status_indicator_before_exec_begin1528–1552 ↗
async fn commentary_completion_restores_status_indicator_before_exec_begin()
作用:确认 commentary 阶段完成后,在命令执行开始前状态条会恢复。
数据流:启动任务并流入前言 → 状态条隐藏 → 完成 commentary 消息 → 状态条恢复 → 开始执行命令后仍可见。
调用关系:它验证 assistant 不同消息阶段对状态条的影响。
调用图:外部调用 1 个(assert_eq!)。
fast_status_indicator_requires_chatgpt_auth1555–1568 ↗
async fn fast_status_indicator_requires_chatgpt_auth()
作用:确认 Fast 模式状态只有在 ChatGPT 账号认证后才显示。
数据流:选择支持 fast 的模型并设置 fast 服务层 → 未认证时不显示 → 设置认证后再检查 → 显示 fast 状态。
调用关系:它测试 fast 状态显示的认证条件。
调用图:外部调用 1 个(assert!)。
ui_snapshots_small_heights_idle1598–1610 ↗
async fn ui_snapshots_small_heights_idle()
作用:用快照检查终端高度只有 1、2、3 行时,空闲聊天界面不会画崩。
数据流:创建空闲 ChatWidget → 在 40 列、不同极小高度的测试终端里渲染 → 输出快照对比。
调用关系:它验证整体布局在极端小窗口下的兜底表现。
调用图:外部调用 4 个(new, assert_chatwidget_snapshot!, format!, new)。
ui_snapshots_small_heights_task_running1615–1630 ↗
async fn ui_snapshots_small_heights_task_running()
作用:用快照检查任务运行中遇到极小终端高度时,状态栏和输入框怎么取舍显示。
数据流:启动任务并设置推理标题 → 在 1、2、3 行终端中渲染 → 与快照对比。
调用关系:它和空闲小窗口测试成对,覆盖运行状态下的压缩布局。
调用图:外部调用 4 个(new, assert_chatwidget_snapshot!, format!, new)。
ambient_pet_screen_bottom_anchor_uses_terminal_bottom1675–1696 ↗
async fn ambient_pet_screen_bottom_anchor_uses_terminal_bottom()
作用:检查宠物锚点设为屏幕底部时,会以终端底部而不是输入框底部定位。
数据流:启用测试宠物 → 默认按 composer_bottom_y 得到一个 y → 改配置为 ScreenBottom → 新 y 更靠近终端底部。
调用关系:它依赖 enable_test_ambient_pet,验证宠物定位配置。
调用图:调用 1 个内部函数(enable_test_ambient_pet);外部调用 2 个(new, assert_eq!)。
ambient_pet_can_be_disabled1700–1706 ↗
async fn ambient_pet_can_be_disabled()
作用:确认把宠物设置成禁用 ID 后,聊天窗口不会保留环境宠物。
数据流:调用 set_tui_pet 并传入 DISABLED_PET_ID → ambient_pet 变为 None。
调用关系:它测试宠物功能的关闭入口。
调用图:外部调用 1 个(assert!)。
ambient_pet_reserves_history_wrap_width1710–1719 ↗
async fn ambient_pet_reserves_history_wrap_width()
作用:确认宠物显示时,历史文本会给宠物留出横向空间;禁用后恢复全宽。
数据流:启用宠物后查询 80 列下历史换行宽度 → 得到 69 → 禁用宠物后再查 → 得到 80。
调用关系:它验证宠物不只是画在屏幕上,还会影响文本排版宽度。
调用图:调用 1 个内部函数(enable_test_ambient_pet);外部调用 1 个(assert_eq!)。
ambient_pet_reduces_stream_width_and_composer_text_width1723–1776 ↗
async fn ambient_pet_reduces_stream_width_and_composer_text_width()
作用:确认宠物会同时缩窄流式回答宽度和输入框文本宽度,避免文字盖住宠物。
数据流:分别创建有宠物和禁用宠物的窗口 → 比较 stream_width → 渲染同一段输入文本 → 检查有宠物时右侧预留区域为空。
调用关系:它使用 buffer_row_containing 和 row_tail_is_blank 检查实际终端缓冲区。
调用图:调用 2 个内部函数(buffer_row_containing, enable_test_ambient_pet);外部调用 5 个(new, new, assert!, assert_eq!, new)。
buffer_row_containing1778–1786 ↗
fn buffer_row_containing(buffer: &ratatui::buffer::Buffer, text: &str) -> Option<String>
作用:在测试终端缓冲区里找到包含某段文字的整行内容。
数据流:输入 ratatui 缓冲区和目标文字 → 逐行拼出字符串并查找 → 找到则返回该行,否则返回 None。
调用关系:它是宠物宽度测试的小工具,用来定位输入框渲染在哪一行。
调用图:被 1 处调用(ambient_pet_reduces_stream_width_and_composer_text_width)。
row_tail_is_blank1788–1790 ↗
fn row_tail_is_blank(row: &str, start_col: usize) -> bool
作用:检查一行文本从某列开始到结尾是不是全空白。
数据流:输入一行字符串和起始列 → 跳过前面的字符 → 后面全部是空白则返回 true。
调用关系:它辅助判断宠物预留区域有没有被输入框文字占用。
ambient_pet_draw_uses_terminal_screen_area_not_short_inline_viewport1794–1821 ↗
async fn ambient_pet_draw_uses_terminal_screen_area_not_short_inline_viewport()
作用:确认普通很矮的内联视口不会强行画宠物,但完整终端区域足够时可以画。
数据流:用 3 行高视口请求宠物绘制 → 返回 None → 用完整 24 行终端请求 → 返回具体 x、y 坐标。
调用关系:它验证宠物布局使用真正屏幕区域,避免在滚动历史的小区域里挤出怪图。
调用图:调用 1 个内部函数(enable_test_ambient_pet);外部调用 3 个(new, assert!, assert_eq!)。
ambient_pet_hides_notification_text_overlay1825–1847 ↗
async fn ambient_pet_hides_notification_text_overlay()
作用:确认启用宠物图片时,不再显示 Running、Needs input 等文字通知覆盖层。
数据流:遍历几种宠物通知状态 → 渲染终端 → 快照文本中不应包含对应标签。
调用关系:它防止宠物图像和文字通知同时出现造成重复或遮挡。
调用图:调用 1 个内部函数(enable_test_ambient_pet);外部调用 3 个(new, assert!, new)。
status_widget_and_approval_modal_snapshot1853–1895 ↗
async fn status_widget_and_approval_modal_snapshot()
作用:用快照检查任务状态条和审批弹窗同时存在时的最终布局。
数据流:启动任务并设置状态标题 → 构造执行命令审批请求 → 渲染完整 ChatWidget → 与快照对比。
调用关系:它验证审批弹窗在视觉上优先于状态指示器。
调用图:外部调用 5 个(new, assert_chatwidget_snapshot!, new, new, vec!)。
status_widget_active_snapshot1900–1917 ↗
async fn status_widget_active_snapshot()
作用:用快照固定任务运行中状态指示器的终端外观。
数据流:模拟 turn 开始和推理标题 → 按组件期望高度渲染 → 保存并对比快照。
调用关系:它是状态组件视觉稳定性的基础快照测试。
调用图:外部调用 3 个(assert_chatwidget_snapshot!, new, new)。
stream_error_updates_status_indicator1920–1938 ↗
async fn stream_error_updates_status_indicator()
作用:确认流连接错误不会插入历史消息,而是更新当前状态指示器。
数据流:标记任务运行 → 发送 StreamError 文案和详情 → 历史区为空 → 状态组件 header 和 details 更新。
调用关系:它验证可恢复流错误走状态条路径,而不是污染聊天记录。
调用图:外部调用 2 个(assert!, assert_eq!)。
warning_event_adds_warning_history_cell1962–1973 ↗
async fn warning_event_adds_warning_history_cell()
作用:确认普通警告事件会在历史区插入一条可见警告。
数据流:调用 handle_warning 传入测试警告 → 读取历史插入 → 应只有一条,并包含原始警告文字。
调用关系:它验证警告事件到聊天记录的基本路径。
调用图:外部调用 2 个(assert!, assert_eq!)。
status_line_invalid_items_warn_once2004–2029 ↗
async fn status_line_invalid_items_warn_once()
作用:确认状态栏配置里有非法项目时,只警告一次。
数据流:配置状态栏包含 bogus_item 两次 → 刷新状态栏 → 历史区出现一次警告 → 再刷新 → 不再出现。
调用关系:它测试配置错误提示的去重,避免每次刷新都刷屏。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_eq!, vec!)。
status_line_context_used_renders_labeled_percent2032–2044 ↗
async fn status_line_context_used_renders_labeled_percent()
作用:确认 context-used 状态项显示“Context 0% used”这种带标签的百分比。
数据流:配置状态栏只有 context-used → 刷新 → 状态栏文本为 Context 0% used,且没有警告。
调用关系:它验证新状态栏项目是合法且格式正确的。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_eq!, vec!)。
status_line_context_remaining_renders_labeled_percent2047–2062 ↗
async fn status_line_context_remaining_renders_labeled_percent()
作用:确认 context-remaining 状态项显示剩余百分比。
数据流:配置 context-remaining → 刷新状态栏 → 得到 Context 100% left,且不插入错误警告。
调用关系:它和 context-used 测试配套,覆盖剩余量显示。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_eq!, vec!)。
status_line_legacy_context_usage_renders_context_used_percent2065–2077 ↗
async fn status_line_legacy_context_usage_renders_context_used_percent()
作用:确认旧名字 context-usage 仍然可用,并等同于 context-used。
数据流:配置旧状态项 context-usage → 刷新 → 状态栏显示 Context 0% used,没有无效项警告。
调用关系:它保护老配置兼容性。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert!, assert_eq!, vec!)。
status_line_branch_state_resets_when_git_branch_disabled2080–2092 ↗
async fn status_line_branch_state_resets_when_git_branch_disabled()
作用:确认状态栏不再包含 git-branch 时,之前的分支查询状态会被清空。
数据流:先设置分支缓存和 pending 标记 → 配置状态栏只显示 model_name → 刷新 → 分支值和查询标记全部重置。
调用关系:它防止禁用分支显示后还保留过期异步状态。
调用图:外部调用 3 个(assert!, assert_eq!, vec!)。
status_line_branch_refreshes_after_turn_complete2095–2105 ↗
async fn status_line_branch_refreshes_after_turn_complete()
作用:确认一次对话完成后,如果状态栏需要 Git 分支,会重新触发分支查询。
数据流:安装空命令执行器并配置 git-branch → 标记此前查询完成 → 处理 turn 完成事件 → branch_pending 变 true。
调用关系:它用 install_noop_workspace_command_runner 提供测试用命令执行能力。
调用图:调用 1 个内部函数(install_noop_workspace_command_runner);外部调用 2 个(assert!, vec!)。
status_line_branch_refreshes_after_interrupt2108–2118 ↗
async fn status_line_branch_refreshes_after_interrupt()
作用:确认对话被中断后,也会刷新 Git 分支状态。
数据流:配置 git-branch 并准备已完成查询状态 → 处理 turn interrupted → branch_pending 变 true。
调用关系:它和 turn 完成测试一起保证结束类事件都会刷新分支。
调用图:调用 1 个内部函数(install_noop_workspace_command_runner);外部调用 2 个(assert!, vec!)。
install_noop_workspace_command_runner2120–2122 ↗
fn install_noop_workspace_command_runner(chat: &mut ChatWidget)
作用:给 ChatWidget 安装一个不会真正运行命令的工作区命令执行器。
数据流:输入可修改 ChatWidget → 把 workspace_command_runner 设置成 NoopWorkspaceCommandRunner 的共享对象 → 后续 Git 查询流程可以启动但不会碰真实系统。
调用关系:它服务于分支刷新测试,避免测试依赖真实 git 命令。
调用图:被 2 处调用(status_line_branch_refreshes_after_interrupt, status_line_branch_refreshes_after_turn_complete);外部调用 1 个(new)。
NoopWorkspaceCommandRunner::run2127–2148 ↗
fn run(
&self,
_command: crate::workspace_command::WorkspaceCommand,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = Result<
作用:测试用的假命令执行函数,永远返回一个失败但空输出的结果。
数据流:输入被忽略的工作区命令 → 返回一个异步 future → future 完成时给出 exit_code 1、stdout/stderr 为空的输出。
调用关系:它实现 WorkspaceCommandExecutor 接口,让 ChatWidget 以为有命令执行器可用。
调用图:外部调用 2 个(pin, new)。
interrupted_turn_clears_visible_running_hook2152–2175 ↗
async fn interrupted_turn_clears_visible_running_hook()
作用:确认对话被中断时,正在显示的运行中钩子会从界面上清掉。
数据流:启动一个 hook 并显示它 → 记录中断前 active hook 文本 → 处理 turn interrupted → 快照比较中断后为空。
调用关系:它使用 hook_started_run 构造测试 hook,验证 turn 生命周期会清理 hook 状态。
调用图:调用 1 个内部函数(hook_started_run);外部调用 1 个(assert_chatwidget_snapshot!)。
completed_turn_clears_visible_running_hook2178–2201 ↗
async fn completed_turn_clears_visible_running_hook()
作用:确认对话正常完成时,也会清掉仍显示的运行中钩子。
数据流:启动并显示一个 hook → 处理 turn completed → 快照显示完成后 active hook 消失。
调用关系:它和中断测试一起覆盖 turn 结束的两条清理路径。
调用图:调用 1 个内部函数(hook_started_run);外部调用 1 个(assert_chatwidget_snapshot!)。
status_line_fast_mode_renders_on_and_off2204–2214 ↗
async fn status_line_fast_mode_renders_on_and_off()
作用:确认状态栏 fast-mode 项能显示 Fast 开和关。
数据流:配置 fast-mode 状态项 → 未设置 fast 时刷新得到 Fast off → 设置 fast 服务层后刷新得到 Fast on。
调用关系:它直接测试 fast-mode 状态栏项的格式。
调用图:外部调用 2 个(assert_eq!, vec!)。
status_line_model_with_reasoning_includes_fast_for_fast_capable_models2240–2272 ↗
async fn status_line_model_with_reasoning_includes_fast_for_fast_capable_models()
作用:确认 model-with-reasoning 状态项在支持 fast 的模型上会附带 fast,而换到不支持的模型后会去掉。
数据流:选择 gpt-5.4、设置 xhigh 和 fast、认证 → 状态栏含“xhigh fast” → 切到 gpt-5.3-codex → 状态栏不含 fast。
调用关系:它测试模型名、推理强度、fast 能力和当前目录多个状态项的组合显示。
调用图:外部调用 3 个(assert!, assert_eq!, vec!)。
terminal_title_model_updates_on_model_change_without_manual_refresh2275–2285 ↗
async fn terminal_title_model_updates_on_model_change_without_manual_refresh()
作用:确认终端标题配置显示模型时,切换模型会自动更新标题。
数据流:配置标题项为 model 并刷新 → 标题为 gpt-5.4 → 调用 set_model → 标题自动变成 gpt-5.3-codex。
调用关系:它验证模型切换函数会联动终端标题刷新。
调用图:外部调用 2 个(assert_eq!, vec!)。
status_line_and_terminal_title_reasoning_render_only_effort2288–2300 ↗
async fn status_line_and_terminal_title_reasoning_render_only_effort()
作用:确认 reasoning 项只显示推理强度,不把 fast 模式混进去。
数据流:状态栏和终端标题都配置 reasoning → 设置 xhigh 和 fast → 刷新后两处都只显示 xhigh。
调用关系:它区分 reasoning 单项和 model-with-reasoning 组合项。
调用图:外部调用 2 个(assert_eq!, vec!)。
status_line_reasoning_updates_on_mode_switch_without_manual_refresh2303–2316 ↗
async fn status_line_reasoning_updates_on_mode_switch_without_manual_refresh()
作用:确认切换协作模式时,reasoning 状态栏项会自动更新。
数据流:启用协作模式,推理强度设 high → 状态栏 high → 切到 plan 模式 → 状态栏自动变 medium。
调用关系:它验证协作模式改变会触发状态栏重算。
调用图:调用 1 个内部函数(plan_mask);外部调用 2 个(assert_eq!, vec!)。
status_line_model_with_reasoning_updates_on_mode_switch_without_manual_refresh2319–2347 ↗
async fn status_line_model_with_reasoning_updates_on_mode_switch_without_manual_refresh()
作用:确认 model-with-reasoning 组合项也会随协作模式自动更新推理强度。
数据流:默认模式显示模型 high → 切 plan 模式显示模型 medium → 切回默认显示模型 high。
调用关系:它和单独 reasoning 测试配套,覆盖组合显示项。
调用图:调用 2 个内部函数(default_mask, plan_mask);外部调用 2 个(assert_eq!, vec!)。
thread_goal_update_for_other_thread_is_ignored2615–2641 ↗
async fn thread_goal_update_for_other_thread_is_ignored()
作用:确认收到别的线程的目标更新时,当前聊天窗口不会误更新。
数据流:当前 thread_id 设为 A → 发送线程 B 的目标更新 → 当前目标状态、指示器和预算记录都保持空。
调用关系:它保护多线程通知场景,避免串线显示。
调用图:调用 2 个内部函数(new, test_thread_goal);外部调用 3 个(ThreadGoalUpdated, assert!, assert_eq!)。
goal_status_indicator_formats_statuses_and_budgets2644–2709 ↗
fn goal_status_indicator_formats_statuses_and_budgets()
作用:检查不同目标状态会被转换成正确的底部目标指示器。
数据流:构造 Active、Blocked、UsageLimited、BudgetLimited、Complete 等目标 → 调用转换函数 → 比对对应 GoalStatusIndicator 和用量文字。
调用关系:它直接验证目标状态到 UI 状态模型的映射。
调用图:外部调用 1 个(assert_eq!)。
goal_status_indicator_line_formats_goal_text2712–2758 ↗
fn goal_status_indicator_line_formats_goal_text()
作用:检查目标指示器最终渲染成用户看到的文字是否正确。
数据流:遍历多种 GoalStatusIndicator → 调用 goal_status_indicator_line → 拼出文本 → 与预期短句比对。
调用关系:它测试底部页脚里目标文案的最终格式。
调用图:外部调用 2 个(assert_eq!, goal_status_indicator_line)。
test_thread_goal2760–2775 ↗
fn test_thread_goal(
status: codex_app_server_protocol::ThreadGoalStatus,
token_budget: Option<i64>,
tokens_used: i64,
) -> codex_app_server_protocol::ThreadGoal
作用:生成测试用的 ThreadGoal 数据,减少每个目标测试重复写字段。
数据流:输入目标状态、token 预算和已用 token → 填入固定线程、目标描述、时间和时间戳 → 返回完整 ThreadGoal。
调用关系:多个目标相关测试用它构造服务器目标更新。
调用图:被 5 处调用(ctrl_c_interrupt_pauses_active_goal_turn, session_configured_clears_goal_status_footer, status_line_goal_active_token_budget_footer_snapshot, status_line_goal_complete_elapsed_footer_snapshot, thread_goal_update_for_other_thread_is_ignored)。
runtime_metrics_websocket_timing_logs_and_final_separator_sums_totals2778–2821 ↗
multiple_agent_messages_in_single_turn_emit_multiple_headers2824–2860 ↗
async fn multiple_agent_messages_in_single_turn_emit_multiple_headers()
作用:确认同一个 turn 里有多条 assistant 最终消息时,两条都按顺序显示。
数据流:开始 turn → 完成第一条 assistant 消息 → 完成第二条 → 完成 turn → 历史合并文本包含两条且顺序正确。
调用关系:它保护多消息 turn 的渲染顺序。
调用图:外部调用 1 个(assert!)。
final_reasoning_then_message_without_deltas_are_rendered2863–2885 ↗
async fn final_reasoning_then_message_without_deltas_are_rendered()
作用:确认没有流式增量,只有最终 reasoning 结束和最终消息时,内容仍会渲染。
数据流:发送 reasoning final → 完成 assistant 消息 → 读取历史文本 → 与快照对比。
调用关系:它覆盖非流式或缺少 delta 的消息路径。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
deltas_then_same_final_message_are_rendered_snapshot2888–2919 ↗
async fn deltas_then_same_final_message_are_rendered_snapshot()
作用:确认先收到流式 reasoning 和回答增量,再收到相同最终消息时,显示结果稳定不重复错乱。
数据流:推入多段 reasoning delta 和 answer delta → 完成同内容最终消息 → 合并历史内容并做快照。
调用关系:它测试流式临时内容和最终消息去重、合并的交界。
调用图:外部调用 1 个(assert_chatwidget_snapshot!)。
user_prompt_submit_app_server_hook_notifications_render_snapshot2922–2991 ↗
async fn user_prompt_submit_app_server_hook_notifications_render_snapshot()
作用:用快照检查用户提交提示词时触发的服务器钩子开始和完成通知如何显示。
数据流:发送 HookStarted 和带 warning/stop 输出的 HookCompleted → 读取历史内容 → 快照确认渲染,并检查状态指示器不再显示。
调用关系:它覆盖 app server 钩子通知到聊天历史的显示路径。
调用图:调用 1 个内部函数(new);外部调用 7 个(from, HookCompleted, HookStarted, new, assert!, assert_chatwidget_snapshot!, vec!)。
pre_tool_use_hook_events_render_snapshot2994–3002 ↗
async fn pre_tool_use_hook_events_render_snapshot()
作用:检查 PreToolUse 钩子事件的显示快照。
数据流:把 PreToolUse 事件名、固定 id 和状态消息交给通用钩子快照断言 → 输出由通用函数负责生成和比对。
调用关系:它是钩子事件类型覆盖的一条入口,复用通用断言逻辑。
post_tool_use_hook_events_render_snapshot3005–3013 ↗
async fn post_tool_use_hook_events_render_snapshot()
作用:检查 PostToolUse 钩子事件的显示快照。
数据流:把 PostToolUse 事件参数交给通用钩子快照断言 → 生成开始和完成场景 → 与快照对比。
调用关系:它和 PreToolUse 测试一起覆盖工具前后钩子的显示。
completed_hook_with_no_entries_stays_out_of_history3016–3050 ↗
async fn completed_hook_with_no_entries_stays_out_of_history()
作用:确认没有输出内容的钩子完成后,不会写入历史,只短暂停留在 live 区域。
数据流:启动 hook 并显示 → 完成且 entries 为空 → 历史区仍空 → linger 过期后 live hook 消失 → 做快照。
调用关系:它使用 hook_started_run、hook_completed_run 和 hook_live_and_history_snapshot 组织钩子状态。
调用图:调用 3 个内部函数(hook_completed_run, hook_live_and_history_snapshot, hook_started_run);外部调用 3 个(new, assert!, assert_chatwidget_snapshot!)。
quiet_hook_linger_starts_when_delayed_redraw_reveals_hook3053–3084 ↗
async fn quiet_hook_linger_starts_when_delayed_redraw_reveals_hook()
作用:确认安静钩子如果是延迟重绘时才显示,完成后仍会按规则短暂停留。
数据流:启动带状态消息的 hook → 延迟揭示 → 完成且无输出 → live 区仍显示一会 → 过期后清空。
调用关系:它测试 hook 可见性和 linger 计时的细微交互。
调用图:调用 2 个内部函数(hook_completed_run, hook_started_run);外部调用 3 个(new, assert!, assert_eq!)。
blocked_and_failed_hooks_render_feedback_and_errors3087–3130 ↗
async fn blocked_and_failed_hooks_render_feedback_and_errors()
作用:确认被阻止和失败的钩子会把反馈或错误写入历史。
数据流:完成一个 Blocked PreToolUse 钩子并带 feedback → 完成一个 Failed PostToolUse 钩子并带 error → 历史文本包含对应分类和内容。
调用关系:它验证有意义的钩子输出会成为聊天记录。
调用图:调用 1 个内部函数(hook_completed_run);外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
completed_hook_with_output_flushes_immediately3133–3169 ↗
async fn completed_hook_with_output_flushes_immediately()
作用:确认钩子一旦带输出完成,会立刻从 live 区刷新到历史。
数据流:启动并显示 hook → 完成且带 feedback → 立即读取历史 → 快照比较 running 和 completed 状态。
调用关系:它保护钩子反馈及时出现在 assistant 后续消息之前。
调用图:调用 3 个内部函数(hook_completed_run, hook_live_and_history_snapshot, hook_started_run);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
completed_hook_output_precedes_following_assistant_message3172–3226 ↗
async fn completed_hook_output_precedes_following_assistant_message()
作用:确认钩子输出在历史里排在随后 assistant 消息之前。
数据流:启动并完成带 feedback 的 hook → 再完成 assistant 消息 → 读取历史 → 检查 hook 文本位置早于 assistant 文本。
调用关系:它验证事件顺序不会被异步刷新打乱。
调用图:调用 2 个内部函数(hook_completed_run, hook_started_run);外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
completed_same_id_hook_output_survives_restart3229–3279 ↗
async fn completed_same_id_hook_output_survives_restart()
作用:确认同一个 hook id 重新开始时,前一次完成输出不会被覆盖丢失。
数据流:启动并完成某 id 的 Stop hook,带 stop 输出 → 用同 id 再启动一次 → 历史仍包含第一次输出。
调用关系:它防止按 id 覆盖 live 状态时误删历史记录。
调用图:调用 2 个内部函数(hook_completed_run, hook_started_run);外部调用 3 个(assert!, assert_chatwidget_snapshot!, vec!)。
identical_parallel_running_hooks_collapse_to_count3282–3301 ↗
async fn identical_parallel_running_hooks_collapse_to_count()
作用:确认多个内容相同、并行运行的钩子会折叠显示成数量,而不是刷多行重复文字。
数据流:用不同 tool_call_id 启动三个相同 PreToolUse hook → 显示 live hook → 快照检查折叠结果。
调用关系:它测试并行 hook 的可读性处理。
调用图:调用 1 个内部函数(hook_started_run);外部调用 2 个(assert_chatwidget_snapshot!, format!)。
overlapping_hook_live_cell_tracks_parallel_quiet_hooks3304–3372 ↗
async fn overlapping_hook_live_cell_tracks_parallel_quiet_hooks()
作用:确认多个安静钩子重叠运行和完成时,live 区能正确追踪每一个的停留和消失。
数据流:启动 pre hook,再启动 post hook → 分别完成且无输出 → 每次记录 live/history 状态 → linger 过期后逐步清空。
调用关系:它验证 active_hook_cell 不会破坏主状态 header,也不会写空历史。
调用图:调用 3 个内部函数(hook_completed_run, hook_live_and_history_snapshot, hook_started_run);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, assert_eq!)。
running_hook_does_not_displace_active_exec_cell3375–3423 ↗
async fn running_hook_does_not_displace_active_exec_cell()
作用:确认运行中的钩子不会挤掉正在显示的命令执行块。
数据流:开始 exec → 启动 hook 并显示 → active exec 仍存在,hook 单独显示 → exec 结束后写历史,hook 继续显示直到完成和过期。
调用关系:它测试 active exec cell 和 active hook cell 两个 live 区域并存。
调用图:调用 2 个内部函数(hook_completed_run, hook_started_run);外部调用 4 个(new, assert!, assert_chatwidget_snapshot!, format!)。
hook_completed_before_reveal_renders_completed_without_running_flash3470–3504 ↗
async fn hook_completed_before_reveal_renders_completed_without_running_flash()
作用:确认钩子在还没来得及显示 running 状态前就完成时,直接把完成内容写入历史,不闪一下 running。
数据流:启动 session-start hook 但不 reveal → 立即完成并带 context 输出 → 历史包含完成内容,live 区启动时仍为空。
调用关系:它覆盖快速钩子完成的视觉体验。
调用图:调用 2 个内部函数(hook_completed_run, hook_started_run);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
session_start_hook_events_render_snapshot3507–3515 ↗
async fn session_start_hook_events_render_snapshot()
作用:检查 SessionStart 钩子事件的显示快照。
数据流:把 SessionStart 事件参数交给通用钩子快照断言 → 生成并比对渲染结果。
调用关系:它补齐会话开始钩子的显示覆盖。
hook_started_run3517–3529 ↗
fn hook_started_run(
id: &str,
event_name: codex_app_server_protocol::HookEventName,
status_message: Option<&str>,
) -> codex_app_server_protocol::HookRunSummary
作用:生成一个测试用的“正在运行”钩子摘要。
数据流:输入 hook id、事件名和可选状态消息 → 调用 hook_run_summary 并固定状态为 Running → 返回 HookRunSummary。
调用关系:大量 hook 测试用它准备开始事件,底层交给 hook_run_summary 填公共字段。
调用图:调用 1 个内部函数(hook_run_summary);被 12 处调用(completed_hook_output_precedes_following_assistant_message, completed_hook_with_no_entries_stays_out_of_history, completed_hook_with_output_flushes_immediately, completed_same_id_hook_output_survives_restart, completed_turn_clears_visible_running_hook, hidden_active_hook_does_not_add_transcript_separator, hook_completed_before_reveal_renders_completed_without_running_flash, identical_parallel_running_hooks_collapse_to_count, interrupted_turn_clears_visible_running_hook, overlapping_hook_live_cell_tracks_parallel_quiet_hooks (+2 more));外部调用 1 个(new)。
hook_completed_run3531–3540 ↗
fn hook_completed_run(
id: &str,
event_name: codex_app_server_protocol::HookEventName,
status: codex_app_server_protocol::HookRunStatus,
entries: Vec<codex_app_server_protocol::HookOut
作用:生成一个测试用的“已完成/失败/阻止”等钩子摘要。
数据流:输入 hook id、事件名、完成状态和输出 entries → 调用 hook_run_summary → 返回带完成信息的 HookRunSummary。
调用关系:它是 hook 完成类测试的构造工具,和 hook_started_run 配套。
调用图:调用 1 个内部函数(hook_run_summary);被 9 处调用(blocked_and_failed_hooks_render_feedback_and_errors, completed_hook_output_precedes_following_assistant_message, completed_hook_with_no_entries_stays_out_of_history, completed_hook_with_output_flushes_immediately, completed_same_id_hook_output_survives_restart, hook_completed_before_reveal_renders_completed_without_running_flash, overlapping_hook_live_cell_tracks_parallel_quiet_hooks, quiet_hook_linger_starts_when_delayed_redraw_reveals_hook, running_hook_does_not_displace_active_exec_cell)。
hook_run_summary3542–3565 ↗
fn hook_run_summary(
id: &str,
event_name: codex_app_server_protocol::HookEventName,
status: codex_app_server_protocol::HookRunStatus,
status_message: Option<&str>,
entries: Vec<co
作用:集中创建 HookRunSummary,避免每个测试重复填一堆固定字段。
数据流:输入 id、事件名、状态、状态消息和输出项 → 填入处理器类型、作用域、路径、时间和完成时长 → 返回完整摘要。
调用关系:hook_started_run 和 hook_completed_run 都调用它,是钩子测试数据的工厂函数。
调用图:被 2 处调用(hook_completed_run, hook_started_run);外部调用 1 个(from)。
hook_live_and_history_snapshot3567–3577 ↗
fn hook_live_and_history_snapshot(chat: &ChatWidget, phase: &str, history: &str) -> String
作用:把当前 live hook 区和历史文本拼成一段适合做快照的字符串。
数据流:输入 ChatWidget、阶段名和历史字符串 → 空历史替换成 <empty> → 拼接阶段、live hooks 和 history → 返回文本。
调用关系:多个 hook 快照测试用它统一输出格式,方便比较不同阶段。
调用图:被 3 处调用(completed_hook_with_no_entries_stays_out_of_history, completed_hook_with_output_flushes_immediately, overlapping_hook_live_cell_tracks_parallel_quiet_hooks);外部调用 1 个(format!)。
chatwidget_exec_and_status_layout_vt100_snapshot3583–3672 ↗
async fn chatwidget_exec_and_status_layout_vt100_snapshot()
作用:用接近真实终端的 vt100 后端检查历史、命令执行块、状态栏和输入框组合布局。
数据流:模拟 assistant 消息、命令开始和结束、任务状态和输入框文本 → 把历史插入 vt100 终端,再渲染 UI 覆盖层 → 输出整屏快照。
调用关系:它是复杂布局端到端测试,覆盖历史插入和当前 UI 渲染的叠加效果。
调用图:调用 4 个内部函数(shlex_join, with_options, insert_history_lines, new);外部调用 4 个(new, new, assert_chatwidget_snapshot!, vec!)。
chatwidget_markdown_code_blocks_vt100_snapshot3676–3754 ↗
async fn chatwidget_markdown_code_blocks_vt100_snapshot()
作用:检查复杂 Markdown,特别是缩进代码块和嵌套 fenced code block,在流式输出中渲染稳定。
数据流:把一段 Markdown 按两字符切块模拟流式 delta → 每次提交并插入历史到 vt100 → turn 完成后刷新尾部 → 对整屏做快照。
调用关系:它验证流式 Markdown 渲染器和历史插入管道,防止代码块格式破坏。
调用图:调用 3 个内部函数(with_options, insert_history_lines, new);外部调用 3 个(new, new, assert_chatwidget_snapshot!)。
chatwidget_tall3757–3778 ↗
async fn chatwidget_tall()
作用:检查聊天窗口在较高内容、很多排队消息和有限终端高度下的整体显示。
数据流:创建聊天窗口并启动 turn → 排队 30 条用户消息 → 用 vt100 渲染 80x24 终端 → 与快照对比。
调用关系:它覆盖输入队列很长时的主界面布局稳定性。
调用图:调用 3 个内部函数(new, with_options, new);外部调用 3 个(new, assert_chatwidget_snapshot!, format!)。
tui/src/chatwidget/tests/status_surface_previews.rs源码 ↗
这个文件像一组“界面验收照片”。它先造出一个假的 ChatWidget,也就是聊天界面组件,然后手动塞进去项目名、Git 分支、对话标题、任务进度、额度使用情况等数据,再看状态栏和终端标题会预览成什么文字。测试里有两类场景:一类是直接检查预览行文字,另一类是打开设置弹窗后,把整个弹窗画面转成文本快照来比对。快照测试可以理解成“拍一张标准照片”,以后每次运行测试都和这张照片比,变了就提醒开发者确认是不是故意改的。文件还特别覆盖了边界情况,比如没有线程名时用线程 ID 兜底、没有某种额度数据时不要硬显示、终端标题太长时要截断。这样用户看到的状态信息就更稳定,不会出现空白、错位或奇怪的占位内容。
line_text6–11 ↗
fn line_text(line: Line<'static>) -> String
作用:把一行带样式的界面文字变成普通字符串,方便测试比较。界面库里的 Line 可能分成很多段,每段有颜色或样式;测试只关心最终读出来的字。
数据流:输入是一条 Line,也就是界面上准备显示的一行文字 → 它把这一行里的每个文字片段取出来并拼在一起 → 输出一个普通 String,不保留颜色、粗体这些样式信息。
调用关系:它是预览测试里的小工具。根据调用关系,title_preview_line 会用它把终端标题预览结果转成纯文字,之后测试才能拿这段文字和期望结果比较。
调用图:被 1 处调用(title_preview_line)。
status_preview_line_option13–18 ↗
fn status_preview_line_option(chat: &mut ChatWidget, items: &[StatusLineItem]) -> Option<String>
作用:尝试生成状态栏预览文字,但允许结果为空。这个函数适合测试“某些项目没数据时应该不显示”的情况。
数据流:输入是一个可变的聊天界面和一组状态栏项目 → 它从聊天界面取出状态栏预览所需的数据,再把项目列表交给预览生成逻辑,并要求使用主题颜色 → 输出可能是一个字符串,也可能是 None,None 表示没有可显示的状态栏内容。
调用关系:它是状态栏预览测试的底层 helper。status_preview_line 会在需要“必须有结果”的测试里调用它;它自己依赖 ChatWidget 提供的 status_surface_preview_data 和项目迭代能力来拿到真实预览数据。
调用图:被 1 处调用(status_preview_line);外部调用 2 个(iter, status_surface_preview_data)。
status_preview_line20–22 ↗
fn status_preview_line(chat: &mut ChatWidget, items: &[StatusLineItem]) -> String
作用:生成一条必须存在的状态栏预览文字。测试用它来表达:这个场景下状态栏一定应该有内容。
数据流:输入是聊天界面和状态栏项目列表 → 它调用 status_preview_line_option 生成可选结果 → 如果有结果就返回字符串;如果没有结果,测试会直接失败并提示“status preview line”。
调用关系:它包了一层更严格的检查。missing_project_root_uses_different_status_and_title_preview_sources 会用它验证项目根目录缺失时,状态栏预览使用什么兜底文字。
调用图:调用 1 个内部函数(status_preview_line_option);被 1 处调用(missing_project_root_uses_different_status_and_title_preview_sources)。
title_preview_line24–29 ↗
fn title_preview_line(chat: &mut ChatWidget, items: &[TerminalTitleItem]) -> String
作用:生成终端标题的预览文字。终端标题就是终端窗口顶部可能显示的那串标题,比如项目名、线程名、分支名拼起来的结果。
数据流:输入是聊天界面和终端标题项目列表 → 它从聊天界面取出终端标题预览数据,调用 preview_line_for_title_items 生成带样式的一行文字 → 再用 line_text 去掉样式,输出普通字符串。
调用关系:它是终端标题相关测试的主要入口。missing_project_root_uses_different_status_and_title_preview_sources 用它比较标题和状态栏的兜底来源,terminal_title_preview_uses_title_truncation_for_live_values 用它检查长标题是否被正确截短。
调用图:调用 1 个内部函数(line_text);被 2 处调用(missing_project_root_uses_different_status_and_title_preview_sources, terminal_title_preview_uses_title_truncation_for_live_values);外部调用 2 个(preview_line_for_title_items, terminal_title_preview_data)。
combined_preview_snapshot31–41 ↗
fn combined_preview_snapshot(
chat: &mut ChatWidget,
status_items: &[StatusLineItem],
title_items: &[TerminalTitleItem],
) -> String
作用:把状态栏预览和终端标题预览合成一段快照文本。这样一个测试就能同时看到两块界面信息是否一致。
数据流:输入是聊天界面、状态栏项目列表、终端标题项目列表 → 它分别生成两边的预览文字,再格式化成两行文本,并把路径类内容做标准化 → 输出一段适合做快照比对的字符串。
调用关系:它被多个“预览行快照”测试调用,包括纯实时数据、纯兜底数据、混合数据和额度限制数据场景。它的作用像照相机,把两处预览一次性拍下来。
调用图:被 4 处调用(status_surface_preview_lines_hardcoded_only_snapshot, status_surface_preview_lines_live_only_snapshot, status_surface_preview_lines_mixed_snapshot, status_surface_preview_lines_rate_limits_snapshot);外部调用 1 个(format!)。
status_line_popup_snapshot43–48 ↗
fn status_line_popup_snapshot(chat: &mut ChatWidget) -> String
作用:打开状态栏设置弹窗,并把弹窗画面变成可比较的文本快照。它测试的不只是某一行文字,而是整个设置弹窗长什么样。
数据流:输入是聊天界面 → 它先调用 open_status_line_setup 打开状态栏设置界面,再用固定宽度渲染底部弹窗,去掉不适合快照比较的 OSC8 链接标记,并标准化路径 → 输出一段弹窗文本快照。
调用关系:它服务于状态栏设置弹窗的快照测试。那些测试先配置当前状态栏项目,再调用它拿到画面,最后用 assert_chatwidget_snapshot! 和标准快照对比。
调用图:外部调用 1 个(open_status_line_setup)。
terminal_title_popup_snapshot50–55 ↗
fn terminal_title_popup_snapshot(chat: &mut ChatWidget) -> String
作用:打开终端标题设置弹窗,并把弹窗画面转成快照文本。它用来确认用户配置终端标题时看到的预览和说明没有坏掉。
数据流:输入是聊天界面 → 它调用 open_terminal_title_setup 打开终端标题设置界面,按固定宽度渲染底部弹窗,去掉快照中不稳定的链接标记,再标准化路径 → 输出一段可比较的弹窗文本。
调用关系:它服务于终端标题设置弹窗的测试。不同测试会先塞入不同配置,比如项目名、线程名、额度限制,然后通过它拍下弹窗快照。
调用图:外部调用 1 个(open_terminal_title_setup)。
cache_project_root57–62 ↗
fn cache_project_root(chat: &mut ChatWidget, root_name: &str)
作用:给测试用的聊天界面预先塞一个“已经算好的项目根目录名”。这样测试不用真的去磁盘上找项目目录,也能模拟真实界面显示项目名。
数据流:输入是聊天界面和一个根目录名称 → 它读取当前配置里的工作目录 cwd,并把这个目录和传入的根目录名一起放进 status_line_project_root_name_cache → 输出没有返回值,但聊天界面内部缓存被改好了。
调用关系:它被实时数据相关测试调用,比如状态栏预览、状态栏设置弹窗、终端标题设置弹窗。它让这些测试集中检查显示效果,而不是依赖真实文件系统。
调用图:被 3 处调用(status_line_setup_popup_live_only_snapshot, status_surface_preview_lines_live_only_snapshot, terminal_title_setup_popup_live_only_snapshot)。
cache_rate_limit_snapshot64–83 ↗
fn cache_rate_limit_snapshot(chat: &mut ChatWidget)
作用:给测试用的聊天界面塞一份假的额度使用情况。这里的额度限制可以理解成“这个账号在某段时间内还能用多少”。
数据流:输入是聊天界面 → 它构造一份 RateLimitSnapshot,其中主窗口用了 35%,次窗口用了 50%,并设置对应的时间窗口长度 → 然后调用 on_rate_limit_snapshot 把这份数据送进聊天界面,函数本身不返回值。
调用关系:它被额度限制相关的状态栏和终端标题测试调用。测试先用它制造“有额度数据”的状态,再检查预览里是否出现类似剩余额度的文字。
调用图:被 3 处调用(status_line_setup_popup_rate_limits_snapshot, status_surface_preview_lines_rate_limits_snapshot, terminal_title_setup_popup_rate_limits_snapshot);外部调用 1 个(on_rate_limit_snapshot)。
status_surface_preview_lines_live_only_snapshot86–109 ↗
async fn status_surface_preview_lines_live_only_snapshot()
作用:测试状态栏和终端标题在全都有实时数据时的预览结果。实时数据包括项目名、Git 分支、线程标题和任务进度。
数据流:开始时创建一个测试用聊天界面 → 塞入项目根目录、分支名、线程名和计划进度 → 调用 combined_preview_snapshot 生成两行预览快照 → 用 assert_chatwidget_snapshot! 和标准结果比较。
调用关系:这是一个完整场景测试。它先借助 cache_project_root 准备项目名,再把工作交给 combined_preview_snapshot 生成快照,最后由快照断言确认界面文字没变。
调用图:调用 2 个内部函数(cache_project_root, combined_preview_snapshot);外部调用 1 个(assert_chatwidget_snapshot!)。
status_line_setup_popup_live_only_snapshot112–127 ↗
async fn status_line_setup_popup_live_only_snapshot()
作用:测试状态栏设置弹窗在有实时数据时显示得是否正确。它关注用户打开配置界面时看到的预览和项目列表。
数据流:开始时创建聊天界面 → 缓存项目根目录,设置分支名、线程名,并把配置里的状态栏项目设为项目名、分支、线程标题 → 打开并渲染状态栏设置弹窗 → 和快照比对。
调用关系:这个测试用 cache_project_root 准备数据,用 vec! 构造配置列表,再通过 status_line_popup_snapshot 的流程间接生成弹窗画面,最后用 assert_chatwidget_snapshot! 验证。
调用图:调用 1 个内部函数(cache_project_root);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
status_surface_preview_lines_hardcoded_only_snapshot130–150 ↗
async fn status_surface_preview_lines_hardcoded_only_snapshot()
作用:测试没有实时数据时,状态栏和终端标题会不会用预设的兜底文字正常显示。兜底文字就是“真实值暂时没有时先显示的示例”。
数据流:开始时创建一个空白测试聊天界面,不额外塞项目名、分支或线程名 → 要求生成多种状态栏项目和终端标题项目的预览 → 输出组合快照并与标准快照比较。
调用关系:它调用 combined_preview_snapshot 拍下状态栏和终端标题的预览,再交给 assert_chatwidget_snapshot!。它和 live_only 测试形成对照:一个看真实数据,一个看兜底数据。
调用图:调用 1 个内部函数(combined_preview_snapshot);外部调用 1 个(assert_chatwidget_snapshot!)。
thread_title_falls_back_to_thread_id_when_unnamed153–166 ↗
async fn thread_title_falls_back_to_thread_id_when_unnamed()
作用:测试线程没有名字时,界面会用线程 ID 代替。线程 ID 是一串唯一编号,至少能让用户区分这是哪段对话。
数据流:开始时创建聊天界面 → 新建一个 ThreadId 并放进聊天界面,但不设置 thread_name → 分别生成状态栏线程标题和终端标题线程项 → 断言它们都等于这个线程 ID 的字符串。
调用关系:它直接调用 ThreadId::new 造出编号,再用 assert_eq! 比较结果。这个测试保护的是兜底规则,避免无名线程在界面上显示为空。
调用图:调用 1 个内部函数(new);外部调用 1 个(assert_eq!)。
status_line_setup_popup_hardcoded_only_snapshot169–181 ↗
async fn status_line_setup_popup_hardcoded_only_snapshot()
作用:测试状态栏设置弹窗在没有实时项目数据时的样子。它确认弹窗仍然能展示可理解的示例内容,而不是空白。
数据流:开始时创建测试聊天界面 → 只设置配置里的状态栏项目列表,不设置实际项目名、分支名或线程名 → 渲染状态栏设置弹窗 → 和标准快照比较。
调用关系:它用 vec! 组成配置项,然后调用状态栏弹窗快照流程,最后由 assert_chatwidget_snapshot! 判断画面是否符合预期。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
status_surface_preview_lines_mixed_snapshot184–204 ↗
async fn status_surface_preview_lines_mixed_snapshot()
作用:测试一部分数据是真实的、一部分只能用兜底值时,预览能不能自然地拼在一起。真实界面经常会遇到这种半有半没有的状态。
数据流:开始时创建聊天界面 → 设置 Git 分支和线程名,但不准备所有可能的数据 → 生成状态栏和终端标题的组合预览 → 和快照比对。
调用关系:它调用 combined_preview_snapshot 来观察混合场景的最终显示。这个测试夹在 live_only 和 hardcoded_only 之间,确认两种来源混用时不会乱。
调用图:调用 1 个内部函数(combined_preview_snapshot);外部调用 1 个(assert_chatwidget_snapshot!)。
status_surface_preview_lines_rate_limits_snapshot207–221 ↗
async fn status_surface_preview_lines_rate_limits_snapshot()
作用:测试状态栏和终端标题里显示额度限制信息时的预览。它确认“五小时限制”和“每周限制”这类信息会被正确翻成用户能看懂的文字。
数据流:开始时创建聊天界面 → 调用 cache_rate_limit_snapshot 塞入假的额度数据 → 生成包含额度项目的状态栏和终端标题预览 → 和快照比较。
调用关系:它依赖 cache_rate_limit_snapshot 制造额度环境,再调用 combined_preview_snapshot 拍下预览。assert_chatwidget_snapshot! 负责发现显示格式的意外变化。
调用图:调用 2 个内部函数(cache_rate_limit_snapshot, combined_preview_snapshot);外部调用 1 个(assert_chatwidget_snapshot!)。
status_line_setup_popup_rate_limits_snapshot266–278 ↗
async fn status_line_setup_popup_rate_limits_snapshot()
作用:测试状态栏设置弹窗中配置额度限制项目时的显示效果。它确保用户在设置里能看到额度项目的正确预览。
数据流:开始时创建聊天界面 → 用 cache_rate_limit_snapshot 塞入额度数据,并把状态栏配置设成五小时限制和每周限制 → 渲染状态栏设置弹窗 → 和标准快照比较。
调用关系:它把额度数据准备工作交给 cache_rate_limit_snapshot,用 vec! 写出配置列表,最后通过 assert_chatwidget_snapshot! 验证弹窗画面。
调用图:调用 1 个内部函数(cache_rate_limit_snapshot);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
status_line_setup_popup_mixed_snapshot281–295 ↗
async fn status_line_setup_popup_mixed_snapshot()
作用:测试状态栏设置弹窗在混合数据场景下的样子。也就是有些项目有真实值,有些项目可能用默认或示例值。
数据流:开始时创建聊天界面 → 设置分支名和线程名,并配置状态栏包含项目名、分支、线程标题 → 渲染状态栏设置弹窗 → 和快照比较。
调用关系:它用 vec! 配置状态栏项目,用 assert_chatwidget_snapshot! 做最终比对。它补充覆盖了设置弹窗里的混合来源显示。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
terminal_title_setup_popup_live_only_snapshot298–315 ↗
async fn terminal_title_setup_popup_live_only_snapshot()
作用:测试终端标题设置弹窗在实时数据齐全时的显示效果。它确保项目名、线程名、分支名和任务进度都能进入预览。
数据流:开始时创建聊天界面 → 缓存项目根目录,设置分支、线程标题和任务进度,再配置终端标题项目列表 → 打开并渲染终端标题设置弹窗 → 和标准快照比较。
调用关系:它调用 cache_project_root 准备项目名,用 vec! 组成终端标题配置,最后用 assert_chatwidget_snapshot! 检查弹窗快照。
调用图:调用 1 个内部函数(cache_project_root);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
terminal_title_setup_popup_hardcoded_only_snapshot318–330 ↗
async fn terminal_title_setup_popup_hardcoded_only_snapshot()
作用:测试终端标题设置弹窗在没有实时数据时是否仍然可读。它保证缺少真实线程、分支、进度时,设置界面仍能显示合理示例。
数据流:开始时创建空白测试聊天界面 → 只配置终端标题包含线程标题、Git 分支和任务进度 → 渲染终端标题设置弹窗 → 和标准快照比较。
调用关系:它用 vec! 构造配置,并通过 assert_chatwidget_snapshot! 比对结果。它对应终端标题弹窗里的“纯兜底值”场景。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
terminal_title_setup_popup_mixed_snapshot333–346 ↗
async fn terminal_title_setup_popup_mixed_snapshot()
作用:测试终端标题设置弹窗在有一部分真实数据时是否显示正确。比如有线程名,但项目名或任务进度可能仍走默认来源。
数据流:开始时创建聊天界面 → 设置线程名,并配置终端标题包含项目名、线程标题和任务进度 → 渲染终端标题设置弹窗 → 和标准快照比较。
调用关系:它关注终端标题设置弹窗的混合场景。配置列表由 vec! 生成,最终画面由 assert_chatwidget_snapshot! 和标准快照比较。
调用图:外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
terminal_title_setup_popup_rate_limits_snapshot349–361 ↗
async fn terminal_title_setup_popup_rate_limits_snapshot()
作用:测试终端标题设置弹窗里配置额度限制项目时的显示。它确认额度项目不只在状态栏可用,在终端标题配置中也能正确预览。
数据流:开始时创建聊天界面 → 用 cache_rate_limit_snapshot 塞入额度使用情况,并配置终端标题包含五小时限制和每周限制 → 渲染终端标题设置弹窗 → 和快照比较。
调用关系:它把额度准备交给 cache_rate_limit_snapshot,用 vec! 写配置,再让 assert_chatwidget_snapshot! 检查最终弹窗文本。
调用图:调用 1 个内部函数(cache_rate_limit_snapshot);外部调用 2 个(assert_chatwidget_snapshot!, vec!)。
missing_project_root_uses_different_status_and_title_preview_sources364–372 ↗
async fn missing_project_root_uses_different_status_and_title_preview_sources()
作用:测试项目根目录缺失时,状态栏预览和终端标题预览会使用不同的兜底来源。这个差异是有意设计的,测试把它固定下来。
数据流:开始时创建聊天界面,不缓存真实项目根目录 → 分别生成状态栏的 ProjectRoot 预览和终端标题的 Project 预览 → 断言状态栏显示 my-project,而终端标题显示 project。
调用关系:它调用 status_preview_line 和 title_preview_line 拿到两边结果,再用 assert_eq! 精确比较。这个测试提醒后来改代码的人:两处兜底文字不应该被随手统一掉。
调用图:调用 2 个内部函数(status_preview_line, title_preview_line);外部调用 1 个(assert_eq!)。
terminal_title_preview_uses_title_truncation_for_live_values375–392 ↗
async fn terminal_title_preview_uses_title_truncation_for_live_values()
作用:测试终端标题里的实时线程名和分支名太长时会被截短。终端标题空间有限,不截短会让窗口标题变得很难看。
数据流:开始时创建聊天界面 → 塞入很长的线程标题和很长的 Git 分支名 → 生成终端标题预览 → 再用 truncate_terminal_title_part 按规定长度算出期望的截短结果 → 断言预览等于“截短线程名 | 截短分支名”。
调用关系:它调用 title_preview_line 获取真实预览,用 ChatWidget::truncate_terminal_title_part 计算标准答案,再由 assert_eq! 比较。它保护的是终端标题专用的长度控制规则。
调用图:调用 1 个内部函数(title_preview_line);外部调用 2 个(assert_eq!, truncate_terminal_title_part)。
tui/src/chatwidget/tests/terminal_title.rs源码 ↗
这个文件是一组自动测试,关注的是聊天界面在运行任务时,终端窗口标题上显示什么。可以把终端标题理解成窗口上方的小招牌:平时显示项目名,忙的时候可能显示旋转提示,遇到必须用户确认的命令时,要醒目地写出“Action Required”。这些测试会造出一个假的聊天界面,让它进入“任务正在运行”的状态,再模拟一次执行命令需要批准的请求。然后测试检查标题是不是变成了预期文字,动画是不是该动就动、该停就停。这里还特别覆盖了几个容易出错的情况:用户自定义标题后不要乱加提示;动画关闭后不要闪;批准完成后标题要恢复正常。这些测试的价值是防止界面提示失灵,否则用户可能错过需要确认的操作。
terminal_title_shows_action_required_while_exec_approval_is_pending7–45 ↗
async fn terminal_title_shows_action_required_while_exec_approval_is_pending()
作用:这个测试确认:当有命令执行请求正在等待用户批准时,终端标题会显示“Action Required”,提醒用户必须做决定。它还检查用户批准后,这个提醒会消失,并恢复成普通运行中的标题动画。
数据流:测试先创建一个聊天界面,把它设成“任务正在跑”,并刷新一次标题。接着构造一个需要批准的执行请求,里面包括命令、当前目录和说明原因,再交给聊天界面处理。界面下一次准备绘制时,测试读取保存下来的终端标题,确认它变成“[ ! ] Action Required | project”,并确认普通旋转动画没有继续动。随后测试模拟用户按下 y 表示同意,再让界面更新一次,最后检查标题里还有 project,但不再包含 Action Required,同时普通运行中的动画恢复。
调用关系:这是一个端到端味道的界面测试:它从 make_chatwidget_manual 得到可控的聊天界面,通过 handle_exec_approval_request 把“等待批准”事件送进去,再用 pre_draw_tick 触发标题更新。它自己只负责搭场景和断言结果,具体标题怎么生成由聊天组件内部完成。
调用图:调用 1 个内部函数(current_dir);外部调用 5 个(Char, new, assert!, assert_eq!, vec!)。
terminal_title_action_required_respects_spinner_setting48–73 ↗
async fn terminal_title_action_required_respects_spinner_setting()
作用:这个测试确认:如果用户把终端标题配置成只显示固定的 project,那么即使命令需要批准,也不会强行在标题里加动画式提醒。它是在保护用户配置的优先级。
数据流:测试先创建聊天界面,然后把配置里的 tui_terminal_title 改成只包含 project。接着让任务处于运行中,并送入一个等待批准的执行请求。界面更新后,测试读取终端标题,确认它仍然只是 project;同时检查“需要操作”的标题动画没有开启。
调用关系:这个测试仍然通过 handle_exec_approval_request 制造“需要用户批准”的状态,通过 pre_draw_tick 让界面重新计算标题。它关注的不是批准流程本身,而是标题逻辑在遇到用户自定义配置时有没有克制,不要擅自覆盖用户想看的内容。
调用图:调用 1 个内部函数(current_dir);外部调用 3 个(assert!, assert_eq!, vec!)。
terminal_title_action_required_blinks_when_animations_are_enabled76–104 ↗
async fn terminal_title_action_required_blinks_when_animations_are_enabled()
作用:这个测试确认:在动画允许开启时,“需要操作”的终端标题会用闪烁状态提醒用户。它检查的是提醒不只是出现,还会按时间变化,变得更醒目。
数据流:测试创建聊天界面后,把任务设为运行中,并人为把标题动画的起点调到 1.5 秒前,让动画进入某个可预测的时间片。然后它构造并送入一个等待批准的执行请求。界面更新后,测试检查标题变成“[ . ] Action Required | project”,这表示提醒处在闪烁动画的某一帧;同时确认“需要操作”的动画确实处于应该继续更新的状态。
调用关系:这个测试依赖时间相关的设置:它调用当前时间并减去一段固定时长,目的是让动画结果稳定可测。事件仍由 handle_exec_approval_request 送入,标题刷新仍由 pre_draw_tick 触发;测试本身验证动画开关和时间计算配合后的外在效果。
调用图:调用 1 个内部函数(current_dir);外部调用 5 个(now, assert!, assert_eq!, from_millis, vec!)。
terminal_title_activity_indicators_do_not_animate_when_animations_are_disabled107–139 ↗
async fn terminal_title_activity_indicators_do_not_animate_when_animations_are_disabled()
作用:这个测试确认:当用户关闭动画后,终端标题里的活动提示不会动。即使有运行任务或等待批准的命令,也只能显示静态提示,避免打扰或不符合用户偏好。
数据流:测试先创建聊天界面,把配置里的 animations 设为 false,表示关闭动画。然后它让任务运行,并把动画起点设置到 1.5 秒前。刷新标题后,测试确认标题只是 project,普通旋转动画没有开启。接着它送入一个等待批准的执行请求,再让界面更新一次。最后测试确认标题显示静态的“[ ! ] Action Required | project”,并且“需要操作”的闪烁动画也没有开启。
调用关系:这个测试把同一个配置开关同时覆盖到两类提示:普通运行中的活动指示,以及需要用户批准时的醒目提醒。它通过刷新标题和处理批准请求推动聊天界面进入不同状态,最后用断言确保内部动画逻辑尊重“关闭动画”的全局设置。
调用图:调用 1 个内部函数(current_dir);外部调用 5 个(now, assert!, assert_eq!, from_millis, vec!)。
渲染基础组件和记录格式化
这些较低层级的渲染测试用于验证 TUI 使用的 markdown、历史单元格格式、布局分配、状态输出和令牌活动可视化。
tui/src/chatwidget/tokens/chart/palette_tests.rs源码 ↗
这个文件是一组自动测试,用来检查 TokenActivityPalette 这套配色方案。可以把它想成在给终端里的“小能量条”试衣服:背景是黑的还是白的,终端能不能显示真彩色,主题给的是 RGB 颜色还是普通命名颜色,都会影响最后穿什么颜色。测试会手动准备默认前景色、背景色、活动状态样式,然后调用 TokenActivityPalette::from_parts 生成调色板,再检查不同等级的小格子颜色是否符合预期。重点不只是“有颜色”,还包括空格子要不要变淡、满格是不是用主题强调色、柱状条颜色是否稍微压暗,以及在条件不足时是否退回到安全样式。这里的“退回”就是不用复杂混色,直接用主题提供的活动样式,避免显示出错。
truecolor_palette_blends_theme_accent_against_dark_background7–35 ↗
fn truecolor_palette_blends_theme_accent_against_dark_background()
作用:这个测试确认:在黑色背景、终端支持真彩色时,调色板会把主题强调色和背景正确混合,生成从暗到亮的几个等级。它防止深色主题下的活动图太亮、太刺眼,或颜色阶梯不对。
数据流:进去的是默认前景色 240/240/240、默认背景色黑色、真彩色能力,以及一个绿色的加粗活动样式。测试把这些交给 from_parts 生成调色板,然后分别查看等级 0、1、4 和柱状条等级 4 的前景色。出来的结果必须是预先算好的 RGB 颜色,并且 uses_color 为真,表示它真的启用了精细的彩色混合。
调用关系:它直接调用 from_parts 来构造被测试的调色板,用 rgb_color 把普通数字颜色转成终端样式能理解的颜色,再用 assert_eq! 和 assert! 检查结果。它验证的是深色背景下最理想的一条主流程。
调用图:调用 2 个内部函数(from_parts, rgb_color);外部调用 3 个(default, assert!, assert_eq!)。
truecolor_palette_blends_empty_cell_for_light_background38–58 ↗
fn truecolor_palette_blends_empty_cell_for_light_background()
作用:这个测试确认:在白色背景、终端支持真彩色时,空格子的颜色会变成适合浅色背景的淡灰色,而不是沿用深色主题的算法。它防止浅色主题里图表格子太暗或太突兀。
数据流:进去的是默认前景色黑色、默认背景色白色、真彩色能力,以及一个蓝绿色的加粗活动样式。测试用 from_parts 生成调色板,然后检查等级 0 的空格子是不是浅灰色,等级 4 是不是主题强调色。最后还确认 uses_color 为真,说明这次确实走了真彩色混色路线。
调用关系:它和深色背景测试形成一组对照:同样是调用 from_parts,但背景亮度不同,所以期望的空格子颜色不同。它依靠 rgb_color 表达精确颜色,用断言确认浅色主题的分支没有坏掉。
调用图:调用 2 个内部函数(from_parts, rgb_color);外部调用 3 个(default, assert!, assert_eq!)。
ansi16_palette_uses_theme_accent_without_green_fallback61–76 ↗
fn ansi16_palette_uses_theme_accent_without_green_fallback()
作用:这个测试确认:当终端只支持 16 色这类基础颜色时,系统不会硬套某个绿色备用色,而是尊重主题给出的强调色。这样用户选了紫色主题时,活动图不会突然变成不搭调的绿色。
数据流:进去的是深色终端的默认颜色、Ansi16 色彩能力,以及一个洋红色加粗活动样式。from_parts 生成调色板后,测试检查等级 0 是默认样式加变暗效果,等级 1 和柱状条等级 4 都直接使用活动样式。出来的 uses_color 为假,表示它没有启用真彩色混合,只用了安全的主题样式。
调用关系:它测试的是低色彩能力终端的退路。流程上仍然先调用 from_parts,再用断言检查 for_level 和 for_bar_level 的结果,确保非真彩色环境不会误走复杂混色逻辑。
调用图:调用 1 个内部函数(from_parts);外部调用 3 个(default, assert!, assert_eq!)。
non_rgb_theme_accent_remains_active_fallback79–98 ↗
fn non_rgb_theme_accent_remains_active_fallback()
作用:这个测试确认:即使终端支持真彩色,如果主题强调色不是 RGB 这种可计算的颜色,而是 Cyan 这种命名颜色,系统也会稳妥地退回到活动样式。它防止程序拿无法混合的颜色硬算,导致显示错误。
数据流:进去的是深色终端默认颜色、真彩色能力,以及一个青色加粗活动样式。from_parts 尝试生成调色板,但因为强调色不是 RGB,测试期望等级 1 直接等于原活动样式,并且仍保留加粗效果。最后 uses_color 为假,表示这次没有进行 RGB 混色。
调用关系:它覆盖的是“终端能力够,但主题颜色类型不够”的边界情况。它调用 from_parts 后,除了比较完整样式,还检查 Modifier::BOLD,也就是加粗标记没有在退回过程中丢失。
调用图:调用 1 个内部函数(from_parts);外部调用 3 个(default, assert!, assert_eq!)。
missing_terminal_colors_use_theme_accent_fallback101–115 ↗
fn missing_terminal_colors_use_theme_accent_fallback()
作用:这个测试确认:如果终端没有提供完整的默认颜色信息,比如缺少默认前景色,调色板会放弃精细计算,改用可靠的主题强调样式。它防止颜色信息不全时算出不可信的颜色。
数据流:进去的是缺失的默认前景色、黑色默认背景、真彩色能力,以及一个蓝色加粗活动样式。from_parts 收到这些信息后生成调色板,测试检查等级 0 是变暗的默认样式,等级 4 是活动样式。出来的 uses_color 为假,说明因为资料不够,它没有启用真彩色混色。
调用关系:它测试的是信息缺失时的保护机制。它和非 RGB 颜色测试类似,都是确认 from_parts 会选择安全退路,而不是勉强做颜色计算。
调用图:调用 1 个内部函数(from_parts);外部调用 3 个(default, assert!, assert_eq!)。
tui/src/chatwidget/tokens/chart_tests.rs源码 ↗
这个文件不是真正画图的代码,而是给画图代码做“质检”。它准备一些假的每日 token 用量数据,然后调用上层模块里的函数,看输出是不是符合预期。token 可以简单理解为模型处理文字时的计量单位。这里重点检查几类容易出错的事:同一天有多条记录时要相加,负数用量不能让图表变怪;每日视图里空白日和有用量的日子要用不同方块显示;终端很宽时图表仍然靠左,不要被拉散;周视图和累计视图要画出正确的柱状图;摘要信息在窄屏下要自动换行。文件里用了快照测试,也就是把一整段终端输出保存成标准答案,以后每次测试都拿新输出和它对照,像核对打印出来的报表有没有走样。
duplicate_dates_sum_and_negative_values_clamp8–29 ↗
fn duplicate_dates_sum_and_negative_values_clamp()
作用:这个测试确认每日用量整理时,同一天的多条记录会加在一起,而且负数 token 不会被当成真实消耗继续累加。这样可以避免后端数据有重复或异常值时,图表总数被画错。
数据流:进去的是一个固定的“今天”日期,以及三条假的每日记录:两条同一天的正数记录,一条前一天的负数记录。测试把这些数据交给 daily_values 生成每天的数值序列,再把结果求和。出来的结果必须是 15,说明 10 和 5 被合并了,而 -4 被压成了不影响总数的值。
调用关系:这是对图表数据清洗入口的基础检查。它自己只搭测试数据和断言结果,真正的整理工作交给被测的 daily_values;日期由 from_ymd_opt 创建,最后用 assert_eq! 核对总和。
调用图:外部调用 3 个(from_ymd_opt, assert_eq!, vec!)。
bar_levels_fill_from_bottom32–37 ↗
fn bar_levels_fill_from_bottom()
作用:这个测试确认柱状图的高度等级是从底部往上填的。对用户来说,这保证了柱子看起来像正常的柱状图,而不是从中间或顶部乱长出来。
数据流:进去的是两个数值:0 和 10。测试调用 bar_levels,把原始数值换算成图表每一格该填到多高的等级。出来的数组前半段应该全是 0,表示没有柱子;后半段应该全是 4,表示最大值填满到最高等级。
调用关系:它专门盯住柱状图缩放和填充规则。测试本身不画图,只检查 bar_levels 给后续绘图函数准备的高度数据是否正确,最后用 assert_eq! 对比。
调用图:外部调用 1 个(assert_eq!)。
token_activity_view_aliases_parse40–55 ↗
fn token_activity_view_aliases_parse()
作用:这个测试确认用户输入的视图名字能被正确识别,比如空输入代表每日视图,day、week、cumulative 分别进入不同图表。它也确认不支持的词不会被误认。
数据流:进去的是几种字符串输入:空字符串、day、week、cumulative、year。测试逐个调用 TokenActivityView::parse,把文字变成内部的视图枚举值。出来的结果中,合法别名会变成 Daily、Weekly 或 Cumulative,year 会得到 None,表示无法识别。
调用关系:它站在用户命令和图表切换之间,检查解析规则。真正解析的是 TokenActivityView::parse,测试只负责喂入不同文字并用 assert_eq! 看返回值对不对。
调用图:外部调用 1 个(assert_eq!)。
daily_graph_snapshot_uses_distinct_empty_and_active_cells58–91 ↗
fn daily_graph_snapshot_uses_distinct_empty_and_active_cells()
作用:这个测试确认每日活动图里,没用量的日子和有用量的日子会显示成不同符号。这样用户一眼就能看出哪几天真的有使用记录。
数据流:进去的是一个固定今天日期、两条有 token 的日期记录,以及一个较窄的显示宽度。测试调用 chart_lines 生成每日视图的多行终端文字,再去掉每行末尾空格并拼成一段文本。出来的整段图表会和快照标准答案比对,必须包含正确的月份、星期行、空方块、实方块、颜色强度说明和视图切换提示。
调用关系:这是对 daily 图最终显示效果的验收。它把数据交给 chart_lines,让被测代码完成排版和符号选择,然后用 assert_snapshot! 把完整输出和保存的标准画面对照。
调用图:外部调用 3 个(from_ymd_opt, assert_snapshot!, vec!)。
daily_graph_snapshot_stays_left_aligned_in_wide_terminal94–112 ↗
fn daily_graph_snapshot_stays_left_aligned_in_wide_terminal()
作用:这个测试确认终端窗口很宽时,每日图不会被奇怪地居中或拉伸,而是保持靠左显示。它还检查图表宽度的计算不会超过合理边界。
数据流:进去的是两个宽度值和一个固定今天日期。测试先调用 graph_width,确认普通宽屏会得到 107,极大宽度会原样保留;然后用空数据调用 chart_lines 生成每日图,只取标题、第一行和底部图例。出来的文本必须和快照一致,说明宽屏下月份标题、星期行和图例仍然从左边稳定排起。
调用关系:它连接了宽度计算和每日图排版两个环节。graph_width 先决定图能用多宽,chart_lines 再按这个环境产出图表,最后 assert_eq! 和 assert_snapshot! 分别检查数值和画面。
调用图:外部调用 3 个(from_ymd_opt, assert_eq!, assert_snapshot!)。
weekly_graph_snapshot_renders_bar_chart_and_caption115–157 ↗
fn weekly_graph_snapshot_renders_bar_chart_and_caption()
作用:这个测试确认周视图会把每日数据汇总成按周排列的柱状图,并且底部说明文字写清楚“一列代表一周”和最高值。这样用户不会误解每根柱子代表什么。
数据流:进去的是固定今天日期、三条分布在不同周的 token 记录,以及显示宽度。测试调用 chart_lines 生成 Weekly 视图,再把多行输出整理成一段文本。出来的结果要和快照一致:上方有月份提示,中间有从 0 到 max 的柱状图,底部有“Each column = 1 week”和“tallest 9”的说明。
调用关系:这是周图的端到端显示测试。测试只提供样本数据和视图类型,具体按周聚合、计算柱高、排版字幕都由 chart_lines 背后的实现完成,最终用 assert_snapshot! 验收整体画面。
调用图:外部调用 3 个(from_ymd_opt, assert_snapshot!, vec!)。
cumulative_graph_snapshot_renders_running_total_bar_chart_and_caption160–202 ↗
fn cumulative_graph_snapshot_renders_running_total_bar_chart_and_caption()
作用:这个测试确认累计视图显示的是一路累加的总量,而不是每周单独的用量。它保证用户看到的是“到这一周为止一共用了多少”的趋势。
数据流:进去的是和周视图类似的三条日期记录,以及固定今天日期和宽度。测试调用 chart_lines,但指定 Cumulative 视图。出来的多行文字必须和快照相同:柱子应随着累计值上升,底部说明是“Running total”,最高值显示为 18,也就是 3、6、9 累加后的总量。
调用关系:它和周视图测试形成对照:同样的数据,换成 Cumulative 后应该走累计图的绘制路径。chart_lines 负责选择对应算法并生成画面,assert_snapshot! 负责确认输出没有偏差。
调用图:外部调用 3 个(from_ymd_opt, assert_snapshot!, vec!)。
summary_snapshot_left_aligns_and_splits_when_needed205–243 ↗
fn summary_snapshot_left_aligns_and_splits_when_needed()
作用:这个测试确认 token 使用摘要在不同终端宽度下都好读:宽的时候放一行,窄到放不下时再拆成两行。它防止摘要文字在小窗口里挤成难看的长串。
数据流:进去的是一份假的账户 token 使用摘要,包括总用量、最高单日用量、最长任务时间、当前连续使用天数和最长连续天数。测试针对 120、80、62 三种宽度,先用 graph_width 算可用宽度,再调用 summary_lines 生成摘要行。出来的三组文字被合在一起和快照比较,宽屏和中等宽度保持一行,较窄宽度会把“Longest task”拆到下一行。
调用关系:它检查摘要区的最终排版策略。GetAccountTokenUsageResponse 提供原始摘要数据,summary_lines 负责把数字压缩成人能读的格式并按宽度换行,assert_snapshot! 负责把宽、窄、很窄三种情况一起验收。
调用图:外部调用 1 个(assert_snapshot!)。
tui/src/chatwidget/tokens_tests.rs源码 ↗
这个测试文件专门检查令牌用量图表的日期锚点。这里的“令牌”可以理解成模型处理文字时消耗的计量单位,界面会把每天用了多少画成图。问题是:如果数据加载时用的是某一天,图表就应该一直按那一天来解释数据;否则跨过午夜、刷新界面或异步任务结束后,图表可能突然按另一天来画,用户看到的统计就会变得奇怪。测试先造出一个“正在加载”的状态,再假装服务器返回了空的用量统计,然后明确告诉代码“今天是 2026-05-29”。最后它读取状态,确认状态已经变成“已加载”,并且里面保存的 today 正是传进去的日期。可以把它想成给照片盖时间戳:照片洗出来后,时间戳就不能因为现在过了几天而自动改掉。
loaded_state_freezes_chart_anchor_date_at_completion6–38 ↗
fn loaded_state_freezes_chart_anchor_date_at_completion()
作用:这个测试确认:令牌活动数据加载完成后,界面状态里保存的“今天”就是完成加载时传入的日期。这样图表的日期基准不会后来偷偷变化。
数据流:一开始它创建一个共享的状态,内容是“正在加载”。然后它造出一个固定日期 2026-05-29,并用一份成功但没有具体统计值的服务器响应调用 finish_with_today。调用之后,它重新读取状态;如果状态变成“已加载”,就检查里面保存的日期是否等于刚才传入的日期;如果不是“已加载”,测试直接失败。
调用关系:它是一个独立的单元测试,由测试运行器在跑测试时调用。它自己准备 TokenActivityState 和 TokenActivityHandle,再把模拟的服务器结果交给 finish_with_today,最后用 assert_eq! 做核对;如果流程没有进入预期状态,就用 panic! 报错。
调用图:外部调用 6 个(clone, new, from_ymd_opt, new, assert_eq!, panic!)。
tui/src/history_cell/tests.rs源码 ↗
这个文件像一本“显示效果验收清单”。项目里的历史记录不是普通文本,而是一格一格的内容:用户输入、助手回复、执行命令、MCP 工具结果、计划更新、会话信息等。终端宽度一变,文字要换行;有些内容要带前缀、缩进、链接或隐藏敏感值;还有“富文本模式”和“原始复制模式”的区别。这里的测试会构造各种典型和刁钻场景,再把格子渲染成纯字符串或快照,确认结果稳定。没有它,界面改动很容易让长 URL 被切碎、图片提示丢失、旧内容残留、密钥泄露,或者复制出来的记录变得不好用。
test_config33–40 ↗
async fn test_config() -> Config
作用:生成一份测试用配置。它让测试不用依赖真实用户目录,也不用读真实配置文件。
数据流:进去没有业务输入,只读取系统临时目录 → 用临时目录当 Codex 的家目录创建 Config → 出来一份可在测试里安全修改的配置。
调用关系:多个异步测试会先调用它拿到干净配置,再去测试会话信息、MCP 工具列表和推理摘要的显示。
调用图:被 7 处调用(mcp_tools_output_lists_tools_for_hyphenated_server_names, mcp_tools_output_masks_sensitive_values, reasoning_summary_block_respects_config_overrides, session_info_availability_nux_tooltip_snapshot, session_info_first_event_suppresses_tooltips_and_nux, session_info_hides_tooltips_when_disabled, session_info_uses_availability_nux_tooltip_override);外部调用 2 个(default, temp_dir)。
test_cwd42–46 ↗
fn test_cwd() -> PathBuf
作用:返回一个稳定的测试工作目录。这样测试不会因为 Windows、Linux、macOS 的根目录写法不同而失败。
数据流:进去没有参数 → 读取系统临时目录 → 出来一个 PathBuf 路径。
调用关系:凡是需要“当前项目目录”的历史格子都会用它,比如 Markdown 渲染、计划渲染、推理摘要和合并流式消息测试。
调用图:被 17 处调用(agent_markdown_cell_does_not_split_words_after_inline_markdown, agent_markdown_cell_narrow_width_shows_prefix_only, agent_markdown_cell_renders_source_at_different_widths, agent_markdown_cell_survives_insert_history_rewrap, consolidation_walker_replaces_agent_message_cells, proposed_plan_cell_preserves_wrapped_table_web_links, proposed_plan_cell_renders_markdown_table, proposed_plan_cell_unwraps_markdown_fenced_table, reasoning_summary_block, reasoning_summary_block_falls_back_when_header_is_missing (+7 more));外部调用 1 个(temp_dir)。
streaming_agent_tail_blank_line_uses_one_viewport_row49–64 ↗
fn streaming_agent_tail_blank_line_uses_one_viewport_row()
作用:检查助手流式输出尾部里显式空行只占一行。它防止空行在终端里被多算高度。
数据流:进去是写死的三行内容,其中中间一行为空 → 创建 StreamingAgentTailCell 并渲染 → 断言显示文本和期望高度都是三行。
调用关系:它直接测试 StreamingAgentTailCell,不依赖本文件的辅助函数,只用快照和相等断言确认结果。
调用图:调用 1 个内部函数(new);外部调用 3 个(assert_eq!, assert_snapshot!, vec!)。
stdio_server_config66–105 ↗
fn stdio_server_config(
command: &str,
args: Vec<&str>,
env: Option<HashMap<String, String>>,
env_vars: Vec<&str>,
) -> McpServerConfig
作用:快速造一份“通过标准输入输出启动 MCP 服务器”的测试配置。标准输入输出可以理解成程序之间用键盘输入和屏幕输出那样传话。
数据流:进去命令、参数、环境变量和要继承的环境变量名 → 拼成 TOML 表并反序列化 → 出来 McpServerConfig。
调用关系:MCP 工具列表测试用它模拟本地服务器配置;它把环境变量表交给 string_map_to_toml_value 转成 TOML。
调用图:调用 1 个内部函数(string_map_to_toml_value);被 2 处调用(mcp_tools_output_lists_tools_for_hyphenated_server_names, mcp_tools_output_masks_sensitive_values);外部调用 4 个(new, Array, String, Table)。
streamable_http_server_config107–137 ↗
fn streamable_http_server_config(
url: &str,
bearer_token_env_var: Option<&str>,
http_headers: Option<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
) ->
作用:快速造一份“通过 HTTP 网络地址连接 MCP 服务器”的测试配置。HTTP 就是网页和接口常用的传输方式。
数据流:进去 URL、令牌环境变量名、固定请求头和来自环境变量的请求头 → 拼成 TOML 表并反序列化 → 出来 McpServerConfig。
调用关系:敏感值遮罩测试用它模拟远程 MCP 服务器;它也复用 string_map_to_toml_value 处理键值表。
调用图:调用 1 个内部函数(string_map_to_toml_value);被 1 处调用(mcp_tools_output_masks_sensitive_values);外部调用 3 个(new, String, Table)。
string_map_to_toml_value139–146 ↗
fn string_map_to_toml_value(entries: HashMap<String, String>) -> toml::Value
作用:把普通字符串键值表变成 TOML 配置值。TOML 是一种常见配置文件格式。
数据流:进去 HashMap<String, String> → 每个值包装成 TOML 字符串并收进 TOML 表 → 出来 toml::Value。
调用关系:stdio_server_config 和 streamable_http_server_config 都把环境变量或请求头交给它转换。
调用图:被 2 处调用(stdio_server_config, streamable_http_server_config);外部调用 1 个(Table)。
render_lines148–158 ↗
fn render_lines(lines: &[Line<'static>]) -> Vec<String>
作用:把带样式的终端行转成普通字符串,方便测试比较。它只看文字,不看颜色、粗体这些样式。
数据流:进去一组 ratatui 的 Line → 把每行里的多个 span 文本拼起来 → 出来 Vec<String>。
调用关系:几乎所有显示测试都会用它,把 HistoryCell 的渲染结果变成好断言的纯文本。
调用图:被 68 处调用(active_mcp_tool_call_snapshot, agent_markdown_cell_does_not_split_words_after_inline_markdown, agent_markdown_cell_narrow_width_shows_prefix_only, agent_markdown_cell_renders_source_at_different_widths, agent_markdown_cell_survives_insert_history_rewrap, coalesced_reads_dedupe_names, coalesces_reads_across_multiple_calls, coalesces_sequential_reads_within_one_call, completed_mcp_tool_call_accepts_data_url_image_blocks, completed_mcp_tool_call_error_snapshot (+15 more));外部调用 1 个(iter)。
render_transcript160–162 ↗
fn render_transcript(cell: &dyn HistoryCell) -> Vec<String>
作用:把某个历史格子的“ transcript 文本”转成普通字符串。transcript 可以理解成导出或复制用的聊天记录。
数据流:进去一个 HistoryCell → 用最大宽度取 transcript_lines → 再交给 render_lines 拼成字符串列表。
调用关系:会话信息、执行交互、推理摘要等测试用它检查复制记录里的内容,而不是屏幕富文本效果。
调用图:调用 2 个内部函数(transcript_lines, render_lines);被 11 处调用(reasoning_summary_block, reasoning_summary_block_falls_back_when_header_is_missing, reasoning_summary_block_falls_back_when_summary_is_missing, reasoning_summary_block_returns_reasoning_cell_when_feature_disabled, reasoning_summary_block_splits_header_and_summary_when_present, session_info_availability_nux_tooltip_snapshot, session_info_first_event_suppresses_tooltips_and_nux, session_info_hides_tooltips_when_disabled, session_info_uses_availability_nux_tooltip_override, unified_exec_interaction_cell_renders_input (+1 more))。
assert_unstyled_lines164–171 ↗
fn assert_unstyled_lines(lines: &[Line<'static>])
作用:确认一组行完全没有额外样式。它用来保护“原始模式”真的适合复制粘贴。
数据流:进去一组 Line → 检查每行和每个 span 的 Style 都是默认值 → 不返回值,失败就让测试报错。
调用关系:原始源码行、原始模式工具输出等测试会调用它,确保没有颜色或强调样式混进来。
调用图:被 3 处调用(raw_lines_from_source_preserves_explicit_blank_lines, source_backed_cells_render_raw_source_without_prefix_or_style, structured_tool_cell_renders_raw_plain_text_without_prefix_or_style);外部调用 1 个(assert_eq!)。
image_block173–176 ↗
fn image_block(data: &str) -> serde_json::Value
作用:造一个测试用的 MCP 图片内容块。内容块就是工具返回结果里的一个小片段。
数据流:进去图片数据字符串 → 包成 image/png 的 rmcp Content 并转成 JSON → 出来 serde_json::Value。
调用关系:MCP 工具调用完成后返回图片的测试用它构造假结果。
调用图:外部调用 2 个(image, to_value)。
text_block178–180 ↗
resource_link_block182–199 ↗
fn resource_link_block(
uri: &str,
name: &str,
title: Option<&str>,
description: Option<&str>,
) -> serde_json::Value
作用:造一个测试用的 MCP 资源链接内容块。资源链接可以理解成工具返回的文件或资料入口。
数据流:进去 URI、名字、标题和描述 → 组装 RawResource 并转成 JSON → 出来 serde_json::Value。
调用关系:多输出 MCP 工具结果测试用它确认资源链接能和文本一起显示。
调用图:外部调用 2 个(resource_link, to_value)。
raw_lines_from_source_preserves_explicit_blank_lines202–210 ↗
fn raw_lines_from_source_preserves_explicit_blank_lines()
作用:检查从源码文本提取原始行时,会保留中间的空白行。
数据流:进去固定字符串 alpha、空行、beta → 调 raw_lines_from_source → 断言结果包含空字符串行且没有样式。
调用关系:它测试底层原始行提取行为,并用 assert_unstyled_lines 做样式检查。
调用图:调用 1 个内部函数(assert_unstyled_lines);外部调用 1 个(assert_eq!)。
raw_lines_from_source_preserves_trailing_blank_but_not_trailing_newline213–219 ↗
fn raw_lines_from_source_preserves_trailing_blank_but_not_trailing_newline()
作用:检查原始行提取能保留真正的末尾空行,但不会把普通结尾换行多算一行。
数据流:进去带两个换行或空字符串的文本 → 调 raw_lines_from_source → 出来结果应分别是“内容加一个空行”或空列表。
调用关系:它直接守住 raw_lines_from_source 的边界行为,避免复制模式多出或少掉空行。
调用图:外部调用 1 个(assert_eq!)。
source_backed_cells_render_raw_source_without_prefix_or_style222–287 ↗
fn source_backed_cells_render_raw_source_without_prefix_or_style()
作用:确认用户、助手、推理摘要和计划这些有源码的格子,在原始模式下输出原文。
数据流:进去几段带空行、表格、代码块的文本 → 创建对应历史格子并读取 raw_lines → 断言没有前缀、没有样式、空行保留。
调用关系:它调用 test_cwd 提供目录,并用 assert_unstyled_lines 检查原始复制体验。
调用图:调用 4 个内部函数(new, new, assert_unstyled_lines, test_cwd);外部调用 2 个(new, assert_eq!)。
proposed_plan_cell_renders_markdown_table290–314 ↗
fn proposed_plan_cell_renders_markdown_table()
作用:检查建议计划里的 Markdown 表格在富文本模式下会被渲染成漂亮表格,而不是原样露出竖线文本。
数据流:进去一段含表格的计划 Markdown → 创建计划格子并渲染 → 断言富文本有表格分隔线,原始模式仍保留 Markdown 源码。
调用关系:它通过 test_cwd 创建上下文,用 render_lines 把显示结果转成字符串比较。
调用图:调用 2 个内部函数(render_lines, test_cwd);外部调用 1 个(assert!)。
proposed_plan_cell_preserves_wrapped_table_web_links317–336 ↗
composite_cell_preserves_child_web_links339–355 ↗
fn composite_cell_preserves_child_web_links()
作用:检查组合格子不会弄丢子格子里的网页链接。
数据流:进去一个普通文本格子和一个链接格子 → 组合后渲染超链接行 → 断言链接位置和目标还在。
调用关系:它直接验证 CompositeHistoryCell 聚合多个 HistoryCell 时会保留子项的链接信息。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, vec!)。
proposed_plan_cell_unwraps_markdown_fenced_table358–375 ↗
fn proposed_plan_cell_unwraps_markdown_fenced_table()
作用:检查被 ```markdown 包起来的计划表格也会当表格显示,而不是当代码块显示。
数据流:进去一个 Markdown 代码围栏里的表格 → 创建计划格子并渲染 → 断言出现表格分隔线且不显示围栏标记。
调用关系:它和表格渲染测试一起保护 proposed plan 的特殊解析逻辑。
调用图:调用 2 个内部函数(render_lines, test_cwd);外部调用 1 个(assert!)。
structured_tool_cell_renders_raw_plain_text_without_prefix_or_style378–405 ↗
fn structured_tool_cell_renders_raw_plain_text_without_prefix_or_style()
作用:检查结构化工具调用在原始模式下给出可复制的纯文本结果。
数据流:进去一个模拟 MCP 调用和两行文本结果 → 完成工具格子后读取 raw_lines → 断言先有调用说明,后面是原始输出且无样式。
调用关系:它通过 text_block 构造工具结果,并用 assert_unstyled_lines 保证原始模式干净。
调用图:调用 2 个内部函数(assert_unstyled_lines, render_lines);外部调用 4 个(assert!, assert_eq!, json!, vec!)。
raw_mode_toggle_transcript_snapshot408–466 ↗
fn raw_mode_toggle_transcript_snapshot()
作用:用快照检查富文本模式和原始模式来回切换时,聊天记录显示是否稳定。
数据流:进去用户消息、助手 Markdown、工具输出的组合 → 分别按 Rich、Raw、Rich 渲染 → 出来一个快照字符串。
调用关系:它覆盖 display_lines_for_mode,是多种历史格子一起工作的集成式测试。
调用图:外部调用 5 个(assert!, format!, assert_snapshot!, json!, vec!)。
image_generation_call_renders_saved_path469–490 ↗
fn image_generation_call_renders_saved_path()
作用:检查图片生成记录会显示说明文字和保存路径。
数据流:进去调用 ID、状态、提示词和保存文件路径 → 创建图片生成格子并渲染 → 断言包含生成说明和 file URL 形式的保存位置。
调用关系:它直接测试 new_image_generation_call 产出的历史格子。
调用图:外部调用 2 个(assert_eq!, format!)。
session_configured_event492–515 ↗
fn session_configured_event(model: &str) -> ThreadSessionState
作用:造一个测试用的会话状态事件。它相当于模拟“一个聊天线程已经配置好了”。
数据流:进去模型名 → 填入线程 ID、权限、目录、审批策略等默认测试值 → 出来 ThreadSessionState。
调用关系:会话信息相关测试用它提供输入,避免每个测试重复写大段状态字段。
调用图:调用 2 个内部函数(read_only, new);被 4 处调用(session_info_availability_nux_tooltip_snapshot, session_info_first_event_suppresses_tooltips_and_nux, session_info_hides_tooltips_when_disabled, session_info_uses_availability_nux_tooltip_override);外部调用 2 个(new, new)。
unified_exec_interaction_cell_renders_input518–529 ↗
fn unified_exec_interaction_cell_renders_input()
作用:检查向后台终端发送输入时,聊天记录会写清楚发给了哪个命令和发送了什么。
数据流:进去命令显示名和多行输入 → 创建交互格子并取 transcript → 断言首行说明交互,后续行缩进显示输入。
调用关系:它用 render_transcript 检查导出记录里的表现。
调用图:调用 1 个内部函数(render_transcript);外部调用 1 个(assert_eq!)。
unified_exec_interaction_cell_renders_wait532–536 ↗
fn unified_exec_interaction_cell_renders_wait()
作用:检查没有输入内容时,后台终端交互会显示成“等待”。
数据流:进去空命令显示和空输入 → 创建交互格子 → transcript 应只有一行等待说明。
调用关系:它覆盖 UnifiedExecInteractionCell 的另一个分支,和输入渲染测试互补。
调用图:调用 1 个内部函数(render_transcript);外部调用 2 个(new, assert_eq!)。
final_message_separator_hides_short_worked_label_and_includes_runtime_metrics539–584 ↗
fn final_message_separator_hides_short_worked_label_and_includes_runtime_metrics()
作用:检查最终消息分隔线在耗时很短时不显示“工作了多久”,但会显示运行指标。
数据流:进去 12 秒和一组工具、API、WebSocket、流式事件耗时统计 → 渲染分隔线 → 断言指标都出现而 Worked for 不出现。
调用关系:它直接测试 FinalMessageSeparator 对 RuntimeMetricsSummary 的格式化。
调用图:调用 2 个内部函数(new, render_lines);外部调用 2 个(assert!, assert_eq!)。
final_message_separator_includes_worked_label_after_one_minute587–593 ↗
fn final_message_separator_includes_worked_label_after_one_minute()
作用:检查运行超过一分钟后,最终消息分隔线会显示“Worked for”。
数据流:进去 61 秒且没有指标 → 渲染分隔线 → 断言出现工作耗时标签。
调用关系:它补充前一个测试,确认短时隐藏、长时显示的阈值行为。
调用图:调用 2 个内部函数(new, render_lines);外部调用 2 个(assert!, assert_eq!)。
ps_output_empty_snapshot596–600 ↗
fn ps_output_empty_snapshot()
作用:检查后台进程列表为空时的显示快照。
数据流:进去空进程列表 → 创建输出格子并渲染 → 保存快照。
调用关系:它测试 new_unified_exec_processes_output 的空状态。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(new, assert_snapshot!)。
session_info_uses_availability_nux_tooltip_override603–617 ↗
async fn session_info_uses_availability_nux_tooltip_override()
作用:检查会话信息能显示模型可用性提示。NUX 通常指新用户引导提示。
数据流:进去测试配置、会话状态、提示文字和免费套餐 → 渲染 transcript → 断言提示文字出现。
调用关系:它先用 test_config 和 session_configured_event 造输入,再测试 new_session_info。
调用图:调用 3 个内部函数(render_transcript, session_configured_event, test_config);外部调用 1 个(assert!)。
session_info_availability_nux_tooltip_snapshot624–639 ↗
async fn session_info_availability_nux_tooltip_snapshot()
作用:用快照记录模型可用性提示在会话信息里的完整样子。
数据流:进去带固定 cwd 的配置和会话状态 → 创建会话信息格子 → 输出 transcript 快照。
调用关系:它和上一项类似,但更关注完整排版;Windows 路径不同所以跳过。
调用图:调用 3 个内部函数(render_transcript, session_configured_event, test_config);外部调用 1 个(assert_snapshot!)。
session_info_first_event_suppresses_tooltips_and_nux642–657 ↗
async fn session_info_first_event_suppresses_tooltips_and_nux()
作用:检查第一条会话事件会显示入门提示,而不会显示额外可用性提示。
数据流:进去 is_first_event 为 true 的会话信息参数 → 渲染 transcript → 断言不含覆盖提示但含 To get started。
调用关系:它测试 new_session_info 在首次展示时的特殊分支。
调用图:调用 3 个内部函数(render_transcript, session_configured_event, test_config);外部调用 1 个(assert!)。
session_info_hides_tooltips_when_disabled660–675 ↗
async fn session_info_hides_tooltips_when_disabled()
作用:检查配置关闭工具提示后,会话信息不会显示提示文字。
数据流:进去 show_tooltips=false 的配置和提示文字 → 渲染 transcript → 断言提示文字被隐藏。
调用关系:它复用 test_config 和 session_configured_event,确认配置开关生效。
调用图:调用 3 个内部函数(render_transcript, session_configured_event, test_config);外部调用 1 个(assert!)。
ps_output_multiline_snapshot678–691 ↗
fn ps_output_multiline_snapshot()
作用:检查后台进程列表里命令和输出片段都是多行时的显示。
数据流:进去两个进程详情,包括多行命令和 recent chunks → 渲染窄宽度文本 → 保存快照。
调用关系:它覆盖后台进程列表的常见复杂排版。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
cyber_policy_error_event_snapshot694–698 ↗
fn cyber_policy_error_event_snapshot()
作用:检查网络安全策略错误事件在正常宽度下的显示。
数据流:进去无额外参数 → 创建策略错误格子并渲染 → 保存快照。
调用关系:它直接测试 new_cyber_policy_error_event 的默认排版。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
cyber_policy_error_event_narrow_snapshot701–705 ↗
fn cyber_policy_error_event_narrow_snapshot()
作用:检查网络安全策略错误事件在窄终端下的换行。
数据流:进去无额外参数 → 用宽度 36 渲染错误格子 → 保存快照。
调用关系:它和正常宽度快照配对,防止窄屏提示变乱。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
ps_output_long_command_snapshot708–717 ↗
fn ps_output_long_command_snapshot()
作用:检查后台进程列表遇到很长命令时怎么换行。
数据流:进去一个长 rg 命令和一段输出 → 用窄宽度渲染 → 保存快照。
调用关系:它测试进程列表命令显示的长文本处理。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
ps_output_many_sessions_snapshot720–731 ↗
fn ps_output_many_sessions_snapshot()
作用:检查后台进程很多时,列表显示是否稳定。
数据流:进去 20 个模拟进程 → 渲染成文本 → 保存快照。
调用关系:它覆盖 new_unified_exec_processes_output 的大量项目场景。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
ps_output_chunk_leading_whitespace_snapshot734–744 ↗
fn ps_output_chunk_leading_whitespace_snapshot()
作用:检查后台进程输出片段开头有空格时不会被错误吃掉。
数据流:进去带缩进的 recent chunks → 渲染 → 保存快照。
调用关系:它保护输出片段的原始缩进,避免日志或代码片段被改形。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
error_event_oversized_input_snapshot747–753 ↗
fn error_event_oversized_input_snapshot()
作用:检查输入超长错误提示的显示。
数据流:进去一条最大长度错误消息 → 创建错误事件格子并渲染 → 保存快照。
调用关系:它直接测试 new_error_event 对长错误文案的呈现。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
mcp_tools_output_masks_sensitive_values756–819 ↗
async fn mcp_tools_output_masks_sensitive_values()
作用:检查 MCP 工具列表不会把令牌、请求头等敏感值明文显示出来。
数据流:进去含 secret、Authorization、API key 环境变量名的配置和工具清单 → 渲染 MCP 工具输出 → 快照应显示遮罩后的信息。
调用关系:它用 stdio_server_config、streamable_http_server_config 和 test_config 构造本地与 HTTP 服务器。
调用图:调用 4 个内部函数(render_lines, stdio_server_config, streamable_http_server_config, test_config);外部调用 4 个(new, assert_snapshot!, json!, vec!)。
mcp_tools_output_lists_tools_for_hyphenated_server_names822–859 ↗
async fn mcp_tools_output_lists_tools_for_hyphenated_server_names()
作用:检查服务器名字里有连字符时,工具仍能被正确归到对应服务器下。
数据流:进去 some-server 配置和 mcp__some_server__lookup 工具名 → 渲染工具列表 → 保存快照。
调用关系:它验证工具命名规则和服务器名转换之间能对上。
调用图:调用 3 个内部函数(render_lines, stdio_server_config, test_config);外部调用 5 个(from, new, assert_snapshot!, json!, vec!)。
mcp_tools_output_from_statuses_renders_status_only_servers862–889 ↗
fn mcp_tools_output_from_statuses_renders_status_only_servers()
作用:检查只从服务器状态生成 MCP 输出时,也能列出工具和认证状态。
数据流:进去一个带工具、无 server_info 的状态对象 → 用 ToolsAndAuthOnly 模式渲染 → 保存快照。
调用关系:它测试 new_mcp_tools_output_from_statuses 的简略模式。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
mcp_tools_output_from_statuses_renders_verbose_inventory892–935 ↗
fn mcp_tools_output_from_statuses_renders_verbose_inventory()
作用:检查 MCP 详细清单模式会显示工具、资源和资源模板。
数据流:进去带工具、资源、资源模板的服务器状态 → 用 Full 模式渲染 → 保存快照。
调用关系:它和简略模式测试互补,覆盖完整库存展示。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
empty_agent_message_cell_transcript938–942 ↗
fn empty_agent_message_cell_transcript()
作用:检查空助手消息在 transcript 里仍占一行。
数据流:进去一个默认空 Line → 创建 AgentMessageCell → 断言 transcript 是两个空格且高度为 1。
调用关系:它保护流式助手消息空行的导出行为。
调用图:调用 1 个内部函数(new);外部调用 2 个(assert_eq!, vec!)。
prefixed_wrapped_history_cell_indents_wrapped_lines945–965 ↗
fn prefixed_wrapped_history_cell_indents_wrapped_lines()
作用:检查带前缀的长行换行后,后续行会按规则缩进。
数据流:进去带样式的长摘要、前缀和续行缩进 → 渲染 24 宽 → 断言每行文本和缩进正确。
调用关系:它直接测试 PrefixedWrappedHistoryCell 的换行排版。
调用图:调用 2 个内部函数(new, render_lines);外部调用 3 个(from, assert_eq!, vec!)。
prefixed_wrapped_history_cell_does_not_split_url_like_token968–981 ↗
fn prefixed_wrapped_history_cell_does_not_split_url_like_token()
作用:检查像 URL 的长词不会被普通换行逻辑硬拆成多段。
数据流:进去一个很长的 URL-like 字符串 → 窄宽度渲染 → 断言完整 token 只出现在一行。
调用关系:它保护 PrefixedWrappedHistoryCell 对链接样文本的特殊处理。
调用图:调用 2 个内部函数(new, render_lines);外部调用 2 个(from, assert_eq!)。
unified_exec_interaction_cell_does_not_split_url_like_stdin_token984–997 ↗
fn unified_exec_interaction_cell_does_not_split_url_like_stdin_token()
作用:检查发给后台终端的长 URL-like 输入不会被拆开。
数据流:进去命令 true 和长 URL-like stdin → 窄宽度渲染 → 断言完整 token 保持一行。
调用关系:它把 URL-like 不拆分规则覆盖到 UnifiedExecInteractionCell。
调用图:调用 2 个内部函数(new, render_lines);外部调用 1 个(assert_eq!)。
prefixed_wrapped_history_cell_height_matches_wrapped_rendering1000–1034 ↗
unified_exec_interaction_cell_height_matches_wrapped_rendering1037–1070 ↗
web_search_history_cell_snapshot1073–1086 ↗
fn web_search_history_cell_snapshot()
作用:检查网页搜索记录在普通场景下的显示快照。
数据流:进去较长搜索词和 Search 动作 → 渲染宽度 64 → 保存快照。
调用关系:它测试 new_web_search_call 生成的历史格子。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
standalone_unix_update_available_history_cell_snapshot1089–1095 ↗
fn standalone_unix_update_available_history_cell_snapshot()
作用:检查 Unix 独立安装包有新版本时的更新提示。
数据流:进去版本号和 StandaloneUnix 更新动作 → 渲染 → 保存快照。
调用关系:它测试 UpdateAvailableHistoryCell 针对 Unix 的文案。
调用图:调用 2 个内部函数(new, render_lines);外部调用 1 个(assert_snapshot!)。
standalone_windows_update_available_history_cell_snapshot1098–1104 ↗
fn standalone_windows_update_available_history_cell_snapshot()
作用:检查 Windows 独立安装包有新版本时的更新提示。
数据流:进去版本号和 StandaloneWindows 更新动作 → 渲染 → 保存快照。
调用关系:它和 Unix 更新提示测试配对,覆盖不同平台文案。
调用图:调用 2 个内部函数(new, render_lines);外部调用 1 个(assert_snapshot!)。
web_search_history_cell_without_detail_snapshot1107–1112 ↗
fn web_search_history_cell_without_detail_snapshot()
作用:检查没有搜索详情时网页搜索记录的简略显示。
数据流:进去空查询和 Other 动作 → 渲染 → 保存快照。
调用关系:它覆盖 web search 格子的兜底分支。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(new, assert_snapshot!)。
web_search_history_cell_wraps_with_indented_continuation1115–1134 ↗
fn web_search_history_cell_wraps_with_indented_continuation()
作用:检查长搜索词换行后第二行会缩进。
数据流:进去长搜索词 → 宽度 64 渲染 → 断言第一行带搜索说明,第二行以两个空格开头。
调用关系:它精确断言网页搜索格子的换行格式。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_eq!)。
web_search_history_cell_short_query_does_not_wrap1137–1153 ↗
fn web_search_history_cell_short_query_does_not_wrap()
作用:检查短搜索词不会被无意义换行。
数据流:进去 short query → 宽度 64 渲染 → 断言只有一行。
调用关系:它和长搜索词换行测试互补。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_eq!)。
web_search_history_cell_transcript_snapshot1156–1169 ↗
fn web_search_history_cell_transcript_snapshot()
作用:检查网页搜索记录在 transcript 里的样子。
数据流:进去长搜索词 → 取 transcript_lines 并转字符串 → 保存快照。
调用关系:它区别于屏幕显示测试,关注导出记录。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
active_mcp_tool_call_snapshot1172–1190 ↗
fn active_mcp_tool_call_snapshot()
作用:检查正在执行的 MCP 工具调用怎么显示。
数据流:进去服务器、工具名和参数 → 创建 active 工具格子 → 渲染并保存快照。
调用关系:它测试 new_active_mcp_tool_call 的进行中状态,动画开启。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, json!)。
mcp_inventory_loading_snapshot1193–1198 ↗
fn mcp_inventory_loading_snapshot()
作用:检查 MCP 清单加载中的提示显示。
数据流:进去 animations_enabled=true → 创建加载格子并渲染 → 保存快照。
调用关系:它测试 new_mcp_inventory_loading 的动画状态。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_snapshot!)。
mcp_inventory_loading_without_animations_is_stable1201–1208 ↗
fn mcp_inventory_loading_without_animations_is_stable()
作用:检查关闭动画后,MCP 加载提示每次渲染都一样。
数据流:进去 animations_enabled=false → 连续渲染两次 → 断言两次完全相同且文案固定。
调用关系:它保证无动画模式适合测试和稳定终端输出。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_eq!)。
completed_mcp_tool_call_success_snapshot1211–1241 ↗
fn completed_mcp_tool_call_success_snapshot()
作用:检查 MCP 工具成功完成后的显示。
数据流:进去工具调用和一段文本结果 → complete 标记耗时并渲染 → 保存成功快照。
调用关系:它从 active 工具格子走到完成状态,测试 complete 的正常路径。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(assert!, assert_snapshot!, json!, vec!)。
completed_mcp_tool_call_image_after_text_returns_extra_cell1244–1274 ↗
fn completed_mcp_tool_call_image_after_text_returns_extra_cell()
作用:检查工具结果里文本后跟图片时,会额外生成图片显示格子。
数据流:进去文本块和图片块 → 完成工具调用 → complete 返回 extra_cell,渲染后应显示 image output。
调用关系:它用 image_block 和 text_block 测试图片结果分离逻辑。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(from_millis, assert_eq!, json!, vec!)。
completed_mcp_tool_call_accepts_data_url_image_blocks1277–1305 ↗
fn completed_mcp_tool_call_accepts_data_url_image_blocks()
作用:检查工具返回 data URL 形式的图片也能被识别。data URL 是把图片数据直接塞进字符串的一种格式。
数据流:进去 data:image/png;base64 开头的图片内容 → 完成工具调用 → 返回图片 extra_cell。
调用关系:它补充普通 base64 图片测试,确认图片解析更宽容。
调用图:调用 1 个内部函数(render_lines);外部调用 5 个(from_millis, assert_eq!, format!, json!, vec!)。
completed_mcp_tool_call_skips_invalid_image_blocks1308–1335 ↗
fn completed_mcp_tool_call_skips_invalid_image_blocks()
作用:检查工具结果里有坏图片数据时,会跳过坏的并使用后面的有效图片。
数据流:进去一个 not-base64 图片块和一个有效图片块 → 完成工具调用 → 仍返回图片 extra_cell。
调用关系:它测试图片结果解析的容错行为。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(from_millis, assert_eq!, json!, vec!)。
completed_mcp_tool_call_error_snapshot1338–1361 ↗
fn completed_mcp_tool_call_error_snapshot()
作用:检查 MCP 工具调用失败时的显示。
数据流:进去工具调用和 network timeout 错误 → complete 进入错误状态 → 渲染并保存快照。
调用关系:它覆盖 active 工具格子的失败路径。
调用图:调用 1 个内部函数(render_lines);外部调用 3 个(assert!, assert_snapshot!, json!)。
completed_mcp_tool_call_multiple_outputs_snapshot1364–1404 ↗
fn completed_mcp_tool_call_multiple_outputs_snapshot()
作用:检查 MCP 工具返回文本和资源链接等多个输出时的显示。
数据流:进去文本块和资源链接块 → 完成工具调用 → 用宽度 48 渲染并快照。
调用关系:它用 resource_link_block 覆盖多类型内容组合。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(assert!, assert_snapshot!, json!, vec!)。
completed_mcp_tool_call_wrapped_outputs_snapshot1407–1439 ↗
fn completed_mcp_tool_call_wrapped_outputs_snapshot()
作用:检查 MCP 工具参数和输出很长时会正确换行。
数据流:进去长查询参数和多行长文本结果 → 完成工具调用 → 窄宽度渲染快照。
调用关系:它保护工具调用显示在窄终端下仍可读。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(assert!, assert_snapshot!, json!, vec!)。
completed_mcp_tool_call_multiple_outputs_inline_snapshot1442–1475 ↗
fn completed_mcp_tool_call_multiple_outputs_inline_snapshot()
作用:检查多个短文本输出能紧凑地显示在工具结果里。
数据流:进去两个短文本块 → 完成工具调用 → 宽屏渲染并快照。
调用关系:它和长输出换行测试互补,覆盖内联展示路径。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(assert!, assert_snapshot!, json!, vec!)。
session_header_includes_reasoning_level_when_present1478–1495 ↗
fn session_header_includes_reasoning_level_when_present()
作用:检查会话头会显示模型的推理强度和 fast 状态。
数据流:进去模型名、高推理强度、show_fast_status=true → 渲染会话头 → 断言 model 行包含 gpt-4o high fast 和修改提示。
调用关系:它直接测试 SessionHeaderHistoryCell::new 的模型行格式。
调用图:调用 2 个内部函数(new, render_lines);外部调用 2 个(assert!, temp_dir)。
session_header_hides_fast_status_when_disabled1498–1515 ↗
fn session_header_hides_fast_status_when_disabled()
作用:检查关闭 fast 状态显示后,会话头不出现 fast 字样。
数据流:进去模型名、高推理强度、show_fast_status=false → 渲染 → 断言有 high 但没有 fast。
调用关系:它补充会话头模型行的显示开关。
调用图:调用 2 个内部函数(new, render_lines);外部调用 2 个(assert!, temp_dir)。
session_header_indicates_yolo_mode1522–1534 ↗
fn session_header_indicates_yolo_mode()
作用:检查会话头会标出 YOLO 模式。这里 YOLO 指无需审批、权限很宽的运行方式。
数据流:进去模型、目录和 yolo_mode=true → 渲染会话头 → 保存快照。
调用关系:它测试 with_yolo_mode 对 SessionHeaderHistoryCell 的影响。
调用图:调用 2 个内部函数(new, render_lines);外部调用 1 个(assert_snapshot!)。
yolo_mode_includes_managed_full_access_profiles1537–1547 ↗
fn yolo_mode_includes_managed_full_access_profiles()
作用:检查托管文件系统无限制权限加永不审批会被认定为 YOLO 权限。
数据流:进去 Managed 权限、网络启用、文件系统无限制和 AskForApproval::Never → 调 has_yolo_permissions → 断言为 true。
调用关系:它测试权限判断函数的包含条件。
调用图:外部调用 1 个(assert!)。
yolo_mode_excludes_external_sandbox_profiles1550–1559 ↗
fn yolo_mode_excludes_external_sandbox_profiles()
作用:检查外部沙箱权限不会被误判成 YOLO。
数据流:进去 External 权限和永不审批 → 调 has_yolo_permissions → 断言为 false。
调用关系:它和托管全权限测试一起防止权限提示误报。
调用图:外部调用 1 个(assert!)。
session_header_directory_center_truncates1562–1572 ↗
fn session_header_directory_center_truncates()
作用:检查会话头里的长目录会从中间省略,保留开头和结尾。
数据流:进去 home 目录下多级路径和最大宽度 24 → 调 format_directory_inner → 断言得到带省略号的路径。
调用关系:它测试 SessionHeaderHistoryCell 的路径格式化辅助函数。
调用图:调用 1 个内部函数(format_directory_inner);外部调用 3 个(assert_eq!, home_dir, format!)。
session_header_directory_front_truncates_long_segment1575–1583 ↗
fn session_header_directory_front_truncates_long_segment()
作用:检查单个路径段太长时,会从前面省略。
数据流:进去 home 下一个超长目录名和宽度 18 → 格式化 → 断言保留尾部字符并加省略号。
调用关系:它补充目录格式化对“一个长名字”的处理。
调用图:调用 1 个内部函数(format_directory_inner);外部调用 3 个(assert_eq!, home_dir, format!)。
coalesces_sequential_reads_within_one_call1586–1624 ↗
fn coalesces_sequential_reads_within_one_call()
作用:检查一次命令里连续读取多个文件时,显示会把读取动作合并得更简洁。
数据流:进去一个 ExecCell,包含搜索后连续两个 Read 命令 → 标记完成并渲染 → 保存快照。
调用关系:它测试 ExecCell 对 ParsedCommand::Read 的合并显示。
调用图:调用 2 个内部函数(new, render_lines);外部调用 5 个(from_millis, now, assert_snapshot!, default, vec!)。
coalesces_reads_across_multiple_calls1627–1681 ↗
fn coalesces_reads_across_multiple_calls()
作用:检查多个连续命令调用中的文件读取也能被合并展示。
数据流:进去一个先搜索、后两次分别读取文件的 ExecCell 流程 → 每次完成后追加调用 → 最后渲染快照。
调用关系:它覆盖 ExecCell::with_added_call 和跨调用合并逻辑。
调用图:调用 2 个内部函数(new, render_lines);外部调用 5 个(from_millis, now, assert_snapshot!, default, vec!)。
coalesced_reads_dedupe_names1684–1718 ↗
fn coalesced_reads_dedupe_names()
作用:检查合并读取文件时,同名文件不会重复显示。
数据流:进去包含两次 auth.rs 和一次 shimmer.rs 的 Read 列表 → 完成命令并渲染 → 保存快照。
调用关系:它测试读取合并里的去重规则。
调用图:调用 2 个内部函数(new, render_lines);外部调用 5 个(from_millis, now, assert_snapshot!, default, vec!)。
multiline_command_wraps_with_extra_indent_on_subsequent_lines1721–1746 ↗
fn multiline_command_wraps_with_extra_indent_on_subsequent_lines()
作用:检查多行命令在窄宽度下换行时,后续行有额外缩进。
数据流:进去两行 shell 命令 → 完成 ExecCell → 用宽度 28 渲染快照。
调用关系:它测试 ExecCell 命令文本的多行加换行排版。
调用图:调用 2 个内部函数(new, render_lines);外部调用 6 个(from_millis, now, new, assert_snapshot!, default, vec!)。
single_line_command_compact_when_fits1749–1769 ↗
fn single_line_command_compact_when_fits()
作用:检查短单行命令能紧凑显示在一行里。
数据流:进去 echo ok 命令 → 完成 ExecCell → 宽度 80 渲染快照。
调用关系:它覆盖 ExecCell 命令显示的简单路径。
调用图:调用 2 个内部函数(new, render_lines);外部调用 6 个(from_millis, now, new, assert_snapshot!, default, vec!)。
single_line_command_wraps_with_four_space_continuation1772–1792 ↗
fn single_line_command_wraps_with_four_space_continuation()
作用:检查很长单行命令换行后使用四个空格的续行缩进。
数据流:进去一个无空格长 token 的命令 → 完成 ExecCell → 宽度 24 渲染快照。
调用关系:它补充单行命令的窄屏换行路径。
调用图:调用 2 个内部函数(new, render_lines);外部调用 6 个(from_millis, now, new, assert_snapshot!, default, vec!)。
multiline_command_without_wrap_uses_branch_then_eight_spaces1795–1815 ↗
fn multiline_command_without_wrap_uses_branch_then_eight_spaces()
作用:检查多行命令本身不需要换行时,第二行使用分支符号和八空格缩进。
数据流:进去 echo one 和 echo two 两行命令 → 完成并宽屏渲染 → 保存快照。
调用关系:它测试多行命令但不软换行的排版分支。
调用图:调用 2 个内部函数(new, render_lines);外部调用 6 个(from_millis, now, new, assert_snapshot!, default, vec!)。
multiline_command_both_lines_wrap_with_correct_prefixes1818–1839 ↗
fn multiline_command_both_lines_wrap_with_correct_prefixes()
作用:检查多行命令的每一行都很长时,各自换行前缀正确。
数据流:进去两行都会换行的长 token → 完成 ExecCell → 宽度 28 渲染快照。
调用关系:它覆盖 ExecCell 命令排版最复杂的多行多换行场景。
调用图:调用 2 个内部函数(new, render_lines);外部调用 6 个(from_millis, now, new, assert_snapshot!, default, vec!)。
stderr_tail_more_than_five_lines_snapshot1842–1885 ↗
fn stderr_tail_more_than_five_lines_snapshot()
作用:检查命令失败且 stderr 超过五行时,错误输出如何截取和显示。
数据流:进去一个失败命令和 10 行 stderr → 完成 ExecCell → 渲染并保存快照。
调用关系:它测试 ExecCell 对非零退出码和长错误尾部的显示。
调用图:调用 1 个内部函数(new);外部调用 6 个(from_millis, now, new, new, assert_snapshot!, vec!)。
ran_cell_multiline_with_stderr_snapshot1888–1935 ↗
fn ran_cell_multiline_with_stderr_snapshot()
作用:检查已运行命令又长又失败时,命令和 stderr 块都能正确换行。
数据流:进去长命令和两行 stderr → 完成 ExecCell,退出码为 1 → 窄宽度渲染快照。
调用关系:它把命令换行和错误输出显示放在一起测试。
调用图:调用 1 个内部函数(new);外部调用 6 个(from_millis, now, new, new, assert_snapshot!, vec!)。
user_history_cell_wraps_and_prefixes_each_line_snapshot1937–1952 ↗
fn user_history_cell_wraps_and_prefixes_each_line_snapshot()
作用:检查用户消息长文本换行时,每行都有用户前缀。
数据流:进去一段七个词的消息 → 宽度 12 渲染 → 保存快照。
调用关系:它直接测试 UserHistoryCell 的基础显示。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(new, assert_snapshot!)。
user_history_cell_renders_remote_image_urls1955–1968 ↗
fn user_history_cell_renders_remote_image_urls()
作用:检查用户消息附带远程图片 URL 时,会显示图片编号和文字。
数据流:进去消息和一个 https 图片地址 → 渲染 → 断言包含 [Image #1] 和消息文本并快照。
调用关系:它测试 UserHistoryCell 对远程图片附件的展示。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(new, assert!, assert_snapshot!, vec!)。
user_history_cell_summarizes_inline_data_urls1971–1983 ↗
fn user_history_cell_summarizes_inline_data_urls()
作用:检查用户消息里的内联 data URL 图片不会把整串图片数据全显示出来。
数据流:进去消息和 data:image/png;base64 图片地址 → 渲染 → 断言显示图片编号和消息文本。
调用关系:它保护界面不会被内联图片大字符串撑爆。
调用图:调用 1 个内部函数(render_lines);外部调用 3 个(new, assert!, vec!)。
user_history_cell_numbers_multiple_remote_images1986–2002 ↗
fn user_history_cell_numbers_multiple_remote_images()
作用:检查多张远程图片会按顺序编号。
数据流:进去消息和两个图片 URL → 渲染 → 断言有 [Image #1]、[Image #2] 并保存快照。
调用关系:它补充 UserHistoryCell 多附件场景。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(new, assert!, assert_snapshot!, vec!)。
user_history_cell_height_matches_rendered_lines_with_remote_images2005–2024 ↗
fn user_history_cell_height_matches_rendered_lines_with_remote_images()
作用:检查带远程图片的用户消息,高度计算等于实际渲染行数。
数据流:进去两行消息和两张图片 → 计算 display_lines 长度、desired_height、desired_transcript_height → 断言三者一致。
调用关系:它防止图片附件让历史列表高度算错。
调用图:外部调用 3 个(new, assert_eq!, vec!)。
user_history_cell_trims_trailing_blank_message_lines2027–2043 ↗
fn user_history_cell_trims_trailing_blank_message_lines()
作用:检查用户消息末尾多余空白行会被收敛,不会拖出一大片空白。
数据流:进去带多个尾部空白行的消息和一张图片 → 渲染 → 断言末尾空白只剩一个且正文还在。
调用关系:它测试 UserHistoryCell 文本清理与图片显示的组合。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(new, assert!, assert_eq!, vec!)。
user_history_cell_trims_trailing_blank_message_lines_with_text_elements2046–2066 ↗
fn user_history_cell_trims_trailing_blank_message_lines_with_text_elements()
作用:检查带文本元素标注的用户消息,也会正确修剪尾部空白行。
数据流:进去 message、TextElement 和图片 URL → 渲染 → 断言尾部空白数量正确且 tokenized 出现。
调用关系:它覆盖 UserHistoryCell 使用 text_elements 的路径。
调用图:调用 1 个内部函数(render_lines);外部调用 4 个(new, assert!, assert_eq!, vec!)。
render_uses_wrapping_for_long_url_like_line2069–2115 ↗
plan_update_with_note_and_wrapping_snapshot2118–2146 ↗
fn plan_update_with_note_and_wrapping_snapshot()
作用:检查计划更新带说明文字和多步计划时,在窄宽度下如何换行。
数据流:进去一个 explanation 和三条不同状态的计划项 → 渲染宽度 32 → 保存快照。
调用关系:它测试 new_plan_update 的完整排版。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
plan_update_without_note_snapshot2149–2168 ↗
fn plan_update_without_note_snapshot()
作用:检查没有说明文字的计划更新显示。
数据流:进去两条计划项、explanation=None → 渲染宽度 40 → 保存快照。
调用关系:它覆盖 new_plan_update 的无备注分支。
调用图:调用 1 个内部函数(render_lines);外部调用 2 个(assert_snapshot!, vec!)。
plan_update_does_not_split_url_like_tokens_in_note_or_step2171–2203 ↗
fn plan_update_does_not_split_url_like_tokens_in_note_or_step()
作用:检查计划说明和步骤里的 URL-like 长词都不会被拆开。
数据流:进去备注 URL 和步骤 URL → 窄宽度渲染 → 断言两个完整 token 都各只出现在一行。
调用关系:它把 URL-like 不拆分规则覆盖到计划更新格子。
调用图:调用 1 个内部函数(render_lines);外部调用 3 个(assert_eq!, format!, vec!)。
reasoning_summary_block2206–2217 ↗
fn reasoning_summary_block()
作用:检查推理摘要块会把标题和正文分开,只显示摘要正文。
数据流:进去带粗体标题和正文的 Markdown → 创建推理摘要格子 → 显示和 transcript 都应是正文项目符号行。
调用关系:它调用 test_cwd、render_lines 和 render_transcript 测试新建摘要块的常规路径。
调用图:调用 3 个内部函数(render_lines, render_transcript, test_cwd);外部调用 1 个(assert_eq!)。
reasoning_summary_height_matches_wrapped_rendering_for_url_like_content2220–2262 ↗
fn reasoning_summary_height_matches_wrapped_rendering_for_url_like_content()
作用:检查推理摘要含超长 URL-like 内容时,高度计算和实际段落换行一致。
数据流:进去标题、长摘要和窄宽度 → 比较 desired_height、Paragraph 计算高度和 transcript 高度 → 渲染后断言项目符号可见。
调用关系:它深入测试 ReasoningSummaryCell 的高度和 render 配合。
调用图:调用 2 个内部函数(new, test_cwd);外部调用 7 个(new, new, new, from, assert!, assert_eq!, empty)。
reasoning_summary_block_returns_reasoning_cell_when_feature_disabled2265–2271 ↗
fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled()
作用:检查没有特殊标题结构时,推理摘要仍会作为普通推理格子显示。
数据流:进去普通正文 → 创建摘要块并渲染 transcript → 断言显示项目符号加正文。
调用关系:它覆盖 new_reasoning_summary_block 的回退路径之一。
调用图:调用 2 个内部函数(render_transcript, test_cwd);外部调用 1 个(assert_eq!)。
reasoning_summary_block_respects_config_overrides2274–2285 ↗
async fn reasoning_summary_block_respects_config_overrides()
作用:检查配置覆盖模型支持状态时,推理摘要块仍按预期显示。
数据流:进去测试配置并修改模型和支持标记,然后创建摘要块 → 渲染 → 断言只显示正文。
调用关系:它虽然创建了 config,但核心仍是验证摘要块解析不会被配置场景破坏。
调用图:调用 3 个内部函数(render_lines, test_config, test_cwd);外部调用 1 个(assert_eq!)。
reasoning_summary_block_falls_back_when_header_is_missing2288–2296 ↗
fn reasoning_summary_block_falls_back_when_header_is_missing()
作用:检查推理摘要标题格式不完整时,会保守地显示原内容。
数据流:进去缺少结束粗体标记的文本 → 渲染 transcript → 断言原文本带项目符号显示。
调用关系:它测试 Markdown-like 解析失败时的兜底行为。
调用图:调用 2 个内部函数(render_transcript, test_cwd);外部调用 1 个(assert_eq!)。
reasoning_summary_block_falls_back_when_summary_is_missing2299–2315 ↗
fn reasoning_summary_block_falls_back_when_summary_is_missing()
作用:检查只有标题没有正文时,会把标题当成可显示内容。
数据流:进去只有粗体标题或标题后只有空白的文本 → 渲染 transcript → 断言显示去掉粗体后的标题。
调用关系:它覆盖摘要正文缺失的两个边界场景。
调用图:调用 2 个内部函数(render_transcript, test_cwd);外部调用 1 个(assert_eq!)。
reasoning_summary_block_splits_header_and_summary_when_present2318–2329 ↗
fn reasoning_summary_block_splits_header_and_summary_when_present()
作用:检查标题和摘要都存在时,真正显示的是摘要。
数据流:进去“高层计划”标题和一句摘要 → 渲染显示与 transcript → 两者都应只含摘要正文。
调用关系:它是 reasoning_summary_block 的更明确拆分测试。
调用图:调用 3 个内部函数(render_lines, render_transcript, test_cwd);外部调用 1 个(assert_eq!)。
deprecation_notice_renders_summary_with_details2332–2346 ↗
fn deprecation_notice_renders_summary_with_details()
作用:检查弃用提示会显示概要和详细替代建议。
数据流:进去摘要和 details → 创建弃用提示格子并渲染 → 断言第一行有警告图标,第二行是详细说明。
调用关系:它直接测试 new_deprecation_notice 的输出。
调用图:调用 1 个内部函数(render_lines);外部调用 1 个(assert_eq!)。
agent_markdown_cell_renders_source_at_different_widths2349–2366 ↗
fn agent_markdown_cell_renders_source_at_different_widths()
作用:检查助手 Markdown 消息会随终端宽度不同而重新换行。
数据流:进去一段长助手消息 → 分别用宽度 80 和 32 渲染 → 断言首行有助手项目符号且窄屏行数更多。
调用关系:它测试 AgentMarkdownCell 的响应式排版。
调用图:调用 3 个内部函数(new, render_lines, test_cwd);外部调用 1 个(assert!)。
agent_markdown_cell_does_not_split_words_after_inline_markdown2369–2382 ↗
fn agent_markdown_cell_does_not_split_words_after_inline_markdown()
作用:检查内联 Markdown 样式附近换行时,不会把单词切坏。
数据流:进去含粗体、斜体、代码、删除线和链接的长段落 → 宽度 190 渲染 → 断言换行发生在完整单词边界。
调用关系:它保护 Markdown 渲染和软换行之间的配合。
调用图:调用 3 个内部函数(new, render_lines, test_cwd);外部调用 1 个(assert!)。
streamed_agent_list_paragraph_preserves_item_indent_when_wrapped2385–2406 ↗
fn streamed_agent_list_paragraph_preserves_item_indent_when_wrapped()
作用:检查流式助手消息里列表段落换行后仍保留列表缩进。
数据流:进去编号列表标题、空行和缩进段落 → 宽度 64 渲染 → 断言相关续行都有足够空格并保存快照。
调用关系:它测试 AgentMessageCell 的流式文本缩进,而不是最终 Markdown 格子。
调用图:调用 2 个内部函数(new, render_lines);外部调用 3 个(assert!, assert_snapshot!, vec!)。
agent_markdown_cell_narrow_width_shows_prefix_only2409–2415 ↗
fn agent_markdown_cell_narrow_width_shows_prefix_only()
作用:检查终端极窄时,助手 Markdown 至少显示前缀而不会崩。
数据流:进去普通文本 → 用宽度 2 渲染 → 断言结果只有助手前缀。
调用关系:它覆盖 AgentMarkdownCell 的极端宽度保护。
调用图:调用 3 个内部函数(new, render_lines, test_cwd);外部调用 1 个(assert_eq!)。
wrapped_and_prefixed_cells_handle_tiny_widths2418–2456 ↗
render_clears_area_when_cell_content_shrinks2459–2493 ↗
agent_markdown_cell_survives_insert_history_rewrap2496–2516 ↗
fn agent_markdown_cell_survives_insert_history_rewrap()
作用:检查已经适配宽度的助手 Markdown 行,再经过历史插入时的换行函数不会被改坏。
数据流:进去多行助手源码 → AgentMarkdownCell 渲染后再交给 word_wrap_lines → 比较前后纯文本完全相同。
调用关系:它模拟 App 插入历史行时的二次换行流程。
调用图:调用 4 个内部函数(new, render_lines, test_cwd, word_wrap_lines);外部调用 1 个(assert_eq!)。
consolidation_walker_replaces_agent_message_cells2521–2590 ↗
fn consolidation_walker_replaces_agent_message_cells()
作用:模拟应用把多段流式助手消息合并成一个最终 Markdown 消息的过程。
数据流:进去用户格子和三个 AgentMessageCell → 向后查找同一段助手消息范围 → 用 AgentMarkdownCell 替换并断言列表结构正确。
调用关系:它复刻 App::handle_event 里的 backward-walk 合并逻辑,确保连续流式消息能被正确替换。
调用图:调用 3 个内部函数(new, new, test_cwd);外部调用 6 个(new, new, assert!, assert_eq!, once, vec!)。
tui/src/markdown_render_tests.rs源码 ↗
这个文件像一份“显示效果验收清单”。Markdown 是一种用普通符号写格式的文本,比如 # 表示标题、- 表示列表、``` 表示代码块。这里的测试把各种 Markdown 片段喂给渲染器,再检查输出的终端文本、颜色样式、缩进、换行和链接目标是否正确。它覆盖了很多容易出错的地方:引用块套列表、代码块保留空行、文件链接变成相对路径、长表格在窄屏下改成键值记录、URL 里的特殊字符不被截断等。测试里还用快照测试(把一次正确输出存起来,以后对比)来保护复杂场景。没有这些测试,改渲染器时很容易“修好一个地方,弄坏另一个地方”。
render_markdown_text_for_cwd18–20 ↗
fn render_markdown_text_for_cwd(input: &str, cwd: &Path) -> Text<'static>
作用:这是测试用的小帮手,用指定的当前目录来渲染 Markdown。它主要服务文件链接测试,因为文件路径要根据当前目录决定显示成相对路径还是绝对路径。
数据流:输入是一段 Markdown 字符串和一个目录路径;它把宽度设为空、把目录传进去,调用真正的渲染函数;输出是一份可以在终端显示的 Text 文本。
调用关系:多个文件链接测试会先找它帮忙;它自己不做渲染,只把活交给 render_markdown_text_with_width_and_cwd。
调用图:调用 1 个内部函数(render_markdown_text_with_width_and_cwd);被 12 处调用(file_link_appends_hash_anchor_when_label_lacks_it, file_link_appends_hash_range_when_label_lacks_it, file_link_appends_line_number_when_label_lacks_it, file_link_appends_range_when_label_lacks_it, file_link_decodes_percent_encoded_bare_path_destination, file_link_hides_destination, file_link_keeps_absolute_paths_outside_cwd, file_link_uses_target_path_for_hash_anchor, file_link_uses_target_path_for_hash_range, file_link_uses_target_path_for_range (+2 more))。
plain_lines22–32 ↗
fn plain_lines(text: &Text<'_>) -> Vec<String>
作用:这是测试用的取纯文本工具,把带颜色和样式的终端文本抽成普通字符串行。这样测试可以只关心内容和换行,不被颜色干扰。
数据流:输入是渲染后的 Text;它逐行把所有 Span 小片段拼起来;输出是 Vec<String>,也就是一行一行的纯文字。
调用关系:一些列表和表格测试会调用它做简化比较;它不再调用项目里的渲染逻辑,只负责把结果摊平。
调用图:被 3 处调用(list_item_after_code_block_keeps_blank_separator, outer_list_item_after_nested_code_block_keeps_blank_separator, table_key_value_fallback_preserves_rich_values_and_themed_labels)。
bare_url_with_tilde_keeps_complete_hyperlink35–62 ↗
fn bare_url_with_tilde_keeps_complete_hyperlink()
作用:检查裸 URL 里带 ~ 符号时,链接不会被截断。这个很重要,因为很多大学或个人网页地址都含有 ~。
数据流:输入是一个包含 ~ 的网址;测试把它交给按宽度渲染的函数,取出显示文字和超链接范围;最后用快照确认文字和链接目标都完整。
调用关系:测试运行器调用它;它直接检查 render_markdown_lines_with_width_and_cwd 生成的带链接行。
调用图:调用 1 个内部函数(render_markdown_lines_with_width_and_cwd);外部调用 1 个(assert_debug_snapshot!)。
table_url_with_tilde_keeps_complete_hyperlink65–82 ↗
fn table_url_with_tilde_keeps_complete_hyperlink()
作用:检查表格单元格里的带 ~ URL 也能保持完整链接。它防止表格渲染时把链接目标切坏。
数据流:输入是一张只有 URL 的 Markdown 表格;渲染后收集所有超链接目标;断言每个目标都等于原始网址。
调用关系:测试运行器调用它;它通过 render_markdown_lines_with_width_and_cwd 验证表格场景下的链接处理。
调用图:调用 1 个内部函数(render_markdown_lines_with_width_and_cwd);外部调用 3 个(assert!, assert_eq!, format!)。
merged_text_events_preserve_entity_decoding85–118 ↗
fn merged_text_events_preserve_entity_decoding()
作用:检查 HTML 实体,比如 &,在合并文本片段后仍会正确变成 &。这避免用户看到一堆转义符号。
数据流:输入是包含 & 和 ~ 的网址;渲染后取显示文字和链接范围;输出预期是解码后的网址和完整超链接。
调用关系:测试运行器调用它;它用 render_markdown_lines_with_width_and_cwd 验证解析和链接识别的配合。
调用图:调用 1 个内部函数(render_markdown_lines_with_width_and_cwd);外部调用 1 个(assert_eq!)。
empty121–123 ↗
fn empty()
作用:检查空 Markdown 会渲染成空文本。它保证最简单输入不会产生多余空行。
数据流:输入是空字符串;渲染函数返回默认 Text;测试比较两者完全相同。
调用关系:测试运行器调用它;它验证 render_markdown_text 的空输入行为。
调用图:外部调用 1 个(assert_eq!)。
paragraph_single126–131 ↗
fn paragraph_single()
作用:检查普通一句话会原样显示。它是段落渲染的最基础用例。
数据流:输入是 Hello, world!;渲染后应得到同样的一行文字;测试用相等断言确认。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的普通段落路径。
调用图:外部调用 1 个(assert_eq!)。
paragraph_soft_break134–139 ↗
fn paragraph_soft_break()
作用:检查段落里的普通换行会显示成两行。这样用户写的换行不会被吞掉。
数据流:输入是 Hello 换行 World;渲染后得到两行 Text;测试比较行内容。
调用关系:测试运行器调用它;它验证 render_markdown_text 对软换行的处理。
调用图:外部调用 1 个(assert_eq!)。
paragraph_multiple142–147 ↗
fn paragraph_multiple()
作用:检查两个段落之间会保留空行。它保证文章结构不会挤在一起。
数据流:输入是两段文字,中间有空行;渲染后应是第一段、空行、第二段;测试比较结果。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的段落分隔规则。
调用图:外部调用 1 个(assert_eq!)。
headings150–167 ↗
fn headings()
作用:检查一到六级标题的显示样式。标题要有不同粗体、斜体、下划线效果,用户才能看出层级。
数据流:输入是 # 到 ###### 六种标题;渲染后构造预期 Text,包含标题符号和样式;最后比较完整结果。
调用关系:测试运行器调用它;它把样例交给 render_markdown_text,再和 ratatui 的 Line/Text 预期值比较。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
blockquote_single170–174 ↗
fn blockquote_single()
作用:检查单行引用块会带上 > 前缀并变成绿色。引用块就像书里引用别人说的话,要和正文区分开。
数据流:输入是 > Blockquote;渲染后应显示 > Blockquote 且整行有绿色样式;测试比较结果。
调用关系:测试运行器调用它;它验证 render_markdown_text 的基础引用块渲染。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from, assert_eq!)。
blockquote_soft_break177–197 ↗
fn blockquote_soft_break()
作用:检查引用块里的软换行会继续带 >。这防止引用第二行看起来像普通正文。
数据流:输入是两行引用内容,其中第二行是懒续行;渲染后抽出纯文字;输出应是两行都带 >。
调用关系:测试运行器调用它;它通过 render_markdown_text 验证引用块续行逻辑。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_multiple_with_break200–208 ↗
fn blockquote_multiple_with_break()
作用:检查两个引用段落中间会有空行。这样分开的引用不会粘成一段。
数据流:输入是两个 > 段落,中间空行;渲染后应有引用行、空行、引用行;测试比较 Text。
调用关系:测试运行器调用它;它验证 render_markdown_text 对引用块段落间隔的处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
blockquote_three_paragraphs_short_lines211–222 ↗
fn blockquote_three_paragraphs_short_lines()
作用:检查引用块里短段落和空引用行都能保留。它保护 > 单独一行这种常见写法。
数据流:输入是 three 段引用,段间有 > 空行;渲染后每行都带引用前缀;测试比较完整行序列。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的引用块空行处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
blockquote_nested_two_levels225–234 ↗
fn blockquote_nested_two_levels()
作用:检查两层嵌套引用会显示两个 >。这让读者能看出“引用里的引用”。
数据流:输入是一层和两层引用;渲染后应出现外层行、分隔行、内层带两个 > 的行;测试比较结果。
调用关系:测试运行器调用它;它验证 render_markdown_text 的嵌套引用层级。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
blockquote_with_list_items237–245 ↗
fn blockquote_with_list_items()
作用:检查引用块里放无序列表时,> 和 - 都能正确出现。它避免引用中的列表丢掉层次。
数据流:输入是 > - item 两行;渲染后每行先有引用前缀,再有列表符号;测试比较 Text。
调用关系:测试运行器调用它;它验证 render_markdown_text 中引用块和列表的组合。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
blockquote_with_ordered_list248–266 ↗
fn blockquote_with_ordered_list()
作用:检查引用块里的有序列表能保留编号样式。编号要显示成浅蓝色,同时整行仍属于引用。
数据流:输入是引用里的 1. 和 2.;渲染后构造带绿色引用和浅蓝编号的预期;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的引用块加有序列表路径。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(from_iter, from_iter, assert_eq!, vec!)。
blockquote_list_then_nested_blockquote269–277 ↗
fn blockquote_list_then_nested_blockquote()
作用:检查引用块里先是列表,再嵌套子引用时缩进正确。它防止复杂层级错位。
数据流:输入是引用中的列表项和该项下的子引用;渲染后应显示父项和缩进后的 > child;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 对列表和嵌套引用的协同处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
list_item_with_inline_blockquote_on_same_line280–288 ↗
fn list_item_with_inline_blockquote_on_same_line()
作用:检查列表项同一行直接写引用符号时不会拆坏。比如 1. > quoted 应看起来还是一行。
数据流:输入是 1. > quoted;渲染后把第一行 Span 拼成字符串;输出必须等于原来的可见文字。
调用关系:测试运行器调用它;它用 render_markdown_text 检查特殊列表写法。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_surrounded_by_blank_lines291–314 ↗
fn blockquote_surrounded_by_blank_lines()
作用:检查引用块前后的空行会保留。它保证正文、引用、正文之间有清楚分隔。
数据流:输入是 foo、空行、引用、空行、baz;渲染后抽纯文字;输出应保持五行结构。
调用关系:测试运行器调用它;它验证 render_markdown_text 的块级元素间距。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_in_ordered_list_on_next_line317–333 ↗
fn blockquote_in_ordered_list_on_next_line()
作用:检查有序列表项下一行的引用能贴回同一个列表标记后。它处理 1. 后面才写内容的情况。
数据流:输入是 1. 空项后跟缩进引用;渲染后纯文字应是一行 1. > quoted;测试确认。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的列表项内引用合并逻辑。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_in_unordered_list_on_next_line336–352 ↗
fn blockquote_in_unordered_list_on_next_line()
作用:检查无序列表空项下一行的引用能合并显示。这样 - 和引用内容不会被拆成奇怪两行。
数据流:输入是 - 后跟缩进引用;渲染后应是一行 - > quoted;测试比较纯文字。
调用关系:测试运行器调用它;它验证 render_markdown_text 对无序列表内引用的处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_two_paragraphs_inside_ordered_list_has_blank_line355–378 ↗
fn blockquote_two_paragraphs_inside_ordered_list_has_blank_line()
作用:检查有序列表里的引用有两段时,中间的空引用行和缩进都正确。它保护长说明里的引用格式。
数据流:输入是 1. 下的两段引用;渲染后应有第一段、缩进的 > 空行、第二段;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的列表内多段引用。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_inside_nested_list381–395 ↗
fn blockquote_inside_nested_list()
作用:检查嵌套列表里的引用保持深层缩进。它防止内层内容跑到外层去。
数据流:输入是有序列表、其中的无序列表、再里面的引用;渲染后抽纯文字;输出应保留 1、-、> 的层级。
调用关系:测试运行器调用它;它验证 render_markdown_text 的多层嵌套缩进。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
list_item_text_then_blockquote398–412 ↗
fn list_item_text_then_blockquote()
作用:检查列表项先有文字、后有引用时,两行都对齐。它避免后续引用被误认为新列表项。
数据流:输入是 1. before 后接缩进引用;渲染后应是列表文字一行、引用一行;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的列表项续块处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
list_item_blockquote_then_text415–429 ↗
fn list_item_blockquote_then_text()
作用:检查列表项先是引用、后接普通文字时,后面的文字仍归入引用。它符合 Markdown 的懒续行规则。
数据流:输入是空列表项、引用、再一行 after;渲染后应是 1. > quoted 和缩进 > after;测试确认。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的引用懒续行场景。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
list_item_text_blockquote_text432–446 ↗
fn list_item_text_blockquote_text()
作用:检查列表项里正文、引用、正文连续出现时格式稳定。它防止状态切换后缩进丢失。
数据流:输入是 before、quoted、after 三段;渲染后得到三行,后两行带相同引用缩进;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 在列表和引用之间来回切换。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_with_heading_and_paragraph449–471 ↗
fn blockquote_with_heading_and_paragraph()
作用:检查引用块里有标题和段落时,内容形状正确。这里主要看行结构,不重点看颜色。
数据流:输入是引用中的 # Heading 和 paragraph;渲染后抽纯文字;输出应含标题行、空引用行、段落行。
调用关系:测试运行器调用它;它通过 render_markdown_text 检查引用块内标题分隔。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_heading_inherits_heading_style474–489 ↗
fn blockquote_heading_inherits_heading_style()
作用:检查引用块里的标题仍然像标题一样加粗下划线,同时也保留引用样式。它保证嵌套格式不会互相覆盖。
数据流:输入是 > # test header;渲染后预期第一行同时有引用前缀和标题样式;测试比较 Text。
调用关系:测试运行器调用它;它验证 render_markdown_text 的样式叠加。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_with_code_block492–506 ↗
fn blockquote_with_code_block()
作用:检查引用块里的代码块只显示代码内容,并带引用前缀。代码围栏本身不应显示出来。
数据流:输入是引用中的 `` code ``;渲染后抽纯文字;输出应只有 > code。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的引用内代码块。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
blockquote_with_multiline_code_block509–523 ↗
fn blockquote_with_multiline_code_block()
作用:检查引用块中的多行代码块每行都带引用前缀。它保证代码不会脱离引用区域。
数据流:输入是引用里的两行代码;渲染后得到 > first 和 > second;测试比较纯文字。
调用关系:测试运行器调用它;它验证 render_markdown_text 对多行代码块和引用的组合。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
nested_blockquote_with_inline_and_fenced_code526–566 ↗
fn nested_blockquote_with_inline_and_fenced_code()
作用:检查嵌套引用里同时有行内代码和围栏代码时,显示结构不乱。它覆盖很复杂但真实会出现的回复格式。
数据流:输入是外层引用、内层引用、行内代码和代码块;渲染后抽纯文字;输出应保留层级并去掉代码围栏。
调用关系:测试运行器调用它;它综合验证 render_markdown_text 的引用、行内代码、代码块处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
list_unordered_single569–573 ↗
fn list_unordered_single()
作用:检查单个无序列表项能显示 - 符号和内容。它是列表渲染的基本用例。
数据流:输入是 - List item 1;渲染后预期为一行 - 加文本;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的无序列表入口。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
list_unordered_multiple576–583 ↗
fn list_unordered_multiple()
作用:检查多个无序列表项逐行显示。它保证列表不会合并成一行。
数据流:输入是两个 - 列表项;渲染后应得到两行;测试比较 Text。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的连续无序列表。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
list_ordered586–593 ↗
fn list_ordered()
作用:检查有序列表显示编号,并给编号上色。编号是结构提示,不能丢。
数据流:输入是 1. 和 2.;渲染后编号 Span 应为浅蓝色,文字正常;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的有序列表样式。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
list_nested596–603 ↗
fn list_nested()
作用:检查无序列表嵌套时内层会缩进。它让层级关系一眼能看出来。
数据流:输入是外层 - 和内层 -;渲染后第二行前面应有更多空格;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的列表缩进。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
list_ordered_custom_start606–613 ↗
fn list_ordered_custom_start()
作用:检查有序列表从 3 开始时不会被重写成 1。它尊重用户原文编号。
数据流:输入是 3. First 和 4. Second;渲染后编号保持 3、4;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 保留自定义起始编号。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
nested_unordered_in_ordered616–627 ↗
fn nested_unordered_in_ordered()
作用:检查有序列表里嵌套无序列表时,内外列表和空行处理正确。它防止第二个外层项粘到内层后面。
数据流:输入是 1. Outer、两个内层 -、2. Next;渲染后内层缩进,外层项前有分隔;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的混合列表嵌套。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
nested_ordered_in_unordered630–641 ↗
fn nested_ordered_in_unordered()
作用:检查无序列表里嵌套有序列表时编号和缩进正确。它保证混合列表可读。
数据流:输入是 - Outer、内层 1/2、- Last;渲染后内层编号缩进且上色;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的另一种混合列表嵌套。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
loose_list_item_multiple_paragraphs644–655 ↗
fn loose_list_item_multiple_paragraphs()
作用:检查一个列表项里有多个段落时会保留空行和缩进。它让长列表项读起来不挤。
数据流:输入是一个编号项含两段,再接第二项;渲染后有空行、续段缩进、第二项;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的松散列表项。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
tight_item_with_soft_break658–666 ↗
fn tight_item_with_soft_break()
作用:检查紧凑列表项里的软换行继续缩进。它防止同一项的第二行变成新项目。
数据流:输入是 - item line1 和缩进续行;渲染后第二行有两个空格缩进;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的紧凑列表续行。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
deeply_nested_mixed_three_levels669–680 ↗
fn deeply_nested_mixed_three_levels()
作用:检查三层混合列表的缩进和分隔。它保护深层列表不会错位。
数据流:输入是有序、无序、有序三层再回到外层;渲染后层级缩进和空行符合预期;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的深层混合列表。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
loose_items_due_to_blank_line_between_items683–691 ↗
fn loose_items_due_to_blank_line_between_items()
作用:检查两个列表项之间有空行时,最终显示仍保持紧凑。它避免多余空白破坏列表观感。
数据流:输入是 1. First、空行、2. Second;渲染后只显示两项;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 对列表项间空行的取舍。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
mixed_tight_then_loose_in_one_list694–702 ↗
fn mixed_tight_then_loose_in_one_list()
作用:检查同一个列表里紧凑项和松散项混用时,内容不会多出空行。它处理不规则 Markdown。
数据流:输入是一个普通项和一个下一行才写内容的项;渲染后显示两行列表;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的混合松紧列表。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
ordered_item_with_indented_continuation_is_tight705–713 ↗
fn ordered_item_with_indented_continuation_is_tight()
作用:检查有序列表项的缩进续行会作为同一项显示。它防止续行被误分段。
数据流:输入是 1. Foo 后接缩进 Bar;渲染后第二行只缩进,不加编号;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的有序列表续行。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
inline_code716–720 ↗
fn inline_code()
作用:检查反引号包住的行内代码会显示为代码样式。代码样式通常用青色突出。
数据流:输入是 Example of Inline code;渲染后普通文本和青色代码片段分开;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的行内代码样式。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(from_iter, assert_eq!)。
strong723–728 ↗
fn strong()
作用:检查双星号加粗能生效。加粗用于强调重要文字。
数据流:输入是 Strong;渲染后文字 Strong 带粗体样式;测试比较。
调用关系:测试运行器调用它;它验证 Markdown 加粗渲染。
调用图:外部调用 1 个(assert_eq!)。
emphasis731–736 ↗
fn emphasis()
作用:检查单星号斜体能生效。斜体用于轻度强调。
数据流:输入是 Emphasis;渲染后文字带斜体样式;测试比较。
调用关系:测试运行器调用它;它验证 Markdown 斜体渲染。
调用图:外部调用 1 个(assert_eq!)。
strikethrough739–744 ↗
fn strikethrough()
作用:检查双波浪线删除线能生效。删除线常用来表示过时或被划掉的内容。
数据流:输入是 ~~Strikethrough~~;渲染后文字带删除线样式;测试比较。
调用关系:测试运行器调用它;它验证 Markdown 删除线渲染。
调用图:外部调用 1 个(assert_eq!)。
strong_emphasis747–754 ↗
fn strong_emphasis()
作用:检查加粗里面再嵌斜体时样式能叠加。它防止复杂强调格式互相覆盖。
数据流:输入是 **Strong *emphasis***;渲染后 Strong 只加粗,emphasis 同时加粗和斜体;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text 验证嵌套样式。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from, assert_eq!)。
link757–766 ↗
fn link()
作用:检查普通网页链接会显示文字和目标地址。这样用户既看到标签,也知道点向哪里。
数据流:输入是 Link;渲染后显示 Link、括号和带下划线青色 URL;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的普通链接显示规则。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from, assert_eq!)。
load_location_suffix_regexes769–772 ↗
fn load_location_suffix_regexes()
作用:检查用于识别文件位置后缀的正则表达式能成功加载。正则表达式就是按规则匹配文字的工具。
数据流:它读取两个全局正则:冒号格式和井号锚点格式;如果初始化失败测试会失败;没有额外输出。
调用关系:测试运行器调用它;它不调用渲染器,只确保 COLON_LOCATION_SUFFIX_RE 和 HASH_LOCATION_SUFFIX_RE 可用。
file_link_hides_destination775–783 ↗
fn file_link_hides_destination()
作用:检查本地文件链接不会把完整目标地址再显示一遍。对文件链接来说,只显示清爽的路径更适合终端。
数据流:输入是标签和绝对文件路径,另给当前目录;渲染后应显示相对路径并标成青色;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 间接调用带当前目录的渲染函数。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_decodes_percent_encoded_bare_path_destination786–795 ↗
fn file_link_decodes_percent_encoded_bare_path_destination()
作用:检查文件路径里的百分号编码会变回正常字符。比如 %20 要显示成空格,%C3%A9 要显示成 é。
数据流:输入是带编码的文件路径链接;渲染时根据当前目录裁成相对路径并解码;输出是可读路径。
调用关系:测试运行器调用它;它使用 render_markdown_text_for_cwd 验证文件链接清理。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_appends_line_number_when_label_lacks_it798–807 ↗
fn file_link_appends_line_number_when_label_lacks_it()
作用:检查文件链接目标带行号,而标签没写行号时,会把行号补到显示文本。这样用户知道跳到哪一行。
数据流:输入是标签 markdown_render.rs 和目标路径 :74;渲染后显示相对路径加 :74;测试比较。
调用关系:测试运行器调用它;它依赖 render_markdown_text_for_cwd 处理当前目录和行号。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_keeps_absolute_paths_outside_cwd810–817 ↗
fn file_link_keeps_absolute_paths_outside_cwd()
作用:检查文件不在当前目录下面时,会保留绝对路径。这样不会显示成误导性的相对路径。
数据流:输入是一个位于当前目录外的文件路径;渲染后仍显示完整绝对路径和行号;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 检查路径裁剪边界。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_appends_hash_anchor_when_label_lacks_it820–830 ↗
fn file_link_appends_hash_anchor_when_label_lacks_it()
作用:检查 file:// 链接里的 #L74C3 锚点会变成 :74:3 并补到显示文本。锚点表示行列位置。
数据流:输入是 file URL,目标带 #L74C3,标签无位置;渲染后显示相对路径加 :74:3;测试比较。
调用关系:测试运行器调用它;它用 render_markdown_text_for_cwd 验证 hash 锚点解析。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_uses_target_path_for_hash_anchor833–843 ↗
fn file_link_uses_target_path_for_hash_anchor()
作用:检查标签自己带锚点时,最终仍以目标路径为准。这样显示不会被标签里的简写误导。
数据流:输入标签和目标都带 #L74C3;渲染后从目标文件路径生成相对路径和位置;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 检查文件链接规范化。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_appends_range_when_label_lacks_it846–856 ↗
fn file_link_appends_range_when_label_lacks_it()
作用:检查目标里带行列范围时,标签没写也会补上。范围告诉用户这段链接指向代码哪一段。
数据流:输入目标路径 :74:3-76:9;渲染后显示相对路径和完整范围;测试比较。
调用关系:测试运行器调用它;它依赖 render_markdown_text_for_cwd 解析冒号格式范围。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_uses_target_path_for_range859–869 ↗
fn file_link_uses_target_path_for_range()
作用:检查标签已经写范围时,显示仍根据目标路径生成。这样不会只信标签文字。
数据流:输入标签和目标都带 :74:3-76:9;渲染后显示目标相对路径加范围;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 验证目标优先规则。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_appends_hash_range_when_label_lacks_it872–882 ↗
fn file_link_appends_hash_range_when_label_lacks_it()
作用:检查 file:// 的 #L74C3-L76C9 范围能转成 :74:3-76:9。它让编辑器式链接在终端里可读。
数据流:输入是带 hash 范围的 file URL;渲染后显示相对路径和转换后的范围;测试比较。
调用关系:测试运行器调用它;它使用 render_markdown_text_for_cwd 覆盖 hash 范围解析。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
multiline_file_link_label_after_styled_prefix_does_not_panic885–896 ↗
fn multiline_file_link_label_after_styled_prefix_does_not_panic()
作用:检查加粗文字后面跟跨行文件链接标签时不会崩溃。这个测试主要防止程序因为奇怪输入直接 panic,也就是异常退出。
数据流:输入是加粗前缀、普通文字和跨行链接标签;渲染后应正常显示加粗前缀和文件位置;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 覆盖一个曾经容易出错的边界情况。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
file_link_uses_target_path_for_hash_range899–909 ↗
fn file_link_uses_target_path_for_hash_range()
作用:检查标签带 hash 范围时,最终仍用目标文件路径和范围显示。它保持文件链接规则一致。
数据流:输入标签和 file URL 都有 #L74C3-L76C9;渲染后输出相对路径加 :74:3-76:9;测试比较。
调用关系:测试运行器调用它;它使用 render_markdown_text_for_cwd 验证目标优先的 hash 范围。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 4 个(from_iter, new, from, assert_eq!)。
url_link_shows_destination912–921 ↗
fn url_link_shows_destination()
作用:检查普通网页链接会显示目标 URL,而不是像本地文件那样隐藏。用户需要知道外部链接会打开哪里。
数据流:输入是 docs;渲染后显示 docs 和括号里的青色下划线网址;测试比较。
调用关系:测试运行器调用它;它直接验证 render_markdown_text 的外部 URL 规则。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from, assert_eq!)。
markdown_render_file_link_snapshot924–942 ↗
fn markdown_render_file_link_snapshot()
作用:用快照记录文件链接在句子里的最终纯文本。它帮助发现以后输出格式的意外变化。
数据流:输入是一句话,里面有文件链接;渲染后拼成纯文本字符串;和保存的快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_for_cwd 生成结果,再交给 assert_snapshot!。
调用图:调用 1 个内部函数(render_markdown_text_for_cwd);外部调用 2 个(new, assert_snapshot!)。
unordered_list_local_file_link_stays_inline_with_following_text945–969 ↗
fn unordered_list_local_file_link_stays_inline_with_following_text()
作用:检查无序列表里的本地文件链接后接冒号说明时,链接和说明保持在同一段。它防止路径单独掉到一行。
数据流:输入是很长的列表项,开头是文件链接;按 72 宽度渲染;输出应正常换行并保持冒号说明紧跟路径。
调用关系:测试运行器调用它;它直接使用 render_markdown_text_with_width_and_cwd 测宽度和当前目录组合。
调用图:调用 1 个内部函数(render_markdown_text_with_width_and_cwd);外部调用 2 个(new, assert_eq!)。
unordered_list_local_file_link_soft_break_before_colon_stays_inline972–992 ↗
fn unordered_list_local_file_link_soft_break_before_colon_stays_inline()
作用:检查文件链接后面换行再写冒号时,渲染后仍合成同一行说明。它处理 Markdown 里的软换行。
数据流:输入是列表项中文件链接和下一行冒号说明;渲染后应是一行路径加冒号说明;测试比较。
调用关系:测试运行器调用它;它用 render_markdown_text_with_width_and_cwd 验证列表、文件链接、软换行的组合。
调用图:调用 1 个内部函数(render_markdown_text_with_width_and_cwd);外部调用 2 个(new, assert_eq!)。
consecutive_unordered_list_local_file_links_do_not_detach_paths995–1018 ↗
fn consecutive_unordered_list_local_file_links_do_not_detach_paths()
作用:检查连续两个文件链接列表项不会把路径和说明拆散。它保护成批文件说明的可读性。
数据流:输入是两个列表项,每个文件链接后下一行是冒号说明;渲染后应得到两行完整说明;测试比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_with_width_and_cwd 检查连续场景。
调用图:调用 1 个内部函数(render_markdown_text_with_width_and_cwd);外部调用 2 个(new, assert_eq!)。
code_block_known_lang_has_syntax_colors1021–1048 ↗
fn code_block_known_lang_has_syntax_colors()
作用:检查已知语言的代码块会有语法高亮。语法高亮就是给代码不同部分上色,方便阅读。
数据流:输入是 rust 代码块;渲染后确认内容保留,并检查至少一个片段有颜色;测试断言成功。
调用关系:测试运行器调用它;它验证 render_markdown_text 的代码高亮路径。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(assert!, assert_eq!)。
code_block_unknown_lang_plain1051–1077 ↗
fn code_block_unknown_lang_plain()
作用:检查未知语言的代码块按普通文本显示。这样不会乱套错误颜色。
数据流:输入是 xyzlang 代码块;渲染后内容保留,但所有片段都不带前景色;测试断言。
调用关系:测试运行器调用它;它验证 render_markdown_text 在无法识别语言时的降级行为。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(assert!, assert_eq!)。
code_block_no_lang_plain1080–1098 ↗
fn code_block_no_lang_plain()
作用:检查没有标语言的代码块会原样显示。它避免无语言代码被误高亮。
数据流:输入是不带语言名的围栏代码;渲染后抽出非空内容,应是 no lang specified;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的普通代码块路径。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
code_block_multiple_lines_root1101–1109 ↗
fn code_block_multiple_lines_root()
作用:检查顶层多行代码块会逐行保留。代码块里的换行不能丢。
数据流:输入是 first 和 second 两行代码;渲染后应得到两行代码文本;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的多行代码块输出。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
code_block_indented1112–1121 ↗
fn code_block_indented()
作用:检查缩进代码块会保留代码缩进。缩进是代码含义的一部分,尤其对 Python 这类语言很重要。
数据流:输入是四空格缩进的代码;渲染后每行保留代码块缩进和内部空格;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的缩进代码块。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
horizontal_rule_renders_em_dashes1124–1138 ↗
fn horizontal_rule_renders_em_dashes()
作用:检查 Markdown 分隔线 --- 会显示成一排长破折号。它让视觉分隔更清楚。
数据流:输入是 Before、分隔线、After;渲染后纯文字应为 Before、空行、———、空行、After;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的水平分割线规则。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
code_block_with_inner_triple_backticks_outer_four1141–1184 ↗
fn code_block_with_inner_triple_backticks_outer_four()
作用:检查四个反引号包住的代码块里可以包含三个反引号。它防止展示 Markdown 代码示例时提前结束代码块。
数据流:输入是外层四反引号的 text 代码块,里面有三反引号;渲染后修剪尾部空行并比较内容;输出应保留内部围栏。
调用关系:测试运行器调用它;它通过 render_markdown_text 验证复杂代码围栏解析。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
code_block_inside_unordered_list_item_is_indented1187–1201 ↗
fn code_block_inside_unordered_list_item_is_indented()
作用:检查无序列表项里的代码块会按列表缩进显示。它防止代码跑出列表范围。
数据流:输入是 - Item 后接缩进代码块;渲染后应有列表项、空行、缩进代码行;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的列表内代码块。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
code_block_multiple_lines_inside_unordered_list1204–1218 ↗
fn code_block_multiple_lines_inside_unordered_list()
作用:检查无序列表里的多行代码块每行都保持缩进。它保护列表内代码的结构。
数据流:输入是列表项和两行代码;渲染后输出 - Item、空行、两行缩进代码;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的列表内多行代码。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
code_block_inside_unordered_list_item_multiple_lines1221–1235 ↗
fn code_block_inside_unordered_list_item_multiple_lines()
作用:这是另一个无序列表多行代码块测试,用来防止相同行为回退。它重复覆盖一个重要边界。
数据流:输入是列表项和 first、second 两行代码;渲染后应保持同样缩进;测试比较。
调用关系:测试运行器调用它;它同样通过 render_markdown_text 检查列表内代码块稳定性。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
list_item_after_code_block_keeps_blank_separator1238–1250 ↗
fn list_item_after_code_block_keeps_blank_separator()
作用:检查列表项里的代码块结束后,下一个列表项前保留空行。它让代码块和后续项目不粘连。
数据流:输入是第一项带 rust 代码块,后接第二项;渲染后用 plain_lines 抽纯文本并比较,还保存快照。
调用关系:测试运行器调用它;它调用 render_markdown_text 和 plain_lines,再用断言和快照保护格式。
调用图:调用 2 个内部函数(render_markdown_text, plain_lines);外部调用 2 个(assert_eq!, assert_snapshot!)。
outer_list_item_after_nested_code_block_keeps_blank_separator1253–1268 ↗
fn outer_list_item_after_nested_code_block_keeps_blank_separator()
作用:检查嵌套列表里的代码块结束后,外层下一个项目前也有空行。它防止复杂列表层级被挤坏。
数据流:输入是外层第一项、内层项和代码块,再接外层第二项;渲染后抽纯文本比较行序。
调用关系:测试运行器调用它;它通过 render_markdown_text 和 plain_lines 验证嵌套代码块后的分隔。
调用图:调用 2 个内部函数(render_markdown_text, plain_lines);外部调用 1 个(assert_eq!)。
list_item_after_simple_item_stays_compact1271–1275 ↗
fn list_item_after_simple_item_stays_compact()
作用:检查普通列表项之间即使源码有空行,显示也保持紧凑。它避免不必要的空白。
数据流:输入是 1. First、空行、2. Second;渲染后纯行应只有两项;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的普通列表压缩规则。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
multiline_finding_items_are_separated_snapshot1278–1295 ↗
fn multiline_finding_items_are_separated_snapshot()
作用:用快照检查多段“发现项”列表的整体排版。它模拟真实报告里的长列表。
数据流:输入是一段含标题、编号项、缩进段落和行内代码的 Markdown;渲染后抽纯文本并保存快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text 生成复杂列表输出,再交给 assert_snapshot!。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_snapshot!)。
wrapped_list_item_is_separated_from_next_sibling1298–1311 ↗
fn wrapped_list_item_is_separated_from_next_sibling()
作用:检查列表项因为窄屏换行后,和下一个兄弟项之间有空行。它避免读者分不清上一项的续行和下一项。
数据流:输入是会在 24 宽度下换行的编号列表;渲染后应有续行缩进和分隔空行;测试比较。
调用关系:测试运行器调用它;它用 render_markdown_text_with_width 验证自动换行后的列表分隔。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_eq!)。
mixed_url_markdown_wraps_prose_without_splitting_words_snapshot1314–1318 ↗
fn mixed_url_markdown_wraps_prose_without_splitting_words_snapshot()
作用:用快照检查带删除线和链接的长段落换行时不会把单词硬切开。它保护英文说明的可读性。
数据流:输入是一段含样式和链接的长文本;按 48 宽渲染后抽纯文本;和快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_with_width 检查宽度换行。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_snapshot!)。
markdown_render_complex_snapshot1321–1389 ↗
fn markdown_render_complex_snapshot()
作用:这是一个大型综合快照测试,覆盖标题、链接、图片、引用、列表、表格、HTML、脚注、代码块等。它像一次总体彩排。
数据流:输入是一大段复杂 Markdown;渲染后把所有 Span 拼成纯文本;输出与保存的快照比较。
调用关系:测试运行器调用它;它直接检验 render_markdown_text 在复杂真实输入下的整体表现。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_snapshot!)。
ordered_item_with_code_block_and_nested_bullet1392–1415 ↗
fn ordered_item_with_code_block_and_nested_bullet()
作用:检查有序列表项里有代码块和嵌套 bullet 时,行序和缩进正确。它防止代码块后面的子项错位。
数据流:输入是两个编号项,第二项含代码块和嵌套列表;渲染后抽纯文字并比较预期行。
调用关系:测试运行器调用它;它通过 render_markdown_text 验证列表、代码块、行内代码组合。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
nested_five_levels_mixed_lists1418–1432 ↗
fn nested_five_levels_mixed_lists()
作用:检查五层混合列表的缩进一致。它保证很深的结构仍然可读。
数据流:输入是有序和无序交替的五层列表;渲染后构造预期 Text,逐层增加缩进;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的深度嵌套极限场景。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
html_inline_is_verbatim1435–1440 ↗
fn html_inline_is_verbatim()
作用:检查行内 HTML 会按原样显示。HTML 是网页标签,这里不解析成网页效果,只展示文字。
数据流:输入是 Hello <span>world</span>!;渲染后应保留 <span> 和 </span>;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的行内 HTML 原样输出。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(from_iter, assert_eq!)。
html_block_is_verbatim_multiline1443–1452 ↗
fn html_block_is_verbatim_multiline()
作用:检查多行 HTML 块会原样逐行显示。这样用户能看到真实标签内容。
数据流:输入是 div 块三行;渲染后输出三行对应标签和内容;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的 HTML 块处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
html_in_tight_ordered_item_soft_breaks_with_space1455–1463 ↗
fn html_in_tight_ordered_item_soft_breaks_with_space()
作用:检查紧凑有序列表项里的 HTML 续行缩进正确。它防止 HTML 行脱离列表。
数据流:输入是 1. Foo 后接缩进 <i>Bar</i>;渲染后第二行带列表续行缩进并保留标签;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的列表内 HTML 续行。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 3 个(from_iter, from_iter, assert_eq!)。
html_continuation_paragraph_in_unordered_item_indented1466–1475 ↗
fn html_continuation_paragraph_in_unordered_item_indented()
作用:检查无序列表项的续段落如果是 HTML,也会缩进。它让续段仍属于这个列表项。
数据流:输入是 - Item、空行、缩进 HTML;渲染后保留空行和两个空格缩进;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的列表续段 HTML。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
unordered_item_continuation_paragraph_is_indented1478–1500 ↗
fn unordered_item_continuation_paragraph_is_indented()
作用:检查无序列表项的普通续段落会缩进。它防止续段看起来像正文。
数据流:输入是 - Intro 后接两行续段;渲染后有空行和两行缩进内容;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的无序列表续段落。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
ordered_item_continuation_paragraph_is_indented1503–1512 ↗
fn ordered_item_continuation_paragraph_is_indented()
作用:检查有序列表项的续段落会按编号宽度缩进。这样段落仍明显属于该项。
数据流:输入是 1. Intro 后接缩进说明;渲染后有空行和三空格续段;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的有序列表续段落。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
nested_item_continuation_paragraph_is_indented1515–1527 ↗
fn nested_item_continuation_paragraph_is_indented()
作用:检查嵌套列表项的续段落缩进到正确层级。它防止续段跑出内层列表。
数据流:输入是 1. A、内层 - B、B 的续段、2. C;渲染后续段深缩进,外层第二项前有空行;测试比较。
调用关系:测试运行器调用它;它验证 render_markdown_text 的嵌套续段落。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 4 个(default, from_iter, from_iter, assert_eq!)。
code_block_preserves_trailing_blank_lines1530–1560 ↗
fn code_block_preserves_trailing_blank_lines()
作用:检查代码块内部结尾的空行不会丢。代码里的空行可能有意义,至少用户期待它被保留。
数据流:输入是 rust 代码块,代码行后还有一个空白行;渲染后找到代码行,并确认下一行是空字符串。
调用关系:测试运行器调用它;它通过 render_markdown_text 检查代码块空白保真。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(assert!, assert_eq!)。
table_renders_app_style_rows_with_themed_bold_header1563–1602 ↗
fn table_renders_app_style_rows_with_themed_bold_header()
作用:检查表格按应用自己的样式显示:表头加粗、有主题色,分隔线变暗。它保证表格看起来不像普通文本乱排。
数据流:输入是一张两列表格;渲染后比较可见行,并检查表头、分隔线、数据行的样式属性。
调用关系:测试运行器调用它;它验证 render_markdown_text 的表格样式输出。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 2 个(assert!, assert_eq!)。
table_alignment_respects_markers1605–1616 ↗
fn table_alignment_respects_markers()
作用:检查表格里的左对齐、居中、右对齐标记会被尊重。这样数字和文字列排版才清楚。
数据流:输入是带 :---、:---:、---: 的表格;渲染后检查表头和数据行的空格位置;测试比较。
调用关系:测试运行器调用它;它覆盖 render_markdown_text 的表格对齐规则。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert_eq!)。
table_separates_logical_rows_after_wrapped_content1619–1655 ↗
fn table_separates_logical_rows_after_wrapped_content()
作用:检查表格单元格内容换行后,逻辑行之间仍有分隔线。它防止长内容把下一行吞在一起。
数据流:输入是宽度 30 下会换行的表格;渲染后找包含长描述的多行和分隔线位置;断言第二条分隔线在换行内容之后。
调用关系:测试运行器调用它;它使用 render_markdown_text_with_width 检查窄宽度表格布局。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 2 个(assert!, assert_eq!)。
table_wraps_file_paths_before_collapsing_narrative_columns_snapshot1658–1671 ↗
fn table_wraps_file_paths_before_collapsing_narrative_columns_snapshot()
作用:用快照检查宽表格里文件路径列优先换行,而不是马上把整张表改成记录样式。它保护表格在较宽屏幕下的可读性。
数据流:输入是含本地文件链接和长说明的表格;按 120 宽度和当前目录渲染;抽纯文本和快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_with_width_and_cwd 同时测试表格、文件链接、宽度。
调用图:调用 1 个内部函数(render_markdown_text_with_width_and_cwd);外部调用 2 个(new, assert_snapshot!)。
table_renders_stacked_key_value_records_when_path_column_becomes_too_narrow_snapshot1674–1684 ↗
fn table_renders_stacked_key_value_records_when_path_column_becomes_too_narrow_snapshot()
作用:用快照检查当路径列太窄时,表格会退化成一条条键值记录。就像宽表格放不下时改成卡片列表。
数据流:输入是含很长路径的三列表格;按 42 宽渲染;输出纯文本和快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_with_width 验证表格窄屏降级。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_snapshot!)。
table_renders_records_when_multiple_prose_columns_are_starved_snapshot1687–1699 ↗
fn table_renders_records_when_multiple_prose_columns_are_starved_snapshot()
作用:用快照检查多个文字列都太挤时会转成记录式显示。它避免窄屏表格每列只剩几个字。
数据流:输入是 issue、activity、complexity、why start 表格;按 76 宽渲染;纯文本和快照比较。
调用关系:测试运行器调用它;它验证 render_markdown_text_with_width 的表格自适应策略。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_snapshot!)。
table_keeps_grid_when_only_one_compact_record_fragments_snapshot1702–1712 ↗
fn table_keeps_grid_when_only_one_compact_record_fragments_snapshot()
作用:检查只有少数紧凑内容稍微碎裂时,表格仍保持网格。它防止过早退化成键值记录。
数据流:输入是短键、日期、状态表;按 40 宽渲染;输出和快照比较。
调用关系:测试运行器调用它;它通过 render_markdown_text_with_width 保护表格降级阈值。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_snapshot!)。
table_renders_key_value_records_when_compact_fragmentation_is_systemic_snapshot1715–1725 ↗
fn table_renders_key_value_records_when_compact_fragmentation_is_systemic_snapshot()
作用:检查当紧凑表格整体都放不下时,会系统性改成键值记录。它保证极窄屏幕还有可读输出。
数据流:输入是 Key/Notes 表格,宽度只有 17;渲染后和快照比较记录式布局。
调用关系:测试运行器调用它;它验证 render_markdown_text_with_width 的极窄表格 fallback。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert_snapshot!)。
table_inside_blockquote_has_quote_prefix1728–1739 ↗
fn table_inside_blockquote_has_quote_prefix()
作用:检查引用块里的表格每行都有 > 前缀。它让表格仍然看起来属于引用内容。
数据流:输入是 > 开头的表格;渲染后抽纯文字;断言所有行都以 > 开头,并包含表格分隔线。
调用关系:测试运行器调用它;它通过 render_markdown_text 验证表格和引用块组合。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert!)。
escaped_pipes_render_in_table_cells1742–1752 ↗
fn escaped_pipes_render_in_table_cells()
作用:检查表格单元格里转义的竖线 \| 会显示成普通 |,而不是被当成分列。它保护含管道符的内容。
数据流:输入是一列表格,单元格内容是 a \| b;渲染后查找 a | b;测试断言存在。
调用关系:测试运行器调用它;它验证 render_markdown_text 的表格转义处理。
调用图:调用 1 个内部函数(render_markdown_text);外部调用 1 个(assert!)。
table_falls_back_to_key_value_records_if_grid_cannot_fit1755–1771 ↗
fn table_falls_back_to_key_value_records_if_grid_cannot_fit()
作用:检查列太多、网格完全放不下时,会退成键值记录而不是画破表格。它保证小宽度下仍可读。
数据流:输入是十列表格,宽度 20;渲染后确认能看到 c1 和 c10/10,且没有传统表格线;测试断言。
调用关系:测试运行器调用它;它使用 render_markdown_text_with_width 检查表格无法适配时的降级。
调用图:调用 1 个内部函数(render_markdown_text_with_width);外部调用 1 个(assert!)。
table_key_value_fallback_preserves_rich_values_and_themed_labels1774–1797 ↗
fn table_key_value_fallback_preserves_rich_values_and_themed_labels()
作用:检查表格退成键值记录后,链接、加粗、代码等丰富样式仍保留,标签也有主题样式。降级不能丢信息。
数据流:输入是含链接、粗体和代码的表格,宽度 16;渲染后用 plain_lines 查内容,再检查样式里有加粗、颜色和下划线。
调用关系:测试运行器调用它;它调用 render_markdown_text_with_width 和 plain_lines,验证窄屏 fallback 的内容与样式。
调用图:调用 2 个内部函数(render_markdown_text_with_width, plain_lines);外部调用 1 个(assert!)。
tui/src/render/renderable_tests.rs源码 ↗
终端界面空间很有限,几个组件上下排列时,谁该拿多少高度很容易出问题。这个测试文件就是给这种“分高度”的规则做验算。它先定义了一个假的 HeightRenderable,像一个只会说“我想要这么高”的占位组件,不真正画东西。然后用 FlexRenderable 把多个组件装进去,再给它一个固定大小的区域,让它算每个子组件最终能拿到多高。两个测试分别检查两件事:如果某个组件本来就很矮,剩下的空间要让给更需要空间的组件;如果有不参与弹性伸缩的组件,要先把它固定需要的高度留出来,再把剩余空间分给可伸缩组件。它重要的地方在于,这些规则一旦错了,用户看到的终端界面就可能出现空白、挤压或内容被错误截断。
HeightRenderable::with_height7–9 ↗
fn with_height(height: u16) -> Self
作用:这个函数用来快速造一个假的可显示组件,并指定它“想要”的高度。测试里用它来准备不同身高的样本,好检查分配规则。
数据流:进去的是一个高度数字 → 它把这个数字包进 HeightRenderable 这个简单结构里 → 出来的是一个带有固定期望高度的假组件,不会改动外部东西。
调用关系:两个测试函数都会先调用它来搭建测试场景。它不负责分配空间,只负责提供“这个子组件想要多高”的测试材料,之后这些材料会被放进 FlexRenderable 里参与计算。
调用图:被 2 处调用(flex_redistributes_space_unused_by_short_children, flex_reserves_non_flex_space_before_flexible_children)。
HeightRenderable::render13–13 ↗
fn render(&self, _area: Rect, _buf: &mut Buffer)
作用:这是为了满足 Renderable 这个接口而写的空绘制函数。因为这些测试只关心高度分配,不关心真正画到屏幕上的内容,所以它什么也不做。
数据流:进去的是一个绘制区域和一个缓冲区,缓冲区可以理解成准备写到终端屏幕上的草稿纸 → 它故意不往里面写任何东西 → 出来没有可见结果,也不改变缓冲区。
调用关系:它属于 HeightRenderable 对 Renderable 接口的实现,让这个假组件能被 FlexRenderable 当成正常组件接收。测试流程没有直接依赖它的绘制效果,因为这里验证的是布局,不是画面内容。
HeightRenderable::desired_height15–17 ↗
fn desired_height(&self, _width: u16) -> u16
作用:这个函数告诉布局系统:这个假组件在给定宽度下希望占多少高度。测试正是靠它模拟“有的组件很高,有的组件很矮”。
数据流:进去的是宽度数字,但这个假组件不看宽度 → 它直接读取自己保存的高度值 → 出来的是这个组件的期望高度。
调用关系:FlexRenderable 在计算每个子组件高度时会用到这种信息。HeightRenderable::with_height 先把期望高度存好,之后 desired_height 把这个高度报给布局算法。
flex_redistributes_space_unused_by_short_children21–43 ↗
fn flex_redistributes_space_unused_by_short_children()
作用:这个测试确认:当两个可伸缩组件一起分高度时,如果其中一个只需要很少高度,省下来的高度应该给另一个组件。也就是说,空间不能被矮组件白白占住。
数据流:开始时创建一个 FlexRenderable,再放入两个弹性权重相同的假组件:一个想要 20 高,一个只想要 2 高 → 给整个容器一个 80 宽、10 高的区域,让它计算分配结果 → 最后检查两个子区域的高度是否是 8 和 2,证明矮组件只拿自己需要的,剩余空间给了高组件。
调用关系:这个测试会调用 FlexRenderable::new 建立容器,用 HeightRenderable::with_height 准备假组件,再把它们包成 RenderableItem::Owned 放进去。最后它通过断言检查 allocate 的结果是否符合预期,用来保护“剩余空间重新分配”这条布局规则。
调用图:调用 2 个内部函数(new, with_height);外部调用 4 个(new, new, assert_eq!, Owned)。
flex_reserves_non_flex_space_before_flexible_children46–72 ↗
fn flex_reserves_non_flex_space_before_flexible_children()
作用:这个测试确认:不参与弹性伸缩的组件要先拿到自己的固定高度,然后剩下的高度才分给可伸缩组件。这样固定内容不会被弹性组件挤掉。
数据流:开始时创建一个 FlexRenderable,依次放入三个组件:第一个可伸缩且想要 20 高,中间一个不伸缩且想要 2 高,第三个可伸缩且想要 20 高 → 给整个容器一个 10 高的区域 → 它应先给中间固定组件 2 高,再把剩下 8 高平均分给两边,最后断言结果是 4、2、4。
调用关系:这个测试同样借助 HeightRenderable::with_height 创建假组件,并用 RenderableItem::Owned 交给 FlexRenderable。它专门覆盖“固定空间优先保留”的场景,和另一个测试一起说明 FlexRenderable 的高度分配既要尊重固定组件,也要合理处理弹性组件。
调用图:调用 2 个内部函数(new, with_height);外部调用 4 个(new, new, assert_eq!, Owned)。
tui/src/status/tests.rs源码 ↗
状态页是用户判断当前 Codex 正在用什么模型、能访问哪些文件、还剩多少额度的重要窗口。这个测试文件用临时配置和假数据,模拟很多真实场景:只读权限、工作区可写、自动审批、Bedrock 供应商、令牌用量、月度额度、积分、额度刷新中、额度过期、窄终端换行等。它先搭好测试用的配置,再调用真正生成状态卡片的函数,最后把终端行文本取出来,用断言或快照测试(把输出保存成标准答案,下次对比)确认显示没有变。文件里还有几个小帮手,比如把路径替换成固定占位符,避免不同电脑上的目录名导致测试不稳定。
stale_monthly_limit_marks_fresh_rolling_snapshot_stale55–80 ↗
fn stale_monthly_limit_marks_fresh_rolling_snapshot_stale()
作用:这个测试确认一个细节:如果月度花费限制的数据已经旧了,即使滚动窗口额度看起来是新的,整体也应该被标记为过期。这样用户不会被部分新鲜、部分陈旧的数据误导。
数据流:进去的是当前时间和一份手工造出的额度快照,其中滚动额度是当前时间,月度限制比当前时间早 20 分钟。函数把这份快照交给额度数据合成逻辑。出来的结果必须是「过期」状态,否则测试失败。
调用关系:它直接测试 compose_rate_limit_data_many 的判断规则,不依赖本文件的其他帮手。这个测试守住额度显示的安全边界:只要关键的月度限制不新鲜,状态页就不能装作一切正常。
app_server_workspace_write_profile82–119 ↗
fn app_server_workspace_write_profile(network_enabled: bool) -> PermissionProfile
作用:这个帮手造出一种接近应用服务器默认行为的权限配置:项目目录和临时目录可写,根目录只读,网络可按参数打开或限制。测试用它来模拟「工作区可写」但不是简单内置模板的情况。
数据流:进去的是一个布尔值,表示网络是否允许。函数据此拼出一个 PermissionProfile,也就是一份「哪些地方能读写、网络能不能用」的权限说明。出来的是这份权限配置,不改动外部状态。
调用关系:test_config 用它设置默认测试权限,status_permissions_non_default_workspace_write_uses_workspace_label 也直接用它验证自定义权限的显示文案。它让多个测试不用重复写一大段权限条目。
调用图:被 2 处调用(status_permissions_non_default_workspace_write_uses_workspace_label, test_config);外部调用 1 个(vec!)。
test_config121–136 ↗
async fn test_config(temp_home: &TempDir) -> Config
作用:这个异步帮手创建一份干净、可预测的测试配置。它避免测试读到用户电脑上的真实配置,保证每次测试都在同样的起点开始。
数据流:进去的是临时主目录。函数用 ConfigBuilder 加载配置,并关闭托管配置干扰,然后把审批人设为用户、把权限设成测试用的工作区可写配置。出来的是一份 Config,供各个测试继续修改。
调用关系:大多数测试都会先调用它,再按自己的场景改模型、权限、供应商或工作目录。它内部调用 app_server_workspace_write_profile 来准备基础权限,是整个测试文件的共同地基。
调用图:调用 2 个内部函数(without_managed_config_for_tests, app_server_workspace_write_profile);被 35 处调用(status_card_token_usage_excludes_cached_tokens, status_context_window_uses_last_usage, status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link, status_permissions_broadened_workspace_profile_shows_builtin_label, status_permissions_full_disk_managed_with_network_is_danger_full_access, status_permissions_full_disk_managed_without_network_is_external_sandbox, status_permissions_named_profile_shows_additional_writable_roots, status_permissions_named_read_only_profile_shows_builtin_label, status_permissions_named_workspace_profile_shows_builtin_label, status_permissions_non_default_workspace_write_uses_workspace_label (+15 more));外部调用 2 个(path, default)。
set_workspace_cwd138–144 ↗
fn set_workspace_cwd(config: &mut Config, cwd: AbsolutePathBuf)
作用:这个帮手把配置里的当前目录和工作区根目录设成同一个指定路径。测试需要它来让状态卡片显示稳定的项目目录。
数据流:进去的是可修改的 Config 和一个绝对路径。函数把 config.cwd 改成这个路径,把 workspace_roots 改成只包含这个路径,并同步通知权限系统这些工作区根目录。出来没有返回值,但配置被改好了。
调用关系:很多状态快照测试在生成状态页前调用它。它不生成 UI,只是确保后面的 new_status_output 看到的是测试指定的工作区。
调用图:被 20 处调用(status_card_token_usage_excludes_cached_tokens, status_permissions_non_default_workspace_write_uses_workspace_label, status_permissions_workspace_roots_include_profile_defined_directories, status_permissions_workspace_roots_show_additional_directories, status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_forked_from, status_snapshot_includes_monthly_limit, status_snapshot_includes_reasoning_details (+10 more));外部调用 2 个(clone, vec!)。
test_status_account_display146–148 ↗
fn test_status_account_display() -> Option<StatusAccountDisplay>
作用:这个帮手统一表示测试里不显示账号信息。这样测试可以专注在权限、模型、额度等内容上。
数据流:不需要输入,也不读取外部信息。函数直接返回 None,意思是没有账号展示数据。它不改动任何东西。
调用关系:permissions_text_for 和大量状态页测试都会调用它,把结果传给状态输出函数。它让测试保持一致,避免账号字段影响快照。
调用图:被 22 处调用(permissions_text_for, status_card_token_usage_excludes_cached_tokens, status_context_window_uses_last_usage, status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link, status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_hides_when_has_no_credits_flag, status_snapshot_hides_zero_credits, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_forked_from (+12 more))。
token_info_for150–159 ↗
fn token_info_for(model_slug: &str, config: &Config, usage: &TokenUsage) -> TokenUsageInfo
作用:这个帮手根据模型和用量造出状态页需要的令牌信息。令牌可以理解为模型处理文字的计量单位,状态页用它展示用了多少上下文。
数据流:进去的是模型代号、配置和一份 TokenUsage 用量。函数离线查出该模型的上下文窗口大小,把总用量和最近一次用量都设成传入的用量。出来的是 TokenUsageInfo。
调用关系:许多测试先用它准备 token_info,再交给 new_status_output。它调用离线模型信息构造函数,避免测试访问网络或依赖真实模型服务。
调用图:调用 1 个内部函数(construct_model_info_offline_for_tests);被 21 处调用(status_card_token_usage_excludes_cached_tokens, status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_hides_when_has_no_credits_flag, status_snapshot_hides_zero_credits, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_forked_from, status_snapshot_includes_monthly_limit, status_snapshot_includes_reasoning_details, status_snapshot_shows_active_user_defined_profile (+11 more));外部调用 2 个(to_models_manager_config, clone)。
render_lines161–171 ↗
fn render_lines(lines: &[Line<'static>]) -> Vec<String>
作用:这个帮手把终端 UI 的 Line 对象变成普通字符串列表,方便测试比较。可以把它理解成把带样式的字幕擦掉颜色,只留下文字。
数据流:进去的是一组 ratatui 的 Line,每行里面有多个 span 片段。函数把每行的片段文字拼起来。出来的是 Vec<String>,不包含颜色、链接等样式信息。
调用关系:几乎所有检查显示内容的测试都会先调用 display_lines,再用它转成字符串。它是 UI 输出和普通断言之间的桥。
调用图:被 24 处调用(permissions_text_for, status_card_token_usage_excludes_cached_tokens, status_context_window_uses_last_usage, status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link, status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_hides_when_has_no_credits_flag, status_snapshot_hides_zero_credits, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_forked_from (+14 more));外部调用 1 个(iter)。
sanitize_directory173–203 ↗
fn sanitize_directory(lines: Vec<String>) -> Vec<String>
作用:这个帮手把状态卡片里的真实目录替换成固定的 [[workspace]]。这样不同系统、不同机器上的路径不同,也不会让快照测试无意义地失败。
数据流:进去的是已经渲染好的字符串行。函数找到边框宽度和包含 Directory: 的那一行,保留左右边框,把目录内容换成占位符,并补空格保持宽度。出来的是清理后的字符串行。
调用关系:多个快照测试在 assert_snapshot 前调用它。它不改变状态页逻辑,只消除机器环境差异,让快照专注检查真正重要的显示内容。
调用图:被 16 处调用(status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_forked_from, status_snapshot_includes_monthly_limit, status_snapshot_includes_reasoning_details, status_snapshot_shows_active_user_defined_profile, status_snapshot_shows_auto_review_permissions, status_snapshot_shows_missing_limits_message, status_snapshot_shows_refreshing_limits_notice (+6 more))。
reset_at_from205–209 ↗
fn reset_at_from(captured_at: &chrono::DateTime<chrono::Local>, seconds: i64) -> i64
作用:这个帮手把「从某个时间起再过多少秒重置」转换成额度接口使用的时间戳。测试用它方便地制造额度重置时间。
数据流:进去的是本地时间 captured_at 和秒数。函数把秒数加到 captured_at 上,再转成 UTC 时间戳。出来的是一个整数时间戳。
调用关系:很多额度相关测试用它填 RateLimitWindow 的 resets_at 字段,然后交给 rate_limit_snapshot_display。它让测试里的重置时间和展示时间保持一致。
调用图:被 9 处调用(status_snapshot_cached_limits_hide_credits_without_flag, status_snapshot_includes_credits_and_limits, status_snapshot_includes_enterprise_monthly_credit_limit, status_snapshot_includes_monthly_limit, status_snapshot_includes_reasoning_details, status_snapshot_shows_refreshing_limits_notice, status_snapshot_shows_stale_limits_message, status_snapshot_truncates_in_narrow_terminal, status_snapshot_uses_generic_limit_labels_for_unsupported_windows);外部调用 1 个(seconds)。
permissions_text_for211–244 ↗
fn permissions_text_for(config: &Config) -> Option<String>
作用:这个帮手专门取出状态卡片里的 Permissions 这一段文字。权限测试用它快速确认最终显示给用户的权限标签对不对。
数据流:进去的是 Config。函数准备默认用量、固定时间和模型,调用 new_status_output 生成状态卡片,再把渲染行转成字符串,找到包含 Permissions: 的行并裁掉边框。出来的是权限文字,找不到就返回 None。
调用关系:多组权限标签测试都围绕它写断言。它把完整状态页生成流程包起来,让测试只关心「权限那一行最终长什么样」。
调用图:调用 3 个内部函数(get_model_offline_for_tests, render_lines, test_status_account_display);外部调用 2 个(default, new_status_output)。
status_snapshot_includes_reasoning_details247–319 ↗
async fn status_snapshot_includes_reasoning_details()
作用:这个快照测试确认状态页会显示推理相关设置和用量。推理指模型在回答前用于思考的额外计算,用户需要知道当前是否开启以及用了多少。
数据流:进去的是临时配置、固定用量、固定时间和额度快照。函数设置模型、详细推理摘要、工作区权限和高推理强度覆盖值,生成状态页,清理目录后和保存的快照比较。输出是测试通过或失败。
调用关系:它使用 test_config、set_workspace_cwd、token_info_for、reset_at_from、render_lines、sanitize_directory 等帮手,并把数据交给 new_status_output。它覆盖状态页里模型、推理、用量、额度同时出现的综合场景。
调用图:调用 9 个内部函数(get_model_offline_for_tests, workspace_write, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_permissions_non_default_workspace_write_uses_workspace_label322–344 ↗
async fn status_permissions_non_default_workspace_write_uses_workspace_label()
作用:这个测试确认一种自定义但等价于工作区可写的权限,会显示成用户容易懂的工作区标签。否则用户可能看到很吓人的 Custom,却不知道实际权限范围。
数据流:进去的是新建的测试配置。函数设置模型、审批策略、工作区目录和 app_server_workspace_write_profile 权限,然后取出 Permissions 文本。出来必须是 Custom 加工作区和网络访问说明,否则断言失败。
调用关系:它调用 test_config、set_workspace_cwd、app_server_workspace_write_profile 和 permissions_text_for。它专门验证权限归类和文字说明的边界情况。
调用图:调用 3 个内部函数(app_server_workspace_write_profile, set_workspace_cwd, test_config);外部调用 3 个(new, assert_eq!, test_path_buf)。
status_permissions_named_read_only_profile_shows_builtin_label347–367 ↗
async fn status_permissions_named_read_only_profile_shows_builtin_label()
作用:这个测试确认内置只读权限会显示为 Read Only,而不是暴露底层权限细节。只读表示程序只能看文件,不能改项目文件。
数据流:进去的是测试配置。函数把审批设为按需询问,把权限快照设为内置只读,然后读取权限显示文字。出来必须是 Read Only 加审批提示。
调用关系:它用 test_config 准备配置,再用权限快照模拟会话里的内置配置,最后通过 permissions_text_for 检查状态页。
调用图:调用 4 个内部函数(read_only, active, read_only, test_config);外部调用 2 个(new, assert_eq!)。
status_permissions_read_only_profile_shows_additional_writable_roots370–397 ↗
async fn status_permissions_read_only_profile_shows_additional_writable_roots()
作用:这个测试确认只读配置即使内部带了额外可写目录,仍然按内置只读标签显示。这样状态页不会因为运行时的小调整就把内置配置误判成陌生配置。
数据流:进去的是测试配置和一个额外目录。函数从只读权限派生出带额外可写根目录的文件系统策略,再设置为会话快照,最后读取权限文字。出来仍应是 Read Only 加审批提示。
调用关系:它调用 test_config、test_path_buf 和 permissions_text_for,围绕 PermissionProfileSnapshot 验证命名内置配置的显示优先级。
调用图:调用 5 个内部函数(read_only, active, from_runtime_permissions, read_only, test_config);外部调用 4 个(new, assert_eq!, test_path_buf, from_ref)。
status_permissions_named_workspace_profile_shows_builtin_label400–420 ↗
async fn status_permissions_named_workspace_profile_shows_builtin_label()
作用:这个测试确认内置工作区权限会显示为 Workspace。工作区权限通常表示项目内可写,项目外受限。
数据流:进去的是测试配置。函数设置按需审批,并把权限快照设为内置 workspace_write。然后提取状态页权限文字。出来必须是 Workspace 加审批提示。
调用关系:它通过 test_config 建配置,通过 permissions_text_for 走完整状态页生成流程,检查内置工作区 profile 的用户可见名称。
调用图:调用 4 个内部函数(new, active, workspace_write, test_config);外部调用 2 个(new, assert_eq!)。
status_permissions_workspace_auto_review_shows_reviewer_label423–444 ↗
async fn status_permissions_workspace_auto_review_shows_reviewer_label()
作用:这个测试确认当审批人是自动审查时,状态页显示 Approve for me,而不是 Ask for approval。用户能由此知道系统会替他处理审批。
数据流:进去的是测试配置。函数把 approvals_reviewer 改成 AutoReview,设置按需审批和工作区权限,然后提取权限文字。出来必须包含 Approve for me。
调用关系:它依赖 test_config 和 permissions_text_for,验证审批显示文字不仅看审批策略,也看审批由谁处理。
调用图:调用 4 个内部函数(new, active, workspace_write, test_config);外部调用 2 个(new, assert_eq!)。
status_permissions_named_profile_shows_additional_writable_roots447–473 ↗
async fn status_permissions_named_profile_shows_additional_writable_roots()
作用:这个测试确认工作区内置权限即使带额外可写目录,仍显示为 Workspace。它保护的是用户熟悉的内置标签不要被无关细节打散。
数据流:进去的是测试配置和一个额外目录。函数用 workspace_write_with 造出带额外目录的权限快照,再读取状态页权限文字。出来应该是 Workspace 加审批提示。
调用关系:它调用 test_config、test_path_buf 和 permissions_text_for,验证内置 profile 名称在有扩展目录时仍然保留。
调用图:调用 4 个内部函数(new, active, workspace_write_with, test_config);外部调用 4 个(new, assert_eq!, test_path_buf, from_ref)。
status_permissions_workspace_roots_show_additional_directories476–505 ↗
async fn status_permissions_workspace_roots_show_additional_directories()
作用:这个测试确认如果工作区根目录不止一个,状态页会把额外目录显示出来。这样用户知道程序除了当前目录,还能写哪些目录。
数据流:进去的是测试配置、主工作区和额外工作区路径。函数设置 workspace_roots 为两个目录,同步到权限系统,再读取权限文字。出来应包含额外目录路径和审批提示。
调用关系:它使用 set_workspace_cwd 建立主目录,再手动加入额外根目录,最后通过 permissions_text_for 检查状态页是否把额外访问范围说清楚。
调用图:调用 5 个内部函数(new, active, workspace_write, set_workspace_cwd, test_config);外部调用 4 个(new, assert_eq!, test_path_buf, vec!)。
status_permissions_workspace_roots_include_profile_defined_directories508–541 ↗
async fn status_permissions_workspace_roots_include_profile_defined_directories()
作用:这个测试确认由权限配置本身定义的额外工作区目录也会显示出来。不是只有 config.workspace_roots 里的目录才算数。
数据流:进去的是测试配置和 profile_root。函数建立主工作区,把权限快照设置成带 profile 工作区根目录的版本,然后读取权限文字。出来应显示这个 profile_root。
调用关系:它调用 set_workspace_cwd、workspace_write_with 和 active_with_profile_workspace_roots。它检查权限系统和状态页之间对额外目录来源的理解一致。
调用图:调用 5 个内部函数(new, active_with_profile_workspace_roots, workspace_write_with, set_workspace_cwd, test_config);外部调用 5 个(new, assert_eq!, test_path_buf, from_ref, vec!)。
status_permissions_broadened_workspace_profile_shows_builtin_label544–569 ↗
async fn status_permissions_broadened_workspace_profile_shows_builtin_label()
作用:这个测试确认工作区权限如果打开了网络,会显示为 Workspace with network access。网络访问是权限范围变宽的重要变化,必须明说。
数据流:进去的是测试配置。函数设置按需审批,用 workspace_write_with 创建网络允许的工作区权限,并提取权限文字。出来应是带网络访问说明的 Workspace 标签。
调用关系:它通过 test_config 和 permissions_text_for 验证权限显示逻辑能识别工作区权限的网络开关。
调用图:调用 4 个内部函数(new, active, workspace_write_with, test_config);外部调用 2 个(new, assert_eq!)。
status_permissions_user_defined_profile_shows_name572–587 ↗
async fn status_permissions_user_defined_profile_shows_name()
作用:这个测试确认用户自定义的权限配置会显示用户给的名字。这样用户在状态页能认出自己选择的是哪个 profile。
数据流:进去的是测试配置。函数把只读权限包装成名为 locked 的活动 profile,然后读取权限文字。出来必须包含 Profile locked,并说明它是只读和需要审批。
调用关系:它调用 test_config 和 permissions_text_for,覆盖非内置 profile 的显示规则。
调用图:调用 4 个内部函数(new, active, read_only, test_config);外部调用 2 个(new, assert_eq!)。
status_snapshot_shows_active_user_defined_profile590–634 ↗
async fn status_snapshot_shows_active_user_defined_profile()
作用:这个快照测试确认完整状态卡片会展示当前活动的用户自定义 profile。它不只检查一行文字,还检查整体排版。
数据流:进去的是测试配置、固定模型、工作区、只读 locked profile、默认用量和固定时间。函数生成状态页,渲染成文本,清理目录后和快照比较。出来是快照匹配结果。
调用关系:它组合 test_config、set_workspace_cwd、token_info_for、render_lines 和 sanitize_directory,最后调用 new_status_output。它比单行权限测试覆盖更完整的卡片显示。
调用图:调用 10 个内部函数(new, active, get_model_offline_for_tests, read_only, render_lines, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, default, new_status_output)。
status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link637–741 ↗
async fn status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link()
作用:这个测试确认模型供应商显示规则正确:Amazon Bedrock 要显示运行时实际 URL,并且不显示 ChatGPT 用量链接;OpenAI 认证代理则可以显示用量链接。
数据流:进去的是测试配置和两套供应商设置。函数先配置 Bedrock,并传入运行时 base URL,检查渲染文本包含运行时 URL、不包含配置里的旧 URL、也没有 ChatGPT 用量链接;再配置 OpenAI Proxy,检查链接文本和宽屏超链接存在、窄屏超链接隐藏。出来是多条断言结果。
调用关系:它调用 test_config、new_status_output_with_rate_limits_handle、render_lines 和供应商构造函数。它覆盖状态页和超链接输出两条路径,是供应商显示的重点回归测试。
调用图:调用 5 个内部函数(create_amazon_bedrock_provider, get_model_offline_for_tests, render_lines, test_config, test_status_account_display);外部调用 6 个(new, assert!, assert_eq!, default, default, new_status_output_with_rate_limits_handle)。
status_snapshot_shows_auto_review_permissions744–789 ↗
async fn status_snapshot_shows_auto_review_permissions()
作用:这个快照测试确认自动审批模式会在完整状态卡片里正确显示。用户应该能看出审批不再由自己手动点。
数据流:进去的是测试配置、模型、工作区和 AutoReview 设置。函数生成状态页,渲染后清理目录,再和快照比较。出来是快照测试结果。
调用关系:它调用 test_config、set_workspace_cwd、token_info_for、render_lines、sanitize_directory 和 new_status_output。它把自动审批文字放进完整 UI 场景里验证。
调用图:调用 10 个内部函数(new, active, get_model_offline_for_tests, workspace_write, render_lines, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, default, new_status_output)。
status_permissions_full_disk_managed_with_network_is_danger_full_access792–812 ↗
async fn status_permissions_full_disk_managed_with_network_is_danger_full_access()
作用:这个测试确认如果文件系统无限制且网络也打开,状态页会显示 danger-full-access。这个标签是在提醒用户:权限非常大。
数据流:进去的是测试配置。函数设置按需审批,并把权限改成磁盘不受限、网络允许。然后读取权限文字。出来必须是 Custom 加 danger-full-access。
调用关系:它依赖 test_config 和 permissions_text_for,验证高风险权限组合被明确标出来。
调用图:调用 1 个内部函数(test_config);外部调用 2 个(new, assert_eq!)。
status_permissions_full_disk_managed_without_network_is_external_sandbox815–835 ↗
async fn status_permissions_full_disk_managed_without_network_is_external_sandbox()
作用:这个测试确认如果磁盘不受限但网络受限,状态页显示 external-sandbox。它区分了有网络和没网络两种不同风险级别。
数据流:进去的是测试配置。函数设置按需审批,把文件系统权限设成不受限、网络设成受限,再读取权限文字。出来必须是 Custom 加 external-sandbox。
调用关系:它和上一个 full access 测试成对出现,使用同样的 permissions_text_for 路径检查网络开关如何影响权限标签。
调用图:调用 1 个内部函数(test_config);外部调用 2 个(new, assert_eq!)。
status_snapshot_includes_forked_from838–889 ↗
async fn status_snapshot_includes_forked_from()
作用:这个快照测试确认状态页会显示当前会话是从哪个会话 fork 出来的。fork 可以理解成从一个旧对话复制出新分支。
数据流:进去的是测试配置、当前会话 ID、来源会话 ID、固定用量和时间。函数生成带 forked_from 的状态页,渲染并清理目录后和快照比较。出来是快照匹配结果。
调用关系:它调用 ThreadId 解析、test_config、set_workspace_cwd、token_info_for、render_lines 和 sanitize_directory。它验证会话血缘信息会被 new_status_output 放进状态卡片。
调用图:调用 8 个内部函数(get_model_offline_for_tests, from_string, render_lines, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output)。
status_snapshot_includes_monthly_limit892–953 ↗
async fn status_snapshot_includes_monthly_limit()
作用:这个快照测试确认状态页能显示月度额度限制。月度限制帮助用户知道这个月总体用量离上限还有多远。
数据流:进去的是测试配置、固定用量、时间和一个 43200 分钟窗口的额度快照。函数把快照转换成显示数据,生成状态页,清理目录后做快照比对。出来是测试结果。
调用关系:它使用 reset_at_from 和 rate_limit_snapshot_display 准备额度,再通过 new_status_output 验证月度窗口的用户可见显示。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_includes_enterprise_monthly_credit_limit956–1029 ↗
async fn status_snapshot_includes_enterprise_monthly_credit_limit()
作用:这个快照测试确认企业月度花费控制会显示出来,并且窄屏时能正确换行。花费控制和普通速率限制不同,展示格式也要单独守住。
数据流:进去的是测试配置、固定用量、时间和 individual_limit 快照。函数生成状态页,先用宽屏宽度渲染并比对快照,再用窄屏宽度渲染并比对另一个快照。出来是两次快照结果。
调用关系:它调用 reset_at_from、rate_limit_snapshot_display、token_info_for、render_lines 和 sanitize_directory。它覆盖企业额度显示和窄终端排版两个方面。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_uses_generic_limit_labels_for_unsupported_windows1032–1097 ↗
async fn status_snapshot_uses_generic_limit_labels_for_unsupported_windows()
作用:这个快照测试确认遇到不认识的额度窗口长度时,状态页会用通用标签,而不是乱写成每日或每周。这样显示保持诚实。
数据流:进去的是包含 2 小时和 3 小时窗口的额度快照。函数生成状态页并做快照比较。出来确认这些不常见窗口被通用方式展示。
调用关系:它通过 reset_at_from 和 rate_limit_snapshot_display 构造特殊窗口,再用 new_status_output 检查显示。它保护额度标签推断逻辑。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_shows_unlimited_credits1100–1148 ↗
async fn status_snapshot_shows_unlimited_credits()
作用:这个测试确认当积分是无限时,状态页会显示 Credits: Unlimited。积分可以理解成账户可用的消费余额。
数据流:进去的是测试配置、默认用量、固定时间和 unlimited=true 的积分快照。函数生成状态页,渲染成文本,查找同时包含 Credits: 和 Unlimited 的行。出来是断言结果。
调用关系:它调用 test_config、token_info_for、rate_limit_snapshot_display 和 render_lines。它专门检查积分展示的无限额度分支。
调用图:调用 5 个内部函数(get_model_offline_for_tests, render_lines, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert!, default, new_status_output, rate_limit_snapshot_display)。
status_snapshot_shows_positive_credits1151–1199 ↗
async fn status_snapshot_shows_positive_credits()
作用:这个测试确认正数积分会显示,并且会四舍五入成用户友好的整数。比如 12.5 会显示成 13 credits。
数据流:进去的是 balance 为 12.5 的积分快照。函数生成状态页,渲染文本,然后查找 Credits 行是否包含 13 credits。出来是断言结果。
调用关系:它通过 rate_limit_snapshot_display 把原始积分转成显示数据,再由 new_status_output 展示,检查余额格式化逻辑。
调用图:调用 5 个内部函数(get_model_offline_for_tests, render_lines, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert!, default, new_status_output, rate_limit_snapshot_display)。
status_snapshot_hides_zero_credits1202–1248 ↗
async fn status_snapshot_hides_zero_credits()
作用:这个测试确认余额为 0 的积分不会显示。这样状态页不会用一条没有帮助的信息占空间。
数据流:进去的是 balance 为 0 的积分快照。函数生成状态页并渲染文本,然后确认所有行都不包含 Credits:。出来是断言结果。
调用关系:它和正数积分测试形成对照,验证积分显示只在有意义时出现。
调用图:调用 5 个内部函数(get_model_offline_for_tests, render_lines, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert!, default, new_status_output, rate_limit_snapshot_display)。
status_snapshot_hides_when_has_no_credits_flag1251–1297 ↗
async fn status_snapshot_hides_when_has_no_credits_flag()
作用:这个测试确认当服务端明确说没有积分体系时,即使其他字段像 unlimited,也不显示 Credits。状态页要尊重 has_credits 这个开关。
数据流:进去的是 has_credits=false 的积分快照。函数生成并渲染状态页,检查没有 Credits 行。出来是断言结果。
调用关系:它调用 test_config、token_info_for、rate_limit_snapshot_display 和 render_lines,验证积分显示的总开关优先于余额内容。
调用图:调用 5 个内部函数(get_model_offline_for_tests, render_lines, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert!, default, new_status_output, rate_limit_snapshot_display)。
status_card_token_usage_excludes_cached_tokens1300–1343 ↗
async fn status_card_token_usage_excludes_cached_tokens()
作用:这个测试确认状态卡片不会单独展示 cached tokens。cached tokens 是缓存命中的输入令牌,内部有用,但状态页不想让用户被这个细节干扰。
数据流:进去的是包含 cached_input_tokens 的用量数据。函数生成状态页并渲染文本,然后确认所有行都不包含 cached。出来是断言结果。
调用关系:它使用 test_config、set_workspace_cwd、token_info_for 和 render_lines,检查令牌用量显示的取舍。
调用图:调用 6 个内部函数(get_model_offline_for_tests, render_lines, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 4 个(new, assert!, test_path_buf, new_status_output)。
status_snapshot_truncates_in_narrow_terminal1346–1410 ↗
async fn status_snapshot_truncates_in_narrow_terminal()
作用:这个快照测试确认终端很窄时,状态卡片会截断或换行得合理,不会把边框撑坏。窄屏是命令行工具常见场景。
数据流:进去的是模型、推理信息、用量、额度和 70 列宽度。函数生成状态页,用窄宽度渲染,清理目录后和快照比较。出来是快照结果。
调用关系:它和 reasoning details 测试使用类似数据,但把显示宽度调窄,专门验证 display_lines 的排版行为。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_shows_missing_limits_message1413–1458 ↗
async fn status_snapshot_shows_missing_limits_message()
作用:这个快照测试确认没有传入额度数据时,状态页会显示缺少额度信息的提示。用户因此知道不是没有限制,而是当前没拿到数据。
数据流:进去的是测试配置、用量和固定时间,但 rate_limits 传 None。函数生成状态页,渲染并清理目录后比对快照。出来是测试结果。
调用关系:它通过 new_status_output 的无额度路径检查提示文案,使用常规帮手稳定快照。
调用图:调用 7 个内部函数(get_model_offline_for_tests, render_lines, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output)。
status_snapshot_uses_default_reasoning_when_config_empty1461–1514 ↗
async fn status_snapshot_uses_default_reasoning_when_config_empty()
作用:这个快照测试确认配置里没有推理设置时,状态页能显示默认推理强度,并且能显示远程连接信息。默认值如果不清楚,用户会不知道当前模型怎么工作。
数据流:进去的是测试配置、远程连接地址和版本、用量、固定时间,以及 Medium 推理强度覆盖值。函数通过 new_status_output_with_rate_limits_handle 生成状态页,渲染后清理目录并比对快照。出来是快照结果。
调用关系:它调用 test_config、set_workspace_cwd、token_info_for、render_lines 和 sanitize_directory。它覆盖带远程连接状态的状态页构造路径。
调用图:调用 7 个内部函数(get_model_offline_for_tests, render_lines, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 5 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output_with_rate_limits_handle)。
status_snapshot_shows_refreshing_limits_notice1517–1580 ↗
async fn status_snapshot_shows_refreshing_limits_notice()
作用:这个快照测试确认额度正在刷新时,状态页会显示刷新中的提示。用户能知道旧数据可能马上更新,而不是误以为页面卡住了。
数据流:进去的是测试配置、用量、两段额度窗口和 refreshing_rate_limits=true。函数生成带额度的状态页,渲染并清理目录后比对快照。出来是测试结果。
调用关系:它使用 new_status_output_with_rate_limits 而不是普通构造函数,专门覆盖刷新标志位影响显示的路径。
调用图:调用 7 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, token_info_for);外部调用 7 个(new, assert_snapshot!, cfg!, test_path_buf, from_ref, new_status_output_with_rate_limits, rate_limit_snapshot_display)。
status_snapshot_includes_credits_and_limits1583–1651 ↗
async fn status_snapshot_includes_credits_and_limits()
作用:这个快照测试确认积分和速率限制可以同时显示。用户既能看到余额,也能看到短期和长期使用比例。
数据流:进去的是测试配置、模型、用量、两段额度窗口和 37.5 积分。函数生成状态页,渲染后清理目录并比对快照。出来是快照结果。
调用关系:它把 credits、primary、secondary 三类额度数据一起传入 rate_limit_snapshot_display,再由 new_status_output 展示,覆盖组合场景。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 6 个(new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_shows_stale_limits_message1772–1837 ↗
async fn status_snapshot_shows_stale_limits_message()
作用:这个快照测试确认额度数据过了一段时间后会显示陈旧提示。陈旧提示很重要,因为旧额度可能已经不可信。
数据流:进去的是 captured_at 时刻的额度快照,以及比它晚 20 分钟的 now。函数生成状态页,渲染并清理目录后比对快照。出来确认状态页提示额度数据过期。
调用关系:它使用 reset_at_from、rate_limit_snapshot_display 和 new_status_output,把时间差传给状态显示逻辑,验证 stale 判断会反映到 UI 上。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 7 个(minutes, new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_snapshot_cached_limits_hide_credits_without_flag1840–1909 ↗
async fn status_snapshot_cached_limits_hide_credits_without_flag()
作用:这个快照测试确认缓存下来的额度里,如果 has_credits=false,就算带了余额字段也不显示积分。它防止旧缓存里的残留字段误导用户。
数据流:进去的是带 primary、secondary 限制和 credits balance=80 但 has_credits=false 的快照,再把当前时间设到 captured_at 之后 20 分钟。函数生成状态页并做快照比较。出来应看不到积分显示。
调用关系:它结合缓存变旧、速率限制和积分开关三个条件,调用 rate_limit_snapshot_display 和 new_status_output 检查最终状态页。
调用图:调用 8 个内部函数(get_model_offline_for_tests, render_lines, reset_at_from, sanitize_directory, set_workspace_cwd, test_config, test_status_account_display, token_info_for);外部调用 7 个(minutes, new, assert_snapshot!, cfg!, test_path_buf, new_status_output, rate_limit_snapshot_display)。
status_context_window_uses_last_usage1912–1973 ↗
async fn status_context_window_uses_last_usage()
作用:这个测试确认上下文窗口显示用的是最近一次请求的用量,而不是累计总用量。上下文窗口是模型一次能处理的最大文字容量,累计用量拿来算会误导用户。
数据流:进去的是总用量 102000、最近一次用量 13679,以及上下文窗口 272000。函数生成状态页,找到 Context window 行,确认里面有 13.7K used / 272K,且没有 102K。出来是断言结果。
调用关系:它手工构造 TokenUsageInfo,而不是用 token_info_for,因为这里需要总用量和最近用量不同。它通过 new_status_output 和 render_lines 验证上下文窗口算法选对数据来源。
调用图:调用 4 个内部函数(get_model_offline_for_tests, render_lines, test_config, test_status_account_display);外部调用 3 个(new, assert!, new_status_output)。
集成和 VT100 冒烟测试
这些集成套件以端到端或冒烟测试形式,验证基于终端的行为、架构边界、调整大小/重排行为,以及 VT100 历史/实时渲染。
tui/tests/manager_dependency_regression.rs源码 ↗
这个测试文件解决的是“代码越写越乱、模块边界被绕开”的问题。TUI 可以理解成终端里的用户界面,这里希望它的运行时代码不要直接碰 AuthManager、ThreadManager 这类管理器,也不要调用 auth_manager()、thread_manager() 这样的入口。否则界面层就会知道太多内部细节,以后改认证、线程等底层机制时更容易牵一发动全身。文件先从 chatwidget.rs 找到 TUI 源码目录,再递归找出这个目录下所有 Rust 源文件。然后逐个读文件内容,搜索一组禁止出现的字符串。如果发现任何一个文件包含这些字样,就把“哪个文件包含了哪个禁词”收集起来,并让测试失败。它不是测试某个运行结果,而是在检查源码本身,像代码仓库里的安全检查员。
rust_sources_under6–20 ↗
fn rust_sources_under(dir: &Path) -> Vec<PathBuf>
作用:这个函数从一个目录开始,把里面所有 Rust 源文件找出来,包括子目录里的文件。别人用它时,是为了拿到一份完整、排序好的源码文件清单,方便后面逐个检查。
数据流:进去的是一个目录路径。它读取这个目录里的条目,遇到子目录就继续往里找,遇到扩展名是 .rs 的文件就加入列表;最后把列表排序后返回。它不修改源码,只是读取文件系统并产出文件路径列表。
调用关系:它是这个测试里的“搜文件”小助手。tui_runtime_source_does_not_depend_on_manager_escape_hatches 先定位到 TUI 源码目录,然后把目录交给它;它再通过系统的目录读取功能一路递归,把要检查的 Rust 文件交还给测试主流程。
调用图:被 1 处调用(tui_runtime_source_does_not_depend_on_manager_escape_hatches);外部调用 2 个(new, read_dir)。
tui_runtime_source_does_not_depend_on_manager_escape_hatches23–54 ↗
fn tui_runtime_source_does_not_depend_on_manager_escape_hatches()
作用:这是实际的测试用例,用来确认 TUI 运行时代码没有重新依赖被禁止的管理器接口。有人改代码后运行测试,如果误把这些名字写回源码里,这个函数会让测试失败。
数据流:开始时它通过资源查找拿到 chatwidget.rs 的位置,并由此确定源码目录。接着调用 rust_sources_under 得到所有 Rust 源文件,逐个读出文本内容,再搜索 AuthManager、ThreadManager、auth_manager(、thread_manager( 这些禁用字符串。最后如果没有发现问题,测试通过;如果发现了,就输出具体违规文件和命中的字符串,并触发断言失败。
调用关系:它是整个文件的主角,由测试框架在测试阶段自动运行。它把“找源码文件”的活交给 rust_sources_under,把“定位测试资源”的活交给 find_resource!,最后用 assert! 做守门:只要违规清单不为空,就把这次改动拦下来。
调用图:调用 1 个内部函数(rust_sources_under);外部调用 2 个(assert!, find_resource!)。
tui/tests/suite/resize_reflow.rs源码 ↗
这个文件测试的是终端界面里的“重排”问题。终端变窄、变矮时,长文本会重新换行,界面也要重新画;如果算错了,就像纸上排版被人推歪一样,输入框可能下沉,历史消息也可能挪位置。测试会先准备一个临时的 Codex 配置和假登录信息,再启动一个假的 OpenAI 服务,让 Codex 收到固定的长回复,不用真的连模型。然后它用 tmux(一个能在终端里分屏和抓屏的工具)启动 Codex,输入一段草稿文字,记录输入框和一段标记文本所在的行号。接着它反复做竖向分屏、横向分屏、关闭分屏等操作,并抓取屏幕文字来比较行号。TmuxSession 像清洁工,测试结束时会自动杀掉临时 tmux 会话,避免留下脏环境。整个文件默认被忽略,需要人工在有 tmux 和本地 codex 二进制的环境里运行。
tmux_split_preserves_fresh_session_composer_row_after_resize_reflow18–164 ↗
async fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Result<()>
作用:这个测试检查:刚启动的 Codex 界面在 tmux 里被上下分屏后,输入框所在行不会因为重新排版而慢慢下移。它保护的是用户肉眼最容易看到的稳定性。
数据流:进去的是本地仓库路径、临时 CODEX_HOME、假的模型回复服务器和一个 tmux 窗格;它启动 Codex,等待固定回复出现,输入一段草稿,记录输入框行号和历史文本行号;然后创建一个竖向分屏、等待界面重画、再关闭分屏;出来的是测试成功或失败,失败时会带上分屏前后的屏幕截图文字,方便看哪里漂了。
调用关系:它是顶层测试之一,自己搭好假服务、临时配置和 tmux 会话。过程中会调用 codex_binary 找可执行文件,调用 write_config 和 write_auth 写临时环境,调用 resize_reflow_sse 准备假回复,用 checked_output、check 跑 tmux 命令,用 wait_for_capture_contains、capture_pane 抓屏,再用 last_composer_row 和 first_row_containing 判断位置有没有变。
调用图:调用 12 个内部函数(mount_sse_once, capture_pane, check, checked_output, codex_binary, first_row_containing, last_composer_row, resize_reflow_sse, stdout_text, wait_for_capture_contains (+2 more));外部调用 12 个(from_millis, from_secs, start, ensure!, cfg!, new, repo_root, eprintln!, format!, skip_if_no_network! (+2 more))。
tmux_repeated_resizes_do_not_push_composer_down168–181 ↗
async fn tmux_repeated_resizes_do_not_push_composer_down() -> Result<()>
作用:这个测试检查连续多次改变 tmux 高度后,Codex 的输入框不会一次比一次更低。它像反复开合抽屉,确认轨道不会越用越歪。
数据流:进去的是当前运行环境;它先跳过 Windows、没有网络或没有 tmux 的情况;如果环境合适,就把真正的重复缩放流程交给 run_repeated_resize_smoke;出来的是测试通过、跳过,或者在位置漂移时返回错误。
调用关系:它本身只是一个外层入口,负责环境检查和调用 run_repeated_resize_smoke。这样重复 resize 的长流程可以单独放在辅助函数里,测试入口看起来更清楚。
调用图:调用 1 个内部函数(run_repeated_resize_smoke);外部调用 4 个(cfg!, new, eprintln!, skip_if_no_network!)。
tmux_width_resize_restore_keeps_visible_content_anchored185–312 ↗
async fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()>
作用:这个测试检查左右分屏导致窗口变窄、再恢复原宽度后,屏幕上可见内容还停在原来的位置。它防止横向宽度变化把历史内容或输入框重新锚到错误地方。
数据流:进去的是临时 Codex 环境、假模型回复和 tmux 会话;它启动 Codex,等长文本回复出现,输入草稿并记录基准行号;然后做一次横向分屏让 Codex 窗格变窄,再杀掉分屏恢复宽度;最后重新抓屏,比较输入框和标记历史行是否回到原行;出来的是通过或带截图的错误。
调用关系:它和第一个测试结构很像,但关注的是宽度变化而不是高度变化。它会使用同一套工具函数:codex_binary 找程序,write_config、write_auth 准备配置,resize_reflow_sse 提供固定长回复,checked_output 和 check 操作 tmux,capture_pane 等函数读取并分析屏幕。
调用图:调用 12 个内部函数(mount_sse_once, capture_pane, check, checked_output, codex_binary, first_row_containing, last_composer_row, resize_reflow_sse, stdout_text, wait_for_capture_contains (+2 more));外部调用 12 个(from_millis, from_secs, start, ensure!, cfg!, new, repo_root, eprintln!, format!, skip_if_no_network! (+2 more))。
run_repeated_resize_smoke314–435 ↗
async fn run_repeated_resize_smoke() -> Result<()>
作用:这个函数执行“反复上下分屏再恢复”的核心测试流程。它专门用来确认同一个 resize 问题不会在多轮操作后累积成位置漂移。
数据流:进去的是运行时能找到的仓库、codex 二进制和系统 tmux;它创建临时配置、启动假服务器、打开 tmux 中的 Codex,记录输入框和历史文本的初始行号;随后循环三次创建竖向分屏、关闭分屏、抓屏比较;出来的是成功结果,或者指出第几轮后输入框或历史行发生了漂移。
调用关系:它被 tmux_repeated_resizes_do_not_push_composer_down 调用,是那个测试的实际干活部分。它内部串起 codex_binary、write_config、write_auth、resize_reflow_sse、checked_output、check、capture_pane、last_composer_row 和 first_row_containing,把“启动、等待、缩放、验证”连成完整流程。
调用图:调用 12 个内部函数(mount_sse_once, capture_pane, check, checked_output, codex_binary, first_row_containing, last_composer_row, resize_reflow_sse, stdout_text, wait_for_capture_contains (+2 more));被 1 处调用(tmux_repeated_resizes_do_not_push_composer_down);外部调用 9 个(from_millis, from_secs, start, ensure!, new, repo_root, format!, sleep, tempdir)。
TmuxSession::drop442–448 ↗
fn drop(&mut self)
作用:这个清理函数在 TmuxSession 被丢弃时自动杀掉对应的 tmux 会话。它的作用是防止测试失败或提前结束后,后台还残留一个 Codex 窗口。
数据流:进去的是 TmuxSession 里保存的会话名字;它执行 tmux kill-session -t <名字>;出来没有正常业务结果,并且刻意忽略命令错误,因为清理失败不应该盖过原来的测试结果。
调用关系:各个测试创建 _session 后,不需要手动调用它。Rust 会在变量离开作用域时自动触发 drop,所以它像测试的自动收尾钩子。
调用图:外部调用 1 个(new)。
codex_binary451–462 ↗
fn codex_binary(repo_root: &Path) -> Result<PathBuf>
作用:这个函数负责找到本地编译好的 codex 可执行文件。没有它,测试就不知道该启动哪个程序来做真实界面验证。
数据流:进去的是仓库根目录;它先尝试用测试工具查找 cargo 构建出的 codex,找不到就退回到 codex-rs/target/debug/codex;出来的是可执行文件路径,如果两个地方都没有,就返回一条提示先构建的错误。
调用关系:三个主要测试流程都会在启动 tmux 前调用它。它不运行 Codex,只负责交出路径,之后由 checked_output 包装的 tmux 命令把这个路径放进新会话里执行。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 3 个(join, ensure!, cargo_bin)。
write_config464–477 ↗
fn write_config(codex_home: &Path, repo_root: &Path) -> Result<()>
作用:这个函数给临时的 Codex 主目录写一份测试专用配置。它让 Codex 使用固定模型名、信任当前仓库,并关闭不必要的提示,保证测试画面稳定。
数据流:进去的是临时 CODEX_HOME 路径和仓库根目录;它拼出一段 config.toml 内容,其中包含模型、服务商、关闭警告、当前项目可信任等设置;出来是在临时目录里多了一个配置文件,写失败则返回错误。
调用关系:各个测试在启动 Codex 前都会调用它。它和 write_auth 一起准备一个“像真实用户环境但完全临时”的 Codex 家目录。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 4 个(display, join, format!, write)。
write_auth479–485 ↗
fn write_auth(codex_home: &Path) -> Result<()>
作用:这个函数写入一份假的认证文件,让 Codex 启动时以为已经有 API key。这里的 key 是 dummy,因为请求会打到假服务器,不会真的访问 OpenAI。
数据流:进去的是临时 CODEX_HOME 路径;它把固定 JSON 写到 auth.json,里面放了 OPENAI_API_KEY 和空 token 信息;出来的是临时目录里出现认证文件,写失败则返回错误。
调用关系:它被三个主要测试流程调用,配合环境变量 OPENAI_API_KEY=dummy 和假服务器使用。这样测试能走完整启动流程,但不会依赖真实账号。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 2 个(join, write)。
resize_reflow_sse487–494 ↗
fn resize_reflow_sse() -> String
作用:这个函数生成一段假的流式模型回复。回复里有固定标记词和足够长的文字,用来触发终端自动换行和重排。
数据流:进去没有外部输入;它创建一段包含 resize reflow sentinel 的长文本,再包装成 SSE。SSE 是 Server-Sent Events,简单说就是服务器一段一段推送消息的格式;出来的是一整段可被假服务返回的字符串。
调用关系:主要测试和 run_repeated_resize_smoke 会把它交给 responses::mount_sse_once,让 MockServer 下一次请求返回这段固定内容。后续 wait_for_capture_contains 会等待其中的标记词出现在 tmux 画面里。
调用图:调用 1 个内部函数(sse);被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 1 个(vec!)。
wait_for_capture_contains496–508 ↗
fn wait_for_capture_contains(pane: &str, needle: &str, timeout: Duration) -> Result<String>
作用:这个函数反复抓取 tmux 窗格内容,直到看到某个目标文字,或者等到超时。它用于等待 Codex 界面真的把回复或输入内容画出来。
数据流:进去的是窗格编号、要找的文字和最长等待时间;它每 100 毫秒调用 capture_pane 抓一次屏,检查里面是否包含目标文字;出来的是包含目标文字的屏幕文本,或者在超时后返回错误并附上最后一次抓到的画面。
调用关系:主要测试在等待假模型回复、等待用户草稿显示时都会用它。它把底层的 capture_pane 包成“等到画面稳定到某个状态”的工具。
调用图:调用 1 个内部函数(capture_pane);被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 5 个(from_millis, now, new, bail!, sleep)。
capture_pane510–519 ↗
fn capture_pane(pane: &str) -> Result<String>
作用:这个函数读取一个 tmux 窗格当前显示的文字。测试不是看像素,而是把屏幕内容抓成文本,再据此判断行号有没有漂移。
数据流:进去的是 tmux 窗格编号;它执行 tmux capture-pane -p -t <窗格>,拿到标准输出;然后把字节转成字符串;出来的是整块屏幕文本,或者命令执行失败的错误。
调用关系:它被主要测试直接调用,也被 wait_for_capture_contains 循环调用。底层运行命令的工作交给 output,自己负责把 tmux 输出变成 Rust 字符串。
调用图:调用 1 个内部函数(output);被 4 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored, wait_for_capture_contains);外部调用 2 个(from_utf8_lossy, new)。
last_composer_row521–533 ↗
fn last_composer_row(capture: &str) -> Option<usize>
作用:这个函数从抓到的屏幕文本里找 Codex 输入框所在的最后一行。这里用行首的 › 符号作为输入框提示符来识别。
数据流:进去的是一整段屏幕文本;它逐行编号,挑出左侧去掉空白后以 › 开头的行,并取最后一个匹配;出来的是行号,找不到则返回空值。
调用关系:主要测试用它记录 resize 前后的输入框位置。它不碰 tmux,也不运行命令,只负责分析 capture_pane 或 wait_for_capture_contains 得到的文本。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored)。
first_row_containing535–540 ↗
fn first_row_containing(capture: &str, needle: &str) -> Option<usize>
作用:这个函数找出屏幕文本里第一次出现某段标记文字的行号。测试用它定位历史回复中的固定句子,看历史内容有没有被挪动。
数据流:进去的是屏幕文本和要找的文字;它从上到下逐行查找;出来的是第一次命中的行号,找不到则返回空值。
调用关系:主要测试用它追踪 resize reflow sentinel 这段假回复的位置。它和 last_composer_row 一起组成位置检测的两把尺子:一个量输入框,一个量历史内容。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored)。
check542–545 ↗
fn check(command: &mut Command) -> Result<()>
作用:这个函数运行一个外部命令,并只关心它是否成功。适合用在发送按键、杀掉分屏这类不需要读取输出的 tmux 操作上。
数据流:进去的是一个已经拼好的命令;它调用 checked_output 执行并检查退出状态;出来的是成功的空结果,或者包含 stdout、stderr 的错误。
调用关系:主要测试在执行 tmux send-keys 和 tmux kill-pane 时调用它。它把“我不需要输出,只要确认成功”这个常见动作简化了一层。
调用图:调用 1 个内部函数(checked_output);被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored)。
checked_output547–557 ↗
fn checked_output(command: &mut Command) -> Result<Output>
作用:这个函数运行外部命令,并确认命令退出状态是成功。它比普通运行更安全,因为失败时会把标准输出和错误输出一起报出来。
数据流:进去的是一个命令;它先交给 output 真正执行,拿到退出状态、stdout 和 stderr;如果退出状态不是成功,就返回带详细日志的错误;成功则把完整 Output 返回给调用者。
调用关系:启动 tmux 会话、创建分屏这类需要读取输出的地方会直接用它;check 也建立在它之上。它是本文件所有“必须成功的外部命令”的统一门卫。
调用图:调用 1 个内部函数(output);被 4 处调用(check, run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 1 个(ensure!)。
output559–563 ↗
fn output(command: &mut Command) -> Result<Output>
作用:这个函数是最底层的命令执行包装器。它负责真的运行外部程序,并在启动失败时加上一句更好懂的错误上下文。
数据流:进去的是一个命令对象;它调用系统执行这个命令;出来的是系统返回的 Output,也就是退出状态、标准输出和错误输出;如果连命令都跑不起来,就返回“failed to run ...”这样的错误。
调用关系:capture_pane 和 checked_output 都把实际执行命令的工作交给它。这样所有外部命令启动失败时,错误信息格式比较一致。
调用图:被 2 处调用(capture_pane, checked_output);外部调用 1 个(output)。
stdout_text565–567 ↗
fn stdout_text(output: &Output) -> String
作用:这个函数把命令的标准输出转成普通字符串。测试用它读取 tmux 返回的窗格编号。
数据流:进去的是一个命令执行结果 Output;它取出 stdout 字节,并用宽容的 UTF-8 转换方式变成字符串;出来的是可直接查找、裁剪的文本。
调用关系:主要测试和 run_repeated_resize_smoke 在创建 tmux 会话或分屏后调用它,用来从 tmux 输出里拿到 pane id,也就是后续抓屏和操作要用的窗格编号。
调用图:被 3 处调用(run_repeated_resize_smoke, tmux_split_preserves_fresh_session_composer_row_after_resize_reflow, tmux_width_resize_restore_keeps_visible_content_anchored);外部调用 1 个(from_utf8_lossy)。
tui/tests/suite/status_indicator.rs源码 ↗
这个测试关注一个很具体的问题:有些文字会带 ANSI 转义序列,也就是终端里用来控制颜色、加粗之类效果的特殊字符,比如“把 RED 显示成红色”。这些字符不是普通文字,如果原样写进界面的底层缓冲区,就像把舞台灯光控制指令当成台词打印出来,可能造成显示异常。这里不直接测试整个状态提示组件,因为完整渲染过程比较难测;它选择测试组件依赖的公开函数 ansi_escape_line()。测试输入一段带红色控制码的字符串,期望输出只剩下可见的 RED,并且没有原始的转义字节。这样可以保证以后有人改代码时,不会不小心把这个清理行为弄坏。
ansi_escape_line_strips_escape_sequences10–24 ↗
fn ansi_escape_line_strips_escape_sequences()
作用:这个测试函数确认 ansi_escape_line() 会去掉 ANSI 转义序列,只保留真正能让人看见的文字。有人修改状态提示或文字渲染相关代码后,可以靠它发现“控制字符又漏进来了”的问题。
数据流:进去的是一段带颜色控制码的字符串,内容看起来是红色的 RED,但里面夹着不可见的 \x1b 控制字节。函数把这段字符串交给 ansi_escape_line(),再把返回结果里每一段文字拼成一个普通字符串。最后它检查拼出来的结果必须正好是 RED;如果还残留控制码或文字被弄丢,测试就会失败。
调用关系:它是测试运行器在执行测试套件时调用的测试用例。它把核心检查交给外部的 ansi_escape_line(),然后用 assert_eq! 做最后判断,证明这个公开函数满足状态提示组件需要的安全输出约定。
调用图:外部调用 2 个(assert_eq!, ansi_escape_line)。
tui/tests/suite/vt100_history.rs源码 ↗
这个测试文件像一个小型“假终端实验室”。它不用真的打开人的终端,而是用 VT100Backend 模拟一个老式终端屏幕,VT100 可以理解成一套终端显示规则。测试先造出一个固定宽高的屏幕,再指定当前可绘制的视口区域,也就是程序此刻允许写字的那块地方。然后它调用 codex_tui::insert_history_lines,把历史文本塞进终端,最后读取模拟屏幕上的内容来检查结果。它重点盯住几个容易出问题的地方:普通短行要出现;超长单词换行后不能少字; emoji 和中文这类“占位宽度特殊”的字符不能消失;带颜色的文字最后仍要显示成正确文本;插入历史后光标位置要恢复;普通英文单词不应该被硬生生从中间切开。文件里的 TestScenario 是测试用的小工具,把“建终端”和“插历史”这两步包装起来,让每个测试只关心自己要验证的显示行为。
TestScenario::new27–33 ↗
fn new(width: u16, height: u16, viewport: Rect) -> Self
作用:创建一个专门给测试用的假终端场景。测试不用每次都手写建终端、设屏幕区域这些重复步骤。
数据流:进去的是屏幕宽度、高度,以及一个 viewport(视口,也就是允许绘制的矩形区域)→ 它先用 VT100Backend::new 建一个模拟终端后端,再用 Terminal::with_options 把后端包装成 codex_tui 的终端对象,最后把视口设置进去 → 出来的是一个 TestScenario,里面装着已经准备好的终端。
调用关系:它是所有具体测试的开场准备步骤。basic_insertion_no_wrap、long_token_wraps、emoji_and_cjk、mixed_ansi_spans、cursor_restoration、word_wrap_no_mid_word_split 和 em_dash_and_space_word_wrap 都会先调用它,之后再往这个假终端里插入历史内容。
调用图:调用 2 个内部函数(with_options, new);被 7 处调用(basic_insertion_no_wrap, cursor_restoration, em_dash_and_space_word_wrap, emoji_and_cjk, long_token_wraps, mixed_ansi_spans, word_wrap_no_mid_word_split)。
TestScenario::run_insert35–38 ↗
fn run_insert(&mut self, lines: Vec<Line<'static>>)
作用:把一批历史文本行放进测试终端里。它把真正要测的 codex_tui::insert_history_lines 包起来,让测试代码更短、更像在描述场景。
数据流:进去的是若干 Line,也就是 ratatui 用来表示一行带样式文字的数据 → 它把这些行和当前测试终端一起交给 codex_tui::insert_history_lines → 如果成功,终端屏幕内容被改写;如果失败,测试会直接报错并说明插入历史行失败。
调用关系:各个测试在用 TestScenario::new 搭好假终端后,会通过这个方法触发真正的历史插入动作。它本身不判断对错,只负责把活交给 insert_history_lines,之后由具体测试读取屏幕并断言。
调用图:外部调用 1 个(insert_history_lines)。
basic_insertion_no_wrap42–54 ↗
fn basic_insertion_no_wrap()
作用:检查最普通的情况:两行短文本插入后,都能在终端历史里看到。这个测试确认基础功能没有坏。
数据流:进去的是测试自己创建的 20 列、6 行假屏幕,以及两行文字 first 和 second → 它建立场景,插入这两行,再读取模拟屏幕的全部可见内容 → 结果要求屏幕内容里同时包含 first 和 second,否则测试失败。
调用关系:它先借 TestScenario::new 搭建终端,再把文字交给插入流程,最后用 assert_contains! 这个小断言宏检查屏幕内容。它是整组测试里最基础的冒烟测试,用来证明普通插入可用。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_contains!, vec!)。
long_token_wraps57–86 ↗
fn long_token_wraps()
作用:检查一个特别长、没有空格的词在窄屏幕里换行时不会丢字符。没有这个测试,长串 ID、路径或连续字符可能显示不完整却没人发现。
数据流:进去的是一个宽 20、高 6 的假屏幕,以及 45 个 A 组成的长字符串 → 它插入这串文字,然后逐格扫描模拟屏幕上的每个单元格,数一共有多少个 A → 出来的判断是:屏幕上 A 的数量必须等于原字符串长度,说明换行没有吞字。
调用关系:它使用 TestScenario::new 准备屏幕,再走同一套历史插入流程。和普通短文本测试不同,它不只看整行字符串,而是逐个格子数字符,因为长文本会被拆到多行。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, vec!)。
emoji_and_cjk89–105 ↗
fn emoji_and_cjk()
作用:检查 emoji 表情和中文、日文、韩文这类宽字符能正确显示。它防止终端处理特殊宽度字符时出现少字、错位或被忽略的问题。
数据流:进去的是一行包含多个 和“你好世界”的文字 → 它插入这行文字,然后把模拟屏幕内容拼出来 → 对原文里每个非空白字符逐个检查,要求屏幕内容里都能找到它们。
调用关系:它先通过 TestScenario::new 得到假终端,再调用历史插入流程。它关注的是字符是否保留下来,而不是具体落在哪一列,因为 emoji 和中文在终端里通常占两个英文字符宽度。
mixed_ansi_spans108–118 ↗
fn mixed_ansi_spans()
作用:检查带样式的文字和普通文字混在一起时,最终显示的文字内容仍然正确。比如红色的 red 后面接普通的 +plain,屏幕上应该看到 red+plain。
数据流:进去的是一行由两个片段组成的文字:red 带红色样式,+plain 没有样式 → 它把这行插入终端,再读取屏幕文本内容 → 结果要求屏幕里包含 red+plain,说明样式控制没有把文字拆坏或漏掉。
调用关系:它用 ratatui 的 Stylize 给 red 加颜色,再通过 TestScenario::new 和插入流程送进假终端。最后用 assert_contains! 检查显示文本,重点不是颜色本身,而是样式混排后文字没有变形。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_contains!, vec!)。
cursor_restoration121–130 ↗
fn cursor_restoration()
作用:检查插入历史内容后,终端记录的光标位置会回到原来的位置。光标就是下一次写字会出现的地方,位置错了会导致后续界面画歪。
数据流:进去的是一个假终端和一行 x → 它插入这行历史内容 → 然后读取 terminal 的 last_known_cursor_pos,要求它等于 (0, 0),也就是测试预期的初始光标位置。
调用关系:它和其他测试一样先用 TestScenario::new 建场景,再触发历史插入。不同的是,它不检查屏幕文字,而是检查插入动作对终端状态的副作用,确保 insert_history_lines 做完后没有把光标留在错误位置。
调用图:调用 1 个内部函数(new);外部调用 3 个(new, assert_eq!, vec!)。
word_wrap_no_mid_word_split133–147 ↗
fn word_wrap_no_mid_word_split()
作用:检查普通英文段落自动换行时,尽量不要把一个单词从中间切开。这样历史内容看起来更像自然排版,而不是机器硬切的碎片。
数据流:进去的是一个宽 40、高 10 的假屏幕,以及一段较长的英文故事 → 它插入这段文字,再读取屏幕内容 → 结果明确要求不要出现 bo 换行 th 这种把 both 拆成两半的情况。
调用关系:它通过 TestScenario::new 准备比前面稍大的屏幕,然后走历史插入流程。这个测试专门守住“按词换行”的行为,防止后续修改把排版退化成简单按列硬切。
em_dash_and_space_word_wrap150–164 ↗
fn em_dash_and_space_word_wrap()
作用:检查带破折号和空格的英文句子换行时,不会把 inside 这样的单词从中间切开。它来自一个具体问题复现,用来防止同类 bug 回来。
数据流:进去的是一个宽 40、高 10 的假屏幕,以及包含 sand—and inside 的英文句子 → 它插入文字,再读取模拟屏幕内容 → 结果要求不能出现 insi 换行 de,说明换行点应该选在单词前后,而不是单词中间。
调用关系:它和 word_wrap_no_mid_word_split 一样测试自动换行,但加入了 em dash(长破折号)这种标点场景。它先用 TestScenario::new 建场景,再依赖历史插入逻辑完成排版,最后用断言确认特定旧问题没有复发。
tui/tests/suite/vt100_live_commit.rs源码 ↗
这个测试模拟一个很窄、很矮的终端窗口,然后手工造出 5 行文字:one 到 five。界面里有一块“实时区”,可以理解成正在显示、还没正式存档的几行;如果实时区太满,就要把前面的旧行挪到历史区,像排队时前面的人先进大厅,后面的人继续在队尾等。这里要求最多保留最后 3 行,所以 one 和 two 应该被提交到历史区。测试把这两行插入终端历史后,读取虚拟终端屏幕内容,确认 one 和 two 确实能在视口上方看到。这样可以保证滚动、换行和历史保存配合正常。
live_001_commit_on_overflow6–43 ↗
fn live_001_commit_on_overflow()
作用:这个测试检查:当实时显示的行超过保留上限时,较早的行会被正确提交到终端历史里,而不是悄悄消失。有人改动终端滚动或历史插入逻辑时,它能及时发现问题。
数据流:进去的是一个模拟出来的 20 列、6 行终端,以及 5 行测试文字。函数先创建终端,设置可见区域,再用 RowBuilder 把文字拆成行;接着只保留最后 3 行,把前 2 行取出来转成可插入的文本行,并插入历史区。最后它读取虚拟屏幕内容,检查结果里必须包含 one 和 two;如果没有,测试就失败。
调用关系:它先调用 VT100Backend::new 准备一个假的终端后端,再用 codex_tui::Terminal::with_options 建出测试用终端;随后用 Rect::new 指定视口位置,用 RowBuilder::new 组装待显示的行。真正验证的关键步骤是调用 codex_tui::insert_history_lines,把已经该“存档”的行放入历史区,然后通过断言检查虚拟屏幕是否显示了预期内容。
调用图:调用 3 个内部函数(with_options, new, new);外部调用 3 个(new, assert!, insert_history_lines)。