186 lines
5.7 KiB
TypeScript
186 lines
5.7 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { TestUserStoreProvider, type User } from "../providers/UserStoreProvider";
|
|
import { UpgradeBlockProvider, useMaybeUpgradeBlock, useUpgradeBlock } from "./UpgradeBlockContext";
|
|
|
|
// Mock the organization hook
|
|
vi.mock("../hooks/organization", () => ({
|
|
useOrganization: vi.fn(),
|
|
}));
|
|
|
|
import { useOrganization } from "../hooks/organization";
|
|
|
|
const mockUseOrganization = vi.mocked(useOrganization);
|
|
|
|
const baseUser: User = {
|
|
id: "user-1",
|
|
short_user_id: "u1",
|
|
name: "Test User",
|
|
first_name: "Test",
|
|
last_name: "User",
|
|
email: "test@example.com",
|
|
avatar_url: null,
|
|
is_temporary: false,
|
|
is_client: false,
|
|
client_onboarded_at: null,
|
|
last_signed_in: null,
|
|
plan: "none",
|
|
created_at: new Date().toISOString(),
|
|
};
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: { queries: { retry: false } },
|
|
});
|
|
|
|
function TestConsumer() {
|
|
const context = useUpgradeBlock();
|
|
return (
|
|
<div>
|
|
<span data-testid="blocked">{String(context.isBlocked)}</span>
|
|
<span data-testid="reason">{context.reason ?? "none"}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MaybeTestConsumer() {
|
|
const context = useMaybeUpgradeBlock();
|
|
return (
|
|
<div>
|
|
<span data-testid="has-context">{String(context !== null)}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function renderWithUser(user: User | null, orgData: ReturnType<typeof useOrganization>["data"]) {
|
|
mockUseOrganization.mockReturnValue({
|
|
data: orgData,
|
|
isLoading: false,
|
|
error: null,
|
|
} as unknown as ReturnType<typeof useOrganization>);
|
|
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<TestUserStoreProvider user={user}>
|
|
<UpgradeBlockProvider>
|
|
<TestConsumer />
|
|
</UpgradeBlockProvider>
|
|
</TestUserStoreProvider>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
const compliantOrgData = {
|
|
organization: {
|
|
id: 1,
|
|
name: "Test Org",
|
|
plan: "team",
|
|
member_count: 2,
|
|
tablo_count: 5,
|
|
logo_url: null,
|
|
},
|
|
members: [],
|
|
invites_sent: [],
|
|
trial_starts_at: "2026-01-01",
|
|
trial_ends_at: "2026-02-01",
|
|
is_trial_expired: false,
|
|
required_plan: "solo" as const,
|
|
required_team_quantity: 1,
|
|
active_subscription_plan: "team" as const,
|
|
active_subscription_quantity: 2,
|
|
is_billing_owner: true,
|
|
};
|
|
|
|
const noPlanOrgData = {
|
|
...compliantOrgData,
|
|
active_subscription_plan: null,
|
|
active_subscription_quantity: 0,
|
|
};
|
|
|
|
const trialExpiredOrgData = {
|
|
...compliantOrgData,
|
|
is_trial_expired: true,
|
|
required_plan: "team" as const,
|
|
required_team_quantity: 5,
|
|
active_subscription_plan: "team" as const,
|
|
active_subscription_quantity: 2,
|
|
};
|
|
|
|
describe("UpgradeBlockProvider", () => {
|
|
it("is not blocked when user and org are loading (null)", () => {
|
|
renderWithUser(null, undefined);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
expect(screen.getByTestId("reason").textContent).toBe("none");
|
|
});
|
|
|
|
it("is not blocked when user is loaded but org data is still loading", () => {
|
|
renderWithUser(baseUser, undefined);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
});
|
|
|
|
it("is not blocked for temporary users regardless of org billing state", () => {
|
|
const temporaryUser = { ...baseUser, is_temporary: true };
|
|
renderWithUser(temporaryUser, noPlanOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
expect(screen.getByTestId("reason").textContent).toBe("none");
|
|
});
|
|
|
|
it("is not blocked for temporary users even with expired trial", () => {
|
|
const temporaryUser = { ...baseUser, is_temporary: true };
|
|
renderWithUser(temporaryUser, trialExpiredOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
});
|
|
|
|
it("blocks regular users when org has no active plan", () => {
|
|
renderWithUser(baseUser, noPlanOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("true");
|
|
expect(screen.getByTestId("reason").textContent).toBe("no_plan");
|
|
});
|
|
|
|
it("blocks regular users when trial expired and plan is insufficient", () => {
|
|
renderWithUser(baseUser, trialExpiredOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("true");
|
|
expect(screen.getByTestId("reason").textContent).toBe("trial_expired");
|
|
});
|
|
|
|
it("is not blocked for regular users with a compliant subscription", () => {
|
|
renderWithUser(baseUser, compliantOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
expect(screen.getByTestId("reason").textContent).toBe("none");
|
|
});
|
|
|
|
it("is not blocked with an annual plan even if team quantity is insufficient", () => {
|
|
const annualOrgData = {
|
|
...trialExpiredOrgData,
|
|
active_subscription_plan: "annual" as const,
|
|
};
|
|
renderWithUser(baseUser, annualOrgData);
|
|
expect(screen.getByTestId("blocked").textContent).toBe("false");
|
|
});
|
|
});
|
|
|
|
describe("useMaybeUpgradeBlock", () => {
|
|
it("returns null when outside provider", () => {
|
|
render(<MaybeTestConsumer />);
|
|
expect(screen.getByTestId("has-context").textContent).toBe("false");
|
|
});
|
|
|
|
it("returns context when inside provider", () => {
|
|
mockUseOrganization.mockReturnValue({
|
|
data: compliantOrgData,
|
|
isLoading: false,
|
|
error: null,
|
|
} as unknown as ReturnType<typeof useOrganization>);
|
|
|
|
render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<TestUserStoreProvider user={baseUser}>
|
|
<UpgradeBlockProvider>
|
|
<MaybeTestConsumer />
|
|
</UpgradeBlockProvider>
|
|
</TestUserStoreProvider>
|
|
</QueryClientProvider>
|
|
);
|
|
expect(screen.getByTestId("has-context").textContent).toBe("true");
|
|
});
|
|
});
|