diff --git a/client/electron/test/harmony-logic.test.mjs b/client/electron/test/harmony-logic.test.mjs index 7f8b93b..929c1c5 100644 --- a/client/electron/test/harmony-logic.test.mjs +++ b/client/electron/test/harmony-logic.test.mjs @@ -985,3 +985,48 @@ test('★ 附件区:空文件名要显式占位,不能留一条看起来坏 assert.notEqual(empty.filename, '', '空文件名要有占位符,不能是空串'); assert.equal(empty.size, '100 B', '空文件名不影响大小显示'); }); + +/** + * ★★ 2026-09-24 新增(用户:「发件箱内容也点不开」)。 + * + * ── 实测复现 ── + * 点发件箱里「致 pi」那一行: + * · 日志里**没有** `GET /mail/{id}`(收件箱同样操作是会发的); + * · 右栏仍是占位(「选择一封邮件查看…」)。 + * + * ── 根因 ── + * 点击挂在**外层** `Column` 上,而 `SentRow` 自己**一个 `.onClick` 都没有**。 + * 更要命的是:`isFlatGroup(g)` 那条分支(单封不成组)直接调 `SentRow`, + * **外层那层点击根本不经过** ⇒ 用户的数据恰好是 1 封不成组 ⇒ 完全点不动。 + * + * 收件箱的 `MailRow` 是挂在行自己身上的,所以它两条分支都通。 + * 这就是本仓反复出现的形状:**同一件事两处各写一遍,然后慢慢分叉**。 + * + * 判据钉的是**不变式**(“列表行自带点击”),而不是某个函数的写法: + * 每个能展开邮件的列表行 `@Builder`,它的属性链里必须有自己的 `.onClick`。 + */ +test('★ 列表行必须把 onClick 挂在自己身上(发件箱就漏在这里)', () => { + /* + * 行 Builder 的名单:两栏各自的邮件行。 + * ★ 不扫全部 `@Builder`(那样会把组头、空态、徽标都算进来), + * 只钉**真正代表“一封邮件”的那两个** —— 与 `PAGE_SOURCES` 同一条纪律: + * 名单显式,不把作用域开得比声称的宽。 + */ + const ROWS = ['MailRow', 'SentRow']; + for (const name of ROWS) { + const at = pageCode.indexOf(`${name}(mail: MailLike)`); + assert.ok(at > 0, `${name} 要存在(列表行)`); + /* + * 取到下一个 `@Builder` 为止(下一个 Builder 就是下一个方法的开始)。 + * 比“取 3000 字符”可靠:方法长短会变,而 `@Builder` 是结构边界。 + */ + const nextAt = pageCode.indexOf('@Builder', at); + const body = pageCode.slice(at, nextAt > at ? nextAt : at + 4000); + assert.match(body, /\.onClick\(/, [ + `${name} 的属性链里必须有自己的 \`.onClick\``, + '—— 点击挂在外层容器时,扁平组分支(isFlatGroup)**不经过它**,', + '那一栏就完全点不动(2026-09-24 发件箱实测就是这个形状)。', + ].join('')); + assert.match(body, /openMail\(/, `${name} 的 onClick 要调到 openMail(而不是只写个空壳)`); + } +}); diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index 337b519..03a7b2f 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -1139,6 +1139,30 @@ struct SentTab { }) .borderRadius(Theme.radiusCard) .clip(true) + /* + * ★★ 2026-09-24 补 `.onClick`(用户:「发件箱内容也点不开」)。 + * + * ── 实测复现 ── + * 点发件箱里「致 pi」那一行: + * · 日志里**没有** `GET /mail/{id}`(收件箱同样操作是会发的); + * · 右栏仍是占位(「选择一封邮件查看…」)。 + * ⇒ 点击事件根本到不了处理函数。 + * + * ── 根因 ── + * 点击原来挂在**外层** `Column` 上(`SentTab` 的 `ListItem` 里): + * Column() { this.SentRow(m) } + * .width('100%').margin({ bottom: 6 }) + * .onClick(() => { this.openMail(m); }) + * 而 `SentRow` **自己一个 `.onClick` 都没有** —— 收件箱的 `MailRow` + * 是挂在行自己身上的(同位置 `.clip(true)` 之后直接 `.onClick`)。 + * 本仓反复出现的形状:**同一件事两处各写一遍,然后慢慢分叉** + * (这段注释上方才记过 `SentRow` 漏挂卡片修饰符的那次,同一处、同一类错)。 + * + * ⇒ 与 `MailRow` 取同形:卡在行自己身上,点击也挂在行自己身上。 + */ + .onClick(() => { + this.openMail(mail); + }) } @Builder @@ -1263,7 +1287,16 @@ struct SentTab { } .width('100%') .margin({ bottom: 6 }) - .onClick(() => { this.openMail(m); }) + /* + * ★★ 2026-09-24 删掉这里的 `.onClick`(用户:「发件箱内容也点不开」)。 + * + * 点击现在挂在 `SentRow` **自己身上**(与收件箱的 `MailRow` 同形)—— + * 原来挂在这一层时,**扁平组那条路根本不经过它**: + * `isFlatGroup(g)` 分支直接 `this.SentRow(g.mails[0])`,外层没有点击。 + * 而用户的数据恰好就是 1 封不成组(发件箱「测试-4」)⇒ 完全点不动。 + * + * 留着它还会变成"同一行两处都能点"—— 本仓反复出现的那个形状。 + */ } .width('100%') }, (m: MailLike) => m.mail_id)