refactor(chat): consolidate unlock state
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createActor, waitFor } from "xstate";
|
||||
import { createActor, fromPromise, waitFor } from "xstate";
|
||||
|
||||
import { ChatSendResponse } from "@/data/dto/chat";
|
||||
import type {
|
||||
UnlockMessageOutput,
|
||||
UnlockMessageRequest,
|
||||
} from "@/stores/chat/helper/unlock";
|
||||
|
||||
import {
|
||||
createTestChatMachine,
|
||||
@@ -189,7 +193,6 @@ describe("chat unlock flow", () => {
|
||||
);
|
||||
|
||||
expect(actor.getSnapshot().context).toMatchObject({
|
||||
isUnlockingHistory: false,
|
||||
unlockHistoryPromptVisible: true,
|
||||
unlockHistoryError: "Failed to unlock messages. Please try again.",
|
||||
});
|
||||
@@ -237,7 +240,7 @@ describe("chat unlock flow", () => {
|
||||
);
|
||||
|
||||
expect(actor.getSnapshot().context.unlockPaywallRequest).toBeNull();
|
||||
expect(actor.getSnapshot().context.isUnlockingMessage).toBe(false);
|
||||
expect(actor.getSnapshot().context.unlockingMessage).toBeNull();
|
||||
expect(actor.getSnapshot().context.messages).toMatchObject([
|
||||
{
|
||||
id: "msg-private-locked",
|
||||
@@ -485,7 +488,7 @@ describe("chat unlock flow", () => {
|
||||
},
|
||||
]);
|
||||
expect(actor.getSnapshot().context.messages[0]?.audioUrl).toBeUndefined();
|
||||
expect(actor.getSnapshot().context.isUnlockingMessage).toBe(false);
|
||||
expect(actor.getSnapshot().context.unlockingMessage).toBeNull();
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
@@ -536,8 +539,7 @@ describe("chat unlock flow", () => {
|
||||
);
|
||||
|
||||
expect(actor.getSnapshot().context).toMatchObject({
|
||||
isUnlockingMessage: false,
|
||||
unlockingMessageId: null,
|
||||
unlockingMessage: null,
|
||||
unlockMessageError: "not_found",
|
||||
unlockPaywallRequest: null,
|
||||
});
|
||||
@@ -552,6 +554,92 @@ describe("chat unlock flow", () => {
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("owns the active request in one object and ignores history refreshes while unlocking", async () => {
|
||||
let capturedRequest: UnlockMessageRequest | null = null;
|
||||
let resolveUnlock!: (output: UnlockMessageOutput) => void;
|
||||
const unlockDeferred = new Promise<UnlockMessageOutput>((resolve) => {
|
||||
resolveUnlock = resolve;
|
||||
});
|
||||
const originalMessage = {
|
||||
id: "promotion:lock-1",
|
||||
content: "",
|
||||
isFromAI: true,
|
||||
date: "2026-07-16",
|
||||
locked: true,
|
||||
lockReason: "image_paywall" as const,
|
||||
imagePaywalled: true,
|
||||
};
|
||||
const machine = createTestChatMachine({
|
||||
historyMessages: [originalMessage],
|
||||
}).provide({
|
||||
actors: {
|
||||
unlockMessage: fromPromise<
|
||||
UnlockMessageOutput,
|
||||
UnlockMessageRequest
|
||||
>(async ({ input }) => {
|
||||
capturedRequest = input;
|
||||
return unlockDeferred;
|
||||
}),
|
||||
},
|
||||
});
|
||||
const actor = createActor(machine).start();
|
||||
|
||||
actor.send({ type: "ChatUserLogin", token: "token" });
|
||||
await waitFor(actor, (snapshot) =>
|
||||
snapshot.matches({ userSession: "ready" }),
|
||||
);
|
||||
actor.send({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId: "promotion:lock-1",
|
||||
remoteMessageId: "remote-1",
|
||||
kind: "image",
|
||||
lockType: "image_paywall",
|
||||
clientLockId: "lock-1",
|
||||
});
|
||||
await waitFor(actor, (snapshot) =>
|
||||
snapshot.matches({ userSession: "unlockingMessage" }),
|
||||
);
|
||||
|
||||
expect(actor.getSnapshot().context.unlockingMessage).toEqual({
|
||||
displayMessageId: "promotion:lock-1",
|
||||
messageId: "remote-1",
|
||||
kind: "image",
|
||||
lockType: "image_paywall",
|
||||
clientLockId: "lock-1",
|
||||
});
|
||||
actor.send({
|
||||
type: "ChatNetworkHistoryLoaded",
|
||||
output: {
|
||||
messages: [],
|
||||
localOverwritten: true,
|
||||
localCount: 0,
|
||||
networkCount: 0,
|
||||
total: 0,
|
||||
limit: 50,
|
||||
},
|
||||
});
|
||||
expect(actor.getSnapshot().context.messages).toEqual([originalMessage]);
|
||||
|
||||
if (!capturedRequest) throw new Error("unlock actor did not start");
|
||||
resolveUnlock({
|
||||
displayMessageId: "promotion:lock-1",
|
||||
request: capturedRequest,
|
||||
response: makeUnlockPrivateResponse({
|
||||
messageId: "remote-1",
|
||||
image: {
|
||||
type: "promotion",
|
||||
url: "https://example.com/unlocked.jpg",
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitFor(actor, (snapshot) =>
|
||||
snapshot.matches({ userSession: "ready" }),
|
||||
);
|
||||
expect(actor.getSnapshot().context.unlockingMessage).toBeNull();
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("clears the insufficient credits prompt after payment succeeds", async () => {
|
||||
const actor = createActor(createTestChatMachine()).start();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user