plugin.test.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { describe, expect } from "bun:test"
  2. import { Context, Deferred, Effect, Exit, Fiber, Layer, Scope } from "effect"
  3. import { EventV2 } from "@opencode-ai/core/event"
  4. import { PluginV2 } from "@opencode-ai/core/plugin"
  5. import { State } from "@opencode-ai/core/state"
  6. import { it } from "./lib/effect"
  7. const events = Layer.mock(EventV2.Service)({
  8. publish: (definition, data) =>
  9. Effect.succeed({
  10. id: EventV2.ID.make("evt_plugin_test"),
  11. type: definition.type,
  12. data,
  13. }),
  14. })
  15. const plugins = PluginV2.layer.pipe(Layer.provide(events))
  16. function state() {
  17. return State.create({
  18. initial: () => ({ values: [] as string[] }),
  19. draft: (draft) => ({
  20. add: (value: string) => draft.values.push(value),
  21. }),
  22. })
  23. }
  24. describe("PluginV2", () => {
  25. it.effect("closes plugin-owned scopes when the registry layer finalizes", () =>
  26. Effect.gen(function* () {
  27. const values = state()
  28. const layerScope = yield* Scope.fork(yield* Scope.Scope)
  29. const plugin = Context.get(yield* Layer.buildWithScope(Layer.fresh(plugins), layerScope), PluginV2.Service)
  30. yield* plugin.add({
  31. id: PluginV2.ID.make("scoped"),
  32. effect: Effect.gen(function* () {
  33. yield* values.transform((editor) => {
  34. editor.add("scoped")
  35. })
  36. }),
  37. })
  38. expect(values.get().values).toEqual(["scoped"])
  39. yield* Scope.close(layerScope, Exit.void)
  40. expect(values.get().values).toEqual([])
  41. }),
  42. )
  43. it.effect("serializes same-ID additions and leaves one removable attachment", () =>
  44. Effect.gen(function* () {
  45. const values = state()
  46. const layerScope = yield* Scope.fork(yield* Scope.Scope)
  47. const plugin = Context.get(yield* Layer.buildWithScope(Layer.fresh(plugins), layerScope), PluginV2.Service)
  48. const id = PluginV2.ID.make("shared")
  49. const firstStarted = yield* Deferred.make<void>()
  50. const releaseFirst = yield* Deferred.make<void>()
  51. const first = yield* plugin
  52. .add({
  53. id,
  54. effect: Effect.gen(function* () {
  55. yield* values.transform((editor) => {
  56. editor.add("first")
  57. })
  58. yield* Deferred.succeed(firstStarted, undefined)
  59. yield* Deferred.await(releaseFirst)
  60. }),
  61. })
  62. .pipe(Effect.forkChild)
  63. yield* Deferred.await(firstStarted)
  64. const second = yield* plugin
  65. .add({
  66. id,
  67. effect: Effect.gen(function* () {
  68. yield* values.transform((editor) => {
  69. editor.add("second")
  70. })
  71. }),
  72. })
  73. .pipe(Effect.forkChild({ startImmediately: true }))
  74. expect(values.get().values).toEqual(["first"])
  75. yield* Deferred.succeed(releaseFirst, undefined)
  76. yield* Fiber.join(first)
  77. yield* Fiber.join(second)
  78. expect(values.get().values).toEqual(["second"])
  79. yield* plugin.remove(id)
  80. expect(values.get().values).toEqual([])
  81. }),
  82. )
  83. })