feat: add desktop notification toggle to general settings
Add a default-on persisted desktop notification switch that takes effect immediately for background task results while leaving in-app notifications unchanged. Group General settings into shortcut, conversation and notification, and workspace and download cards. Validation: 756 related tests passed, 1 skipped; typecheck and lint passed. Full suite timed out with three supervision preview failures; native OS notification delivery was not manually verified. Release note: 通用设置新增默认开启的桌面通知开关,可随时关闭系统任务提醒并保留应用内提示;设置项按用途分组,方便查找。
This commit is contained in:
@@ -1130,6 +1130,7 @@ function installRemoteProjectsSetting(enabled: boolean): {
|
||||
} {
|
||||
let settings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github",
|
||||
modelDownloadSource: "modelscope",
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -3144,6 +3145,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -3247,6 +3249,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -3262,6 +3265,7 @@ describe("App", () => {
|
||||
})),
|
||||
updateSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -3363,6 +3367,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -3378,6 +3383,7 @@ describe("App", () => {
|
||||
})),
|
||||
updateSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -5653,6 +5659,7 @@ describe("App", () => {
|
||||
it("updates and removes the composer shortcut hint immediately after saving Settings", async () => {
|
||||
let applicationSettings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github",
|
||||
modelDownloadSource: "modelscope",
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13541,6 +13548,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13556,6 +13564,7 @@ describe("App", () => {
|
||||
})),
|
||||
updateSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13608,6 +13617,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13667,6 +13677,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13703,6 +13714,7 @@ describe("App", () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13718,6 +13730,7 @@ describe("App", () => {
|
||||
})),
|
||||
updateSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github" as const,
|
||||
modelDownloadSource: "modelscope" as const,
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13779,6 +13792,7 @@ describe("App", () => {
|
||||
]);
|
||||
let applicationSettings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github",
|
||||
modelDownloadSource: "modelscope",
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -13837,6 +13851,7 @@ describe("App", () => {
|
||||
it("omits global notes settings while retaining application center settings without navigating the workspace", async () => {
|
||||
let applicationSettings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: "github",
|
||||
modelDownloadSource: "modelscope",
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
|
||||
@@ -73,6 +73,7 @@ const todo: MagicTodoItem = {
|
||||
}
|
||||
const otherTodo: MagicTodoItem = { ...todo, id: '00000000-0000-4000-8000-000000000604', noteId: secondNoteId, noteTitle: second.title, title: '准备演示' }
|
||||
const settings: ApplicationSettings = {
|
||||
desktopNotificationsEnabled: true,
|
||||
checkUpdatesOnStartup: false, updateSource: 'github', modelDownloadSource: 'modelscope',
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings, applicationNavigation: defaultApplicationNavigation,
|
||||
localInferenceEnabled: true, conversationHtmlRenderingEnabled: true, remoteProjectsEnabled: false,
|
||||
|
||||
@@ -493,6 +493,29 @@ export function PlatformFeaturesSettingsSection({
|
||||
}
|
||||
}
|
||||
|
||||
const changeDesktopNotifications = async (
|
||||
enabled: boolean
|
||||
): Promise<void> => {
|
||||
const updates = window.goodbuddy.updates
|
||||
if (!updates || !settings || saving) {
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
setSettings(await updates.updateSettings({
|
||||
desktopNotificationsEnabled: enabled
|
||||
}))
|
||||
} catch {
|
||||
onNotify?.({
|
||||
tone: 'error',
|
||||
message: t('platformFeatures.errors.saveDesktopNotificationsFailed'),
|
||||
dedupeKey: 'desktop-notifications-save'
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const changeRemoteProjects = async (
|
||||
enabled: boolean
|
||||
): Promise<void> => {
|
||||
@@ -556,53 +579,6 @@ export function PlatformFeaturesSettingsSection({
|
||||
id="platform-features-panel-general"
|
||||
role="tabpanel"
|
||||
>
|
||||
<article className="capability-card">
|
||||
<div className="capability-card__header">
|
||||
<span className="inline-help-label">
|
||||
<FolderOpen aria-hidden="true" size={16} />
|
||||
<strong>{settingsT('runtime.workspace.title')}</strong>
|
||||
<InlineHelp label={settingsT('runtime.workspace.title')}>
|
||||
{settingsT('runtime.workspace.description')}
|
||||
</InlineHelp>
|
||||
</span>
|
||||
</div>
|
||||
<label className="field">
|
||||
<span>{settingsT('runtime.workspace.directoryLabel')}</span>
|
||||
<div className="workspace-picker">
|
||||
<input
|
||||
aria-label={settingsT('runtime.workspace.directoryLabel')}
|
||||
onChange={(event) =>
|
||||
onWorkspacePathChange(event.target.value)
|
||||
}
|
||||
value={workspacePath}
|
||||
/>
|
||||
<button
|
||||
className="secondary-button"
|
||||
onClick={() => {
|
||||
void window.goodbuddy.settings
|
||||
.selectWorkspace()
|
||||
.then((selected) => {
|
||||
if (selected) {
|
||||
onWorkspacePathChange(selected)
|
||||
}
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
setError(
|
||||
displayErrorMessage(
|
||||
reason,
|
||||
settingsT('errors.selectWorkspace')
|
||||
)
|
||||
)
|
||||
})
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{settingsT('actions.select')}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
</article>
|
||||
|
||||
<article className="capability-card">
|
||||
<div className="capability-card__header">
|
||||
<span className="inline-help-label">
|
||||
@@ -710,98 +686,164 @@ export function PlatformFeaturesSettingsSection({
|
||||
)}
|
||||
</article>
|
||||
{settings && (
|
||||
<article className="capability-card">
|
||||
<article
|
||||
aria-label={t('platformFeatures.groups.conversation')}
|
||||
className="capability-card"
|
||||
>
|
||||
<div className="capability-card__header">
|
||||
<span className="inline-help-label">
|
||||
<strong>
|
||||
{t('platformFeatures.conversationHtml.title')}
|
||||
</strong>
|
||||
<InlineHelp label={t('platformFeatures.conversationHtml.title')}>
|
||||
{t('platformFeatures.conversationHtml.description')}
|
||||
</InlineHelp>
|
||||
</span>
|
||||
<strong>{t('platformFeatures.groups.conversation')}</strong>
|
||||
</div>
|
||||
<label className="toggle-row">
|
||||
<input
|
||||
checked={settings.conversationHtmlRenderingEnabled}
|
||||
aria-describedby="desktop-notifications-description"
|
||||
checked={settings.desktopNotificationsEnabled}
|
||||
disabled={saving}
|
||||
onChange={(event) =>
|
||||
void changeConversationHtmlRendering(
|
||||
event.target.checked
|
||||
)
|
||||
void changeDesktopNotifications(event.target.checked)
|
||||
}
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>
|
||||
{t('platformFeatures.conversationHtml.enabled')}
|
||||
</span>
|
||||
<span>{t('platformFeatures.desktopNotifications.enabled')}</span>
|
||||
</label>
|
||||
<p id="desktop-notifications-description">
|
||||
{t('platformFeatures.desktopNotifications.description')}
|
||||
</p>
|
||||
<div className="inline-help-label">
|
||||
<label className="toggle-row">
|
||||
<input
|
||||
checked={settings.conversationHtmlRenderingEnabled}
|
||||
disabled={saving}
|
||||
onChange={(event) =>
|
||||
void changeConversationHtmlRendering(
|
||||
event.target.checked
|
||||
)
|
||||
}
|
||||
role="switch"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span>
|
||||
{t('platformFeatures.conversationHtml.enabled')}
|
||||
</span>
|
||||
</label>
|
||||
<InlineHelp label={t('platformFeatures.conversationHtml.title')}>
|
||||
{t('platformFeatures.conversationHtml.description')}
|
||||
</InlineHelp>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
{settings ? (
|
||||
<article className="capability-card">
|
||||
<article
|
||||
aria-label={t('platformFeatures.groups.workspace')}
|
||||
className="capability-card"
|
||||
>
|
||||
<div className="capability-card__header">
|
||||
<span className="inline-help-label">
|
||||
<strong>
|
||||
{t('platformFeatures.modelDownloadSource.title')}
|
||||
</strong>
|
||||
<InlineHelp label={t('platformFeatures.modelDownloadSource.title')}>
|
||||
{t(
|
||||
'platformFeatures.modelDownloadSource.description'
|
||||
)}
|
||||
<FolderOpen aria-hidden="true" size={16} />
|
||||
<strong>{t('platformFeatures.groups.workspace')}</strong>
|
||||
<InlineHelp label={settingsT('runtime.workspace.title')}>
|
||||
{settingsT('runtime.workspace.description')}
|
||||
</InlineHelp>
|
||||
</span>
|
||||
</div>
|
||||
<fieldset className="model-download-source">
|
||||
<legend className="sr-only">
|
||||
{t('platformFeatures.modelDownloadSource.title')}
|
||||
</legend>
|
||||
{(
|
||||
['modelscope', 'hugging-face'] as const
|
||||
).map((source) => (
|
||||
<label
|
||||
className={
|
||||
source === settings.modelDownloadSource
|
||||
? 'model-download-source__option model-download-source__option--selected'
|
||||
: 'model-download-source__option'
|
||||
<label className="field">
|
||||
<span>{settingsT('runtime.workspace.directoryLabel')}</span>
|
||||
<div className="workspace-picker">
|
||||
<input
|
||||
aria-label={settingsT('runtime.workspace.directoryLabel')}
|
||||
onChange={(event) =>
|
||||
onWorkspacePathChange(event.target.value)
|
||||
}
|
||||
key={source}
|
||||
value={workspacePath}
|
||||
/>
|
||||
<button
|
||||
className="secondary-button"
|
||||
onClick={() => {
|
||||
void window.goodbuddy.settings
|
||||
.selectWorkspace()
|
||||
.then((selected) => {
|
||||
if (selected) {
|
||||
onWorkspacePathChange(selected)
|
||||
}
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
setError(
|
||||
displayErrorMessage(
|
||||
reason,
|
||||
settingsT('errors.selectWorkspace')
|
||||
)
|
||||
)
|
||||
})
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<input
|
||||
checked={source === settings.modelDownloadSource}
|
||||
disabled={saving}
|
||||
name="model-download-source"
|
||||
onChange={() =>
|
||||
void changeModelDownloadSource(source)
|
||||
}
|
||||
type="radio"
|
||||
value={source}
|
||||
/>
|
||||
<span>
|
||||
<strong>{t(`modelDownloadSources.${source}`)}</strong>
|
||||
<small>
|
||||
{settingsT('actions.select')}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
{settings ? (
|
||||
<>
|
||||
<div className="capability-card__header">
|
||||
<span className="inline-help-label">
|
||||
<strong>
|
||||
{t('platformFeatures.modelDownloadSource.title')}
|
||||
</strong>
|
||||
<InlineHelp label={t('platformFeatures.modelDownloadSource.title')}>
|
||||
{t(
|
||||
`platformFeatures.modelDownloadSource.options.${source}`
|
||||
'platformFeatures.modelDownloadSource.description'
|
||||
)}
|
||||
</small>
|
||||
</InlineHelp>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
{sourceError && (
|
||||
<p className="settings-warning" role="alert">
|
||||
{sourceError}
|
||||
</p>
|
||||
</div>
|
||||
<fieldset className="model-download-source">
|
||||
<legend className="sr-only">
|
||||
{t('platformFeatures.modelDownloadSource.title')}
|
||||
</legend>
|
||||
{(
|
||||
['modelscope', 'hugging-face'] as const
|
||||
).map((source) => (
|
||||
<label
|
||||
className={
|
||||
source === settings.modelDownloadSource
|
||||
? 'model-download-source__option model-download-source__option--selected'
|
||||
: 'model-download-source__option'
|
||||
}
|
||||
key={source}
|
||||
>
|
||||
<input
|
||||
checked={source === settings.modelDownloadSource}
|
||||
disabled={saving}
|
||||
name="model-download-source"
|
||||
onChange={() =>
|
||||
void changeModelDownloadSource(source)
|
||||
}
|
||||
type="radio"
|
||||
value={source}
|
||||
/>
|
||||
<span>
|
||||
<strong>{t(`modelDownloadSources.${source}`)}</strong>
|
||||
<small>
|
||||
{t(
|
||||
`platformFeatures.modelDownloadSource.options.${source}`
|
||||
)}
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
{sourceError && (
|
||||
<p className="settings-warning" role="alert">
|
||||
{sourceError}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!error && (
|
||||
<p className="settings-notice" role="status">
|
||||
{t('platformFeatures.loading')}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</article>
|
||||
) : (
|
||||
!error && (
|
||||
<p className="settings-notice" role="status">
|
||||
{t('platformFeatures.loading')}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
@@ -546,6 +546,7 @@ const removeEmbeddingModel = vi.fn(
|
||||
)
|
||||
let applicationSettings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'github',
|
||||
modelDownloadSource: 'modelscope',
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -839,6 +840,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
await changeUiLocale('zh-CN')
|
||||
applicationSettings = {
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'github',
|
||||
modelDownloadSource: 'modelscope',
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -1426,7 +1428,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
fireEvent.click(
|
||||
screen.getByRole('tab', { name: /Platform Features/i })
|
||||
)
|
||||
expect(screen.getByText('Default workspace')).toBeInTheDocument()
|
||||
expect(screen.getByText('Workspace & downloads')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByLabelText('Default workspace folder')
|
||||
).toBeInTheDocument()
|
||||
@@ -1574,6 +1576,100 @@ describe('SettingsPanel runtime files', () => {
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['zh-CN', '平台功能', '桌面通知'],
|
||||
['en-US', 'Platform features', 'Desktop notifications']
|
||||
] as const)('saves desktop notifications immediately in %s', async (locale, category, label) => {
|
||||
await changeUiLocale(locale)
|
||||
render(
|
||||
<SettingsPanel
|
||||
{...heartbeatSettingsProps}
|
||||
open
|
||||
onClearLocalData={vi.fn(async () => {})}
|
||||
onClose={vi.fn()}
|
||||
onSaved={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: category }))
|
||||
const toggle = await screen.findByRole('switch', { name: label })
|
||||
expect(toggle).toBeChecked()
|
||||
expect(toggle).toHaveAccessibleDescription()
|
||||
fireEvent.click(toggle)
|
||||
await waitFor(() => expect(toggle).not.toBeChecked())
|
||||
expect(updateApplicationSettings).toHaveBeenLastCalledWith({
|
||||
desktopNotificationsEnabled: false
|
||||
})
|
||||
fireEvent.click(toggle)
|
||||
await waitFor(() => expect(toggle).toBeChecked())
|
||||
expect(updateApplicationSettings).toHaveBeenLastCalledWith({
|
||||
desktopNotificationsEnabled: true
|
||||
})
|
||||
expect(updateApplicationSettings).toHaveBeenCalledTimes(2)
|
||||
expect(updateRuntime).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('loads disabled desktop notifications and groups related General settings', async () => {
|
||||
applicationSettings.desktopNotificationsEnabled = false
|
||||
render(
|
||||
<SettingsPanel
|
||||
{...heartbeatSettingsProps}
|
||||
open
|
||||
onClearLocalData={vi.fn(async () => {})}
|
||||
onClose={vi.fn()}
|
||||
onSaved={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '平台功能' }))
|
||||
const conversation = await screen.findByRole('article', { name: '会话与通知' })
|
||||
expect(within(conversation).getByRole('switch', { name: '桌面通知' })).not.toBeChecked()
|
||||
expect(within(conversation).getByRole('switch', { name: '在会话中渲染 HTML' })).toBeChecked()
|
||||
const workspace = screen.getByRole('article', { name: '工作目录与下载' })
|
||||
expect(within(workspace).getByRole('textbox')).toHaveValue(runtimeSettings.workspacePath)
|
||||
expect(within(workspace).getByRole('radio', { name: /ModelScope/u })).toBeChecked()
|
||||
expect(updateApplicationSettings).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps desktop notifications confirmed while saving and reports failure through onNotify', async () => {
|
||||
const onNotify = vi.fn()
|
||||
let rejectSave!: (reason: Error) => void
|
||||
updateApplicationSettings.mockImplementationOnce(() => new Promise((_, reject) => {
|
||||
rejectSave = reject
|
||||
}))
|
||||
render(
|
||||
<SettingsPanel
|
||||
{...heartbeatSettingsProps}
|
||||
onNotify={onNotify}
|
||||
open
|
||||
onClearLocalData={vi.fn(async () => {})}
|
||||
onClose={vi.fn()}
|
||||
onSaved={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: '平台功能' }))
|
||||
const toggle = await screen.findByRole('switch', { name: '桌面通知' })
|
||||
fireEvent.click(toggle)
|
||||
expect(toggle).toBeDisabled()
|
||||
expect(toggle).toBeChecked()
|
||||
fireEvent.click(toggle)
|
||||
expect(updateApplicationSettings).toHaveBeenCalledTimes(1)
|
||||
await act(async () => rejectSave(new Error('save failed')))
|
||||
expect(toggle).toBeEnabled()
|
||||
expect(toggle).toBeChecked()
|
||||
expect(onNotify).toHaveBeenCalledExactlyOnceWith({
|
||||
tone: 'error',
|
||||
message: '保存桌面通知设置失败,请重试',
|
||||
dedupeKey: 'desktop-notifications-save'
|
||||
})
|
||||
expect(screen.queryByText('保存桌面通知设置失败,请重试')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(toggle)
|
||||
await waitFor(() => expect(toggle).not.toBeChecked())
|
||||
expect(updateApplicationSettings).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('disables conversation HTML rendering from General settings', async () => {
|
||||
const onConversationHtmlRenderingEnabledChange = vi.fn()
|
||||
render(
|
||||
@@ -1629,6 +1725,9 @@ describe('SettingsPanel runtime files', () => {
|
||||
expect(
|
||||
screen.queryByRole('radio', { name: /ModelScope/u })
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('switch', { name: '桌面通知' })
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('当前选择:ModelScope')
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('UpdateSettingsSection', () => {
|
||||
it('checks the official release manifest and updates the startup preference', async () => {
|
||||
let applicationSettings: ApplicationSettings = {
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'github',
|
||||
modelDownloadSource: 'modelscope',
|
||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||
@@ -186,6 +187,7 @@ describe('UpdateSettingsSection', () => {
|
||||
updates: {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'github',
|
||||
magicNotesEnabled: true,
|
||||
magicNoteCommentMode: 'immediate',
|
||||
@@ -193,6 +195,7 @@ describe('UpdateSettingsSection', () => {
|
||||
})),
|
||||
updateSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: true,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'github',
|
||||
magicNotesEnabled: true,
|
||||
magicNoteCommentMode: 'immediate',
|
||||
@@ -239,6 +242,7 @@ describe('UpdateSettingsSection', () => {
|
||||
updates: {
|
||||
getSettings: vi.fn(async () => ({
|
||||
checkUpdatesOnStartup: false,
|
||||
desktopNotificationsEnabled: true,
|
||||
updateSource: 'mirror',
|
||||
magicNotesEnabled: true,
|
||||
magicNoteCommentMode: 'immediate',
|
||||
|
||||
@@ -541,6 +541,8 @@ export const settingsSections = {
|
||||
serviceUnavailable:
|
||||
'Application settings are not available in this version',
|
||||
readFailed: 'Could not load platform feature settings',
|
||||
saveDesktopNotificationsFailed:
|
||||
'Could not save the desktop notification setting. Try again.',
|
||||
saveConversationHtmlRenderingFailed:
|
||||
'Could not save the conversation HTML rendering setting. Try again.',
|
||||
saveRemoteProjectsFailed:
|
||||
@@ -560,6 +562,15 @@ export const settingsSections = {
|
||||
ariaLabel: 'Platform feature settings',
|
||||
general: 'General'
|
||||
},
|
||||
groups: {
|
||||
conversation: 'Conversations & notifications',
|
||||
workspace: 'Workspace & downloads'
|
||||
},
|
||||
desktopNotifications: {
|
||||
enabled: 'Desktop notifications',
|
||||
description:
|
||||
'Show system notifications when background work needs your attention. Turning this off does not affect in-app notifications.'
|
||||
},
|
||||
conversationHtml: {
|
||||
title: 'Conversation HTML rendering',
|
||||
description:
|
||||
|
||||
@@ -516,6 +516,7 @@ export const settingsSections = {
|
||||
errors: {
|
||||
serviceUnavailable: '当前版本未提供应用设置服务',
|
||||
readFailed: '读取平台功能设置失败',
|
||||
saveDesktopNotificationsFailed: '保存桌面通知设置失败,请重试',
|
||||
saveConversationHtmlRenderingFailed:
|
||||
'保存会话 HTML 渲染设置失败,请重试',
|
||||
saveRemoteProjectsFailed: '保存远程项目设置失败,请重试',
|
||||
@@ -531,6 +532,15 @@ export const settingsSections = {
|
||||
ariaLabel: '平台功能设置',
|
||||
general: '通用设置'
|
||||
},
|
||||
groups: {
|
||||
conversation: '会话与通知',
|
||||
workspace: '工作目录与下载'
|
||||
},
|
||||
desktopNotifications: {
|
||||
enabled: '桌面通知',
|
||||
description:
|
||||
'后台工作需要关注时显示系统通知。关闭后不影响应用内通知。'
|
||||
},
|
||||
conversationHtml: {
|
||||
title: '会话 HTML 渲染',
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user