state.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { describe, expect } from "bun:test"
  2. import { State } from "@opencode-ai/core/state"
  3. import { Deferred, Effect, Exit, Fiber, Layer, Scope } from "effect"
  4. import { testEffect } from "./lib/effect"
  5. const it = testEffect(Layer.empty)
  6. describe("State", () => {
  7. it.effect("commits a transform atomically when its updater is interrupted", () =>
  8. Effect.gen(function* () {
  9. const rebuilding = yield* Deferred.make<void>()
  10. const release = yield* Deferred.make<void>()
  11. let block = true
  12. const state = State.create({
  13. initial: () => ({ values: [] as string[] }),
  14. draft: (draft) => ({ add: (value: string) => draft.values.push(value) }),
  15. finalize: () =>
  16. block ? Deferred.succeed(rebuilding, undefined).pipe(Effect.andThen(Deferred.await(release))) : Effect.void,
  17. })
  18. const scope = yield* Scope.make()
  19. const fiber = yield* state
  20. .transform((editor) => {
  21. editor.add("registered")
  22. })
  23. .pipe(Scope.provide(scope), Effect.forkChild)
  24. yield* Deferred.await(rebuilding)
  25. const interruption = yield* Fiber.interrupt(fiber).pipe(Effect.forkChild)
  26. block = false
  27. yield* Deferred.succeed(release, undefined)
  28. yield* Fiber.join(interruption)
  29. expect(state.get().values).toEqual(["registered"])
  30. yield* Scope.close(scope, Exit.void)
  31. expect(state.get().values).toEqual([])
  32. }),
  33. )
  34. it.effect("runs effectful transforms during every reload", () =>
  35. Effect.gen(function* () {
  36. let value = "first"
  37. const state = State.create({
  38. initial: () => ({ values: [] as string[] }),
  39. draft: (draft) => ({ add: (item: string) => draft.values.push(item) }),
  40. })
  41. yield* state.transform((editor) =>
  42. Effect.sync(() => {
  43. editor.add(value)
  44. }),
  45. )
  46. expect(state.get().values).toEqual(["first"])
  47. value = "second"
  48. yield* state.reload()
  49. expect(state.get().values).toEqual(["second"])
  50. }),
  51. )
  52. it.effect("disposes a transform once and rebuilds remaining state", () =>
  53. Effect.gen(function* () {
  54. const state = State.create({
  55. initial: () => ({ values: [] as string[] }),
  56. draft: (draft) => ({ add: (item: string) => draft.values.push(item) }),
  57. })
  58. yield* state.transform((editor) => {
  59. editor.add("first")
  60. })
  61. const registration = yield* state.transform((editor) => {
  62. editor.add("second")
  63. })
  64. expect(state.get().values).toEqual(["first", "second"])
  65. yield* registration.dispose
  66. expect(state.get().values).toEqual(["first"])
  67. yield* registration.dispose
  68. expect(state.get().values).toEqual(["first"])
  69. }),
  70. )
  71. it.effect("batches automatic rebuilds", () =>
  72. Effect.gen(function* () {
  73. let finalized = 0
  74. const first = State.create({
  75. initial: () => ({ values: [] as string[] }),
  76. draft: (draft) => ({ add: (item: string) => draft.values.push(item) }),
  77. finalize: () => Effect.sync(() => finalized++),
  78. })
  79. const second = State.create({
  80. initial: () => ({ values: [] as string[] }),
  81. draft: (draft) => ({ add: (item: string) => draft.values.push(item) }),
  82. finalize: () => Effect.sync(() => finalized++),
  83. })
  84. yield* State.batch(
  85. Effect.gen(function* () {
  86. yield* first.transform((draft) => {
  87. draft.add("first")
  88. })
  89. yield* first.transform((draft) => {
  90. draft.add("second")
  91. })
  92. yield* second.transform((draft) => {
  93. draft.add("third")
  94. })
  95. expect(finalized).toBe(0)
  96. }),
  97. )
  98. expect(first.get().values).toEqual(["first", "second"])
  99. expect(second.get().values).toEqual(["third"])
  100. expect(finalized).toBe(2)
  101. }),
  102. )
  103. })