config.test.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { NodeFileSystem } from "@effect/platform-node"
  2. import { Global } from "@opencode-ai/util/global"
  3. import { Effect } from "effect"
  4. import { expect, test } from "bun:test"
  5. import path from "path"
  6. import { Config } from "../src/config"
  7. function run<A, E>(directory: string, effect: Effect.Effect<A, E, Config.Service>) {
  8. return Effect.runPromise(
  9. effect.pipe(
  10. Effect.provide(Config.layer),
  11. Effect.provide(Global.layerWith({ config: directory, state: directory })),
  12. Effect.provide(NodeFileSystem.layer),
  13. ),
  14. )
  15. }
  16. test("migrates tui and kv config into cli.json", async () => {
  17. const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim())
  18. await Bun.write(
  19. path.join(directory, "tui.json"),
  20. JSON.stringify({
  21. theme: "legacy",
  22. keybinds: { leader: "ctrl+o" },
  23. plugin: [["example", { mode: "safe" }]],
  24. plugin_enabled: { disabled: false },
  25. leader_timeout: 500,
  26. scroll_speed: 2,
  27. scroll_acceleration: { enabled: true },
  28. diff_style: "stacked",
  29. mouse: false,
  30. }),
  31. )
  32. await Bun.write(
  33. path.join(directory, "kv.json"),
  34. JSON.stringify({
  35. theme_mode_lock: "light",
  36. attention_sound_pack: "custom.pack",
  37. diff_wrap_mode: "none",
  38. diff_viewer_show_file_tree: false,
  39. diff_viewer_single_patch: true,
  40. diff_viewer_view: "split",
  41. terminal_title_enabled: false,
  42. file_context_enabled: false,
  43. paste_summary_enabled: false,
  44. sidebar: "hide",
  45. scrollbar_visible: true,
  46. thinking_mode: "show",
  47. exploration_grouping: false,
  48. dismissed_getting_started: true,
  49. animations_enabled: false,
  50. skipped_version: "9.9.9",
  51. which_key_layout: "overlay",
  52. }),
  53. )
  54. try {
  55. const config = await run(
  56. directory,
  57. Effect.gen(function* () {
  58. const service = yield* Config.Service
  59. return yield* service.get()
  60. }),
  61. )
  62. expect(config).toMatchObject({
  63. theme: { name: "legacy", mode: "light" },
  64. keybinds: { leader: "ctrl+o" },
  65. plugins: [{ package: "example", options: { mode: "safe" } }, "-disabled"],
  66. leader: { timeout: 500 },
  67. scroll: { speed: 2, acceleration: true },
  68. attention: { sound_pack: "custom.pack" },
  69. diffs: { wrap: "none", tree: false, single: true, view: "split" },
  70. terminal: { title: false },
  71. prompt: { editor: false, paste: "full" },
  72. session: { sidebar: "hide", scrollbar: true, thinking: "show", grouping: "none" },
  73. animations: false,
  74. mouse: false,
  75. })
  76. expect(config).not.toHaveProperty("skipped_version")
  77. expect(config).not.toHaveProperty("which_key")
  78. expect(config).not.toHaveProperty("hints")
  79. expect((await Bun.file(path.join(directory, "cli.json")).json()).keybinds).toEqual({ leader: "ctrl+o" })
  80. expect(await Bun.file(path.join(directory, "cli.json")).exists()).toBe(true)
  81. expect(await Bun.file(path.join(directory, "tui.json")).exists()).toBe(true)
  82. expect(await Bun.file(path.join(directory, "kv.json")).exists()).toBe(true)
  83. } finally {
  84. await Bun.$`rm -rf ${directory}`
  85. }
  86. })
  87. test("migrates before the first update and does not remigrate afterward", async () => {
  88. const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim())
  89. await Bun.write(path.join(directory, "tui.json"), JSON.stringify({ theme: "legacy" }))
  90. try {
  91. const config = await run(
  92. directory,
  93. Effect.gen(function* () {
  94. const service = yield* Config.Service
  95. yield* service.update((draft) => {
  96. draft.animations = false
  97. draft.mouse = false
  98. })
  99. yield* Effect.promise(() => Bun.write(path.join(directory, "tui.json"), JSON.stringify({ theme: "changed" })))
  100. return yield* service.get()
  101. }),
  102. )
  103. expect(config).toEqual({ theme: { name: "legacy" }, animations: false, mouse: false })
  104. expect(await Bun.file(path.join(directory, "cli.json")).json()).toEqual({
  105. theme: { name: "legacy" },
  106. animations: false,
  107. mouse: false,
  108. })
  109. } finally {
  110. await Bun.$`rm -rf ${directory}`
  111. }
  112. })
  113. test("preserves legacy cursor settings", async () => {
  114. const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim())
  115. await Bun.write(path.join(directory, "tui.json"), JSON.stringify({ cursor: { style: "underline", blinking: false } }))
  116. try {
  117. const config = await run(
  118. directory,
  119. Effect.gen(function* () {
  120. const service = yield* Config.Service
  121. return yield* service.get()
  122. }),
  123. )
  124. expect(config.cursor).toEqual({ style: "underline", blinking: false })
  125. expect((await Bun.file(path.join(directory, "cli.json")).json()).cursor).toEqual({
  126. style: "underline",
  127. blinking: false,
  128. })
  129. } finally {
  130. await Bun.$`rm -rf ${directory}`
  131. }
  132. })
  133. test("updates a config draft while preserving JSONC comments", async () => {
  134. const directory = await Bun.$`mktemp -d`.text().then((value) => value.trim())
  135. await Bun.write(path.join(directory, "cli.json"), '{\n // Keep this comment\n "animations": true\n}\n')
  136. try {
  137. const config = await run(
  138. directory,
  139. Effect.gen(function* () {
  140. const service = yield* Config.Service
  141. return yield* service.update((draft) => {
  142. draft.prompt = { paste: "compact" }
  143. draft.mini = { thinking: "hide", shell_output: "hide", turn_summary: "hide", splash: "hide", mono: true }
  144. })
  145. }),
  146. )
  147. expect(config).toEqual({
  148. animations: true,
  149. prompt: { paste: "compact" },
  150. mini: { thinking: "hide", shell_output: "hide", turn_summary: "hide", splash: "hide", mono: true },
  151. })
  152. expect(await Bun.file(path.join(directory, "cli.json")).text()).toContain("// Keep this comment")
  153. } finally {
  154. await Bun.$`rm -rf ${directory}`
  155. }
  156. })