fix(forge-panel): pin merge confirmation to the reviewed head
This commit is contained in:
1 parent
4969525f33
commit
04db213ad9
8 files changed
+56
-12
No files matched your search
@@ -0,0 +1,7 @@
|
||||
- menu:
|
||||
- text: "将 #9 合并到 main"
|
||||
- menuitem "压缩合并 把全部提交压缩成一个提交"
|
||||
- menuitem "创建合并提交 保留全部提交,并新增一个合并提交"
|
||||
- menuitem "变基合并 把提交逐个变基到 main"
|
||||
- separator
|
||||
- menuitem "提交已更新,请重新检查后再打开合并菜单" [disabled]
|
||||
@@ -115,7 +115,7 @@ if (args[0] === 'api' && args[1] === 'graphql') {
|
||||
9: {
|
||||
number: 9, url: 'https://github.com/octo/demo/pull/9', title: 'Add the merge button', state: state.mergedAt === null ? 'OPEN' : 'MERGED',
|
||||
isDraft: !state.ready, updatedAt: '2026-10-09T00:00:00Z', closedAt: state.mergedAt, author: { login: 'octo' },
|
||||
headRefName: 'feat/merge', headRefOid: 'b'.repeat(40), baseRefName: 'main', additions: 30, deletions: 2, mergeable: 'MERGEABLE',
|
||||
headRefName: 'feat/merge', headRefOid: state.head ?? 'b'.repeat(40), baseRefName: 'main', additions: 30, deletions: 2, mergeable: 'MERGEABLE',
|
||||
mergeStateStatus: state.ready ? 'CLEAN' : 'BLOCKED', reviewDecision: 'APPROVED', reviewThreads: { nodes: [] },
|
||||
commits: { nodes: [{ commit: { statusCheckRollup: { state: 'SUCCESS', contexts: {
|
||||
pageInfo: { hasNextPage: false }, nodes: [check('lint', 'SUCCESS', '2026-10-09T00:00:42Z')],
|
||||
@@ -514,6 +514,14 @@ it.skipIf(MODE === 'record')('marks a draft change request ready for review and
|
||||
const menu = page.getByRole('menu')
|
||||
await menu.getByText('将 #9 合并到 main', { exact: true }).waitFor()
|
||||
await compareOrRefreshGolden(join(EXPECTED, 'merge-menu.expected.md'), await captureStableAria(page, '[role="menu"]', scaffold.workspaceCwd), MODE)
|
||||
await writeFile(join(root, 'state.json'), JSON.stringify({ ready: true, mergedAt: null, head: 'c'.repeat(40) }))
|
||||
scaffold.ctx.forge.refresh({ repository: { forge: 'github', host: 'github.com', owner: 'octo', name: 'demo' }, number: 9 })
|
||||
const stale = menu.getByRole('menuitem', { name: '提交已更新,请重新检查后再打开合并菜单', exact: true })
|
||||
await stale.waitFor()
|
||||
expect(await stale.isDisabled()).toBe(true)
|
||||
await compareOrRefreshGolden(join(EXPECTED, 'merge-stale.expected.md'), await captureStableAria(page, '[role="menu"]', scaffold.workspaceCwd), MODE)
|
||||
await merge.click()
|
||||
await merge.click()
|
||||
await menu.getByRole('menuitem', { name: '确认合并', exact: true }).click()
|
||||
await page.getByRole('alert').filter({ hasText: '已合并 #9' }).waitFor()
|
||||
// Merged, the change request is no longer open, so its card collapses to the merge line.
|
||||
@@ -522,7 +530,7 @@ it.skipIf(MODE === 'record')('marks a draft change request ready for review and
|
||||
const calls = (await readFile(join(root, 'calls.jsonl'), 'utf8')).trim().split('\n').map(line => JSON.parse(line) as string[])
|
||||
expect(calls).toContainEqual(['pr', 'ready', '9', '--repo', 'github.com/octo/demo'])
|
||||
expect(calls).toContainEqual([
|
||||
'api', '--hostname', 'github.com', '--method', 'PUT', 'repos/octo/demo/pulls/9/merge', '-f', 'merge_method=squash', '-f', `sha=${'b'.repeat(40)}`,
|
||||
'api', '--hostname', 'github.com', '--method', 'PUT', 'repos/octo/demo/pulls/9/merge', '-f', 'merge_method=squash', '-f', `sha=${'c'.repeat(40)}`,
|
||||
])
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 2f8a621b991260a1
|
||||
/deepseek-ai-dsh-experimental-client-ui-forge/use-this-package:
|
||||
en: 16052ebea69357e0
|
||||
zh: d64b02c379461433
|
||||
en: ae3fe6a8a396c9b8
|
||||
zh: cf8770f1beb498e5
|
||||
/deepseek-ai-dsh-experimental-client-ui-forge/understand-the-implementation:
|
||||
en: e1a1e0429e88ed5f
|
||||
zh: 5db056d99cd9ef41
|
||||
|
||||
@@ -31,7 +31,7 @@ Enable the [forge bundle](../forge-bundle/README.md) on the Plugins page; it com
|
||||
- **Creation card** — while the worktree is created, a card above the composer shows the new branch and its two steps, creating the branch and checking out files; More details adds the branch and base. Cancel stops the attempt. After a stopped or failed attempt the message keeps waiting, and the card shows the reason of a failure: Retry tries again with a new branch name, and Continue without a worktree withdraws the request so the message goes on in the current checkout.
|
||||
- **Removed worktree** — when the worktree the Session works in has been removed outside the Session, a card above the composer says so, and a message sent meanwhile waits without starting its turn until the worktree is restored or left; the card then says that the message goes on afterwards. Restore worktree, also offered as Restore in the tab, checks the branch out again at the same directory, or creates the branch again at the commit it started from when the branch is gone too; while it runs, the card follows its two steps, preparing the worktree and checking out files, and the tab's Restore turns into a spinner. Continue in the project moves the Session to the source checkout. Meanwhile the tab's first row shows a warning glyph.
|
||||
- **Header button** — appears when the Session has bound change requests or its directory is inside a Git checkout, and shows or hides the tab. A red dot marks an open change request with failed checks, requested changes, or conflicts.
|
||||
- **Tab** — the first row names the checked-out branch, or the abbreviated commit on a detached HEAD, after a folder glyph for the project directory or a branch glyph for a worktree, and the kind of checkout, which is the worktree the Session created with its base, another worktree, or the project directory; its tooltip adds the path with the Host home shortened to `~`, its button copies the full path, and for a removed worktree it offers Restore. Below it, each change request card shows a glyph for its state, its title, which opens it in the browser, and its number; the glyph tells open, draft, merged, and closed apart, and marks an open one that is not a draft with failed checks, requested changes, or conflicts as needing attention, as the header button's red dot does. The card's header also offers Open in browser, Copy change request link, and Remove from this Session, whose toast offers Undo. A collapsed card adds the checks of an open change request and the most urgent of requested changes, conflicts, or draft, or when a merged or closed one ended. An expanded card shows the head and base branches, then one row per fact: for an open change request the checks, whose row opens the list of checks with failed ones first and their durations and links, the review decision with unresolved threads, and the merge state, or when a merged or closed one ended; and the changed lines, whose row opens the changes view. An open draft also offers Ready for review; a change request the host would merge now offers Merge, whose menu lists the methods the repository allows with the user's usual one chosen and merges the head the card shows after Confirm merge. A toast reports each outcome, and new commits since that head stop the merge. When exactly one bound change request is open, its card starts expanded. Refresh inspects the checkout, checks sign-in, and polls every change request again; its icon turns until the inspection and the sign-in check end. A banner shows the `gh auth login` command when sign-in is missing.
|
||||
- **Tab** — the first row names the checked-out branch, or the abbreviated commit on a detached HEAD, after a folder glyph for the project directory or a branch glyph for a worktree, and the kind of checkout, which is the worktree the Session created with its base, another worktree, or the project directory; its tooltip adds the path with the Host home shortened to `~`, its button copies the full path, and for a removed worktree it offers Restore. Below it, each change request card shows a glyph for its state, its title, which opens it in the browser, and its number; the glyph tells open, draft, merged, and closed apart, and marks an open one that is not a draft with failed checks, requested changes, or conflicts as needing attention, as the header button's red dot does. The card's header also offers Open in browser, Copy change request link, and Remove from this Session, whose toast offers Undo. A collapsed card adds the checks of an open change request and the most urgent of requested changes, conflicts, or draft, or when a merged or closed one ended. An expanded card shows the head and base branches, then one row per fact: for an open change request the checks, whose row opens the list of checks with failed ones first and their durations and links, the review decision with unresolved threads, and the merge state, or when a merged or closed one ended; and the changed lines, whose row opens the changes view. An open draft also offers Ready for review; a change request the host would merge now offers Merge, whose menu lists the methods the repository allows with the user's usual one chosen and pins the displayed head when opened. Confirm merge uses that head; a polled head change disables confirmation until the menu is reopened after review. A toast reports each outcome, and new commits since that head stop the merge. When exactly one bound change request is open, its card starts expanded. Refresh inspects the checkout, checks sign-in, and polls every change request again; its icon turns until the inspection and the sign-in check end. A banner shows the `gh auth login` command when sign-in is missing.
|
||||
- **Local changes card** — while the `branch` scope changes files and no open change request has the checked-out branch as its head, a card above the change requests names the ref it compares with, such as `HEAD → origin/main`, and shows the added and deleted lines and the number of files, which open the changes view; its file list, which names files as the changes view does, opens the view at one file. Create change request, offered once the code host repository is recognized, sends the Session a user message in the UI language that asks the Agent to create a change request for these changes, and a toast reports whether it was sent.
|
||||
- **Changes view** — The Changes row of an expanded change request card and the local changes card open a right-sidebar tab that lists the changed files. A local view shows Branch by default: the working tree and untracked files against the fork point, which is the base commit of the worktree the Session created, else the merge base of HEAD with the default branch of the code host remote. Uncommitted compares them with HEAD, Unstaged with the index, and Staged compares the index with HEAD. A change request view shows the host's diff at its head and says how many commits the checked-out branch has beyond that head when it is the change request's head branch. The toolbar jumps to a file, totals the changed lines, refreshes, and switches between two columns and one and between wrapped and cut long lines. Files are named before their directories, which use smaller subdued type and shorten first; hovering one shows its complete path, and both paths for a renamed file. Each file shows badges for a new, deleted, or renamed file, the last naming the old file name, or the old path when only the directory changed, and, in the Branch scope, an untracked one, and a menu that copies its path and opens the whole file in the sidebar for local changes or its page at the head in the browser for a change request. Unchanged lines between hunks collapse into rows that expand from the file's new side: the working tree, the index for Staged, or the head commit through the code host; a file that changed after the diff was read stays collapsed until the view reads again. A binary file and an untracked file over `maxDiffBytes` show a note instead of their lines, a file whose hunks draw more than 1,000 lines starts as one collapsed strip that names its changed lines and draws them when opened, and a file below files that change more than 5,000 lines starts collapsed. The view reads again on Refresh and the page refresh shortcut, on each navigation, when the change request's head moves, and when the Branch scope's summary changes; a change request's view first waits for the status that names its head, unless no provider serves it or that poll failed. While a read replaces changes already shown, the Refresh icon turns. A change request whose diff the code host or its provider refuses over a size limit, such as a GitHub diff over 300 files, says its changes are too large to show here and offers View in browser; another failed read offers Retry and, for a change request, View in browser. A change request's diff is downloaded once per head commit and base branch while it stays cached, so reopening, refreshing, and navigating at the same head read nothing from the host.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ kind: "package-reference"
|
||||
- **创建卡片**——创建 worktree 期间,输入框上方的卡片显示新分支以及“创建分支”“检出文件”两个步骤;“更多详情”会列出分支与基准。“取消”停止这次尝试。尝试被停止或失败后,消息继续等待,失败时卡片显示原因:“重试”换一个新分支名再试;“不使用工作树”撤回请求,消息在当前检出中继续发送。
|
||||
- **工作树已被删除**——Session 所在的 worktree 在 Session 之外被删除后,输入框上方的卡片会说明这一点;此期间发送的消息会等待、不开始这一轮,直到该 worktree 被恢复或离开,卡片也会说明消息之后会接着发送。“恢复工作树”(标签页中的“恢复”与之相同)在原目录重新检出该分支;分支也已删除时,从它开始时的提交重新创建分支;恢复期间卡片显示“准备工作树”“检出文件”两个步骤,标签页中的“恢复”变为加载状态。“在项目目录继续”把 Session 移回源检出。此期间标签页顶部一行显示警示图标。
|
||||
- **标题栏按钮**——Session 有已绑定的变更请求、或其目录位于 Git 检出中时出现,用来显示或隐藏标签页。打开状态的变更请求存在检查失败、被要求修改或冲突时,按钮带红点。
|
||||
- **标签页**——顶部一行显示当前检出的分支(分离 HEAD 时为缩写提交),项目目录前显示目录图标、worktree 前显示分支图标,并说明检出的类型(Session 创建的 worktree 及其基准、其他 worktree 或项目目录);它的提示给出路径,宿主主目录缩写为 `~`,旁边的按钮复制完整路径;worktree 已被删除时提供“恢复”。其下每张变更请求卡片显示状态图标、标题(点击在浏览器中打开)与编号;图标区分打开、草稿、已合并与已关闭,打开中的变更请求(草稿除外)检查失败、被要求修改或有冲突时显示为“需要处理”,与标题栏按钮的红点一致。卡片右上角还提供“在浏览器中打开”“复制变更请求链接”与“从本会话移除”,移除后的 toast 提供“撤销”。折叠的卡片还显示打开的变更请求的检查,以及被要求修改、冲突、草稿中最需要注意的一项;已合并或已关闭的则显示结束时间。展开的卡片显示源分支与目标分支,下面每项一行:打开的变更请求依次是检查(点击该行弹出检查列表,失败的排在前面,带耗时与链接)、评审结论与未解决讨论数、合并状态,已合并或已关闭的显示结束时间;最后是变更行数,点击该行打开变更视图。打开中的草稿还提供“标记为可评审”;主机现在就会合并的变更请求提供“合并”,其菜单列出仓库允许的合并方式并选中用户常用的一种,点“确认合并”后合并卡片显示的那个 head。每次操作的结果都以 toast 报告;该 head 之后有新提交时不会合并。恰好有一个已绑定的变更请求处于打开状态时,它的卡片默认展开。刷新会重新检查检出与登录状态,并立即再次轮询每个变更请求;检查与登录状态确认结束前,刷新图标持续旋转。缺少登录时,横幅显示 `gh auth login` 命令。
|
||||
- **标签页**——顶部一行显示当前检出的分支(分离 HEAD 时为缩写提交),项目目录前显示目录图标、worktree 前显示分支图标,并说明检出的类型(Session 创建的 worktree 及其基准、其他 worktree 或项目目录);它的提示给出路径,宿主主目录缩写为 `~`,旁边的按钮复制完整路径;worktree 已被删除时提供“恢复”。其下每张变更请求卡片显示状态图标、标题(点击在浏览器中打开)与编号;图标区分打开、草稿、已合并与已关闭,打开中的变更请求(草稿除外)检查失败、被要求修改或有冲突时显示为“需要处理”,与标题栏按钮的红点一致。卡片右上角还提供“在浏览器中打开”“复制变更请求链接”与“从本会话移除”,移除后的 toast 提供“撤销”。折叠的卡片还显示打开的变更请求的检查,以及被要求修改、冲突、草稿中最需要注意的一项;已合并或已关闭的则显示结束时间。展开的卡片显示源分支与目标分支,下面每项一行:打开的变更请求依次是检查(点击该行弹出检查列表,失败的排在前面,带耗时与链接)、评审结论与未解决讨论数、合并状态,已合并或已关闭的显示结束时间;最后是变更行数,点击该行打开变更视图。打开中的草稿还提供“标记为可评审”;主机现在就会合并的变更请求提供“合并”,其菜单列出仓库允许的合并方式并选中用户常用的一种,打开时固定卡片显示的 head。“确认合并”使用这个 head;轮询发现 head 变化后禁用确认,需重新检查并打开菜单。每次操作的结果都以 toast 报告;该 head 之后有新提交时不会合并。恰好有一个已绑定的变更请求处于打开状态时,它的卡片默认展开。刷新会重新检查检出与登录状态,并立即再次轮询每个变更请求;检查与登录状态确认结束前,刷新图标持续旋转。缺少登录时,横幅显示 `gh auth login` 命令。
|
||||
- **变更卡片**——`branch` 范围有文件变更、且没有以当前检出分支为源分支的打开中变更请求时,变更请求列表上方的卡片显示比较的基准(例如 `HEAD → origin/main`)以及新增、删除行数与文件数,点击打开变更视图;展开的文件列表像变更视图一样标出各个文件,点击某个文件时,变更视图定位到该文件。识别出代码托管仓库后,卡片提供“创建变更请求”:它以界面语言向 Session 发送一条用户消息,请 Agent 为这些变更创建变更请求,并以 toast 报告是否已发送。
|
||||
- **变更视图**——展开的变更请求卡片上的“变更”一行与变更卡片会打开一个右侧边栏标签页,列出所有变更的文件。本地变更默认显示“分支”:工作树与未跟踪文件相对于分叉点的变更;分叉点是 Session 创建的 worktree 的基准提交,否则是 HEAD 与代码托管远端默认分支的合并基准。“未提交”与 HEAD 比较,“未暂存”与暂存区比较,“已暂存”比较暂存区与 HEAD。变更请求的视图显示托管平台在其 head 处的 diff;当前检出分支就是该变更请求的源分支时,还会说明本地分支比该 head 多出几个提交。工具栏可以跳到某个文件、汇总变更行数、刷新,并在左右对比与单栏对比、自动换行与截断长行之间切换。文件名在前,目录在后,用更小的浅色字显示,空间不够时目录先缩短;悬停时显示完整路径,重命名的文件显示新旧两条路径。每个文件带有新文件、已删除、重命名的标记(重命名标记写出原文件名,只换了目录时写出原路径),“分支”范围下的未跟踪文件还标“未提交”;文件菜单可以复制路径,本地变更可在侧边栏打开整个文件,变更请求可在浏览器中查看该文件在 head 处的页面。hunk 之间未变更的行折叠成一行,点击后从文件的新版本展开:工作树、“已暂存”时为暂存区,或经托管平台读取的 head 提交;读取 diff 之后文件又有修改时不会展开,直到视图重新读取。二进制文件与超过 `maxDiffBytes` 的未跟踪文件只显示一条说明;hunk 超过 1,000 行的文件先显示为一条写出变更行数的折叠条,点击后才绘制;前面的文件累计变更超过 5,000 行后,其后的文件默认收起。刷新与页面刷新快捷键、每次导航、变更请求的 head 变化,以及“分支”范围的摘要变化时,视图都会重新读取;变更请求的视图会先等到给出 head 的状态再读取,除非没有提供方服务它或那次轮询失败。重新读取已显示的变更时,刷新图标会旋转。托管平台或其提供方因超过大小上限拒绝给出 diff 的变更请求(例如 GitHub 上超过 300 个文件的 diff)显示“变更太大,无法在这里显示”并提供“在浏览器中查看”;其他读取失败提供“重试”,变更请求还提供“在浏览器中查看”。变更请求的 diff 在缓存期间对每个 head 提交与目标分支只下载一次,因此在同一 head 下重新打开、刷新与导航都不会再从托管平台读取。
|
||||
|
||||
|
||||
@@ -258,11 +258,13 @@ function MergeMenu({ snapshot, name, usual, busy, onMerge, t }: {
|
||||
readonly name: string
|
||||
readonly usual: MergeMethod
|
||||
readonly busy: boolean
|
||||
readonly onMerge: (method: MergeMethod) => void
|
||||
readonly onMerge: (method: MergeMethod, headSha: string) => void
|
||||
readonly t: Translate
|
||||
}): ReactNode {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [confirmedHead, setConfirmedHead] = useState(snapshot.headSha)
|
||||
const [chosen, setChosen] = useState<string>(usual)
|
||||
const stale = confirmedHead !== snapshot.headSha
|
||||
// The methods can change between polls; a choice the repository no longer allows falls back to the usual one.
|
||||
const method = snapshot.mergeMethods.find(entry => entry === chosen) ?? usual
|
||||
const close = (): void => { setOpen(false) }
|
||||
@@ -293,7 +295,10 @@ function MergeMenu({ snapshot, name, usual, busy, onMerge, t }: {
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
disabled={busy}
|
||||
onClick={() => { setOpen(!open) }}
|
||||
onClick={() => {
|
||||
if (!open) setConfirmedHead(snapshot.headSha)
|
||||
setOpen(!open)
|
||||
}}
|
||||
>
|
||||
{t('change.merge')}
|
||||
<IconChevronDownOutlineRegular size={12} className={clsx(css.chevron, open && css.chevronOpen)} />
|
||||
@@ -305,12 +310,13 @@ function MergeMenu({ snapshot, name, usual, busy, onMerge, t }: {
|
||||
>
|
||||
<MenuItemButton
|
||||
separatorBefore
|
||||
disabled={stale}
|
||||
onSelect={() => {
|
||||
close()
|
||||
onMerge(method)
|
||||
onMerge(method, confirmedHead)
|
||||
}}
|
||||
>
|
||||
{t('change.mergeConfirm')}
|
||||
{t(stale ? 'change.mergeChanged' : 'change.mergeConfirm')}
|
||||
</MenuItemButton>
|
||||
</Menu>
|
||||
)
|
||||
@@ -351,7 +357,7 @@ function Primary({ entry, snapshot, name, actions, t }: {
|
||||
name={name}
|
||||
usual={usual}
|
||||
busy={busy}
|
||||
onMerge={(method) => { run(() => merge(entry, { method, headSha: snapshot.headSha })) }}
|
||||
onMerge={(method, headSha) => { run(() => merge(entry, { method, headSha })) }}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
@@ -481,7 +487,9 @@ export function ChangeRequestCard({ entry, expanded, onToggle, now, actions, t }
|
||||
: <Button size="sm" variant="outline" onClick={actions.retry}>{t('retry')}</Button>}
|
||||
</div>
|
||||
)}
|
||||
{expanded && snapshot !== null && <Details entry={entry} snapshot={snapshot} name={name} now={now} actions={actions} t={t} />}
|
||||
{expanded && snapshot !== null && (
|
||||
<Details entry={entry} snapshot={snapshot} name={name} now={now} actions={actions} t={t} />
|
||||
)}
|
||||
{expanded && snapshot !== null && <Primary entry={entry} snapshot={snapshot} name={name} actions={actions} t={t} />}
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -89,6 +89,7 @@ export const zh = {
|
||||
'change.mergeNote.merge': '保留全部提交,并新增一个合并提交',
|
||||
'change.mergeNote.rebase': '把提交逐个变基到 {base}',
|
||||
'change.mergeConfirm': '确认合并',
|
||||
'change.mergeChanged': '提交已更新,请重新检查后再打开合并菜单',
|
||||
'diff.tab': '变更',
|
||||
'diff.tabNumbered': '#{number} 的变更',
|
||||
'diff.scope.branch': '分支',
|
||||
@@ -290,6 +291,7 @@ export const en: Record<ForgePanelKey, string> = {
|
||||
'change.mergeNote.merge': 'Keep every commit and add a merge commit',
|
||||
'change.mergeNote.rebase': 'Replay each commit onto {base}',
|
||||
'change.mergeConfirm': 'Confirm merge',
|
||||
'change.mergeChanged': 'New commits arrived. Review them before reopening the merge menu.',
|
||||
'diff.tab': 'Changes',
|
||||
'diff.tabNumbered': 'Changes in #{number}',
|
||||
'diff.scope.branch': 'Branch',
|
||||
|
||||
@@ -448,6 +448,25 @@ describe('ChangeRequestCard actions', () => {
|
||||
expect(merge).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('requires a new confirmation after the head changes while the menu is open', async () => {
|
||||
const merge = vi.fn<NonNullable<ChangeRequestActions['merge']>>(async () => {})
|
||||
const actions: ChangeRequestActions = { openLink: vi.fn(), copyLink: vi.fn(), retry: vi.fn(), merge }
|
||||
const view = (headSha: string) => <ChangeRequestCard entry={entry(7, snapshot(7, { mergeReady: true, headSha }))}
|
||||
expanded onToggle={vi.fn()} now={NOW} actions={actions} t={t} />
|
||||
const rendered = render(view(head))
|
||||
const button = screen.getByRole('button', { name: en['change.merge'] })
|
||||
fireEvent.click(button)
|
||||
rendered.rerender(view('c'.repeat(40)))
|
||||
const changed = screen.getByRole('menuitem', { name: en['change.mergeChanged'] })
|
||||
expect(changed.hasAttribute('disabled')).toBe(true)
|
||||
fireEvent.click(changed)
|
||||
expect(merge).not.toHaveBeenCalled()
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
await act(async () => { fireEvent.click(screen.getByRole('menuitem', { name: en['change.mergeConfirm'] })) })
|
||||
expect(merge).toHaveBeenCalledWith(expect.anything(), { method: 'squash', headSha: 'c'.repeat(40) })
|
||||
})
|
||||
|
||||
it('falls back to the usual method once the repository stops allowing the chosen one', async () => {
|
||||
const merge = vi.fn<(entry: ForgePanelChangeRequest, request: MergeRequest) => Promise<void>>(async () => {})
|
||||
const actions: ChangeRequestActions = { openLink: vi.fn(), copyLink: vi.fn(), retry: vi.fn(), merge }
|
||||
|
||||
Reference in new issue
Block a user