fix(characters): reset route actors on character change
This commit is contained in:
@@ -1,11 +1,23 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { getCharacterBySlug } from "@/data/constants/character";
|
||||
import { PaymentRouteProvider } from "@/providers/payment-route-provider";
|
||||
|
||||
export default function CharacterTipLayout({
|
||||
export default async function CharacterTipLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: Promise<{ characterSlug: string }>;
|
||||
}) {
|
||||
return <PaymentRouteProvider>{children}</PaymentRouteProvider>;
|
||||
const { characterSlug } = await params;
|
||||
const character = getCharacterBySlug(characterSlug);
|
||||
if (!character) notFound();
|
||||
|
||||
return (
|
||||
<PaymentRouteProvider scopeKey={character.id}>
|
||||
{children}
|
||||
</PaymentRouteProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { act, type ReactNode } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const instances = vi.hoisted(() => ({
|
||||
chat: 0,
|
||||
payment: 0,
|
||||
privateRoom: 0,
|
||||
}));
|
||||
|
||||
vi.mock("@/stores/chat/chat-context", async () => {
|
||||
const { createElement, useState } = await import("react");
|
||||
return {
|
||||
ChatProvider({
|
||||
characterId,
|
||||
children,
|
||||
}: {
|
||||
characterId: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [instanceId] = useState(() => ++instances.chat);
|
||||
return createElement(
|
||||
"div",
|
||||
{
|
||||
"data-character-id": characterId,
|
||||
"data-instance-id": instanceId,
|
||||
"data-testid": "chat-provider",
|
||||
},
|
||||
children,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/stores/payment/payment-context", async () => {
|
||||
const { createElement, useState } = await import("react");
|
||||
return {
|
||||
PaymentProvider({ children }: { children: ReactNode }) {
|
||||
const [instanceId] = useState(() => ++instances.payment);
|
||||
return createElement(
|
||||
"div",
|
||||
{
|
||||
"data-instance-id": instanceId,
|
||||
"data-testid": "payment-provider",
|
||||
},
|
||||
children,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/stores/private-room", async () => {
|
||||
const { createElement, useState } = await import("react");
|
||||
return {
|
||||
PrivateRoomProvider({
|
||||
characterId,
|
||||
children,
|
||||
}: {
|
||||
characterId: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [instanceId] = useState(() => ++instances.privateRoom);
|
||||
return createElement(
|
||||
"div",
|
||||
{
|
||||
"data-character-id": characterId,
|
||||
"data-instance-id": instanceId,
|
||||
"data-testid": "private-room-provider",
|
||||
},
|
||||
children,
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/stores/sync/chat-auth-sync", () => ({ ChatAuthSync: () => null }));
|
||||
vi.mock("@/stores/sync/chat-payment-success-sync", () => ({
|
||||
ChatPaymentSuccessSync: () => null,
|
||||
}));
|
||||
vi.mock("@/stores/sync/payment-success-sync", () => ({
|
||||
PaymentSuccessSync: () => null,
|
||||
}));
|
||||
|
||||
import { ChatRouteProviders } from "@/providers/chat-route-providers";
|
||||
import { PaymentRouteProvider } from "@/providers/payment-route-provider";
|
||||
import { PrivateRoomRouteProvider } from "@/providers/private-room-route-provider";
|
||||
|
||||
describe("character actor route providers", () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
beforeEach(() => {
|
||||
instances.chat = 0;
|
||||
instances.payment = 0;
|
||||
instances.privateRoom = 0;
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
|
||||
.IS_REACT_ACT_ENVIRONMENT = true;
|
||||
container = document.createElement("div");
|
||||
document.body.append(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it("recreates chat and chat payment actors when the character changes", () => {
|
||||
renderChat("character_elio");
|
||||
const firstChatInstance = instanceId("chat-provider");
|
||||
const firstPaymentInstance = instanceId("payment-provider");
|
||||
|
||||
renderChat("character_maya");
|
||||
|
||||
expect(instanceId("chat-provider")).not.toBe(firstChatInstance);
|
||||
expect(instanceId("payment-provider")).not.toBe(firstPaymentInstance);
|
||||
expect(testNode("chat-provider").dataset.characterId).toBe(
|
||||
"character_maya",
|
||||
);
|
||||
});
|
||||
|
||||
it("recreates the private-room actor when the character changes", () => {
|
||||
renderPrivateRoom("character_elio");
|
||||
const firstInstance = instanceId("private-room-provider");
|
||||
|
||||
renderPrivateRoom("character_maya");
|
||||
|
||||
expect(instanceId("private-room-provider")).not.toBe(firstInstance);
|
||||
expect(testNode("private-room-provider").dataset.characterId).toBe(
|
||||
"character_maya",
|
||||
);
|
||||
});
|
||||
|
||||
it("recreates the route payment actor when its character scope changes", () => {
|
||||
renderPayment("character_elio");
|
||||
const firstInstance = instanceId("payment-provider");
|
||||
|
||||
renderPayment("character_maya");
|
||||
|
||||
expect(instanceId("payment-provider")).not.toBe(firstInstance);
|
||||
});
|
||||
|
||||
function renderChat(characterId: string): void {
|
||||
act(() => {
|
||||
root.render(
|
||||
<ChatRouteProviders characterId={characterId}>
|
||||
<span>chat</span>
|
||||
</ChatRouteProviders>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function renderPrivateRoom(characterId: string): void {
|
||||
act(() => {
|
||||
root.render(
|
||||
<PrivateRoomRouteProvider characterId={characterId}>
|
||||
<span>private room</span>
|
||||
</PrivateRoomRouteProvider>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function renderPayment(scopeKey: string): void {
|
||||
act(() => {
|
||||
root.render(
|
||||
<PaymentRouteProvider scopeKey={scopeKey}>
|
||||
<span>payment</span>
|
||||
</PaymentRouteProvider>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function testNode(testId: string): HTMLElement {
|
||||
const node = container.querySelector<HTMLElement>(
|
||||
`[data-testid="${testId}"]`,
|
||||
);
|
||||
if (!node) throw new Error(`Missing ${testId}`);
|
||||
return node;
|
||||
}
|
||||
|
||||
function instanceId(testId: string): string | undefined {
|
||||
return testNode(testId).dataset.instanceId;
|
||||
}
|
||||
});
|
||||
@@ -19,9 +19,9 @@ export function ChatRouteProviders({
|
||||
characterId = DEFAULT_CHARACTER_ID,
|
||||
}: ChatRouteProvidersProps) {
|
||||
return (
|
||||
<PaymentProvider>
|
||||
<PaymentProvider key={characterId}>
|
||||
<PaymentSuccessSync />
|
||||
<ChatProvider characterId={characterId}>
|
||||
<ChatProvider key={characterId} characterId={characterId}>
|
||||
<ChatAuthSync />
|
||||
<ChatPaymentSuccessSync />
|
||||
{children}
|
||||
|
||||
@@ -5,9 +5,17 @@ import type { ReactNode } from "react";
|
||||
import { PaymentProvider } from "@/stores/payment/payment-context";
|
||||
import { PaymentSuccessSync } from "@/stores/sync/payment-success-sync";
|
||||
|
||||
export function PaymentRouteProvider({ children }: { children: ReactNode }) {
|
||||
export interface PaymentRouteProviderProps {
|
||||
children: ReactNode;
|
||||
scopeKey?: string;
|
||||
}
|
||||
|
||||
export function PaymentRouteProvider({
|
||||
children,
|
||||
scopeKey,
|
||||
}: PaymentRouteProviderProps) {
|
||||
return (
|
||||
<PaymentProvider>
|
||||
<PaymentProvider key={scopeKey}>
|
||||
<PaymentSuccessSync />
|
||||
{children}
|
||||
</PaymentProvider>
|
||||
|
||||
@@ -15,7 +15,7 @@ export function PrivateRoomRouteProvider({
|
||||
characterId = DEFAULT_CHARACTER_ID,
|
||||
}: PrivateRoomRouteProviderProps) {
|
||||
return (
|
||||
<PrivateRoomProvider characterId={characterId}>
|
||||
<PrivateRoomProvider key={characterId} characterId={characterId}>
|
||||
{children}
|
||||
</PrivateRoomProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user