This commit is contained in:
Félix Malfait 2024-12-24 09:12:28 +01:00
parent 88cfd97213
commit d55132cc9a
4 changed files with 165 additions and 83 deletions

View File

@ -1,4 +1,5 @@
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { AppPath } from '@/types/AppPath';
@ -32,6 +33,8 @@ export const usePageChangeEffectNavigateLocation = () => {
isMatchingLocation(AppPath.PlanRequired) ||
isMatchingLocation(AppPath.PlanRequiredSuccess);
useBillingCheckout();
if (isMatchingOpenRoute) {
return;
}

View File

@ -0,0 +1,142 @@
import { cleanup, renderHook, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout';
import { BillingPlanKey } from '~/generated/graphql';
type WrapperProps = {
children: React.ReactNode;
initialUrl?: string;
};
const Wrapper = ({ children, initialUrl = '' }: WrapperProps) => (
<MemoryRouter initialEntries={[initialUrl]}>
<RecoilRoot>{children}</RecoilRoot>
</MemoryRouter>
);
describe('useBillingCheckout', () => {
afterEach(() => {
cleanup();
});
it('should return null as default plan', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => <Wrapper>{children}</Wrapper>,
});
await waitFor(() => {
expect(result.current.plan).toBe(null);
});
});
it('should set plan from URL parameter - FREE', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=free">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.plan).toBe(BillingPlanKey.Free);
});
});
it('should set plan from URL parameter - PRO', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=pro">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.plan).toBe(BillingPlanKey.Pro);
});
});
it('should set plan from URL parameter - ENTERPRISE', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=enterprise">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.plan).toBe(BillingPlanKey.Enterprise);
});
});
it('should ignore invalid plan from URL parameter', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=invalid">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.plan).toBe(null);
});
});
it('should handle URL without plan parameter', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?other=param">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.plan).toBe(null);
});
});
it('should set requirePaymentMethod to false when freepass parameter is present', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?freepass=true">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.requirePaymentMethod).toBe(false);
expect(result.current.skipPlanPage).toBe(true);
});
});
it('should set requirePaymentMethod to false for all freepass parameter variations', async () => {
const freePassVariations = [
'freepass=true',
'freePass=true',
'free-pass=true',
'Free-pass=true',
'FreePass=true',
];
for (const param of freePassVariations) {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl={`?${param}`}>{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.requirePaymentMethod).toBe(false);
expect(result.current.skipPlanPage).toBe(true);
});
}
});
it('should set requirePaymentMethod to false when requirePaymentMethod=true is in URL', async () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?requirePaymentMethod=true">{children}</Wrapper>
),
});
await waitFor(() => {
expect(result.current.requirePaymentMethod).toBe(false);
expect(result.current.skipPlanPage).toBe(true);
});
});
});

View File

@ -1,72 +0,0 @@
import { renderHook } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useBillingCheckout } from '@/billing/hooks/useBillingCheckout';
import { BillingPlanKey } from '~/generated/graphql';
const Wrapper = ({ children, initialUrl = '' }: any) => (
<MemoryRouter initialEntries={[initialUrl]}>
<RecoilRoot>{children}</RecoilRoot>
</MemoryRouter>
);
describe('useBillingCheckout', () => {
it('should return null as default plan', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: Wrapper,
});
expect(result.current.plan).toBe(null);
});
it('should set plan from URL parameter - FREE', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=free">{children}</Wrapper>
),
});
expect(result.current.plan).toBe(BillingPlanKey.Free);
});
it('should set plan from URL parameter - PRO', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=pro">{children}</Wrapper>
),
});
expect(result.current.plan).toBe(BillingPlanKey.Pro);
});
it('should set plan from URL parameter - ENTERPRISE', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=enterprise">{children}</Wrapper>
),
});
expect(result.current.plan).toBe(BillingPlanKey.Enterprise);
});
it('should ignore invalid plan from URL parameter', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?plan=invalid">{children}</Wrapper>
),
});
expect(result.current.plan).toBe(null);
});
it('should handle URL without plan parameter', () => {
const { result } = renderHook(() => useBillingCheckout(), {
wrapper: ({ children }) => (
<Wrapper initialUrl="?other=param">{children}</Wrapper>
),
});
expect(result.current.plan).toBe(null);
});
});

View File

@ -1,9 +1,17 @@
import { billingCheckoutState } from '@/billing/states/billingCheckoutState';
import { useLocation } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { BillingPlanKey } from '~/generated/graphql';
export const useBillingCheckout = () => {
type BillingCheckout = {
plan: BillingPlanKey | null;
interval?: string;
requirePaymentMethod: boolean;
skipPlanPage: boolean;
};
export const useBillingCheckout = (): BillingCheckout => {
const { search } = useLocation();
const [billingCheckout, setBillingCheckout] =
useRecoilState(billingCheckoutState);
@ -15,28 +23,29 @@ export const useBillingCheckout = () => {
search.includes('Free-pass') ||
search.includes('FreePass');
if (hasFreePassParameter || search.includes('requirePaymentMethod=true')) {
setBillingCheckout({
plan: billingCheckout.plan,
interval: billingCheckout.interval,
if (
billingCheckout.requirePaymentMethod &&
(hasFreePassParameter || search.includes('requirePaymentMethod=true'))
) {
setBillingCheckout((prev) => ({
...prev,
requirePaymentMethod: false,
skipPlanPage: true,
});
}));
}
const planFromUrl = search.match(/[?&]plan=([^&]+)/)?.[1]?.toUpperCase();
if (
planFromUrl !== null &&
planFromUrl !== undefined &&
planFromUrl !== '' &&
Object.values(BillingPlanKey).includes(planFromUrl as BillingPlanKey)
) {
setBillingCheckout({
setBillingCheckout((prev) => ({
...prev,
plan: planFromUrl as BillingPlanKey,
interval: billingCheckout.interval,
requirePaymentMethod: billingCheckout.requirePaymentMethod,
skipPlanPage: true,
});
}));
}
return billingCheckout;