chore(format): 撤销误入提交的整体重排,并关闭本仓库的格式化器

# 发生了什么

pi-lens 内置「安全格式化」:它会自动安装 biome 并对**编辑过的文件**跑
`biome format --write`。本机原先没有任何 biome 配置,于是 biome 用它自己的
默认值 —— tab 缩进 + 双引号 —— 把文件整体重写。

我在 19a3161 那次提交里用了 `git add -A`,把这批与功能无关的重排一起扫了进去:
约 7000 行改动散落在 20 个文件上,使那次提交无法审查,还掩盖了
server/internal/handler/permission.go 的一处删行(实为文件末尾空行,无代码丢失)。

# 为什么是「关掉」而不是「配置成我们的风格」

试过把缩进/引号/lineWidth 全部对齐本仓库习惯(biome.json + space/2/single/
lineWidth 120):`biome format --write` 仍然改动 17 个文件。原因是本仓库从未按
biome 的规则排版过 —— 注释按语义换行、数组与调用按可读性手工折行,
这些无法由格式化器还原。也就是说只要格式化器开着,每次编辑都会产生与内容无关的
大面积 diff,把真正的改动埋掉。

因此 biome.jsonc 里 formatter 与 linter 都关闭:本仓库的静态检查由
tsc / go vet / tree-sitter / ast-grep 与各自测试套件承担,不引入会改动无关行的
自动修复。

(pi-lens 这一版把 format 服务的 enabled 硬编码为 true,没有配置开关,
所以只能在仓库侧用 biome 配置让它不动文件;已验证 `biome format --write`
对这些文件零改动。)

# 本提交内容

把 19a3161 里除「有意改动」外的 20 个文件还原到重排前的样子。
19a3161 中真正有意的改动是 deploy/install.sh 的扩展注册与
plugins/pi-mail-bridge/extension/index.ts 新文件,两者原样保留。

验证:Go 全量、三桥插件(320/362/409)、前端 196 全绿;
`biome format --write` 对还原后的文件零改动。
This commit is contained in:
2026-09-11 12:03:51 +08:00
parent 19a3161ee4
commit 429149e118
21 changed files with 5603 additions and 6668 deletions

View File

