- Reworked the tenant admin login page
- Updated the User model to implement Filament’s tenancy contracts - Seeded a ready-to-use demo tenant (user, tenant, active package, purchase) - Introduced a branded, translated 403 error page to replace the generic forbidden message for unauthorised admin hits - Removed the public “Register” links from the marketing header - hardened join event logic and improved error handling in the guest pwa.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, beforeEach, afterEach, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import WelcomeLandingPage from '../pages/WelcomeLandingPage';
|
||||
import { OnboardingProgressProvider } from '..';
|
||||
import {
|
||||
ADMIN_EVENTS_PATH,
|
||||
ADMIN_WELCOME_PACKAGES_PATH,
|
||||
} from '../../constants';
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: () => navigateMock,
|
||||
useLocation: () => ({ pathname: '/event-admin', search: '', hash: '', state: null, key: 'test' }),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../components/LanguageSwitcher', () => ({
|
||||
LanguageSwitcher: () => <div data-testid="language-switcher" />,
|
||||
}));
|
||||
|
||||
describe('WelcomeLandingPage', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
navigateMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function renderPage() {
|
||||
return render(
|
||||
<OnboardingProgressProvider>
|
||||
<WelcomeLandingPage />
|
||||
</OnboardingProgressProvider>
|
||||
);
|
||||
}
|
||||
|
||||
it('marks the welcome step as seen on mount', () => {
|
||||
renderPage();
|
||||
const stored = localStorage.getItem('tenant-admin:onboarding-progress');
|
||||
expect(stored).toBeTruthy();
|
||||
expect(stored).toContain('"welcomeSeen":true');
|
||||
expect(stored).toContain('"lastStep":"landing"');
|
||||
});
|
||||
|
||||
it('navigates to package selection when the primary CTA is clicked', async () => {
|
||||
renderPage();
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole('button', { name: /hero.primary.label/i }));
|
||||
expect(navigateMock).toHaveBeenCalledWith(ADMIN_WELCOME_PACKAGES_PATH);
|
||||
});
|
||||
|
||||
it('navigates to events when secondary CTA in hero or footer is used', async () => {
|
||||
renderPage();
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole('button', { name: /hero.secondary.label/i }));
|
||||
expect(navigateMock).toHaveBeenCalledWith(ADMIN_EVENTS_PATH);
|
||||
|
||||
navigateMock.mockClear();
|
||||
await user.click(screen.getByRole('button', { name: /layout.jumpToDashboard/i }));
|
||||
expect(navigateMock).toHaveBeenCalledWith(ADMIN_EVENTS_PATH);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
StripeCheckoutForm,
|
||||
PayPalCheckout,
|
||||
} from '../pages/WelcomeOrderSummaryPage';
|
||||
|
||||
const stripeRef: { current: any } = { current: null };
|
||||
const elementsRef: { current: any } = { current: null };
|
||||
const paypalPropsRef: { current: any } = { current: null };
|
||||
|
||||
const {
|
||||
confirmPaymentMock,
|
||||
completePurchaseMock,
|
||||
createPayPalOrderMock,
|
||||
capturePayPalOrderMock,
|
||||
} = vi.hoisted(() => ({
|
||||
confirmPaymentMock: vi.fn(),
|
||||
completePurchaseMock: vi.fn(),
|
||||
createPayPalOrderMock: vi.fn(),
|
||||
capturePayPalOrderMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@stripe/react-stripe-js', () => ({
|
||||
useStripe: () => stripeRef.current,
|
||||
useElements: () => elementsRef.current,
|
||||
PaymentElement: () => <div data-testid="stripe-payment-element" />,
|
||||
Elements: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock('@paypal/react-paypal-js', () => ({
|
||||
PayPalScriptProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
PayPalButtons: (props: any) => {
|
||||
paypalPropsRef.current = props;
|
||||
return <button type="button" data-testid="paypal-button">PayPal</button>;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../api', () => ({
|
||||
completeTenantPackagePurchase: completePurchaseMock,
|
||||
createTenantPackagePaymentIntent: vi.fn(),
|
||||
assignFreeTenantPackage: vi.fn(),
|
||||
createTenantPayPalOrder: createPayPalOrderMock,
|
||||
captureTenantPayPalOrder: capturePayPalOrderMock,
|
||||
}));
|
||||
|
||||
describe('StripeCheckoutForm', () => {
|
||||
beforeEach(() => {
|
||||
confirmPaymentMock.mockReset();
|
||||
completePurchaseMock.mockReset();
|
||||
stripeRef.current = { confirmPayment: confirmPaymentMock };
|
||||
elementsRef.current = {};
|
||||
});
|
||||
|
||||
const renderStripeForm = (overrides?: Partial<React.ComponentProps<typeof StripeCheckoutForm>>) =>
|
||||
render(
|
||||
<StripeCheckoutForm
|
||||
clientSecret="secret"
|
||||
packageId={42}
|
||||
onSuccess={vi.fn()}
|
||||
t={(key: string) => key}
|
||||
{...overrides}
|
||||
/>
|
||||
);
|
||||
|
||||
it('completes the purchase when Stripe reports a successful payment', async () => {
|
||||
const onSuccess = vi.fn();
|
||||
confirmPaymentMock.mockResolvedValue({
|
||||
error: null,
|
||||
paymentIntent: { payment_method: 'pm_123' },
|
||||
});
|
||||
completePurchaseMock.mockResolvedValue(undefined);
|
||||
|
||||
const { container } = renderStripeForm({ onSuccess });
|
||||
const form = container.querySelector('form');
|
||||
expect(form).toBeTruthy();
|
||||
fireEvent.submit(form!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(completePurchaseMock).toHaveBeenCalledWith({
|
||||
packageId: 42,
|
||||
paymentMethodId: 'pm_123',
|
||||
});
|
||||
});
|
||||
expect(onSuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows Stripe errors returned by confirmPayment', async () => {
|
||||
confirmPaymentMock.mockResolvedValue({
|
||||
error: { message: 'Card declined' },
|
||||
});
|
||||
|
||||
const { container } = renderStripeForm();
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Card declined')).toBeInTheDocument();
|
||||
});
|
||||
expect(completePurchaseMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reports missing payment method id', async () => {
|
||||
confirmPaymentMock.mockResolvedValue({
|
||||
error: null,
|
||||
paymentIntent: {},
|
||||
});
|
||||
|
||||
const { container } = renderStripeForm();
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('summary.stripe.missingPaymentId')).toBeInTheDocument();
|
||||
});
|
||||
expect(completePurchaseMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PayPalCheckout', () => {
|
||||
beforeEach(() => {
|
||||
paypalPropsRef.current = null;
|
||||
createPayPalOrderMock.mockReset();
|
||||
capturePayPalOrderMock.mockReset();
|
||||
});
|
||||
|
||||
it('creates and captures a PayPal order successfully', async () => {
|
||||
createPayPalOrderMock.mockResolvedValue('ORDER-123');
|
||||
capturePayPalOrderMock.mockResolvedValue(undefined);
|
||||
const onSuccess = vi.fn();
|
||||
|
||||
render(
|
||||
<PayPalCheckout
|
||||
packageId={99}
|
||||
onSuccess={onSuccess}
|
||||
t={(key: string) => key}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(paypalPropsRef.current).toBeTruthy();
|
||||
const { createOrder, onApprove } = paypalPropsRef.current;
|
||||
await act(async () => {
|
||||
const orderId = await createOrder();
|
||||
expect(orderId).toBe('ORDER-123');
|
||||
});
|
||||
await act(async () => {
|
||||
await onApprove({ orderID: 'ORDER-123' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createPayPalOrderMock).toHaveBeenCalledWith(99);
|
||||
expect(capturePayPalOrderMock).toHaveBeenCalledWith('ORDER-123');
|
||||
expect(onSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('surfaces missing order id errors', async () => {
|
||||
createPayPalOrderMock.mockResolvedValue('ORDER-123');
|
||||
render(
|
||||
<PayPalCheckout
|
||||
packageId={99}
|
||||
onSuccess={vi.fn()}
|
||||
t={(key: string) => key}
|
||||
/>
|
||||
);
|
||||
|
||||
const { onApprove } = paypalPropsRef.current;
|
||||
await act(async () => {
|
||||
await onApprove({ orderID: undefined });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('summary.paypal.missingOrderId')).toBeInTheDocument();
|
||||
});
|
||||
expect(capturePayPalOrderMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user