From a3cd5510f94baec2f123c08bfb621c4f5c2498a4 Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:18:57 +0000 Subject: [PATCH] fix(client-runtime): refresh edited pull request comments --- .../src/state/pullRequests.test.ts | 142 ++++++++++++++++++ .../client-runtime/src/state/pullRequests.ts | 15 +- 2 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 packages/client-runtime/src/state/pullRequests.test.ts diff --git a/packages/client-runtime/src/state/pullRequests.test.ts b/packages/client-runtime/src/state/pullRequests.test.ts new file mode 100644 index 000000000000..e5dde9d8427c --- /dev/null +++ b/packages/client-runtime/src/state/pullRequests.test.ts @@ -0,0 +1,142 @@ +import { EnvironmentId, ProjectId, WS_METHODS } from "@t3tools/contracts"; +import { expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Stream from "effect/Stream"; +import * as SubscriptionRef from "effect/SubscriptionRef"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; + +import { + AVAILABLE_CONNECTION_STATE, + PrimaryConnectionTarget, + type PreparedConnection, + type SupervisorConnectionState, +} from "../connection/model.ts"; +import * as EnvironmentRegistry from "../connection/registry.ts"; +import * as EnvironmentSupervisor from "../connection/supervisor.ts"; +import type { WsRpcProtocolClient } from "../rpc/protocol.ts"; +import type { RpcSession } from "../rpc/session.ts"; +import { createPullRequestEnvironmentAtoms } from "./pullRequests.ts"; +import { PullRequestDiffLoader } from "./pullRequestDiffHttp.ts"; +import { executeAtomQuery } from "./runtime.ts"; + +const TARGET = new PrimaryConnectionTarget({ + environmentId: EnvironmentId.make("environment-1"), + label: "Test environment", + httpBaseUrl: "https://environment.example.test", + wsBaseUrl: "wss://environment.example.test", +}); + +function session(client: WsRpcProtocolClient): RpcSession { + return { + client, + initialConfig: Effect.never, + ready: Effect.void, + probe: Effect.void, + closed: Effect.never, + }; +} + +it.effect("refreshes pull request activity after a comment is updated", () => + Effect.scoped( + Effect.gen(function* () { + let commentBody = "old comment"; + const client = { + [WS_METHODS.pullRequestsActivity]: () => + Effect.succeed({ + author: null, + reviewers: [], + comments: [ + { + id: "comment-1", + kind: "issue-comment", + author: null, + body: commentBody, + createdAt: "2026-08-24T00:00:00Z", + url: null, + path: null, + reviewState: null, + reactions: [], + }, + ], + commentCount: 1, + commentsTruncated: false, + reviewThreads: [], + commits: [], + reactions: [], + }), + [WS_METHODS.pullRequestsUpdateComment]: (input: { readonly body: string }) => + Effect.sync(() => { + commentBody = input.body; + }), + } as unknown as WsRpcProtocolClient; + const connectionState: SupervisorConnectionState = { + ...AVAILABLE_CONNECTION_STATE, + desired: true, + network: "online", + phase: "connected", + attempt: 1, + generation: 1, + }; + const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ + target: TARGET, + state: yield* SubscriptionRef.make(connectionState), + session: yield* SubscriptionRef.make(Option.some(session(client))), + prepared: yield* SubscriptionRef.make(Option.none()), + connect: Effect.void, + disconnect: Effect.void, + retryNow: Effect.void, + } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); + const environmentRegistry = EnvironmentRegistry.EnvironmentRegistry.of({ + run: (_environmentId, effect) => + Effect.provideService(effect, EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + runStream: (_environmentId, stream) => + Stream.provideService(stream, EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + followStream: (_environmentId, stream) => + Stream.provideService(stream, EnvironmentSupervisor.EnvironmentSupervisor, supervisor), + } as EnvironmentRegistry.EnvironmentRegistry["Service"]); + const runtime = Atom.runtime( + Layer.merge( + Layer.succeed(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), + Layer.succeed( + PullRequestDiffLoader, + PullRequestDiffLoader.of({ load: () => Effect.die("unused") }), + ), + ), + ); + const atoms = createPullRequestEnvironmentAtoms(runtime); + const registry = yield* Effect.acquireRelease(Effect.sync(AtomRegistry.make), (registry) => + Effect.sync(() => registry.dispose()), + ); + const reference = { + projectId: ProjectId.make("project-1"), + repository: "acme/web", + number: 1, + } as const; + const activity = atoms.activity({ environmentId: TARGET.environmentId, input: reference }); + const unmount = registry.mount(activity); + yield* Effect.addFinalizer(() => Effect.sync(unmount)); + + const initial = yield* Effect.promise(() => executeAtomQuery(registry, activity)); + expect(AsyncResult.isSuccess(initial)).toBe(true); + if (!AsyncResult.isSuccess(initial)) { + return yield* Effect.die("activity did not load"); + } + expect(initial.value.comments[0]?.body).toBe("old comment"); + + const update = yield* Effect.promise(() => + atoms.updateComment.run(registry, { + environmentId: TARGET.environmentId, + input: { ...reference, commentId: "comment-1", kind: "issue-comment", body: "updated" }, + }), + ); + + expect(AsyncResult.isSuccess(update)).toBe(true); + expect( + (yield* AtomRegistry.getResult(registry, activity, { suspendOnWaiting: true })).comments[0] + ?.body, + ).toBe("updated"); + }), + ), +); diff --git a/packages/client-runtime/src/state/pullRequests.ts b/packages/client-runtime/src/state/pullRequests.ts index d4830fa197d4..6ec1e6b08d36 100644 --- a/packages/client-runtime/src/state/pullRequests.ts +++ b/packages/client-runtime/src/state/pullRequests.ts @@ -34,6 +34,11 @@ export function createPullRequestEnvironmentAtoms( mode: "serial", key: ({ environmentId }: { readonly environmentId: string }) => environmentId, } as const; + const activity = createEnvironmentRpcQueryAtomFamily(runtime, { + label: "environment-data:pull-requests:activity", + tag: WS_METHODS.pullRequestsActivity, + staleTimeMs: 15_000, + }); return { list: createEnvironmentRpcQueryAtomFamily(runtime, { label: "environment-data:pull-requests:list", @@ -56,11 +61,7 @@ export function createPullRequestEnvironmentAtoms( tag: WS_METHODS.pullRequestsDetail, staleTimeMs: 15_000, }), - activity: createEnvironmentRpcQueryAtomFamily(runtime, { - label: "environment-data:pull-requests:activity", - tag: WS_METHODS.pullRequestsActivity, - staleTimeMs: 15_000, - }), + activity, threadComments: createEnvironmentRpcCommand(runtime, { label: "environment-data:pull-requests:thread-comments", tag: WS_METHODS.pullRequestsThreadComments, @@ -129,6 +130,10 @@ export function createPullRequestEnvironmentAtoms( tag: WS_METHODS.pullRequestsUpdateComment, scheduler: commandScheduler, concurrency: serialPerEnvironment, + onSuccess: ({ environmentId, input: { projectId, repository, number } }, registry) => + Effect.sync(() => + registry.refresh(activity({ environmentId, input: { projectId, repository, number } })), + ), }), submitReview: createEnvironmentRpcCommand(runtime, { label: "environment-data:pull-requests:submit-review",