Skip to content

Commit 6498381

Browse files
committed
Updates
1 parent 96b74ee commit 6498381

5 files changed

Lines changed: 177 additions & 3 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { mkdtempSync } from 'node:fs'
2+
import { tmpdir } from 'node:os'
3+
import { join } from 'node:path'
4+
import { beforeEach, describe, expect, it, vi } from 'vitest'
5+
6+
vi.mock('electron', () => import('@/test/electron-mock'))
7+
8+
import { app, BrowserWindow } from 'electron'
9+
import { createConfigStore } from '@/main/config'
10+
import { createDesktopSettingsService } from '@/main/desktop-settings'
11+
import { Notification } from '@/test/electron-mock'
12+
13+
function makeService() {
14+
const config = createConfigStore(
15+
join(mkdtempSync(join(tmpdir(), 'sim-desktop-settings-')), 'settings.json'),
16+
{}
17+
)
18+
const window = new BrowserWindow()
19+
const openMainWindowAt = vi.fn()
20+
const setAutoDownloadUpdates = vi.fn()
21+
const service = createDesktopSettingsService({
22+
config,
23+
getMainWindow: () => window,
24+
openMainWindowAt,
25+
setAutoDownloadUpdates,
26+
})
27+
return { config, window, openMainWindowAt, setAutoDownloadUpdates, service }
28+
}
29+
30+
describe('desktop settings service', () => {
31+
beforeEach(() => {
32+
Notification.instances.length = 0
33+
Notification.isSupported.mockReturnValue(true)
34+
vi.mocked(app.setLoginItemSettings).mockClear()
35+
Object.defineProperty(app, 'isPackaged', { configurable: true, value: false })
36+
})
37+
38+
it('persists preferences and applies live updater changes', () => {
39+
const { config, service, setAutoDownloadUpdates } = makeService()
40+
expect(service.getPreferences()).toMatchObject({
41+
notificationsEnabled: true,
42+
notificationsOnlyWhenUnfocused: true,
43+
autoDownloadUpdates: true,
44+
})
45+
46+
service.setPreference('autoDownloadUpdates', false)
47+
expect(config.get('autoDownloadUpdates')).toBe(false)
48+
expect(setAutoDownloadUpdates).toHaveBeenCalledWith(false)
49+
})
50+
51+
it('applies login-item changes only for packaged builds', () => {
52+
const { service } = makeService()
53+
service.setPreference('launchAtLogin', true)
54+
expect(app.setLoginItemSettings).not.toHaveBeenCalled()
55+
56+
Object.defineProperty(app, 'isPackaged', { configurable: true, value: true })
57+
service.setPreference('launchAtLogin', false)
58+
expect(app.setLoginItemSettings).toHaveBeenCalledWith({ openAtLogin: false })
59+
})
60+
61+
it('shows notifications only when allowed and opens their route on click', () => {
62+
const { window, openMainWindowAt, service } = makeService()
63+
vi.mocked(window.isFocused).mockReturnValue(true)
64+
expect(service.notify({ title: 'Done', body: 'Ready' })).toBe(false)
65+
66+
vi.mocked(window.isFocused).mockReturnValue(false)
67+
expect(
68+
service.notify({
69+
title: 'Task complete',
70+
body: 'Sim finished responding.',
71+
route: '/workspace/ws1/chat/c1',
72+
})
73+
).toBe(true)
74+
75+
const notification = Notification.instances[0]
76+
expect(notification.options).toMatchObject({ silent: false })
77+
expect(notification.show).toHaveBeenCalled()
78+
const click = notification.on.mock.calls.find(([event]) => event === 'click')?.[1]
79+
expect(click).toBeTypeOf('function')
80+
;(click as () => void)()
81+
expect(openMainWindowAt).toHaveBeenCalledWith('/workspace/ws1/chat/c1')
82+
})
83+
})

