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
158 lines
4.9 KiB
TypeScript
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();
|
|
});
|
|
});
|