Skip to content

Commit 49f9b6f

Browse files
committed
Merge remote-tracking branch 'origin/feat/workspace-team' into fix/remove-theme-force-light
2 parents 29e0aba + 6972561 commit 49f9b6f

6 files changed

Lines changed: 415 additions & 348 deletions

File tree

apps/web/tests/components/SettingsDialog.execution.test.tsx

Lines changed: 62 additions & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -4853,144 +4853,32 @@ describe('SettingsDialog notifications interactions', () => {
48534853
});
48544854
});
48554855

4856-
describe('SettingsDialog appearance interactions', () => {
4856+
// Was 'SettingsDialog appearance interactions'. The eight theme/accent cases
4857+
// this block opened with are retired: the product removed theme selection
4858+
// outright ("主题设置不要了,因为 workspace 功能不支持暗色主题,要干掉"), which
4859+
// also formally overturns the NON-ALIGNMENT #9 note that had argued for keeping
4860+
// the segmented control as the last "follow system" entry point. The document
4861+
// theme/accent teardown went with them — nothing here writes those any more.
4862+
// What survives is the AMR draft-reconciliation coverage that merely happened
4863+
// to live in this block.
4864+
describe('SettingsDialog draft reconciliation', () => {
48574865
afterEach(() => {
48584866
cleanup();
4859-
document.documentElement.removeAttribute('data-theme');
4860-
document.documentElement.style.removeProperty('--accent');
4861-
document.documentElement.style.removeProperty('--accent-strong');
4862-
document.documentElement.style.removeProperty('--accent-soft');
4863-
document.documentElement.style.removeProperty('--accent-tint');
4864-
document.documentElement.style.removeProperty('--accent-hover');
4865-
});
4866-
4867-
// #5517 drops the accent-colour swatches from Appearance but keeps the
4868-
// 系统/浅色/深色 segmented control (its own comment calls the removal
4869-
// *temporary*, and the account menu no longer carries a 切换主题 row, so this
4870-
// is the product's only "follow system" entry point — NON-ALIGNMENT #9).
4871-
it('offers the theme segmented control, and System leaves the document theme unset', () => {
4872-
const { container } = renderSettingsDialog(
4873-
{ theme: 'system' },
4874-
{ initialSection: 'appearance' },
4875-
);
4876-
4877-
const group = screen.getByRole('group', { name: 'Appearance' });
4878-
expect(container.querySelector('.seg-control[aria-label="Appearance"]')).toBeTruthy();
4879-
expect(within(group).getByRole('button', { name: 'System' }).getAttribute('aria-pressed')).toBe('true');
4880-
expect(within(group).getByRole('button', { name: 'Light' }).getAttribute('aria-pressed')).toBe('false');
4881-
expect(within(group).getByRole('button', { name: 'Dark' }).getAttribute('aria-pressed')).toBe('false');
4882-
4883-
// Regression guard: the accent swatches must not come back — #5517 removed
4884-
// that control, and `accentColor` is now a stored value with no editor.
4885-
expect(screen.queryByRole('radiogroup', { name: 'Accent color' })).toBeNull();
4886-
expect(screen.queryByLabelText('Custom color')).toBeNull();
4887-
4888-
// `system` means "no explicit document theme", so the OS media query keeps
4889-
// ownership.
4890-
expect(document.documentElement.hasAttribute('data-theme')).toBe(false);
4891-
});
4892-
4893-
it('applies the stored default accent color even though the picker is gone', () => {
4894-
renderSettingsDialog(
4895-
{ theme: 'system' },
4896-
{ initialSection: 'appearance' },
4897-
);
4898-
4899-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#353535');
4900-
});
4901-
4902-
it('writes the picked theme to the document and autosaves it', async () => {
4903-
const { onPersist } = renderSettingsDialog(
4904-
{ mode: 'daemon', agentId: 'codex', theme: 'system' },
4905-
{ initialSection: 'appearance' },
4906-
);
4907-
4908-
const group = screen.getByRole('group', { name: 'Appearance' });
4909-
fireEvent.click(within(group).getByRole('button', { name: 'Dark' }));
4910-
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
4911-
4912-
await waitForPersist(onPersist, expect.objectContaining({ theme: 'dark' }), {});
4913-
});
4914-
4915-
// The section still runs the live appearance preview on mount; it just reads
4916-
// the theme instead of letting the user set it. Opening Settings after the
4917-
// account menu flipped the theme must show that theme, and an explicit
4918-
// theme/`system` must keep producing/removing `data-theme` respectively.
4919-
// Each mount is one preview pass, so this drives the three modes by
4920-
// remounting rather than by clicking the removed segmented control.
4921-
it('live previews the configured theme on open, and System leaves no explicit document theme', () => {
4922-
renderSettingsDialog({ theme: 'dark' }, { initialSection: 'appearance' });
4923-
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
4924-
cleanup();
4925-
4926-
renderSettingsDialog({ theme: 'light' }, { initialSection: 'appearance' });
4927-
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
4928-
cleanup();
4929-
4930-
renderSettingsDialog({ theme: 'system' }, { initialSection: 'appearance' });
4931-
expect(document.documentElement.hasAttribute('data-theme')).toBe(false);
4932-
});
4933-
4934-
// The close-time revert is unchanged: SettingsDialog's cleanup re-applies the
4935-
// LAST SAVED appearance, so a preview the user never saved is rolled back.
4936-
// The stored accent is asserted too, because cleanup re-applies theme AND
4937-
// accent together and must not drop either on the way back.
4938-
it('reverts an unsaved appearance preview back to the saved appearance when the dialog closes', () => {
4939-
const first = renderSettingsDialog(
4940-
{ theme: 'dark', accentColor: '#2563eb' },
4941-
{ initialSection: 'appearance' },
4942-
);
4943-
4944-
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
4945-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
4946-
4947-
// Autosave is debounced (400ms), so closing immediately leaves this edit
4948-
// unsaved — exactly the case the revert exists for.
4949-
const group = screen.getByRole('group', { name: 'Appearance' });
4950-
fireEvent.click(within(group).getByRole('button', { name: 'Light' }));
4951-
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
4952-
4953-
fireEvent.click(first.container.querySelector('.settings-close') as HTMLElement);
4954-
expect(first.onClose).toHaveBeenCalledTimes(1);
4955-
4956-
first.unmount();
4957-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
4958-
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
4959-
});
4960-
4961-
it('persists System mode explicitly and preserves accent variables without an explicit document theme', async () => {
4962-
const { onPersist } = renderSettingsDialog(
4963-
{ mode: 'daemon', agentId: 'codex', theme: 'light', accentColor: '#2563eb' },
4964-
{ initialSection: 'appearance' },
4965-
);
4966-
4967-
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
4968-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
4969-
4970-
// `system` must be persisted EXPLICITLY (not dropped to undefined), and it
4971-
// must keep the stored accent variables applied while leaving the document
4972-
// theme to the OS media query.
4973-
const group = screen.getByRole('group', { name: 'Appearance' });
4974-
fireEvent.click(within(group).getByRole('button', { name: 'System' }));
4975-
expect(document.documentElement.hasAttribute('data-theme')).toBe(false);
4976-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
4977-
4978-
await waitForPersist(
4979-
onPersist,
4980-
expect.objectContaining({
4981-
theme: 'system',
4982-
accentColor: '#2563eb',
4983-
}),
4984-
{},
4985-
);
49864867
});
49874868

49884869
it('reconciles the open settings draft when the parent agent CLI env changes', async () => {
49894870
const view = renderSettingsDialog(
49904871
{
49914872
mode: 'daemon',
49924873
agentId: 'amr',
4993-
theme: 'dark',
4874+
// Seeded on so the one click below is a real state change: the
4875+
// completion-sound pills are no-ops when clicked in their current state.
4876+
notifications: {
4877+
soundEnabled: true,
4878+
successSoundId: 'chime',
4879+
failureSoundId: 'two-tone-down',
4880+
desktopEnabled: false,
4881+
},
49944882
agentModels: {
49954883
amr: {
49964884
model: 'prod-only-model',
@@ -5005,7 +4893,7 @@ describe('SettingsDialog appearance interactions', () => {
50054893
},
50064894
},
50074895
},
5008-
{ initialSection: 'appearance', agents: [amrAgent, ...availableAgents] },
4896+
{ initialSection: 'notifications', agents: [amrAgent, ...availableAgents] },
50094897
);
50104898

50114899
view.rerender(
@@ -5014,7 +4902,12 @@ describe('SettingsDialog appearance interactions', () => {
50144902
...baseConfig,
50154903
mode: 'daemon',
50164904
agentId: 'amr',
5017-
theme: 'dark',
4905+
notifications: {
4906+
soundEnabled: true,
4907+
successSoundId: 'chime',
4908+
failureSoundId: 'two-tone-down',
4909+
desktopEnabled: false,
4910+
},
50184911
agentCliEnv: {
50194912
amr: {
50204913
OPEN_DESIGN_AMR_PROFILE: 'local',
@@ -5025,7 +4918,7 @@ describe('SettingsDialog appearance interactions', () => {
50254918
agents={[amrAgent, ...availableAgents]}
50264919
daemonLive={true}
50274920
appVersionInfo={null}
5028-
initialSection="appearance"
4921+
initialSection="notifications"
50294922
onPersist={view.onPersist}
50304923
onPersistComposioKey={view.onPersistComposioKey}
50314924
onClose={view.onClose}
@@ -5034,15 +4927,19 @@ describe('SettingsDialog appearance interactions', () => {
50344927
);
50354928

50364929
// Any committed edit will do — this test is about what the draft carries
5037-
// when it autosaves, not about which control fired it.
4930+
// when it autosaves, not about which control fired it. It used to ride the
4931+
// Appearance theme control; with theme selection removed, the notifications
4932+
// completion-sound toggle is the equivalent one-click persisted edit.
50384933
fireEvent.click(
5039-
within(screen.getByRole('group', { name: 'Appearance' })).getByRole('button', { name: 'Light' }),
4934+
within(screen.getByRole('group', { name: 'Completion sound' })).getByRole('button', {
4935+
name: 'inactive',
4936+
}),
50404937
);
50414938

50424939
await waitForPersist(
50434940
view.onPersist,
50444941
expect.objectContaining({
5045-
theme: 'light',
4942+
notifications: expect.objectContaining({ soundEnabled: false }),
50464943
agentModels: {},
50474944
agentCliEnv: {
50484945
codex: { CODEX_BIN: '/tmp/codex-dev' },
@@ -5117,64 +5014,6 @@ describe('SettingsDialog appearance interactions', () => {
51175014
},
51185015
});
51195016
});
5120-
5121-
// #5517 removed the accent-colour editor from Settings, so a stored accent is
5122-
// now read-only from this surface: it must still be applied on open and
5123-
// survive a save driven by any other Appearance control, and it must not be
5124-
// rewritten to the default just because nothing edits it any more.
5125-
it('keeps a stored non-default accent applied and carries it through an autosave', async () => {
5126-
const view = renderSettingsDialog(
5127-
{ mode: 'daemon', agentId: 'codex', theme: 'light', accentColor: '#2563eb' },
5128-
{ initialSection: 'appearance' },
5129-
);
5130-
5131-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
5132-
5133-
fireEvent.click(
5134-
within(screen.getByRole('group', { name: 'Appearance' })).getByRole('button', { name: 'Dark' }),
5135-
);
5136-
5137-
await waitForPersist(
5138-
view.onPersist,
5139-
expect.objectContaining({
5140-
theme: 'dark',
5141-
accentColor: '#2563eb',
5142-
}),
5143-
{},
5144-
);
5145-
5146-
fireEvent.click(view.container.querySelector('.settings-close') as HTMLElement);
5147-
expect(view.onClose).toHaveBeenCalledTimes(1);
5148-
5149-
view.unmount();
5150-
expect(document.documentElement.style.getPropertyValue('--accent')).toBe('#2563eb');
5151-
});
5152-
5153-
it('localizes the theme controls in Chinese', () => {
5154-
render(
5155-
<I18nProvider initial="zh-CN">
5156-
<SettingsDialog
5157-
initial={{ ...baseConfig, theme: 'light' }}
5158-
agents={availableAgents}
5159-
daemonLive={true}
5160-
appVersionInfo={null}
5161-
initialSection="appearance"
5162-
onPersist={vi.fn()}
5163-
onPersistComposioKey={vi.fn()}
5164-
onClose={vi.fn()}
5165-
onRefreshAgents={vi.fn()}
5166-
/>
5167-
</I18nProvider>,
5168-
);
5169-
5170-
const group = screen.getByRole('group', { name: '外观' });
5171-
expect(within(group).getByRole('button', { name: '系统' })).toBeTruthy();
5172-
expect(within(group).getByRole('button', { name: '浅色' })).toBeTruthy();
5173-
expect(within(group).getByRole('button', { name: '深色' })).toBeTruthy();
5174-
// The accent picker and its Chinese labels are gone with it.
5175-
expect(screen.queryByRole('radiogroup', { name: '主题色' })).toBeNull();
5176-
expect(screen.queryByLabelText('自定义颜色')).toBeNull();
5177-
});
51785017
});
51795018

51805019
describe('SettingsDialog pets interactions', () => {
@@ -5623,17 +5462,26 @@ describe('SettingsDialog about interactions', () => {
56235462
mode: 'daemon',
56245463
agentId: 'codex',
56255464
onboardingCompleted: true,
5626-
theme: 'system',
5465+
// Seeded on so the one click below is a real state change.
5466+
notifications: {
5467+
soundEnabled: true,
5468+
successSoundId: 'chime',
5469+
failureSoundId: 'two-tone-down',
5470+
desktopEnabled: false,
5471+
},
56275472
},
56285473
{
5629-
initialSection: 'appearance',
5474+
initialSection: 'notifications',
56305475
onResetOnboarding,
56315476
},
56325477
);
56335478

5479+
// The subject is the pending-autosave drop, not which control queued it.
5480+
// Theme selection is gone, so the completion-sound toggle stands in as the
5481+
// one-click persisted edit that leaves a debounced save in flight.
56345482
fireEvent.click(
5635-
within(screen.getByRole('group', { name: 'Appearance' })).getByRole('button', {
5636-
name: 'Dark',
5483+
within(screen.getByRole('group', { name: 'Completion sound' })).getByRole('button', {
5484+
name: 'inactive',
56375485
}),
56385486
);
56395487
expect(screen.getByText('Saving…')).toBeTruthy();
@@ -5646,7 +5494,7 @@ describe('SettingsDialog about interactions', () => {
56465494
expect(onResetOnboarding).toHaveBeenCalledWith(
56475495
expect.objectContaining({
56485496
onboardingCompleted: false,
5649-
theme: 'dark',
5497+
notifications: expect.objectContaining({ soundEnabled: false }),
56505498
}),
56515499
);
56525500
});
@@ -6109,7 +5957,7 @@ describe('SettingsDialog about interactions', () => {
61095957
it('still autosaves an unrelated edit that lands during a silent-update save', async () => {
61105958
// Regression: success must only advance autosaveLastSavedRef for
61115959
// allowSilentUpdates. Spreading the whole latest draft would mark a
6112-
// concurrent theme (etc.) change as already saved and skip onPersist.
5960+
// concurrent unrelated change as already saved and skip onPersist.
61135961
let resolveSave: (() => void) | null = null;
61145962
const onSilentUpdatePreferenceChange = vi.fn(
61155963
() => new Promise<void>((resolve) => {
@@ -6121,8 +5969,13 @@ describe('SettingsDialog about interactions', () => {
61215969
mode: 'daemon',
61225970
agentId: 'codex',
61235971
allowSilentUpdates: false,
6124-
theme: 'light',
6125-
accentColor: '#2563eb',
5972+
// Seeded on so the concurrent click below is a real state change.
5973+
notifications: {
5974+
soundEnabled: true,
5975+
successSoundId: 'chime',
5976+
failureSoundId: 'two-tone-down',
5977+
desktopEnabled: false,
5978+
},
61265979
},
61275980
{
61285981
initialSection: 'about',
@@ -6147,18 +6000,18 @@ describe('SettingsDialog about interactions', () => {
61476000
expect(onPersist).not.toHaveBeenCalled();
61486001

61496002
// Concurrent persisted edit while the silent-update request is in flight.
6150-
// #5517 dropped the accent-colour picker from Appearance, so the theme
6151-
// segmented control is now the section's persisted-edit vehicle. The
6152-
// invariant under test is the autosave bookkeeping, not the accent field.
6153-
fireEvent.click(screen.getByRole('button', { name: /Appearance/i }));
6003+
// The invariant under test is the autosave bookkeeping, not the field that
6004+
// carries it — theme selection was the old vehicle and is gone, so this
6005+
// reaches for the notifications completion-sound toggle instead.
6006+
fireEvent.click(screen.getByRole('button', { name: /General/i }));
61546007
fireEvent.click(
6155-
within(screen.getByRole('group', { name: 'Appearance' })).getByRole('button', {
6156-
name: 'Dark',
6008+
within(screen.getByRole('group', { name: 'Completion sound' })).getByRole('button', {
6009+
name: 'inactive',
61576010
}),
61586011
);
61596012

61606013
// Resolve silent-update AFTER the concurrent edit is in draft. The success
6161-
// path must not stamp this theme into autosaveLastSavedRef.
6014+
// path must not stamp this edit into autosaveLastSavedRef.
61626015
await act(async () => {
61636016
resolveSave?.();
61646017
await Promise.resolve();
@@ -6167,7 +6020,7 @@ describe('SettingsDialog about interactions', () => {
61676020
await waitForPersist(
61686021
onPersist,
61696022
expect.objectContaining({
6170-
theme: 'dark',
6023+
notifications: expect.objectContaining({ soundEnabled: false }),
61716024
}),
61726025
{},
61736026
);

0 commit comments

Comments
 (0)