mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* fix(web): align export and update-check error copy with product (OPEND-2849)
- S26a: a generic export failure now shows the product title + body
("导出失败" / "本次导出未完成,请重新尝试。") through the same Toast
message + details shape as S26c. fileViewer.exportFailed is split into
fileViewer.exportFailedTitle / fileViewer.exportFailedDescription in all
19 locales; the old key is removed.
- S31b: drop the sentence-final period from the title line of
updater.dialogCheckFailed (zh-CN and 17 other locales; th had none).
* fix(web): never surface raw exporter errors on export failure (OPEND-2849)
Product copy S26: an export failure shows the S26a title + body by default;
only an already-localized, user-facing reason may replace it. Raw exporter
strings (HTTP status lines, renderer/daemon errors, internal exceptions)
no longer reach the toast.
- New export-failure-toast module: LocalizedExportError marks errors whose
message comes from an i18n key; exportFailureToast passes only those
through and falls back to S26a for everything else.
- Localized reasons now thrown as LocalizedExportError: PPTX daemon
unreachable / PPTX not available, version image capture failed.
- Image export catch-all uses the same rule instead of echoing err.message.
- The original error object is unchanged, so console.warn and
exportErrorCode analytics still see the raw message.
* fix(web): align disk-write failure and browser-open hint copy with product (OPEND-2849)
- chat.runError.localStorageFailureMessage now matches the product supplement
row 13 verbatim in zh-CN ("…请确认有足够的剩余空间后,再重新尝试。"); the
old "permission to save to the current folder" clause is dropped and all
19 locales are retranslated to the new meaning.
- settings.amrActivationBrowserFailed (S32 browser not opened): half-width
comma replaced by a full-width one in zh-CN and zh-TW; wording unchanged.
* fix(web): align S03/S04/S06/S09a/S15/S24/S28a/S31c/S32a error copy with product (OPEND-2849)
zh-CN copied verbatim from the product error-copy doc; all 19 locales translated.
- S04 pre-send signed out (balance gate dialog): "Open Design 尚未登录" + body.
- S06 pre-send insufficient allowance: owner dialog "可用额度不足" + body (no
balance figure); member dialog "团队额度不足" + body. Benefits list and
buttons unchanged.
- S15 mid-run allowance exhausted: the turn-anchored upgrade card's $0 state
shows title + body, personal vs. member chosen by the billing audience
ProjectView already resolves. Low-balance tier, amount and Upgrade button
unchanged; the pre-send tail card keeps "现在无法开始新任务".
- S24a unfinished todos: "任务尚未完成" + "本次任务仍有 {n} 项内容未完成。" with the
real unfinished count; S24c user stop: "任务已停止" + body. Buttons kept.
- S03 / S28a Home send failures: the optimistic create's rollback toast (the
surface users actually see) and HomeView's inline block now map
AMR_AUTH_REQUIRED to S03 and transport failures to S28a, as title + body.
- S31c update download failed (updater error code download-failed): update
dialog shows "更新下载未完成" + body; Settings status shows the title.
- S32a browser not opened during sign-in: title + body via one shared
component for the three sign-in surfaces; the sign-in page link is kept.
- S09a rate-limit auto retry: run_retry_attempted now carries
failure_category in the SSE contract; a rate_limit retry row reads
"模型服务请求繁忙" + body, other retries keep "正在重试".
116 lines
5.0 KiB
TypeScript
116 lines
5.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
/**
|
|
* T56 / T57(产品口述 2026-09-06):「找所有者充值」弹窗改成产品文档第四节的
|
|
* **正式文案**,并且**只留一颗按钮**。
|
|
*
|
|
* 产品原话:「不要保留,严格按产品稿,不要私自发挥」——「复制请求」那颗主按钮
|
|
* 及其复制逻辑整个删掉。
|
|
*
|
|
* ⚠️ 这条测试**推翻**了 `ProjectView.amr-balance-branches.test.tsx` 里那条
|
|
* 「成员的弹窗上不能只有一颗『暂不需要』」(§6.Y 死胡同的出口)。删掉复制按钮
|
|
* 之后这一档回到**单出口**,产品知情并明确要求。
|
|
*
|
|
* 文案的两个变体(T57,产品已批):Owner 名字拿得到就插名字,拿不到就换成
|
|
* 角色名 —— **只换插值那一处,其余逐字不动**。
|
|
*/
|
|
import { cleanup, render, screen } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
import { AmrOwnerTopUpDialog } from '../../src/components/chat/AmrOwnerTopUpDialog';
|
|
import { I18nProvider } from '../../src/i18n';
|
|
|
|
function renderDialog(props: { ownerName?: string | null } = {}) {
|
|
return render(
|
|
<I18nProvider>
|
|
<AmrOwnerTopUpDialog inline onClose={() => {}} {...props} />
|
|
</I18nProvider>,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 按**整段可读文本**找那句话,而不是 `getByText(字符串)`。
|
|
*
|
|
* ⚠️ 断言的字符**一个都没动** —— 变的只有量法。原因:2026-09-07 产品稿把
|
|
* Owner 名字单独加粗一档(`.owner-name { font-weight: 700 }`),那一段于是成了
|
|
* 自己的 `<strong>`,整句被拆成三个节点。而 `getByText` 默认的 `getNodeText`
|
|
* **只拼元素的直接文本子节点**,`<strong>` 里的名字会被漏掉 —— 同一句话原样
|
|
* 渲染在屏幕上,断言却报「找不到」。
|
|
*
|
|
* 换成读 `textContent` 之后这条守卫反而更紧:它现在同时挡住「文案被改」和
|
|
* 「文案被拆坏(多出空格 / 少一段 / 顺序反了)」两类退化。
|
|
*/
|
|
function textOf(text: string) {
|
|
return screen.getByText(
|
|
(_content, element) =>
|
|
element?.tagName === 'P' && element.textContent === text,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
// 产品稿是中文原件,判据就钉在中文上;其余 18 个 locale 是它的忠实翻译。
|
|
window.localStorage.setItem('open-design:locale', 'zh-CN');
|
|
window.localStorage.setItem('open-design:locale-source', 'manual');
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
/*
|
|
* ⚠️ OPEND-2849(2026-10-09)换过文案:产品《报错文案|精简版》S06「发送前:团队额度不足」
|
|
* 定稿为「团队额度不足」/「当前团队额度不足,请联系团队管理员充值或升级订阅。」。
|
|
* 定稿句里**没有** Owner 名字的位置,所以 T57 的「拿得到名字就插名字」那一变体不再成立 ——
|
|
* 有没有名字都是同一句。下面两条把这一点正面钉住(名字不出现),而不是删掉断言。
|
|
*/
|
|
describe('AmrOwnerTopUpDialog 的正式文案', () => {
|
|
it('标题逐字照产品稿', () => {
|
|
renderDialog();
|
|
expect(screen.getByText('团队额度不足')).toBeTruthy();
|
|
});
|
|
|
|
it('拿得到 Owner 名字时仍是定稿那一句,名字不插进去', () => {
|
|
renderDialog({ ownerName: '张三' });
|
|
expect(textOf('当前团队额度不足,请联系团队管理员充值或升级订阅。')).toBeTruthy();
|
|
expect(screen.queryByText('张三')).toBeNull();
|
|
});
|
|
|
|
/**
|
|
* 现在走的就是这一条:契约里唯一的 owner 名是 `CollabProject.ownerDisplayName`
|
|
* (项目级,注释逐字 "STUB: the real name source is B's member roster"),
|
|
* `WorkspaceCollabContext` 上根本没有工作区 owner 名。
|
|
*/
|
|
it.each([undefined, null, '', ' '])(
|
|
'拿不到名字(%s)时也是定稿那一句',
|
|
(ownerName) => {
|
|
renderDialog({ ownerName: ownerName as string | null | undefined });
|
|
expect(textOf('当前团队额度不足,请联系团队管理员充值或升级订阅。')).toBeTruthy();
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('AmrOwnerTopUpDialog 只有一颗按钮', () => {
|
|
it('「复制请求」整颗不在了', () => {
|
|
renderDialog();
|
|
expect(screen.queryByTestId('amr-balance-owner-copy')).toBeNull();
|
|
expect(screen.queryByText('复制请求')).toBeNull();
|
|
expect(screen.queryByText('已复制')).toBeNull();
|
|
});
|
|
|
|
/**
|
|
* 单出口是这次裁决的**代价**,所以正面钉住它:弹窗里除了标题行那颗关闭图标
|
|
* 之外,动作区**只有「知道了」这一颗**。
|
|
*/
|
|
it('动作区只剩「知道了」', () => {
|
|
const { container } = renderDialog();
|
|
const dialog = screen.getByTestId('amr-balance-owner-dialog');
|
|
const actionLabels = [...dialog.querySelectorAll('button')]
|
|
.map((button) => button.textContent?.trim() ?? '')
|
|
.filter((label) => label.length > 0);
|
|
expect(actionLabels).toEqual(['知道了']);
|
|
// 那句「可以直接发给所有者的话」是复制机制的载荷,一并撤掉。
|
|
expect(container.textContent).not.toContain('Open Design 控制台');
|
|
});
|
|
});
|