‎apps/desktop/src/main/menu.test.ts‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, expect, it, vi } from 'vitest'
2+
3+
vi.mock('electron', () => import('@/test/electron-mock'))
4+
5+
import type { MenuItemConstructorOptions } from 'electron'
6+
import type { ConfigStore } from '@/main/config'
7+
import { buildMenuTemplate, type MenuDeps } from '@/main/menu'
8+
9+
function makeDeps(): MenuDeps {
10+
return {
11+
isPackaged: true,
12+
config: {
13+
filePath: '/tmp/settings.json',
14+
getOrigin: vi.fn(() => 'https://sim.ai'),
15+
setOrigin: vi.fn(),
16+
get: vi.fn(() => undefined),
17+
set: vi.fn(),
18+
} as unknown as ConfigStore,
19+
getMainWindow: vi.fn(() => null),
20+
allowHttpLocalhost: vi.fn(() => false),
21+
openSettings: vi.fn(),
22+
newChat: vi.fn(),
23+
toggleSidebar: vi.fn(),
24+
signOut: vi.fn(),
25+
checkForUpdates: vi.fn(),
26+
}
27+
}
28+
29+
function submenu(
30+
template: MenuItemConstructorOptions[],
31+
label: string
32+
): MenuItemConstructorOptions[] {
33+
return (template.find((item) => item.label === label || item.role === label.toLowerCase())
34+
?.submenu ?? []) as MenuItemConstructorOptions[]
35+
}
36+
37+
describe('buildMenuTemplate', () => {
38+
it('uses the requested native menu structure', () => {
39+
const template = buildMenuTemplate(makeDeps())
40+
expect(template.map((item) => item.label ?? item.role)).toEqual([
41+
'Sim',
42+
'File',
43+
'editMenu',
44+
'View',
45+
'windowMenu',
46+
'help',
47+
])
48+
49+
expect(submenu(template, 'Sim').map((item) => item.label ?? item.role ?? item.type)).toEqual([
50+
'about',
51+
'Settings…',
52+
'Check for Updates…',
53+
'Sign Out',
54+
'separator',
55+
'services',
56+
'separator',
57+
'hide',
58+
'hideOthers',
59+
'unhide',
60+
'separator',
61+
'quit',
62+
])
63+
expect(submenu(template, 'File').map((item) => item.label ?? item.role ?? item.type)).toEqual([
64+
'New Chat',
65+
'separator',
66+
'close',
67+
])
68+
expect(submenu(template, 'View').map((item) => item.label ?? item.role ?? item.type)).toEqual([
69+
'Toggle Sidebar',
70+
'separator',
71+
'Reload',
72+
'separator',
73+
'Actual Size',
74+
'Zoom In',
75+
'Zoom Out',
76+
'separator',
77+
'togglefullscreen',
78+
])
79+
})
80+
81+
it('keeps Help limited to documentation and system status', () => {
82+
const help = submenu(buildMenuTemplate(makeDeps()), 'Help')
83+
expect(help.map((item) => item.label)).toEqual(['Sim Documentation', 'System Status'])
84+
})
85+
86+
it('never exposes developer tools in the application menu', () => {
87+
const deps = makeDeps()
88+
deps.isPackaged = false
89+
const view = submenu(buildMenuTemplate(deps), 'View')
90+
expect(view.some((item) => item.role === 'toggleDevTools')).toBe(false)
91+
})
92+
})

‎apps/desktop/src/main/menu.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@ export function buildMenuTemplate(deps: MenuDeps): MenuItemConstructorOptions[]
6565
},
6666
{ type: 'separator' },
6767
]
68-
if (!deps.isPackaged) {
69-
viewSubmenu.push({ role: 'toggleDevTools' }, { type: 'separator' })
70-
}
7168
viewSubmenu.push({ role: 'togglefullscreen' })
7269

7370
return [

‎apps/desktop/src/test/electron-mock.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { vi } from 'vitest'
99
*/
1010

1111
export const app = {
12+
name: 'Sim',
1213
isPackaged: false,
1314
getVersion: vi.fn(() => '1.0.0'),
1415
getName: vi.fn(() => 'Sim'),

‎apps/sim/components/settings/navigation.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('settings navigation boundaries', () => {
2626
it('preserves the order of all four settings catalogs', () => {
2727
expect(buildUnifiedSettingsNavigation().map(({ id }) => id)).toEqual([
2828
'general',
29+
'desktop',
2930
'access-control',
3031
'audit-logs',
3132
'forks',

0 commit comments

Comments
 (0)