event.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { describe, expect } from "bun:test"
  2. import { Effect, Fiber, Layer, Schema, Stream } from "effect"
  3. import { EventV2 } from "@opencode-ai/core/event"
  4. import { Location } from "@opencode-ai/core/location"
  5. import { AbsolutePath } from "@opencode-ai/core/schema"
  6. import { location } from "./fixture/location"
  7. import { testEffect } from "./lib/effect"
  8. const locationLayer = Layer.succeed(
  9. Location.Service,
  10. Location.Service.of(location({ directory: AbsolutePath.make("project"), workspaceID: "workspace" })),
  11. )
  12. const it = testEffect(EventV2.layer.pipe(Layer.provideMerge(locationLayer)))
  13. const itWithoutLocation = testEffect(EventV2.layer)
  14. const Message = EventV2.define({
  15. type: "test.message",
  16. schema: {
  17. text: Schema.String,
  18. },
  19. })
  20. const GlobalMessage = EventV2.define({
  21. type: "test.global",
  22. schema: {
  23. text: Schema.String,
  24. },
  25. })
  26. const VersionedMessage = EventV2.define({
  27. type: "test.versioned",
  28. version: 2,
  29. schema: {
  30. text: Schema.String,
  31. },
  32. })
  33. describe("EventV2", () => {
  34. it.effect("publishes events with the current location", () =>
  35. Effect.gen(function* () {
  36. const events = yield* EventV2.Service
  37. const fiber = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
  38. yield* Effect.yieldNow
  39. const event = yield* events.publish(Message, { text: "hello" })
  40. const received = Array.from(yield* Fiber.join(fiber))
  41. expect(received).toEqual([event])
  42. expect(event.type).toBe("test.message")
  43. expect(event).not.toHaveProperty("version")
  44. expect(event.data).toEqual({ text: "hello" })
  45. expect(event.location).toEqual({ directory: AbsolutePath.make("project"), workspaceID: "workspace" })
  46. }),
  47. )
  48. itWithoutLocation.effect("omits location when no location is available", () =>
  49. Effect.gen(function* () {
  50. const events = yield* EventV2.Service
  51. const event = yield* events.publish(GlobalMessage, { text: "hello" })
  52. expect(event).not.toHaveProperty("location")
  53. expect(event.type).toBe("test.global")
  54. }),
  55. )
  56. it.effect("publishes definition version", () =>
  57. Effect.gen(function* () {
  58. const events = yield* EventV2.Service
  59. const event = yield* events.publish(VersionedMessage, { text: "hello" })
  60. expect(event.type).toBe("test.versioned")
  61. expect(event.version).toBe(2)
  62. }),
  63. )
  64. it.effect("stores definitions in the exported registry", () =>
  65. Effect.sync(() => {
  66. expect(EventV2.registry.get(Message.type)).toBe(Message)
  67. }),
  68. )
  69. it.effect("publishes to typed and wildcard subscriptions", () =>
  70. Effect.gen(function* () {
  71. const events = yield* EventV2.Service
  72. const typed = yield* events.subscribe(Message).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
  73. const wildcard = yield* events.all().pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
  74. yield* Effect.yieldNow
  75. const event = yield* events.publish(Message, { text: "hello" })
  76. expect(Array.from(yield* Fiber.join(typed))).toEqual([event])
  77. expect(Array.from(yield* Fiber.join(wildcard))).toEqual([event])
  78. }),
  79. )
  80. it.effect("runs sync handlers inline", () =>
  81. Effect.gen(function* () {
  82. const events = yield* EventV2.Service
  83. const received = new Array<EventV2.Payload>()
  84. const unsubscribe = yield* events.sync((event) =>
  85. Effect.sync(() => {
  86. received.push(event)
  87. }),
  88. )
  89. const event = yield* events.publish(Message, { text: "hello" })
  90. yield* unsubscribe
  91. yield* events.publish(Message, { text: "after unsubscribe" })
  92. expect(received).toEqual([event])
  93. }),
  94. )
  95. it.effect("runs sync handlers before publishing to streams", () =>
  96. Effect.gen(function* () {
  97. const events = yield* EventV2.Service
  98. const received = new Array<string>()
  99. const fiber = yield* events.all().pipe(
  100. Stream.take(1),
  101. Stream.runForEach(() => Effect.sync(() => received.push("stream"))),
  102. Effect.forkScoped,
  103. )
  104. yield* events.sync((event) =>
  105. Effect.sync(() => {
  106. received.push(event.type)
  107. }),
  108. )
  109. yield* Effect.yieldNow
  110. yield* events.publish(Message, { text: "hello" })
  111. yield* Fiber.join(fiber)
  112. expect(received).toEqual([Message.type, "stream"])
  113. }),
  114. )
  115. })