fix(app-iced): M95 r1 五条视觉复审(悬挂缩进 / 会话身份 / 摘要可见 / 贴底 / 描边)
- P2-1 **悬挂缩进真做了**(之前文档在骗人,代码里只有 `row![bar, text]`):
每行拆「前缀 + 正文」两个文本控件(前缀 = 第一个空格及其之前,含空格;
`split_prefix` 在第一个「词」>20 字符时不拆),正文带 Fill 宽度自己折行 ⇒
续行落在前缀宽度之后。于是 `at com.mojang…(GLX.java:64)` 不再拆成「`at` 独占一行」。
文档同步写清代价:续行缩进量 = 该行前缀宽度。
- P2-2 会话身份上页头:`Logs::with_status(lang)` 用 `LogsStatus` 模板拼
`{instance} · {when} · {outcome}`,结局文案走 `LaunchOutcomeNormal/Crash/Silent`
三 key(bridge 文档里那条「LogOutcome 三态 → 三 key」的结构化出口,此前 UI 侧零调用点)。
中文模板差在全角竖线(守卫要求 zh≠en,也确实更符合中文排版)。
- P2-3 摘要钉成诊断卡**固定页脚**(不进滚动区):「One-line summary」+ Copy summary
紧邻它要复制的那段等宽文本;卡片头不再放复制按钮(避免两处入口)。
- P2-4 日志卡改 `panel::scroll_tail`(`Anchor::End`):偏移按「距底多远」记,内容增长时
跟随尾部;「用户上滚后是否冻结」的边界写在 `panel::scroll_tail` 文档里(结论:不冻结,
冻结要 app 层回灌滚动状态,已登记)。
- P2-5 两张主卡补 1px 描边:`panel::card` 改用 `Component::OutlinedCard.spec().shape` +
`ui.outlined(..)`,与 `frame::card` 同一套令牌。
测试:新增 `a_line_is_split_into_prefix_and_body_for_the_hanging_indent`(含 CJK 行与
「不拆」的两种边界)与 `status_line_names_the_session_with_the_launch_outcome_keys`;
key 清单补 `LogsStatus` 与三个 `LaunchOutcome*`;demo 的 state.build 改为收 `Lang`。
验证:`fmt --check` 通过;`clippy -p unlucky-app-iced --all-targets -D warnings` 干净;
`test -p unlucky-app-iced` 274+6 全绿、`-p unlucky-i18n` 23 全绿。
图:`.screens/logs-{empty,live,crash}-{light,dark}.png`(1180×760)、
`r2-live-tall-light.png`(1180×1080,栈帧可见,P2-1 证据)。
This commit is contained in:
1 parent
063a1dba39
commit
1bccf89e5c
10 files changed
+293
-93
No files matched your search
@@ -1,7 +1,8 @@
|
||||
//! 日志页的守门测试。每条断言下面写清「抓得住什么 / 抓不住什么」(这个仓的硬要求)。
|
||||
//!
|
||||
//! 这里**没有截图断言**:像素级验收靠 `pages_demo --screen=logs` 的四张图(浅/深 × 空态/崩溃),
|
||||
//! 单测只钉住「图上看不出来的那部分」——数值令牌、分组顺序、摘要格式、长行不被截断。
|
||||
//! 这里**没有截图断言**:像素级验收靠 `pages_demo --screen=logs` 的六张图(浅/深 × 空态/有日志/崩溃),
|
||||
//! 单测只钉住「图上看不出来的那部分」——数值令牌、分组顺序、摘要格式、悬挂缩进的拆分规则、
|
||||
//! 页头会话身份的拼法。
|
||||
|
||||
use unlucky_design::color::SchemeFlavor;
|
||||
use unlucky_design::typography::{FontFamily, Platform};
|
||||
@@ -11,7 +12,7 @@ use unlucky_i18n::Lang;
|
||||
use crate::theme::Families;
|
||||
|
||||
use super::super::layout::{frame_metrics, measure_cap_px, PaneKit};
|
||||
use super::model::{GROUPS, SUMMARY_HEADER};
|
||||
use super::model::{self, GROUPS, SUMMARY_HEADER};
|
||||
use super::{demo, lines, *};
|
||||
|
||||
fn ui() -> Ui {
|
||||
@@ -136,6 +137,70 @@ fn the_longest_stack_line_reaches_the_view_model_untruncated() {
|
||||
assert!(!logs.lines.iter().any(|line| line.text.ends_with('…')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_is_split_into_prefix_and_body_for_the_hanging_indent() {
|
||||
// 抓得住:悬挂缩进被改回「整行一个文本块」(`at` 会重新独占一行、续行回到行首)。
|
||||
// 抓不住:缩进到底落在第几像素(那要量字形/截图,见报告的前后对比图)。
|
||||
assert_eq!(
|
||||
lines::split_prefix("[19:40:12] [main/INFO] hi"),
|
||||
("[19:40:12] ", "[main/INFO] hi")
|
||||
);
|
||||
assert_eq!(
|
||||
lines::split_prefix("at com.mojang.blaze3d.platform.GLX._initGlfw(GLX.java:64)"),
|
||||
(
|
||||
"at ",
|
||||
"com.mojang.blaze3d.platform.GLX._initGlfw(GLX.java:64)"
|
||||
)
|
||||
);
|
||||
|
||||
// 没有空格 / 第一个「词」过长 ⇒ 不拆(否则正文被挤成一条 1 字符宽的缝)。
|
||||
assert_eq!(
|
||||
lines::split_prefix("java.lang.RuntimeException"),
|
||||
("", "java.lang.RuntimeException")
|
||||
);
|
||||
assert_eq!(
|
||||
lines::split_prefix("https://example.invalid/aaaaaaaaaaaaaaaaaaaaaaaa va"),
|
||||
("", "https://example.invalid/aaaaaaaaaaaaaaaaaaaaaaaa va")
|
||||
);
|
||||
// CJK 行:第一个空格若落在 20 字符以内就正常拆(时间戳 + 中文正文)。
|
||||
assert_eq!(
|
||||
lines::split_prefix("[19:40:12] 正在启动 1.20.4"),
|
||||
("[19:40:12] ", "正在启动 1.20.4")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_line_names_the_session_with_the_launch_outcome_keys() {
|
||||
// 抓得住:页头副标题不再说「这是哪个实例、哪一场、什么结局」(视觉复审 P2-2),
|
||||
// 以及 `LaunchOutcome*` 三 key 又回到「定义了但零调用点」。
|
||||
// 抓不住:副标题在 1180 下会不会撞上右侧动作区(那要看截图)。
|
||||
let en = model::status_line(
|
||||
Some("开发测试"),
|
||||
Some("11-02 19:40"),
|
||||
Some(Outcome::Crashed),
|
||||
Lang::En,
|
||||
)
|
||||
.expect("有实例名与结局就该有副标题");
|
||||
assert_eq!(en, "开发测试 · 11-02 19:40 · The game crashed");
|
||||
|
||||
let zh = model::status_line(
|
||||
Some("开发测试"),
|
||||
Some("11-02 19:40"),
|
||||
Some(Outcome::SilentFail),
|
||||
Lang::ZhCn,
|
||||
)
|
||||
.expect("静默失败也有副标题");
|
||||
assert!(zh.contains("疑似静默失败"), "{zh}");
|
||||
|
||||
// 不知道是哪一场就别编:没有实例名 / 没有结局时退回静态副标题。
|
||||
assert!(model::status_line(None, Some("11-02 19:40"), Some(Outcome::Ok), Lang::En).is_none());
|
||||
assert!(model::status_line(Some("x"), None, None, Lang::En).is_none());
|
||||
|
||||
// demo 的三种状态里,有会话身份的那两种必须带副标题(页面才有东西可显示)。
|
||||
assert!(demo::State::Crash.build(Lang::En).status.is_some());
|
||||
assert!(demo::State::Live.build(Lang::En).status.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_state_is_driven_by_both_lists_being_empty() {
|
||||
let mut logs = Logs::default();
|
||||
@@ -145,7 +210,7 @@ fn empty_state_is_driven_by_both_lists_being_empty() {
|
||||
// 抓得住:把「没有日志行」当成空态(有诊断时那样会藏掉最该看的东西)。
|
||||
assert!(!logs.is_empty(), "只有诊断也不算空态");
|
||||
|
||||
let live = demo::State::Live.build();
|
||||
let live = demo::State::Live.build(Lang::En);
|
||||
assert!(!live.is_empty());
|
||||
assert!(live.findings.is_empty(), "live 状态没有诊断");
|
||||
}
|
||||
@@ -164,7 +229,7 @@ fn every_key_the_page_uses_is_translated_in_both_languages() {
|
||||
let keys = [
|
||||
Key::NavLogs,
|
||||
Key::LogsSubtitleIdle,
|
||||
Key::LogsSubtitleActive,
|
||||
Key::LogsStatus,
|
||||
Key::LogsOpenFolder,
|
||||
Key::LogsClear,
|
||||
Key::LogsOutputTitle,
|
||||
@@ -179,6 +244,10 @@ fn every_key_the_page_uses_is_translated_in_both_languages() {
|
||||
Key::LogsEmptyTitle,
|
||||
Key::LogsEmptyBody,
|
||||
Key::LogsEmptyAction,
|
||||
// 结局文案由页头副标题间接用到(见 `status_line`)
|
||||
Key::LaunchOutcomeNormal,
|
||||
Key::LaunchOutcomeCrash,
|
||||
Key::LaunchOutcomeSilent,
|
||||
];
|
||||
|
||||
// 抓得住:页面用到的 key 被改名/删掉,或只进了其中一套目录(缺译会返回 `⟪missing⟫`)。
|
||||
|
||||
Reference in new issue
Block a user