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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
211 lines
6.2 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|