settings.spec.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. import { test, expect, settingsKey } from "../fixtures"
  2. import { closeDialog, openSettings } from "../actions"
  3. import {
  4. settingsColorSchemeSelector,
  5. settingsCodeFontSelector,
  6. settingsLanguageSelectSelector,
  7. settingsNotificationsAgentSelector,
  8. settingsNotificationsErrorsSelector,
  9. settingsNotificationsPermissionsSelector,
  10. settingsReleaseNotesSelector,
  11. settingsSoundsAgentSelector,
  12. settingsSoundsErrorsSelector,
  13. settingsSoundsPermissionsSelector,
  14. settingsThemeSelector,
  15. settingsUIFontSelector,
  16. settingsUpdatesStartupSelector,
  17. } from "../selectors"
  18. test("smoke settings dialog opens, switches tabs, closes", async ({ page, gotoSession }) => {
  19. await gotoSession()
  20. const dialog = await openSettings(page)
  21. await dialog.getByRole("tab", { name: "Shortcuts" }).click()
  22. await expect(dialog.getByRole("button", { name: "Reset to defaults" })).toBeVisible()
  23. await expect(dialog.getByPlaceholder("Search shortcuts")).toBeVisible()
  24. await closeDialog(page, dialog)
  25. })
  26. test("changing language updates settings labels", async ({ page, gotoSession }) => {
  27. await page.addInitScript(() => {
  28. localStorage.setItem("opencode.global.dat:language", JSON.stringify({ locale: "en" }))
  29. })
  30. await gotoSession()
  31. const dialog = await openSettings(page)
  32. const heading = dialog.getByRole("heading", { level: 2 })
  33. await expect(heading).toHaveText("General")
  34. const select = dialog.locator(settingsLanguageSelectSelector)
  35. await expect(select).toBeVisible()
  36. await select.locator('[data-slot="select-select-trigger"]').click()
  37. await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Deutsch" }).click()
  38. await expect(heading).toHaveText("Allgemein")
  39. await select.locator('[data-slot="select-select-trigger"]').click()
  40. await page.locator('[data-slot="select-select-item"]').filter({ hasText: "English" }).click()
  41. await expect(heading).toHaveText("General")
  42. })
  43. test("changing color scheme persists in localStorage", async ({ page, gotoSession }) => {
  44. await gotoSession()
  45. const dialog = await openSettings(page)
  46. const select = dialog.locator(settingsColorSchemeSelector)
  47. await expect(select).toBeVisible()
  48. await select.locator('[data-slot="select-select-trigger"]').click()
  49. await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
  50. const colorScheme = await page.evaluate(() => {
  51. return document.documentElement.getAttribute("data-color-scheme")
  52. })
  53. expect(colorScheme).toBe("dark")
  54. await select.locator('[data-slot="select-select-trigger"]').click()
  55. await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Light" }).click()
  56. const lightColorScheme = await page.evaluate(() => {
  57. return document.documentElement.getAttribute("data-color-scheme")
  58. })
  59. expect(lightColorScheme).toBe("light")
  60. })
  61. test("changing theme persists in localStorage", async ({ page, gotoSession }) => {
  62. await gotoSession()
  63. const dialog = await openSettings(page)
  64. const select = dialog.locator(settingsThemeSelector)
  65. await expect(select).toBeVisible()
  66. const currentThemeId = await page.evaluate(() => {
  67. return document.documentElement.getAttribute("data-theme")
  68. })
  69. const currentTheme = (await select.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
  70. await select.locator('[data-slot="select-select-trigger"]').click()
  71. const items = page.locator('[data-slot="select-select-item"]')
  72. const count = await items.count()
  73. expect(count).toBeGreaterThan(1)
  74. const nextTheme = (await items.locator('[data-slot="select-select-item-label"]').allTextContents())
  75. .map((x) => x.trim())
  76. .find((x) => x && x !== currentTheme)
  77. expect(nextTheme).toBeTruthy()
  78. await items.filter({ hasText: nextTheme! }).first().click()
  79. await page.keyboard.press("Escape")
  80. const storedThemeId = await page.evaluate(() => {
  81. return localStorage.getItem("opencode-theme-id")
  82. })
  83. expect(storedThemeId).not.toBeNull()
  84. expect(storedThemeId).not.toBe(currentThemeId)
  85. const dataTheme = await page.evaluate(() => {
  86. return document.documentElement.getAttribute("data-theme")
  87. })
  88. expect(dataTheme).toBe(storedThemeId)
  89. })
  90. test("legacy oc-1 theme migrates to oc-2", async ({ page, gotoSession }) => {
  91. await page.addInitScript(() => {
  92. localStorage.setItem("opencode-theme-id", "oc-1")
  93. localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
  94. localStorage.setItem("opencode-theme-css-dark", "--background-base:#000;")
  95. })
  96. await gotoSession()
  97. await expect(page.locator("html")).toHaveAttribute("data-theme", "oc-2")
  98. await expect
  99. .poll(async () => {
  100. return await page.evaluate(() => {
  101. return localStorage.getItem("opencode-theme-id")
  102. })
  103. })
  104. .toBe("oc-2")
  105. await expect
  106. .poll(async () => {
  107. return await page.evaluate(() => {
  108. return localStorage.getItem("opencode-theme-css-light")
  109. })
  110. })
  111. .toBeNull()
  112. await expect
  113. .poll(async () => {
  114. return await page.evaluate(() => {
  115. return localStorage.getItem("opencode-theme-css-dark")
  116. })
  117. })
  118. .toBeNull()
  119. })
  120. test("typing a code font with spaces persists and updates CSS variable", async ({ page, gotoSession }) => {
  121. await gotoSession()
  122. const dialog = await openSettings(page)
  123. const input = dialog.locator(settingsCodeFontSelector)
  124. await expect(input).toBeVisible()
  125. await expect(input).toHaveAttribute("placeholder", "System Mono")
  126. const initialFontFamily = await page.evaluate(() =>
  127. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  128. )
  129. const initialUIFamily = await page.evaluate(() =>
  130. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  131. )
  132. expect(initialFontFamily).toContain("ui-monospace")
  133. const next = "Test Mono"
  134. await input.click()
  135. await input.clear()
  136. await input.pressSequentially(next)
  137. await expect(input).toHaveValue(next)
  138. await expect
  139. .poll(async () => {
  140. return await page.evaluate((key) => {
  141. const raw = localStorage.getItem(key)
  142. return raw ? JSON.parse(raw) : null
  143. }, settingsKey)
  144. })
  145. .toMatchObject({
  146. appearance: {
  147. mono: next,
  148. },
  149. })
  150. const newFontFamily = await page.evaluate(() =>
  151. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  152. )
  153. const newUIFamily = await page.evaluate(() =>
  154. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  155. )
  156. expect(newFontFamily).toContain(next)
  157. expect(newFontFamily).not.toBe(initialFontFamily)
  158. expect(newUIFamily).toBe(initialUIFamily)
  159. })
  160. test("typing a UI font with spaces persists and updates CSS variable", async ({ page, gotoSession }) => {
  161. await gotoSession()
  162. const dialog = await openSettings(page)
  163. const input = dialog.locator(settingsUIFontSelector)
  164. await expect(input).toBeVisible()
  165. await expect(input).toHaveAttribute("placeholder", "System Sans")
  166. const initialFontFamily = await page.evaluate(() =>
  167. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  168. )
  169. const initialCodeFamily = await page.evaluate(() =>
  170. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  171. )
  172. expect(initialFontFamily).toContain("ui-sans-serif")
  173. const next = "Test Sans"
  174. await input.click()
  175. await input.clear()
  176. await input.pressSequentially(next)
  177. await expect(input).toHaveValue(next)
  178. await expect
  179. .poll(async () => {
  180. return await page.evaluate((key) => {
  181. const raw = localStorage.getItem(key)
  182. return raw ? JSON.parse(raw) : null
  183. }, settingsKey)
  184. })
  185. .toMatchObject({
  186. appearance: {
  187. sans: next,
  188. },
  189. })
  190. const newFontFamily = await page.evaluate(() =>
  191. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  192. )
  193. const newCodeFamily = await page.evaluate(() =>
  194. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  195. )
  196. expect(newFontFamily).toContain(next)
  197. expect(newFontFamily).not.toBe(initialFontFamily)
  198. expect(newCodeFamily).toBe(initialCodeFamily)
  199. })
  200. test("clearing the code font field restores the default placeholder and stack", async ({ page, gotoSession }) => {
  201. await gotoSession()
  202. const dialog = await openSettings(page)
  203. const input = dialog.locator(settingsCodeFontSelector)
  204. await expect(input).toBeVisible()
  205. await input.click()
  206. await input.clear()
  207. await input.pressSequentially("Reset Mono")
  208. await expect
  209. .poll(async () => {
  210. return await page.evaluate((key) => {
  211. const raw = localStorage.getItem(key)
  212. return raw ? JSON.parse(raw) : null
  213. }, settingsKey)
  214. })
  215. .toMatchObject({
  216. appearance: {
  217. mono: "Reset Mono",
  218. },
  219. })
  220. await input.clear()
  221. await input.press("Space")
  222. await expect(input).toHaveValue("")
  223. await expect(input).toHaveAttribute("placeholder", "System Mono")
  224. await expect
  225. .poll(async () => {
  226. return await page.evaluate((key) => {
  227. const raw = localStorage.getItem(key)
  228. return raw ? JSON.parse(raw) : null
  229. }, settingsKey)
  230. })
  231. .toMatchObject({
  232. appearance: {
  233. mono: "",
  234. },
  235. })
  236. const fontFamily = await page.evaluate(() =>
  237. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  238. )
  239. expect(fontFamily).toContain("ui-monospace")
  240. expect(fontFamily).not.toContain("Reset Mono")
  241. })
  242. test("clearing the UI font field restores the default placeholder and stack", async ({ page, gotoSession }) => {
  243. await gotoSession()
  244. const dialog = await openSettings(page)
  245. const input = dialog.locator(settingsUIFontSelector)
  246. await expect(input).toBeVisible()
  247. await input.click()
  248. await input.clear()
  249. await input.pressSequentially("Reset Sans")
  250. await expect
  251. .poll(async () => {
  252. return await page.evaluate((key) => {
  253. const raw = localStorage.getItem(key)
  254. return raw ? JSON.parse(raw) : null
  255. }, settingsKey)
  256. })
  257. .toMatchObject({
  258. appearance: {
  259. sans: "Reset Sans",
  260. },
  261. })
  262. await input.clear()
  263. await input.press("Space")
  264. await expect(input).toHaveValue("")
  265. await expect(input).toHaveAttribute("placeholder", "System Sans")
  266. await expect
  267. .poll(async () => {
  268. return await page.evaluate((key) => {
  269. const raw = localStorage.getItem(key)
  270. return raw ? JSON.parse(raw) : null
  271. }, settingsKey)
  272. })
  273. .toMatchObject({
  274. appearance: {
  275. sans: "",
  276. },
  277. })
  278. const fontFamily = await page.evaluate(() =>
  279. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  280. )
  281. expect(fontFamily).toContain("ui-sans-serif")
  282. expect(fontFamily).not.toContain("Reset Sans")
  283. })
  284. test("color scheme, code font, and UI font rehydrate after reload", async ({ page, gotoSession }) => {
  285. await gotoSession()
  286. const dialog = await openSettings(page)
  287. const colorSchemeSelect = dialog.locator(settingsColorSchemeSelector)
  288. await expect(colorSchemeSelect).toBeVisible()
  289. await colorSchemeSelect.locator('[data-slot="select-select-trigger"]').click()
  290. await page.locator('[data-slot="select-select-item"]').filter({ hasText: "Dark" }).click()
  291. await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
  292. const code = dialog.locator(settingsCodeFontSelector)
  293. const ui = dialog.locator(settingsUIFontSelector)
  294. await expect(code).toBeVisible()
  295. await expect(ui).toBeVisible()
  296. const initialMono = await page.evaluate(() =>
  297. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  298. )
  299. const initialSans = await page.evaluate(() =>
  300. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  301. )
  302. const initialSettings = await page.evaluate((key) => {
  303. const raw = localStorage.getItem(key)
  304. return raw ? JSON.parse(raw) : null
  305. }, settingsKey)
  306. const mono = initialSettings?.appearance?.mono === "Reload Mono" ? "Reload Mono 2" : "Reload Mono"
  307. const sans = initialSettings?.appearance?.sans === "Reload Sans" ? "Reload Sans 2" : "Reload Sans"
  308. await code.click()
  309. await code.clear()
  310. await code.pressSequentially(mono)
  311. await expect(code).toHaveValue(mono)
  312. await ui.click()
  313. await ui.clear()
  314. await ui.pressSequentially(sans)
  315. await expect(ui).toHaveValue(sans)
  316. await expect
  317. .poll(async () => {
  318. return await page.evaluate((key) => {
  319. const raw = localStorage.getItem(key)
  320. return raw ? JSON.parse(raw) : null
  321. }, settingsKey)
  322. })
  323. .toMatchObject({
  324. appearance: {
  325. mono,
  326. sans,
  327. },
  328. })
  329. const updatedSettings = await page.evaluate((key) => {
  330. const raw = localStorage.getItem(key)
  331. return raw ? JSON.parse(raw) : null
  332. }, settingsKey)
  333. const updatedMono = await page.evaluate(() =>
  334. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  335. )
  336. const updatedSans = await page.evaluate(() =>
  337. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  338. )
  339. expect(updatedMono).toContain(mono)
  340. expect(updatedMono).not.toBe(initialMono)
  341. expect(updatedSans).toContain(sans)
  342. expect(updatedSans).not.toBe(initialSans)
  343. expect(updatedSettings?.appearance?.mono).toBe(mono)
  344. expect(updatedSettings?.appearance?.sans).toBe(sans)
  345. await closeDialog(page, dialog)
  346. await page.reload()
  347. await expect(page.locator("html")).toHaveAttribute("data-color-scheme", "dark")
  348. await expect
  349. .poll(async () => {
  350. return await page.evaluate((key) => {
  351. const raw = localStorage.getItem(key)
  352. return raw ? JSON.parse(raw) : null
  353. }, settingsKey)
  354. })
  355. .toMatchObject({
  356. appearance: {
  357. mono,
  358. sans,
  359. },
  360. })
  361. const rehydratedSettings = await page.evaluate((key) => {
  362. const raw = localStorage.getItem(key)
  363. return raw ? JSON.parse(raw) : null
  364. }, settingsKey)
  365. await expect
  366. .poll(async () => {
  367. return await page.evaluate(() =>
  368. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  369. )
  370. })
  371. .toContain(mono)
  372. await expect
  373. .poll(async () => {
  374. return await page.evaluate(() =>
  375. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  376. )
  377. })
  378. .toContain(sans)
  379. const rehydratedMono = await page.evaluate(() =>
  380. getComputedStyle(document.documentElement).getPropertyValue("--font-family-mono").trim(),
  381. )
  382. const rehydratedSans = await page.evaluate(() =>
  383. getComputedStyle(document.documentElement).getPropertyValue("--font-family-sans").trim(),
  384. )
  385. expect(rehydratedMono).toContain(mono)
  386. expect(rehydratedMono).not.toBe(initialMono)
  387. expect(rehydratedSans).toContain(sans)
  388. expect(rehydratedSans).not.toBe(initialSans)
  389. expect(rehydratedSettings?.appearance?.mono).toBe(mono)
  390. expect(rehydratedSettings?.appearance?.sans).toBe(sans)
  391. })
  392. test("toggling notification agent switch updates localStorage", async ({ page, gotoSession }) => {
  393. await gotoSession()
  394. const dialog = await openSettings(page)
  395. const switchContainer = dialog.locator(settingsNotificationsAgentSelector)
  396. await expect(switchContainer).toBeVisible()
  397. const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
  398. const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  399. expect(initialState).toBe(true)
  400. await switchContainer.locator('[data-slot="switch-control"]').click()
  401. await page.waitForTimeout(100)
  402. const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  403. expect(newState).toBe(false)
  404. const stored = await page.evaluate((key) => {
  405. const raw = localStorage.getItem(key)
  406. return raw ? JSON.parse(raw) : null
  407. }, settingsKey)
  408. expect(stored?.notifications?.agent).toBe(false)
  409. })
  410. test("toggling notification permissions switch updates localStorage", async ({ page, gotoSession }) => {
  411. await gotoSession()
  412. const dialog = await openSettings(page)
  413. const switchContainer = dialog.locator(settingsNotificationsPermissionsSelector)
  414. await expect(switchContainer).toBeVisible()
  415. const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
  416. const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  417. expect(initialState).toBe(true)
  418. await switchContainer.locator('[data-slot="switch-control"]').click()
  419. await page.waitForTimeout(100)
  420. const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  421. expect(newState).toBe(false)
  422. const stored = await page.evaluate((key) => {
  423. const raw = localStorage.getItem(key)
  424. return raw ? JSON.parse(raw) : null
  425. }, settingsKey)
  426. expect(stored?.notifications?.permissions).toBe(false)
  427. })
  428. test("toggling notification errors switch updates localStorage", async ({ page, gotoSession }) => {
  429. await gotoSession()
  430. const dialog = await openSettings(page)
  431. const switchContainer = dialog.locator(settingsNotificationsErrorsSelector)
  432. await expect(switchContainer).toBeVisible()
  433. const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
  434. const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  435. expect(initialState).toBe(false)
  436. await switchContainer.locator('[data-slot="switch-control"]').click()
  437. await page.waitForTimeout(100)
  438. const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  439. expect(newState).toBe(true)
  440. const stored = await page.evaluate((key) => {
  441. const raw = localStorage.getItem(key)
  442. return raw ? JSON.parse(raw) : null
  443. }, settingsKey)
  444. expect(stored?.notifications?.errors).toBe(true)
  445. })
  446. test("changing sound agent selection persists in localStorage", async ({ page, gotoSession }) => {
  447. await gotoSession()
  448. const dialog = await openSettings(page)
  449. const select = dialog.locator(settingsSoundsAgentSelector)
  450. await expect(select).toBeVisible()
  451. await select.locator('[data-slot="select-select-trigger"]').click()
  452. const items = page.locator('[data-slot="select-select-item"]')
  453. await items.nth(2).click()
  454. const stored = await page.evaluate((key) => {
  455. const raw = localStorage.getItem(key)
  456. return raw ? JSON.parse(raw) : null
  457. }, settingsKey)
  458. expect(stored?.sounds?.agent).not.toBe("staplebops-01")
  459. })
  460. test("selecting none disables agent sound", async ({ page, gotoSession }) => {
  461. await gotoSession()
  462. const dialog = await openSettings(page)
  463. const select = dialog.locator(settingsSoundsAgentSelector)
  464. const trigger = select.locator('[data-slot="select-select-trigger"]')
  465. await expect(select).toBeVisible()
  466. await expect(trigger).toBeEnabled()
  467. await trigger.click()
  468. const items = page.locator('[data-slot="select-select-item"]')
  469. await expect(items.first()).toBeVisible()
  470. await items.first().click()
  471. const stored = await page.evaluate((key) => {
  472. const raw = localStorage.getItem(key)
  473. return raw ? JSON.parse(raw) : null
  474. }, settingsKey)
  475. expect(stored?.sounds?.agentEnabled).toBe(false)
  476. })
  477. test("changing permissions and errors sounds updates localStorage", async ({ page, gotoSession }) => {
  478. await gotoSession()
  479. const dialog = await openSettings(page)
  480. const permissionsSelect = dialog.locator(settingsSoundsPermissionsSelector)
  481. const errorsSelect = dialog.locator(settingsSoundsErrorsSelector)
  482. await expect(permissionsSelect).toBeVisible()
  483. await expect(errorsSelect).toBeVisible()
  484. const initial = await page.evaluate((key) => {
  485. const raw = localStorage.getItem(key)
  486. return raw ? JSON.parse(raw) : null
  487. }, settingsKey)
  488. const permissionsCurrent =
  489. (await permissionsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
  490. await permissionsSelect.locator('[data-slot="select-select-trigger"]').click()
  491. const permissionItems = page.locator('[data-slot="select-select-item"]')
  492. expect(await permissionItems.count()).toBeGreaterThan(1)
  493. if (permissionsCurrent) {
  494. await permissionItems.filter({ hasNotText: permissionsCurrent }).first().click()
  495. }
  496. if (!permissionsCurrent) {
  497. await permissionItems.nth(1).click()
  498. }
  499. const errorsCurrent =
  500. (await errorsSelect.locator('[data-slot="select-select-trigger-value"]').textContent())?.trim() ?? ""
  501. await errorsSelect.locator('[data-slot="select-select-trigger"]').click()
  502. const errorItems = page.locator('[data-slot="select-select-item"]')
  503. expect(await errorItems.count()).toBeGreaterThan(1)
  504. if (errorsCurrent) {
  505. await errorItems.filter({ hasNotText: errorsCurrent }).first().click()
  506. }
  507. if (!errorsCurrent) {
  508. await errorItems.nth(1).click()
  509. }
  510. await expect
  511. .poll(async () => {
  512. return await page.evaluate((key) => {
  513. const raw = localStorage.getItem(key)
  514. return raw ? JSON.parse(raw) : null
  515. }, settingsKey)
  516. })
  517. .toMatchObject({
  518. sounds: {
  519. permissions: expect.any(String),
  520. errors: expect.any(String),
  521. },
  522. })
  523. const stored = await page.evaluate((key) => {
  524. const raw = localStorage.getItem(key)
  525. return raw ? JSON.parse(raw) : null
  526. }, settingsKey)
  527. expect(stored?.sounds?.permissions).not.toBe(initial?.sounds?.permissions)
  528. expect(stored?.sounds?.errors).not.toBe(initial?.sounds?.errors)
  529. })
  530. test("toggling updates startup switch updates localStorage", async ({ page, gotoSession }) => {
  531. await gotoSession()
  532. const dialog = await openSettings(page)
  533. const switchContainer = dialog.locator(settingsUpdatesStartupSelector)
  534. await expect(switchContainer).toBeVisible()
  535. const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
  536. const isDisabled = await toggleInput.evaluate((el: HTMLInputElement) => el.disabled)
  537. if (isDisabled) {
  538. test.skip()
  539. return
  540. }
  541. const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  542. expect(initialState).toBe(true)
  543. await switchContainer.locator('[data-slot="switch-control"]').click()
  544. await page.waitForTimeout(100)
  545. const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  546. expect(newState).toBe(false)
  547. const stored = await page.evaluate((key) => {
  548. const raw = localStorage.getItem(key)
  549. return raw ? JSON.parse(raw) : null
  550. }, settingsKey)
  551. expect(stored?.updates?.startup).toBe(false)
  552. })
  553. test("toggling release notes switch updates localStorage", async ({ page, gotoSession }) => {
  554. await gotoSession()
  555. const dialog = await openSettings(page)
  556. const switchContainer = dialog.locator(settingsReleaseNotesSelector)
  557. await expect(switchContainer).toBeVisible()
  558. const toggleInput = switchContainer.locator('[data-slot="switch-input"]')
  559. const initialState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  560. expect(initialState).toBe(true)
  561. await switchContainer.locator('[data-slot="switch-control"]').click()
  562. await page.waitForTimeout(100)
  563. const newState = await toggleInput.evaluate((el: HTMLInputElement) => el.checked)
  564. expect(newState).toBe(false)
  565. const stored = await page.evaluate((key) => {
  566. const raw = localStorage.getItem(key)
  567. return raw ? JSON.parse(raw) : null
  568. }, settingsKey)
  569. expect(stored?.general?.releaseNotes).toBe(false)
  570. })