From 4ec812c60fb7f7e6ed4df98efd4fd22b9732ee7f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 16 Jul 2026 22:55:34 -0400 Subject: [PATCH] test(core): reproduce hot reload regressions --- packages/core/test/agent.test.ts | 6 +++--- packages/core/test/catalog.test.ts | 11 ++++++++--- packages/core/test/command.test.ts | 21 +++++++++++++++++++-- packages/core/test/public-import.test.ts | 13 +++++++++++++ packages/core/test/skill.test.ts | 16 ++++++++++++++++ 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 packages/core/test/public-import.test.ts diff --git a/packages/core/test/agent.test.ts b/packages/core/test/agent.test.ts index 6a0ca3df7e..4a454880b0 100644 --- a/packages/core/test/agent.test.ts +++ b/packages/core/test/agent.test.ts @@ -23,18 +23,18 @@ const it = testEffect( ) describe("AgentV2", () => { - it.effect("publishes an updated event after agent changes", () => + it.effect("publishes an updated event after agent changes are visible", () => Effect.gen(function* () { const agent = yield* AgentV2.Service const events = yield* EventV2.Service const updated = yield* events .subscribe(AgentV2.Event.Updated) - .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + .pipe(Stream.take(1), Stream.runHead, Effect.andThen(agent.get(AgentV2.ID.make("reviewer"))), Effect.forkScoped) yield* Effect.yieldNow yield* agent.transform((editor) => editor.update(AgentV2.ID.make("reviewer"), () => {})) - expect(yield* Fiber.join(updated)).toMatchObject([{ location: { directory: testLocation.directory } }]) + expect(yield* Fiber.join(updated)).toMatchObject({ id: "reviewer" }) }), ) diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index f14feaee5f..4e61c073bf 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -31,18 +31,23 @@ const catalogLayer = AppNodeBuilder.build( const it = testEffect(catalogLayer) describe("CatalogV2", () => { - it.effect("publishes an updated event after catalog changes", () => + it.effect("publishes an updated event after catalog changes are visible", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const events = yield* EventV2.Service const updated = yield* events .subscribe(Catalog.Event.Updated) - .pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped) + .pipe( + Stream.take(1), + Stream.runHead, + Effect.andThen(catalog.provider.get(ProviderV2.ID.make("test"))), + Effect.forkScoped, + ) yield* Effect.yieldNow yield* catalog.transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {})) - expect((yield* Fiber.join(updated)).length).toBe(1) + expect(yield* Fiber.join(updated)).toMatchObject({ id: "test" }) }), ) diff --git a/packages/core/test/command.test.ts b/packages/core/test/command.test.ts index 8fb2432139..c201728c31 100644 --- a/packages/core/test/command.test.ts +++ b/packages/core/test/command.test.ts @@ -1,8 +1,10 @@ import { describe, expect } from "bun:test" -import { Effect } from "effect" +import { Effect, Fiber, Stream } from "effect" import { CommandV2 } from "@opencode-ai/core/command" import { Config } from "@opencode-ai/core/config" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { EventV2 } from "@opencode-ai/core/event" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Location } from "@opencode-ai/core/location" import { MCP } from "@opencode-ai/core/mcp/index" import { ModelV2 } from "@opencode-ai/core/model" @@ -11,7 +13,7 @@ import { emptyConfigLayer, emptyMcpLayer, testLocationLayer } from "./fixture/mc import { testEffect } from "./lib/effect" const it = testEffect( - AppNodeBuilder.build(CommandV2.node, [ + AppNodeBuilder.build(LayerNode.group([CommandV2.node, EventV2.node]), [ [MCP.node, emptyMcpLayer], [Config.node, emptyConfigLayer], [Location.node, testLocationLayer], @@ -19,6 +21,21 @@ const it = testEffect( ) describe("CommandV2", () => { + it.effect("publishes an updated event after command changes are visible", () => + Effect.gen(function* () { + const command = yield* CommandV2.Service + const events = yield* EventV2.Service + const updated = yield* events + .subscribe(CommandV2.Event.Updated) + .pipe(Stream.take(1), Stream.runHead, Effect.andThen(command.get("review")), Effect.forkScoped) + yield* Effect.yieldNow + + yield* command.transform((editor) => editor.update("review", (item) => (item.template = "Review"))) + + expect(yield* Fiber.join(updated)).toMatchObject({ name: "review", template: "Review" }) + }), + ) + it.effect("applies command transforms and preserves later overrides", () => Effect.gen(function* () { const command = yield* CommandV2.Service diff --git a/packages/core/test/public-import.test.ts b/packages/core/test/public-import.test.ts new file mode 100644 index 0000000000..fc209dba4b --- /dev/null +++ b/packages/core/test/public-import.test.ts @@ -0,0 +1,13 @@ +import { expect, test } from "bun:test" + +test("imports the public skill module in a fresh process", async () => { + const child = Bun.spawn([process.execPath, "-e", 'await import("@opencode-ai/core/skill")'], { + cwd: import.meta.dir, + stdout: "pipe", + stderr: "pipe", + }) + const [exitCode, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]) + + expect(stderr).toBe("") + expect(exitCode).toBe(0) +}) diff --git a/packages/core/test/skill.test.ts b/packages/core/test/skill.test.ts index 428dfaecd0..c5e3e2ee55 100644 --- a/packages/core/test/skill.test.ts +++ b/packages/core/test/skill.test.ts @@ -54,6 +54,22 @@ function waitForSkillUpdate() { } describe("SkillV2", () => { + it.live("publishes an updated event after skill source changes are visible", () => + Effect.gen(function* () { + const skill = yield* SkillV2.Service + const events = yield* EventV2.Service + const source = { type: "directory" as const, path: AbsolutePath.make("/tmp/opencode-skills") } + const updated = yield* events + .subscribe(SkillV2.Event.Updated) + .pipe(Stream.take(1), Stream.runHead, Effect.andThen(skill.sources()), Effect.forkScoped) + yield* Effect.yieldNow + + yield* skill.transform((editor) => editor.source(source)) + + expect(yield* Fiber.join(updated)).toContainEqual(source) + }), + ) + it.live("publishes updates when skill sources change", () => Effect.gen(function* () { const skill = yield* SkillV2.Service