@ -1,13 +1,13 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { PermissionPanel } from "../../src/components/MailView";
import * as api from "../../src/api/client";
import { useMailStore } from "../../src/stores/mailStore";
import { useSessionStore } from "../../src/stores/sessionStore";
import type { Mail } from "../../src/types";
import { PermissionPanel } from '../../src/components/MailView';
import * as api from '../../src/api/client';
import { useMailStore } from '../../src/stores/mailStore';
import { useSessionStore } from '../../src/stores/sessionStore';
import type { Mail } from '../../src/types';
/**
* 权限决策面板。
@ -19,214 +19,187 @@ import type { Mail } from "../../src/types";
*/
function permMail(over: Partial<Mail> = {}): Mail {
return {
mail_id: "m-1",
session_id: "s-1",
parent_mail_id: null,
from_name: "dsh",
from_workspace: "/home/program/agentmail",
to_name: "admin",
to_workspace: "",
cc_list: [],
subject: "请求批准:删除 build/",
body: "将执行 rm -rf build/",
mail_type: "permission_request",
permission_options: undefined,
permission_result: "",
status: "unread",
created_at: "2026-09-03T00:00:00Z",
hop_limit: 5,
...over,
} as Mail;
return {
mail_id: 'm-1',
session_id: 's-1',
parent_mail_id: null,
from_name: 'dsh',
from_workspace: '/home/program/agentmail',
to_name: 'admin',
to_workspace: '',
cc_list: [],
subject: '请求批准:删除 build/',
body: '将执行 rm -rf build/',
mail_type: 'permission_request',
permission_options: undefined,
permission_result: '',
status: 'unread',
created_at: '2026-09-03T00:00:00Z',
hop_limit: 5,
...over
} as Mail;
}
describe("PermissionPanel 决策", () => {
beforeEach(() => {
vi.restoreAllMocks();
// fetchInbox / selectSession 会打网络,替换成空实现
useMailStore.setState({ fetchInbox: vi.fn(async () => {}) } as any);
useSessionStore.setState({ selectSession: vi.fn(async () => {}) } as any);
});
describe('PermissionPanel 决策', () => {
beforeEach(() => {
vi.restoreAllMocks();
// fetchInbox / selectSession 会打网络,替换成空实现
useMailStore.setState({ fetchInbox: vi.fn(async () => {}) } as any);
useSessionStore.setState({ selectSession: vi.fn(async () => {}) } as any);
});
it("没有 permission_options 时给默认的同意/拒绝两个选项", () => {
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('没有 permission_options 时给默认的同意/拒绝两个选项', () => {
render(React.createElement(PermissionPanel, { mail: permMail() }));
expect(screen.getByRole("button", { name: /同意/ })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /拒绝/ })).toBeInTheDocument();
});
expect(screen.getByRole('button', { name: /同意/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /拒绝/ })).toBeInTheDocument();
});
it("有 permission_options 时用它,且顺序保持", () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({
permission_options: ["只这一次", "总是允许", "拒绝"],
}),
}),
);
it('有 permission_options 时用它,且顺序保持', () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_options: ['只这一次', '总是允许', '拒绝'] })
})
);
const btns = screen
.getAllByRole("button")
.map((b) => b.textContent?.trim());
// 顺序是 Agent 给的语义顺序,重排会让「拒绝」跑到人的手指默认位置上
expect(btns).toEqual(["只这一次", "总是允许", "拒绝"]);
});
const btns = screen.getAllByRole('button').map(b => b.textContent?.trim());
// 顺序是 Agent 给的语义顺序,重排会让「拒绝」跑到人的手指默认位置上
expect(btns).toEqual(['只这一次', '总是允许', '拒绝']);
});
it("点选项时把【选项原文】发给服务端", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_options: ["只这一次", "拒绝"] }),
}),
);
it('点选项时把【选项原文】发给服务端', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_options: ['只这一次', '拒绝'] })
})
);
await userEvent.click(screen.getByRole("button", { name: /只这一次/ }));
await userEvent.click(screen.getByRole('button', { name: /只这一次/ }));
// 关键:不能归一化成 allow/deny —— 「只这一次」与「总是允许」的区别
// 只有 Agent 侧的权限机制懂,服务端与前端都不该替它翻译
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "只这一次", undefined),
);
});
// 关键:不能归一化成 allow/deny —— 「只这一次」与「总是允许」的区别
// 只有 Agent 侧的权限机制懂,服务端与前端都不该替它翻译
await waitFor(() =>
expect(spy).toHaveBeenCalledWith('m-1', '只这一次', undefined)
);
});
it("填了备注时一起发出去", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('填了备注时一起发出去', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.type(
screen.getByPlaceholderText("备注(可选)"),
"只删 build别动 dist",
);
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
await userEvent.type(screen.getByPlaceholderText('备注(可选)'), '只删 build别动 dist');
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "同意", "只删 build别动 dist"),
);
});
await waitFor(() =>
expect(spy).toHaveBeenCalledWith('m-1', '同意', '只删 build别动 dist')
);
});
it("备注为空时传 undefined 而不是空字符串", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('备注为空时传 undefined 而不是空字符串', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
// 空串会在决策邮件里留一行空的「备注:」
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "同意", undefined),
);
});
// 空串会在决策邮件里留一行空的「备注:」
await waitFor(() => expect(spy).toHaveBeenCalledWith('m-1', '同意', undefined));
});
it("决策后变成「已处理」,不再显示按钮", async () => {
vi.spyOn(api, "decidePermission").mockResolvedValue({
status: "decided",
} as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('决策后变成「已处理」,不再显示按钮', async () => {
vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
await waitFor(() =>
expect(screen.getByText("已处理:")).toBeInTheDocument(),
);
// 还能点第二次的话人会以为第一次没生效,而服务端那边早已决策
expect(screen.queryByRole("button")).toBeNull();
});
await waitFor(() => expect(screen.getByText('已处理:')).toBeInTheDocument());
// 还能点第二次的话人会以为第一次没生效,而服务端那边早已决策
expect(screen.queryByRole('button')).toBeNull();
});
it("已经有 permission_result 的邮件直接显示结论", () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_result: "拒绝" }),
}),
);
it('已经有 permission_result 的邮件直接显示结论', () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_result: '拒绝' })
})
);
expect(screen.getByText("拒绝")).toBeInTheDocument();
expect(screen.queryByRole("button")).toBeNull();
});
expect(screen.getByText('拒绝')).toBeInTheDocument();
expect(screen.queryByRole('button')).toBeNull();
});
it("提交中禁用所有按钮,避免重复决策", async () => {
let release: (v: any) => void = () => {};
vi.spyOn(api, "decidePermission").mockReturnValue(
new Promise((res) => {
release = res;
}) as any,
);
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('提交中禁用所有按钮,避免重复决策', async () => {
let release: (v: any) => void = () => {};
vi.spyOn(api, 'decidePermission').mockReturnValue(
new Promise(res => {
release = res;
}) as any
);
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
// 一次危险操作被批准两次Agent 那边可能真的执行两遍
await waitFor(() => {
for (const b of screen.getAllByRole("button")) {
expect(b).toBeDisabled();
}
});
// 一次危险操作被批准两次Agent 那边可能真的执行两遍
await waitFor(() => {
for (const b of screen.getAllByRole('button')) {
expect(b).toBeDisabled();
}
});
// 收尾:让悬挂的 Promise 落定并等状态更新走完,
// 否则组件在测试结束后才 setStateReact 会报 act 警告
await act(async () => {
release({ status: "decided" });
});
await waitFor(() =>
expect(screen.getByText("已处理:")).toBeInTheDocument(),
);
});
// 收尾:让悬挂的 Promise 落定并等状态更新走完,
// 否则组件在测试结束后才 setStateReact 会报 act 警告
await act(async () => {
release({ status: 'decided' });
});
await waitFor(() => expect(screen.getByText('已处理:')).toBeInTheDocument());
});
it("提交失败时恢复可点,不假装已决策", async () => {
vi.spyOn(api, "decidePermission").mockRejectedValue(new Error("500"));
vi.spyOn(console, "error").mockImplementation(() => {});
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('提交失败时恢复可点,不假装已决策', async () => {
vi.spyOn(api, 'decidePermission').mockRejectedValue(new Error('500'));
vi.spyOn(console, 'error').mockImplementation(() => {});
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
// 失败后显示「已处理」是最糟的结果人以为批过了Agent 还在等
await waitFor(() =>
expect(screen.getByRole("button", { name: /同意/ })).toBeEnabled(),
);
expect(screen.queryByText("已处理:")).toBeNull();
});
// 失败后显示「已处理」是最糟的结果人以为批过了Agent 还在等
await waitFor(() => expect(screen.getByRole('button', { name: /同意/ })).toBeEnabled());
expect(screen.queryByText('已处理:')).toBeNull();
});
it("决策成功后刷新收件箱并选中该会话", async () => {
vi.spyOn(api, "decidePermission").mockResolvedValue({
status: "decided",
} as any);
const fetchInbox = vi.fn(async () => {});
const selectSession = vi.fn(async () => {});
useMailStore.setState({ fetchInbox } as any);
useSessionStore.setState({ selectSession } as any);
it('决策成功后刷新收件箱并选中该会话', async () => {
vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
const fetchInbox = vi.fn(async () => {});
const selectSession = vi.fn(async () => {});
useMailStore.setState({ fetchInbox } as any);
useSessionStore.setState({ selectSession } as any);
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole("button", { name: /同意/ }));
render(React.createElement(PermissionPanel, { mail: permMail() }));
await userEvent.click(screen.getByRole('button', { name: /同意/ }));
// 不刷新的话列表里那封还是「未读的权限请求」,人会以为没生效
await waitFor(() => {
expect(fetchInbox).toHaveBeenCalledWith("all");
expect(selectSession).toHaveBeenCalledWith("s-1");
});
});
// 不刷新的话列表里那封还是「未读的权限请求」,人会以为没生效
await waitFor(() => {
expect(fetchInbox).toHaveBeenCalledWith('all');
expect(selectSession).toHaveBeenCalledWith('s-1');
});
});
it("同意类选项用绿色,其余用红色", () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({
permission_options: ["允许", "approve", "拒绝", "算了"],
}),
}),
);
it('同意类选项用绿色,其余用红色', () => {
render(
React.createElement(PermissionPanel, {
mail: permMail({ permission_options: ['允许', 'approve', '拒绝', '算了'] })
})
);
const cls = (name: string) =>
screen.getByRole("button", { name: new RegExp(name) }).className;
const cls = (name: string) =>
screen.getByRole('button', { name: new RegExp(name) }).className;
// 颜色是唯一的视觉提示:点错一次就放行了一个危险操作
expect(cls("允许")).toContain("bg-green-700");
expect(cls("approve")).toContain("bg-green-700");
expect(cls("拒绝")).toContain("text-red-700");
// 不在同意词表里的一律按「否」处理 —— 宁可让人多看一眼
expect(cls("算了")).toContain("text-red-700");
});
// 颜色是唯一的视觉提示:点错一次就放行了一个危险操作
expect(cls('允许')).toContain('bg-green-700');
expect(cls('approve')).toContain('bg-green-700');
expect(cls('拒绝')).toContain('text-red-700');
// 不在同意词表里的一律按「否」处理 —— 宁可让人多看一眼
expect(cls('算了')).toContain('text-red-700');
});
});
/**
@ -237,142 +210,117 @@ describe("PermissionPanel 决策", () => {
* - 不能把问题渲染成同意/拒绝(那会让人点出一个毫无意义的答案)
* - 空回答不能提交(模型会拿到一个什么都没说的结果继续跑)
*/
describe("PermissionPanel 回答问题", () => {
beforeEach(() => {
vi.restoreAllMocks();
useMailStore.setState({ fetchInbox: vi.fn(async () => {}) } as any);
useSessionStore.setState({ selectSession: vi.fn(async () => {}) } as any);
});
describe('PermissionPanel 回答问题', () => {
beforeEach(() => {
vi.restoreAllMocks();
useMailStore.setState({ fetchInbox: vi.fn(async () => {}) } as any);
useSessionStore.setState({ selectSession: vi.fn(async () => {}) } as any);
});
const questionMail = (over: Partial<Mail> = {}): Mail =>
permMail({ permission_kind: "question", ...over });
const questionMail = (over: Partial<Mail> = {}): Mail =>
permMail({ permission_kind: 'question', ...over });
it("问题不带选项时:不渲染同意/拒绝,只给自由文本", () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: [] }),
}),
);
it('问题不带选项时:不渲染同意/拒绝,只给自由文本', () => {
render(React.createElement(PermissionPanel, { mail: questionMail({ permission_options: [] }) }));
expect(screen.queryByRole("button", { name: /同意/ })).toBeNull();
expect(screen.queryByRole("button", { name: /拒绝/ })).toBeNull();
expect(screen.getByPlaceholderText("你的回答(必填)")).toBeInTheDocument();
});
expect(screen.queryByRole('button', { name: /同意/ })).toBeNull();
expect(screen.queryByRole('button', { name: /拒绝/ })).toBeNull();
expect(screen.getByPlaceholderText('你的回答(必填)')).toBeInTheDocument();
});
it("问题带选项时:渲染选项按钮(而不是同意/拒绝)", () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ["方案 A", "方案 B"] }),
}),
);
it('问题带选项时:渲染选项按钮(而不是同意/拒绝)', () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ['方案 A', '方案 B'] })
})
);
expect(screen.getByRole("button", { name: /方案 A/ })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /方案 B/ })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /^同意$/ })).toBeNull();
});
expect(screen.getByRole('button', { name: /方案 A/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /方案 B/ })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /^同意$/ })).toBeNull();
});
it("单选:再点已选项会取消,不会同时选中两个", async () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ["A", "B"] }),
}),
);
const a = screen.getByRole("button", { name: /^A$/ });
const b = screen.getByRole("button", { name: /^B$/ });
it('单选:再点已选项会取消,不会同时选中两个', async () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ['A', 'B'] })
})
);
const a = screen.getByRole('button', { name: /^A$/ });
const b = screen.getByRole('button', { name: /^B$/ });
await userEvent.click(a);
expect(a).toHaveAttribute("aria-pressed", "true");
await userEvent.click(b);
expect(b).toHaveAttribute("aria-pressed", "true");
expect(a).toHaveAttribute("aria-pressed", "false");
});
await userEvent.click(a);
expect(a).toHaveAttribute('aria-pressed', 'true');
await userEvent.click(b);
expect(b).toHaveAttribute('aria-pressed', 'true');
expect(a).toHaveAttribute('aria-pressed', 'false');
});
it("多选:可同时选中多项,提交时用换行拼接", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(
React.createElement(PermissionPanel, {
mail: questionMail({
permission_options: ["A", "B"],
permission_multi_select: true,
}),
}),
);
it('多选:可同时选中多项,提交时用换行拼接', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ['A', 'B'], permission_multi_select: true })
})
);
await userEvent.click(screen.getByRole("button", { name: /^A$/ }));
await userEvent.click(screen.getByRole("button", { name: /^B$/ }));
await userEvent.click(screen.getByRole("button", { name: /提交回答/ }));
await userEvent.click(screen.getByRole('button', { name: /^A$/ }));
await userEvent.click(screen.getByRole('button', { name: /^B$/ }));
await userEvent.click(screen.getByRole('button', { name: /提交回答/ }));
// 服务端按换行拆分多选答案,不能拼接成 "AB" 或数组字符串
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "A\nB", undefined),
);
});
// 服务端按换行拆分多选答案,不能拼接成 "AB" 或数组字符串
await waitFor(() => expect(spy).toHaveBeenCalledWith('m-1', 'A\nB', undefined));
});
it("空回答禁止提交(模型不能拿到一个什么都没说的结果)", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: [] }),
}),
);
it('空回答禁止提交(模型不能拿到一个什么都没说的结果)', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(React.createElement(PermissionPanel, { mail: questionMail({ permission_options: [] }) }));
const submit = screen.getByRole("button", { name: /提交回答/ });
expect(submit).toBeDisabled();
expect(screen.getByText("请先选择或填写回答")).toBeInTheDocument();
const submit = screen.getByRole('button', { name: /提交回答/ });
expect(submit).toBeDisabled();
expect(screen.getByText('请先选择或填写回答')).toBeInTheDocument();
await userEvent.type(
screen.getByPlaceholderText("你的回答(必填)"),
"配置在 /etc/foo.conf",
);
expect(submit).toBeEnabled();
await userEvent.click(submit);
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "", "配置在 /etc/foo.conf"),
);
});
await userEvent.type(screen.getByPlaceholderText('你的回答(必填)'), '配置在 /etc/foo.conf');
expect(submit).toBeEnabled();
await userEvent.click(submit);
await waitFor(() =>
expect(spy).toHaveBeenCalledWith('m-1', '', '配置在 /etc/foo.conf')
);
});
it("选了选项又写了备注:两者都发出去", async () => {
const spy = vi
.spyOn(api, "decidePermission")
.mockResolvedValue({ status: "decided" } as any);
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ["方案 A"] }),
}),
);
it('选了选项又写了备注:两者都发出去', async () => {
const spy = vi.spyOn(api, 'decidePermission').mockResolvedValue({ status: 'decided' } as any);
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_options: ['方案 A'] })
})
);
await userEvent.click(screen.getByRole("button", { name: /方案 A/ }));
await userEvent.type(
screen.getByPlaceholderText("补充说明(可选)"),
"但要先备份",
);
await userEvent.click(screen.getByRole("button", { name: /提交回答/ }));
await userEvent.click(screen.getByRole('button', { name: /方案 A/ }));
await userEvent.type(screen.getByPlaceholderText('补充说明(可选)'), '但要先备份');
await userEvent.click(screen.getByRole('button', { name: /提交回答/ }));
await waitFor(() =>
expect(spy).toHaveBeenCalledWith("m-1", "方案 A", "但要先备份"),
);
});
await waitFor(() =>
expect(spy).toHaveBeenCalledWith('m-1', '方案 A', '但要先备份')
);
});
it("问题已回答过:显示结论,不再显示任何输入控件", () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_result: "方案 A" }),
}),
);
it('问题已回答过:显示结论,不再显示任何输入控件', () => {
render(
React.createElement(PermissionPanel, {
mail: questionMail({ permission_result: '方案 A' })
})
);
expect(screen.getByText(/已处理/)).toBeInTheDocument();
expect(screen.queryByRole("button")).toBeNull();
});
expect(screen.getByText(/已处理/)).toBeInTheDocument();
expect(screen.queryByRole('button')).toBeNull();
});
it("审批型(无 permission_kind仍然走同意/拒绝路径", () => {
render(React.createElement(PermissionPanel, { mail: permMail() }));
it('审批型(无 permission_kind仍然走同意/拒绝路径', () => {
render(React.createElement(PermissionPanel, { mail: permMail() }));
// 回归防护question 分支不能把普通审批也带走
expect(screen.getByRole("button", { name: /同意/ })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /提交回答/ })).toBeNull();
});
// 回归防护question 分支不能把普通审批也带走
expect(screen.getByRole('button', { name: /同意/ })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /提交回答/ })).toBeNull();
});
});