event.test.ts 4.1 KB

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