test(stores): add state machine coverage
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createActor, fromPromise, waitFor } from "xstate";
|
||||
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import type { AuthProvider } from "@/lib/auth/auth_platform";
|
||||
import { authMachine } from "@/stores/auth/auth-machine";
|
||||
|
||||
function createTestAuthMachine(
|
||||
overrides: Partial<{
|
||||
checkAuthStatus: LoginStatus;
|
||||
guestLogin: LoginStatus;
|
||||
emailLogin: LoginStatus;
|
||||
logoutSpy: () => void;
|
||||
}> = {},
|
||||
) {
|
||||
const logoutSpy = overrides.logoutSpy ?? vi.fn<() => void>();
|
||||
|
||||
return authMachine.provide({
|
||||
actors: {
|
||||
checkAuthStatus: fromPromise<LoginStatus, void>(
|
||||
async () => overrides.checkAuthStatus ?? "notLoggedIn",
|
||||
),
|
||||
guestLogin: fromPromise<LoginStatus, void>(
|
||||
async () => overrides.guestLogin ?? "guest",
|
||||
),
|
||||
emailLogin: fromPromise<
|
||||
LoginStatus,
|
||||
{ email: string; password: string }
|
||||
>(async () => overrides.emailLogin ?? "email"),
|
||||
emailRegisterThenLogin: fromPromise<
|
||||
LoginStatus,
|
||||
{
|
||||
email: string;
|
||||
password: string;
|
||||
username: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
>(async () => "email"),
|
||||
oauthSignIn: fromPromise<void, AuthProvider>(async () => {}),
|
||||
syncGoogleBackend: fromPromise<LoginStatus, { idToken: string }>(
|
||||
async () => "google",
|
||||
),
|
||||
syncFacebookBackend: fromPromise<
|
||||
LoginStatus,
|
||||
{ accessToken: string }
|
||||
>(async () => "facebook"),
|
||||
logout: fromPromise<void>(async () => {
|
||||
logoutSpy();
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("authMachine", () => {
|
||||
it("updates panel mode, auth mode, and unsupported Apple login errors", () => {
|
||||
const actor = createActor(createTestAuthMachine()).start();
|
||||
|
||||
actor.send({ type: "AuthPanelModeChanged", mode: "email" });
|
||||
actor.send({ type: "AuthModeChanged", mode: "register" });
|
||||
|
||||
expect(actor.getSnapshot().context.authPanelMode).toBe("email");
|
||||
expect(actor.getSnapshot().context.authMode).toBe("register");
|
||||
|
||||
actor.send({ type: "AuthAppleLoginSubmitted" });
|
||||
expect(actor.getSnapshot().context.errorMessage).toBe(
|
||||
"Apple login not implemented",
|
||||
);
|
||||
|
||||
actor.send({ type: "AuthFormCleared" });
|
||||
expect(actor.getSnapshot().context.errorMessage).toBeNull();
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("initializes from storage without triggering a pending redirect", async () => {
|
||||
const actor = createActor(
|
||||
createTestAuthMachine({ checkAuthStatus: "facebook" }),
|
||||
).start();
|
||||
|
||||
actor.send({ type: "AuthInit" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("idle"));
|
||||
|
||||
const context = actor.getSnapshot().context;
|
||||
expect(context.loginStatus).toBe("facebook");
|
||||
expect(context.hasInitialized).toBe(true);
|
||||
expect(context.pendingRedirect).toBe(false);
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("guest login marks initialized but does not set pending redirect", async () => {
|
||||
const actor = createActor(createTestAuthMachine()).start();
|
||||
|
||||
actor.send({ type: "AuthGuestLoginSubmitted" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("idle"));
|
||||
|
||||
const context = actor.getSnapshot().context;
|
||||
expect(context.loginStatus).toBe("guest");
|
||||
expect(context.hasInitialized).toBe(true);
|
||||
expect(context.pendingRedirect).toBe(false);
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("email login sets pending redirect until it is cleared", async () => {
|
||||
const actor = createActor(createTestAuthMachine()).start();
|
||||
|
||||
actor.send({
|
||||
type: "AuthEmailLoginSubmitted",
|
||||
email: "user@example.com",
|
||||
password: "12345678",
|
||||
});
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("idle"));
|
||||
|
||||
expect(actor.getSnapshot().context.loginStatus).toBe("email");
|
||||
expect(actor.getSnapshot().context.pendingRedirect).toBe(true);
|
||||
|
||||
actor.send({ type: "AuthClearPendingRedirect" });
|
||||
expect(actor.getSnapshot().context.pendingRedirect).toBe(false);
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("logout resets auth state and keeps initialization completed", async () => {
|
||||
const logoutSpy = vi.fn<() => void>();
|
||||
const actor = createActor(createTestAuthMachine({ logoutSpy })).start();
|
||||
|
||||
actor.send({
|
||||
type: "AuthEmailLoginSubmitted",
|
||||
email: "user@example.com",
|
||||
password: "12345678",
|
||||
});
|
||||
await waitFor(actor, (snapshot) => snapshot.context.loginStatus === "email");
|
||||
|
||||
actor.send({ type: "AuthLogoutSubmitted" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("idle"));
|
||||
|
||||
const context = actor.getSnapshot().context;
|
||||
expect(logoutSpy).toHaveBeenCalledOnce();
|
||||
expect(context.loginStatus).toBe("notLoggedIn");
|
||||
expect(context.pendingRedirect).toBe(false);
|
||||
expect(context.hasInitialized).toBe(true);
|
||||
|
||||
actor.stop();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user