PSA/packages/scheduling/tests/timeSheetTable.quickAddInteraction.test.ts
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

158 lines
4.9 KiB
TypeScript

// @vitest-environment jsdom
import * as React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createRoot, Root } from 'react-dom/client';
import { flushSync } from 'react-dom';
class ResizeObserverMock {
observe() {}
disconnect() {}
}
Object.defineProperty(globalThis, 'ResizeObserver', {
value: ResizeObserverMock,
configurable: true,
});
vi.mock('@alga-psa/ui/components/Button', () => ({
Button: ({
children,
onClick,
disabled,
title,
id,
}: {
children: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
disabled?: boolean;
title?: string;
id?: string;
}) => React.createElement('button', { type: 'button', onClick, disabled, title, id }, children),
}), { virtual: true });
vi.mock('@alga-psa/ui/components/ConfirmationDialog', () => ({
ConfirmationDialog: () => null,
}), { virtual: true });
vi.mock('@alga-psa/ui/ui-reflection/ReflectionContainer', () => ({
ReflectionContainer: ({ children }: { children: React.ReactNode }) => React.createElement(React.Fragment, null, children),
}), { virtual: true });
const { TimeSheetTable } = await import('../src/components/time-management/time-entry/time-sheet/TimeSheetTable');
describe('TimeSheetTable quick add interaction state', () => {
let container: HTMLDivElement;
let root: Root;
const workItem = {
work_item_id: 'work-item-1',
name: 'Ticket 1001',
type: 'ticket',
description: '',
ticket_number: '1001',
is_billable: true,
};
const commonProps = {
dates: [new Date(2026, 2, 10)],
workItemsByType: {
ticket: [workItem],
},
groupedTimeEntries: {
'work-item-1': [],
},
isEditable: true,
onDeleteWorkItem: vi.fn(async () => undefined),
onAddEntryForCell: vi.fn(),
onAddWorkItem: vi.fn(),
onWorkItemClick: vi.fn(),
onDateNavigatorChange: vi.fn(),
};
beforeEach(() => {
container = document.createElement('div');
Object.defineProperty(container, 'offsetWidth', { value: 900, configurable: true });
document.body.innerHTML = '';
document.body.appendChild(container);
root = createRoot(container);
});
it('renders deterministic quick-add editor controls for the active cell', () => {
flushSync(() => {
root.render(
React.createElement(TimeSheetTable, {
...commonProps,
onCellClick: vi.fn(),
activeQuickAdd: {
workItem,
date: '2026-03-10',
value: '1:30',
},
onActivateQuickAdd: vi.fn(),
onQuickAddValueChange: vi.fn(),
onQuickAddCancel: vi.fn(),
onQuickAddSubmit: vi.fn(async () => undefined),
}),
);
});
expect(container.querySelector('#timesheet-quick-input-work-item-1-2026-03-10')).not.toBeNull();
expect(container.querySelector('#timesheet-quick-save-work-item-1-2026-03-10')).not.toBeNull();
expect(container.querySelector('#timesheet-quick-cancel-work-item-1-2026-03-10')).not.toBeNull();
});
it('keeps quick-add button clicks isolated while the add-area still opens the dialog path', () => {
const onCellClick = vi.fn();
const onAddEntryForCell = vi.fn();
const onQuickAddSubmit = vi.fn(async () => undefined);
const onQuickAddCancel = vi.fn();
flushSync(() => {
root.render(
React.createElement(TimeSheetTable, {
...commonProps,
onCellClick,
onAddEntryForCell,
activeQuickAdd: {
workItem,
date: '2026-03-10',
value: '2',
},
onActivateQuickAdd: vi.fn(),
onQuickAddValueChange: vi.fn(),
onQuickAddCancel,
onQuickAddSubmit,
}),
);
});
const saveButton = container.querySelector('#timesheet-quick-save-work-item-1-2026-03-10');
const cancelButton = container.querySelector('#timesheet-quick-cancel-work-item-1-2026-03-10');
const addArea = container.querySelector('[data-automation-id="time-cell-add-area-work-item-1-2026-03-10"]');
if (!saveButton || !cancelButton || !addArea) {
throw new Error('Expected quick add controls and add area');
}
flushSync(() => {
saveButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(onQuickAddSubmit).toHaveBeenCalledTimes(1);
expect(onCellClick).not.toHaveBeenCalled();
expect(onAddEntryForCell).not.toHaveBeenCalled();
flushSync(() => {
cancelButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(onQuickAddCancel).toHaveBeenCalledTimes(1);
expect(onCellClick).not.toHaveBeenCalled();
expect(onAddEntryForCell).not.toHaveBeenCalled();
flushSync(() => {
addArea.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(onAddEntryForCell).toHaveBeenCalledTimes(1);
expect(onCellClick).not.toHaveBeenCalled();
});
});