PSA/packages/billing/tests/contractsActivationFlow.test.tsx
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

211 lines
6.2 KiB
TypeScript

/**
* @vitest-environment jsdom
*/
import React from 'react';
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom/vitest';
let mockDraftContracts: any[] = [];
let mockClientContracts: any[] = [];
let mockDraftResumeData: any = null;
let mockSearchParams = new URLSearchParams('tab=contracts&subtab=drafts');
const mockRouter = {
push: vi.fn(),
replace: vi.fn(),
};
vi.mock('next/navigation', () => ({
useRouter: () => mockRouter,
useSearchParams: () => mockSearchParams,
}));
vi.mock('@alga-psa/ui/components/CustomTabs', () => ({
default: ({
tabs,
defaultTab,
}: {
tabs: Array<{ id?: string; label: string; content: React.ReactNode }>;
defaultTab: string;
}) => {
const tab =
tabs.find((t) => t.id === defaultTab)
?? tabs.find((t) => t.label === defaultTab)
?? tabs[0];
return (
<div>
<div>{tabs.map((t) => t.label).join(' | ')}</div>
<div>{tab?.content}</div>
</div>
);
},
}));
vi.mock('../src/components/billing-dashboard/contracts/template-wizard/TemplateWizard', () => ({
TemplateWizard: () => null,
}));
vi.mock('../src/components/billing-dashboard/contracts/ContractDialog', () => ({
ContractDialog: () => null,
}));
vi.mock('../src/components/billing-dashboard/contracts/ContractWizard', () => ({
ContractWizard: ({ open, onComplete }: { open: boolean; onComplete?: () => void }) =>
open ? (
<button
type="button"
onClick={() => {
// Simulate wizard completion converting draft -> active.
mockDraftContracts = [];
mockClientContracts = [
{
contract_id: 'contract-1',
contract_name: 'Draft Alpha',
client_name: 'Acme Co',
status: 'active',
client_id: 'client-1',
client_contract_id: 'client-contract-1',
},
];
onComplete?.();
}}
>
Complete Wizard
</button>
) : null,
}));
vi.mock('@alga-psa/billing/actions/contractActions', () => ({
deleteContract: vi.fn(async () => undefined),
getContractTemplates: vi.fn(async () => []),
getContractsWithClients: vi.fn(async () => mockClientContracts),
getDraftContracts: vi.fn(async () => mockDraftContracts),
updateContract: vi.fn(async () => undefined),
}));
vi.mock('@alga-psa/billing/actions/contractWizardActions', () => ({
getDraftContractForResume: vi.fn(async () => mockDraftResumeData),
}));
describe('Contracts activation flow (draft -> active)', () => {
beforeEach(() => {
document.body.removeAttribute('data-scroll-locked');
document.body.removeAttribute('style');
cleanup();
vi.clearAllMocks();
});
beforeAll(() => {
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
configurable: true,
get() {
return 1200;
},
});
});
it('activated contract no longer appears in Drafts tab (T047)', async () => {
mockSearchParams = new URLSearchParams('tab=contracts&subtab=drafts');
mockDraftResumeData = {
contract_id: 'contract-1',
is_draft: true,
client_id: 'client-1',
contract_name: 'Draft Alpha',
start_date: '2026-01-01',
currency_code: 'USD',
enable_proration: false,
fixed_services: [],
product_services: [],
hourly_services: [],
usage_services: [],
};
mockDraftContracts = [
{
contract_id: 'contract-1',
contract_name: 'Draft Alpha',
client_name: 'Acme Co',
created_at: new Date(2026, 0, 1),
updated_at: new Date(2026, 0, 2),
},
];
mockClientContracts = [];
const Contracts = (await import('../src/components/billing-dashboard/contracts/Contracts')).default;
render(<Contracts />);
expect(await screen.findByText('Draft Alpha')).toBeInTheDocument();
const user = userEvent.setup();
await act(async () => {
await user.click(await screen.findByRole('button', { name: /open menu/i }));
});
await act(async () => {
await user.click(await screen.findByText('Resume'));
});
await act(async () => {
await user.click(await screen.findByText('Complete Wizard'));
});
await waitFor(() => {
expect(screen.queryByText('Draft Alpha')).not.toBeInTheDocument();
});
expect(
screen.getByText('No draft contracts. Start creating a new contract to save as draft.'),
).toBeInTheDocument();
});
it('activated contract appears in Client Contracts tab (T048)', async () => {
mockSearchParams = new URLSearchParams('tab=contracts&subtab=drafts');
mockDraftResumeData = {
contract_id: 'contract-1',
is_draft: true,
client_id: 'client-1',
contract_name: 'Draft Alpha',
start_date: '2026-01-01',
currency_code: 'USD',
enable_proration: false,
fixed_services: [],
product_services: [],
hourly_services: [],
usage_services: [],
};
mockDraftContracts = [
{
contract_id: 'contract-1',
contract_name: 'Draft Alpha',
client_name: 'Acme Co',
created_at: new Date(2026, 0, 1),
updated_at: new Date(2026, 0, 2),
},
];
mockClientContracts = [];
const Contracts = (await import('../src/components/billing-dashboard/contracts/Contracts')).default;
const ui = render(<Contracts />);
const user = userEvent.setup();
expect(await screen.findByText('Draft Alpha')).toBeInTheDocument();
await act(async () => {
await user.click(await screen.findByRole('button', { name: /open menu/i }));
});
await act(async () => {
await user.click(await screen.findByText('Resume'));
});
await act(async () => {
await user.click(await screen.findByText('Complete Wizard'));
});
// Switch to the Client Contracts tab after activation + refresh.
mockSearchParams = new URLSearchParams('tab=contracts&subtab=client-contracts');
ui.rerender(<Contracts />);
await waitFor(() => {
expect(screen.getByText('Draft Alpha')).toBeInTheDocument();
});
});
});