Merge pull request #5449 from deepseek-harness/fix/preset-settings-selection-ungated
fix(web): 设置页的预设切换与 Creator 入口不再受代码工作工具开关限制
This commit is contained in:
commit
e8a777fb32
12 files changed
+26
-71
No files matched your search
@@ -9,8 +9,8 @@
|
||||
en: e22ee243b1503319
|
||||
zh: b53c1a932d547f55
|
||||
/agent-note-shared-developer-tool-settings/decision:
|
||||
en: 62f71b49af7b9971
|
||||
zh: f0f12da4cb0c7fc0
|
||||
en: 6b94e4c856dbd92a
|
||||
zh: e923b1118dfad7b6
|
||||
/agent-note-shared-developer-tool-settings/alternatives-considered:
|
||||
en: df1b80e5b8d2a178
|
||||
zh: 0136d16fc44d57bf
|
||||
|
||||
@@ -12,9 +12,9 @@ Diagnostic views, preset selection and changed-file summaries add complexity to
|
||||
|
||||
The settings-owned `ctx.configForms.developerTools` preference uses a shared entry form and exposes one accepted `ui-settings.enabled` preference. The initial default-off choice reduces routine-task complexity; [Developer tools default on after preference resolution](2026-09-20-developer-tools-default-on.md) supersedes that default and owns startup enablement. General Settings offers the same switch on Web and desktop. The existing settings transport owns validation, ordered writes, persistence and recovery; loopback Web and desktop follow Host acceptance. Remote Web owns one browser-local observable shared by all consumers until reload, with no Host writes.
|
||||
|
||||
Off hides the Trajectory View, hides tool Inspect actions when no visible View declares tool-call inspection, hides the conversation View tab bar while fewer than two Views are available, hides the new-session preset chip and refuses the Agent-preset cards' selection, and omits the changed-files card and its summary read. Views registered by other plugins stay available. Turning it off while Trajectory is active activates Chat. The preset composition, saved default, Session events and explicit delivery cards remain unchanged. This preference is presentation policy, not Host authorization.
|
||||
Off hides the Trajectory View, hides tool Inspect actions when no visible View declares tool-call inspection, hides the conversation View tab bar while fewer than two Views are available, hides the new-session preset chip, and omits the changed-files card and its summary read. Views registered by other plugins stay available. Turning it off while Trajectory is active activates Chat. The preset composition, saved default, Session events and explicit delivery cards remain unchanged. This preference is presentation policy, not Host authorization.
|
||||
|
||||
Plugin assembly translates the shared preference into component-specific capabilities (`showCodeDiff`, the Agent-preset surfaces' `developerTools`, and `interactivePreview`). Conversation supplies the inspection callback using the visible target's `toolCallFocus`; Chat does not name the target. The builtin HTML renderer uses DOMPurify for complete-document sanitization and parser-mutation defenses. Off preserves root styling, removes active documents, resource hints, SVG animation and all `href` attributes, and mounts an iframe with no sandbox permissions and a CSP denying scripts and external resources. It reads no related files. On retains the opaque scripted Blob renderer and its bounded related-file reads. Switching modes replaces the browsing context and disposes pending reads. The [document-preview decision](../architecture/2026-09-08-document-preview-operations.md) and [filesystem-read authority](../architecture/2026-09-09-workspace-file-read-authority.md) remain authoritative for advanced rendering and Host read access.
|
||||
Plugin assembly translates the shared preference into component-specific capabilities (`showCodeDiff`, the new-session preset chip's `developerTools`, and `interactivePreview`). Conversation supplies the inspection callback using the visible target's `toolCallFocus`; Chat does not name the target. The builtin HTML renderer uses DOMPurify for complete-document sanitization and parser-mutation defenses. Off preserves root styling, removes active documents, resource hints, SVG animation and all `href` attributes, and mounts an iframe with no sandbox permissions and a CSP denying scripts and external resources. It reads no related files. On retains the opaque scripted Blob renderer and its bounded related-file reads. Switching modes replaces the browsing context and disposes pending reads. The [document-preview decision](../architecture/2026-09-08-document-preview-operations.md) and [filesystem-read authority](../architecture/2026-09-09-workspace-file-read-authority.md) remain authoritative for advanced rendering and Host read access.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ Status: implemented
|
||||
|
||||
设置域内的 `ctx.configForms.developerTools` 偏好对象使用共享条目表单,提供一个已接受的 `ui-settings.enabled` 偏好。最初默认关闭的选择用于降低日常任务的复杂度;[偏好解析后默认开启开发者工具](2026-09-20-developer-tools-default-on.zh.md)取代该默认值并负责启动时的启用策略。Web 和桌面端的通用设置提供同一个开关。现有设置传输负责验证、有序写入、持久化和恢复;回环 Web 和桌面端跟随 Host 接受的状态。远程 Web 持有一个所有消费者共享的浏览器本地可观察值,刷新后重置,不发送 Host 写入。
|
||||
|
||||
关闭时隐藏 Trajectory View,在没有可见 View 声明工具调用检查能力时隐藏 Inspect 操作,可见 View 少于两个时隐藏会话 View 标签栏,隐藏新会话预设入口并拒绝 Agent 预设卡片的选择操作,并省略改动文件卡片及其摘要读取。其他插件注册的 View 保持可用。在 Trajectory 中关闭开关会激活 Chat。预设组合、保存的默认值、Session 事件和显式交付卡片保持不变。此偏好是展示策略,不是 Host 授权。
|
||||
关闭时隐藏 Trajectory View,在没有可见 View 声明工具调用检查能力时隐藏 Inspect 操作,可见 View 少于两个时隐藏会话 View 标签栏,隐藏新会话预设入口,并省略改动文件卡片及其摘要读取。其他插件注册的 View 保持可用。在 Trajectory 中关闭开关会激活 Chat。预设组合、保存的默认值、Session 事件和显式交付卡片保持不变。此偏好是展示策略,不是 Host 授权。
|
||||
|
||||
插件组装层将共享偏好转换为组件自身的能力(`showCodeDiff`、Agent 预设各界面读取的 `developerTools`,以及 `interactivePreview`)。Conversation 根据可见目标的 `toolCallFocus` 提供检查回调;Chat 不再指定目标名称。内置 HTML 渲染器使用 DOMPurify 清理完整文档并防护解析变异。关闭时保留根节点样式,移除主动文档、资源提示、SVG 动画及所有 `href` 属性,再放入不授予沙箱权限的 iframe,并通过 CSP 禁止脚本和外部资源。不读取关联文件。开启时保留不透明源的脚本 Blob 渲染器及其有界关联文件读取。切换模式会替换浏览上下文并清理待处理读取。[文档预览决策](../architecture/2026-09-08-document-preview-operations.zh.md)和[文件系统读取权限](../architecture/2026-09-09-workspace-file-read-authority.zh.md)继续定义高级渲染与 Host 读取访问。
|
||||
插件组装层将共享偏好转换为组件自身的能力(`showCodeDiff`、新会话预设入口读取的 `developerTools`,以及 `interactivePreview`)。Conversation 根据可见目标的 `toolCallFocus` 提供检查回调;Chat 不再指定目标名称。内置 HTML 渲染器使用 DOMPurify 清理完整文档并防护解析变异。关闭时保留根节点样式,移除主动文档、资源提示、SVG 动画及所有 `href` 属性,再放入不授予沙箱权限的 iframe,并通过 CSP 禁止脚本和外部资源。不读取关联文件。开启时保留不透明源的脚本 Blob 渲染器及其有界关联文件读取。切换模式会替换浏览上下文并清理待处理读取。[文档预览决策](../architecture/2026-09-08-document-preview-operations.zh.md)和[文件系统读取权限](../architecture/2026-09-09-workspace-file-read-authority.zh.md)继续定义高级渲染与 Host 读取访问。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -26,17 +26,14 @@ it('persists developer tools in the Host settings document and restores the acce
|
||||
|
||||
await page.getByRole('button', { name: 'Agent presets', exact: true }).click()
|
||||
await expect.poll(() => page.getByRole('heading', { name: 'Agent presets', exact: true }).count()).toBe(1)
|
||||
// The page owns no selection switch: only the General one gates the choice.
|
||||
// The page owns no selection switch; the cards and the Creator entry stay usable with Coding Tools off.
|
||||
const section = page.locator('section')
|
||||
.filter({ has: page.getByRole('heading', { name: 'Agent presets', exact: true }) })
|
||||
const refused = section.getByRole('button', { name: /^Turn on Coding Tools in General settings to choose a default: / })
|
||||
const setDefault = section.getByRole('button', { name: 'Set as new task default: Minimal mode' })
|
||||
expect(await section.getByRole('switch').count()).toBe(0)
|
||||
await expect.poll(() => refused.count()).toBeGreaterThan(0)
|
||||
await expect.poll(() => setDefault.isEnabled()).toBe(true)
|
||||
expect(await section.getByRole('button', { name: 'Let the agent help me create a preset', exact: true }).isEnabled()).toBe(true)
|
||||
await scaffold.ctx.settings.update('ui-settings', { enabled: true })
|
||||
await expect.poll(() => section.getByRole('button', { name: 'Set as new task default: Minimal mode' }).count()).toBe(1)
|
||||
await expect.poll(() => refused.count()).toBe(0)
|
||||
expect(await section.getByRole('switch').count()).toBe(0)
|
||||
await scaffold.ctx.settings.update('ui-settings', { enabled: false })
|
||||
await expect.poll(() => refused.count()).toBeGreaterThan(0)
|
||||
await expect.poll(() => setDefault.isEnabled()).toBe(true)
|
||||
expect(await section.getByRole('switch').count()).toBe(0)
|
||||
})
|
||||
@@ -15,8 +15,8 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: b3ad7867f23dad1c
|
||||
/deepseek-ai-dsh-client-ui-agent-preset/use-this-package:
|
||||
en: cf381c4f00b42c3f
|
||||
zh: 1b226687dcbd4875
|
||||
en: a50da1a9dfdaf624
|
||||
zh: faaffc5fbeec446b
|
||||
/deepseek-ai-dsh-client-ui-agent-preset/understand-the-implementation:
|
||||
en: 7813f0063c15e241
|
||||
zh: 6eabc242edfc45ec
|
||||
|
||||
@@ -27,7 +27,7 @@ Choose Agent presets and the new-task default in Web, read what each mode does a
|
||||
|
||||
Settings shows the built-in and custom card groups with default highlighting and card-body selection; a group without presets is omitted, except the custom group, which keeps its Creator entry on screen. Every card offers “View configuration”, which opens the preset's declared plugin list as read-only YAML in the Loader's own dialect (`!!js` conditions included); a failed preset stays readable because its diagnostic points into that YAML. Escape closes only the viewer and returns focus to its card; leaving Settings clears the viewer, and a late read does not reopen it. The page edits nothing: the Creator entry starts a Creator-mode task that authors or overrides a preset as a bundle, offered while the `cordis` preset is on the roster and a conversation flow exists.
|
||||
|
||||
Coding Tools in General Settings decide whether a mode can be chosen at all: with them off the new-session picker disappears and the cards refuse selection, while the saved default keeps composing new tasks. Choosing a healthy default also synchronizes the blank session on the current new-task surface. Creator starts a new task using the `cordis` preset.
|
||||
Coding Tools in General Settings decide whether a mode can be chosen at all: with them off the new-session picker disappears, while the settings cards still choose the saved default that keeps composing new tasks and Creator stays available. With Coding Tools on, choosing a healthy default also synchronizes the blank session on the current new-task surface; with them off, a reused blank session keeps its preset. Creator starts a new task using the `cordis` preset.
|
||||
|
||||
Known shipped presets offer mode details and usage examples in a read-only dialog. Its tabs preserve each page's scroll position; closing returns focus to the opening action. Help does not change the new-task default. The default badge replaces the card's group badge, and the preset id appears beside the title. Guide copy and examples belong to this package.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ kind: "package-reference"
|
||||
|
||||
设置页显示内置与自定义卡片分组、默认项高亮和点击卡片选择;没有 preset 的分组不显示,但自定义分组会保留其创造入口。每张卡片都提供「查看配置」,以只读 YAML 打开该 preset 声明的插件列表,使用 Loader 自己的方言(含 `!!js` 条件);加载失败的 preset 同样可读,因为其诊断信息正指向这份 YAML。Escape 只关闭查看器并将焦点还给卡片;离开设置页会清空查看器,迟到的读取结果不会重新打开它。本页不编辑任何内容:创造入口启动一个创造模式任务,以 bundle 形式创建或覆盖 preset;当 `cordis` preset 在列表中且存在会话流程时提供。
|
||||
|
||||
通用设置中的代码工作工具决定能否选择模式:关闭后新会话选择器消失、卡片拒绝选择,而已保存的默认值继续用于新任务。选择健康定义作为默认值也会同步当前新任务页面的空白会话。Creator 入口开启一个使用 `cordis` preset 的新任务。
|
||||
通用设置中的代码工作工具决定能否选择模式:关闭后新会话选择器消失,而设置卡片仍可选择继续用于新任务的已保存默认值,Creator 入口也保持可用。开启时,选择健康定义作为默认值也会同步当前新任务页面的空白会话;关闭时,被复用的空白会话保留原预设。Creator 入口开启一个使用 `cordis` preset 的新任务。
|
||||
|
||||
已知的内置预设提供只读的模式说明与使用示例对话框。各页签保留各自的滚动位置;关闭后焦点回到打开它的操作。帮助不会改变新任务默认值。默认徽标取代卡片的分组徽标,预设 id 显示在标题旁。指南文案与示例归本包所有。
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
|
||||
|
||||
.card:hover:not(.cardActive):not(.cardSelectionDisabled) {
|
||||
.card:hover:not(.cardActive) {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
@@ -75,14 +75,6 @@
|
||||
|
||||
/* Mode selection is off, not the preset itself: mute only the selectable
|
||||
body while the independent view/copy/location actions stay fully usable. */
|
||||
.cardSelectionDisabled {
|
||||
background: var(--dsw-alias-bg-module-platform);
|
||||
}
|
||||
|
||||
.cardSelectionDisabled .cardMain {
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
/* A broken preset reads as damaged before anything else: the card cannot be
|
||||
picked, so its border carries the warning the disabled body cannot. */
|
||||
.cardBroken {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Button, IconBrowseOutlineRegular, IconPlusOutlineRegular, Modal, Tag, Tooltip,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { ObservableSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
||||
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { AgentPresetSectionState } from './section-store.ts'
|
||||
import { isBuiltInPreset, presetDisplayText } from './locales.ts'
|
||||
@@ -15,8 +15,6 @@ import css from './AgentPresetSection.module.css'
|
||||
export interface AgentPresetSectionInjected {
|
||||
hooks: {
|
||||
agentPresetSection: SnapshotStore<AgentPresetSectionState>
|
||||
/** Shared Developer tools preference; off hides every selection action. */
|
||||
developerTools: ObservableSnapshot<boolean>
|
||||
}
|
||||
/** Stage the `cordis` preset and start a Creator-mode task; absent without a conversation flow. */
|
||||
startCreatorDraft?: () => void
|
||||
@@ -62,10 +60,9 @@ function CardDescription({ text }: { text: string }): ReactNode {
|
||||
*/
|
||||
export function AgentPresetSection({
|
||||
useAgentPresetSection, load, view, closeView, makeDefault, startCreatorDraft,
|
||||
close: closeSettings, useDeveloperTools, t,
|
||||
close: closeSettings, t,
|
||||
}: AgentPresetSectionProps) {
|
||||
const state = useAgentPresetSection(value => value)
|
||||
const developerTools = useDeveloperTools(enabled => enabled)
|
||||
const [guide, setGuide] = useState<{
|
||||
content: NonNullable<ReturnType<typeof presetGuide>>
|
||||
page: PresetGuidePage
|
||||
@@ -90,8 +87,7 @@ export function AgentPresetSection({
|
||||
<button
|
||||
type="button"
|
||||
className={css.creatorButton}
|
||||
disabled={!developerTools || state.saving}
|
||||
title={developerTools ? undefined : t('enableDevToolsToCreate')}
|
||||
disabled={state.saving}
|
||||
onClick={() => { creator(); closeSettings() }}
|
||||
>
|
||||
<IconPlusOutlineRegular size={14} />
|
||||
@@ -113,15 +109,13 @@ export function AgentPresetSection({
|
||||
const display = presetDisplayText(row, t)
|
||||
const help = presetGuide(row.id, builtIn ? 'system' : 'user')
|
||||
const selectionAction = row.broken !== undefined ? t('brokenBadge')
|
||||
: row.isDefault ? t('inUse')
|
||||
: t(developerTools ? 'setDefault' : 'enableDevToolsToSetDefault')
|
||||
: t(row.isDefault ? 'inUse' : 'setDefault')
|
||||
return <li key={row.id} data-agent-preset-id={row.id} className={[
|
||||
css.card, row.broken === undefined ? undefined : css.cardBroken,
|
||||
row.isDefault ? css.cardActive : undefined,
|
||||
!developerTools && row.broken === undefined && !row.isDefault ? css.cardSelectionDisabled : undefined,
|
||||
].filter(Boolean).join(' ')}>
|
||||
<button type="button" className={css.cardMain} aria-pressed={row.isDefault}
|
||||
disabled={row.isDefault || (row.broken === undefined && (!developerTools || state.saving))}
|
||||
disabled={row.isDefault || (row.broken === undefined && state.saving)}
|
||||
aria-disabled={row.broken !== undefined} aria-label={`${selectionAction}: ${display.name}`} title={selectionAction}
|
||||
onClick={() => { if (row.broken === undefined) void makeDefault(row.id) }}>
|
||||
<span className={css.cardHead}>
|
||||
|
||||
@@ -210,7 +210,7 @@ export function apply(ctx: ClientContext): void {
|
||||
}
|
||||
|
||||
const sectionInjected = (): AgentPresetSectionInjected => ({
|
||||
hooks: { agentPresetSection: section.store, developerTools: ctx.configForms.developerTools.enabled },
|
||||
hooks: { agentPresetSection: section.store },
|
||||
load: () => section.load(),
|
||||
view: (id: string) => section.view(id),
|
||||
closeView: () => { section.closeView() },
|
||||
|
||||
@@ -27,8 +27,6 @@ export type AgentPresetSettingsKey =
|
||||
| 'switchRefused'
|
||||
| 'close'
|
||||
| 'creatorDraft'
|
||||
| 'enableDevToolsToSetDefault'
|
||||
| 'enableDevToolsToCreate'
|
||||
|
||||
/** English copy. */
|
||||
export const en: Record<AgentPresetSettingsKey, string> = {
|
||||
@@ -67,8 +65,6 @@ export const en: Record<AgentPresetSettingsKey, string> = {
|
||||
|
||||
creatorDraft: 'Let the agent help me create a preset',
|
||||
|
||||
enableDevToolsToSetDefault: 'Turn on Coding Tools in General settings to choose a default',
|
||||
enableDevToolsToCreate: 'Turn on Coding Tools in General settings to start Creator mode',
|
||||
}
|
||||
|
||||
/** Simplified Chinese copy. */
|
||||
@@ -104,8 +100,6 @@ export const zh: Record<AgentPresetSettingsKey, string> = {
|
||||
|
||||
creatorDraft: '让 Agent 帮我创建预设模式',
|
||||
|
||||
enableDevToolsToSetDefault: '请先在通用设置中开启代码工作工具,再设置默认值',
|
||||
enableDevToolsToCreate: '请先在通用设置中开启代码工作工具,再启动创造模式',
|
||||
}
|
||||
|
||||
// The resolution itself is the shared fold in `dsh-agent-preset-registry/display`,
|
||||
|
||||
@@ -12,8 +12,7 @@ const translations: ReadonlyMap<string, string> = new Map(Object.entries(en))
|
||||
function unusedHook(): never {
|
||||
throw new Error('This section does not read global slot sources')
|
||||
}
|
||||
function view(partial: Partial<AgentPresetSectionState> = {}, startCreatorDraft?: () => void, developerTools = true,
|
||||
outerClose?: () => void) {
|
||||
function view(partial: Partial<AgentPresetSectionState> = {}, startCreatorDraft?: () => void, outerClose?: () => void) {
|
||||
const store = createSnapshotStore<AgentPresetSectionState>({ status: 'ready', error: null,
|
||||
saving: false, rows: [{ id: 'standard', isDefault: true }, { id: 'mine', name: 'Mine', isDefault: false }],
|
||||
view: null, ...partial })
|
||||
@@ -24,7 +23,6 @@ function view(partial: Partial<AgentPresetSectionState> = {}, startCreatorDraft?
|
||||
usePanelInfo: unusedHook, useSessions: unusedHook, useSessionStatus: unusedHook, useSessionRetainInfo: unusedHook,
|
||||
useWorkspaces: unusedHook, useResource: unusedHook,
|
||||
useAgentPresetSection: bindSnapshotSelector(store),
|
||||
useDeveloperTools: bindSnapshotSelector(createSnapshotStore(developerTools)),
|
||||
t: key => translations.get(key) ?? key }
|
||||
render(outerClose === undefined ? <AgentPresetSection {...props} />
|
||||
: <Modal open onClose={outerClose} title="Settings" closeLabel="Close"><AgentPresetSection {...props} /></Modal>)
|
||||
@@ -35,14 +33,9 @@ function rowFor(id: string): HTMLElement {
|
||||
if (row === null) throw new Error(`no card for ${id}`)
|
||||
return row
|
||||
}
|
||||
it('offers no selection switch and disables the card actions while Developer tools are off', () => {
|
||||
view({}, undefined, false)
|
||||
|
||||
expect(screen.queryByRole('switch')).toBeNull()
|
||||
expect(screen.getByRole<HTMLButtonElement>('button', { name: `${en.enableDevToolsToSetDefault}: Mine` }).disabled).toBe(true)
|
||||
})
|
||||
it('reads the roster once and sets a default from the card body', async () => {
|
||||
const actions = view()
|
||||
expect(screen.queryByRole('switch')).toBeNull()
|
||||
fireEvent.click(screen.getByRole('button', { name: `${en.setDefault}: Mine` }))
|
||||
expect(actions.makeDefault).toHaveBeenCalledWith('mine')
|
||||
await waitFor(() =>{ expect(actions.load).toHaveBeenCalledOnce() })
|
||||
@@ -82,15 +75,6 @@ it('offers no Creator entry without the conversation flow or the cordis preset',
|
||||
view({}, vi.fn())
|
||||
expect(screen.queryByRole('button', { name: en.creatorDraft })).toBeNull()
|
||||
})
|
||||
it('disables the Creator entry while Developer tools are off', () => {
|
||||
const launch = vi.fn()
|
||||
view({ rows: [{ id: 'cordis', isDefault: true }] }, launch, false)
|
||||
const button = screen.getByRole<HTMLButtonElement>('button', { name: en.creatorDraft })
|
||||
expect(button.disabled).toBe(true)
|
||||
expect(button.title).toBe(en.enableDevToolsToCreate)
|
||||
fireEvent.click(button)
|
||||
expect(launch).not.toHaveBeenCalled()
|
||||
})
|
||||
it('reads a declared composition read-only from every card, broken ones included', () => {
|
||||
const actions = view({ rows: [{ id: 'standard', isDefault: true }, { id: 'broken', isDefault: false, broken: 'Missing plugin' }] })
|
||||
expect(screen.queryByRole('dialog')).toBeNull()
|
||||
@@ -114,7 +98,7 @@ it('shows the open composition under the preset display name, without copy, and
|
||||
})
|
||||
it('keeps Escape inside the composition viewer when Settings is also open', () => {
|
||||
const closeSettings = vi.fn()
|
||||
const actions = view({}, undefined, true, closeSettings)
|
||||
const actions = view({}, undefined, closeSettings)
|
||||
const trigger = within(rowFor('standard')).getByRole('button', { name: `${en.view}: ${en.presetStandardName}` })
|
||||
trigger.focus()
|
||||
fireEvent.click(trigger)
|
||||
@@ -181,7 +165,7 @@ it.each([
|
||||
})
|
||||
it('keeps keyboard focus in help and dismisses only the reader on Escape', () => {
|
||||
const closeSettings = vi.fn()
|
||||
const actions = view({}, undefined, true, closeSettings)
|
||||
const actions = view({}, undefined, closeSettings)
|
||||
const trigger = within(rowFor('standard')).getByRole('button', { name: `${en.modeExplanation}: ${en.presetStandardName}` })
|
||||
trigger.focus()
|
||||
fireEvent.click(trigger)
|
||||
@@ -225,12 +209,6 @@ it('does not attach built-in claims to named or unknown presets', () => {
|
||||
expect(screen.queryByRole('button', { name: new RegExp(en.modeExplanation) })).toBeNull()
|
||||
expect(screen.queryByRole('button', { name: new RegExp(en.howToUse) })).toBeNull()
|
||||
})
|
||||
it('leaves help usable while Developer tools are off', () => {
|
||||
const actions = view({}, undefined, false)
|
||||
fireEvent.click(within(rowFor('standard')).getByRole('button', { name: `${en.howToUse}: ${en.presetStandardName}` }))
|
||||
expect(screen.getByRole('dialog', { name: en.presetStandardName })).toBeTruthy()
|
||||
expect(actions.makeDefault).not.toHaveBeenCalled()
|
||||
})
|
||||
it('closes help even when the browser reports no previously focused element', () => {
|
||||
const descriptor: TypedPropertyDescriptor<Element | null> = Object.getOwnPropertyDescriptor(Document.prototype, 'activeElement')!
|
||||
const readActiveElement = descriptor.get!.bind(document)
|
||||
|
||||
Reference in new issue
Block a user