perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one resource cost 10 queries, six cost 33 — about five per resource, because the available-minutes figure walked each resource's calendar on its own. Holidays, tenant overrides and branch hours are identical for every resource in a report, so they now load once outside the loop; shifts and exceptions load for all resources in one query each. The batched path is a new method rather than a change to rawAvailability, which the booking engine also calls. The test pins the shape of the growth, not an exact count. Also landed: - app:segment:seed-templates with beauty, dental and physio presets. Building four segments and their requirements by hand is the first thing a new clinic must do and the most tedious; this gives them something to edit instead of an empty page. It refuses to touch a service that already has segments unless --force, and it will not invent resource types the tenant never defined. - book-all is all-or-nothing, proven rather than asserted: with a calendar open one day a week and a 1-2 day protocol gap, session one finds a slot and session two cannot, and every session must come back planned. - credit_refundable: false takes the credit back with a negative adjustment and deletes nothing — the ledger stays append-only. - the segments editor has frontend tests, including that it sends back what the user sees and renders read-only without the permission. useBranches now returns [] for a non-array payload instead of throwing "branches.map is not a function" and taking the page down with it. BookingLocationsScanTest built a Clinic around a Doctor loaded from a different manager, which Doctrine treats as a new entity; it flushed fine most runs and failed on cascade in others. It now loads the doctor from the same manager. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders } from '../test/utils';
|
||||
|
||||
vi.mock('../lib/api', () => ({
|
||||
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
||||
ApiError: class extends Error {},
|
||||
}));
|
||||
|
||||
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
|
||||
|
||||
import { api } from '../lib/api';
|
||||
import ServiceSegmentsTab from './ServiceSegmentsTab';
|
||||
|
||||
const get = api.get as ReturnType<typeof vi.fn>;
|
||||
const put = api.put as ReturnType<typeof vi.fn>;
|
||||
|
||||
const SEGMENTS = [
|
||||
{
|
||||
sequence: 1,
|
||||
name: 'آمادهسازی',
|
||||
duration_minutes: 10,
|
||||
duration_source: 'fixed',
|
||||
patient_present: true,
|
||||
mergeable: true,
|
||||
requirements: [],
|
||||
},
|
||||
];
|
||||
|
||||
function mockApi(segments = SEGMENTS) {
|
||||
get.mockImplementation((url: string) => {
|
||||
if (url.includes('resource-types')) return Promise.resolve({ data: [] });
|
||||
return Promise.resolve({ data: { segments } });
|
||||
});
|
||||
put.mockResolvedValue({ data: { segments } });
|
||||
}
|
||||
|
||||
describe('ServiceSegmentsTab', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockApi();
|
||||
});
|
||||
|
||||
it('loads the service segments it was given', async () => {
|
||||
renderWithProviders(<ServiceSegmentsTab serviceUuid="s-1" canEdit />);
|
||||
|
||||
expect(await screen.findByDisplayValue('آمادهسازی')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
/** ⭐ ویرایشگر باید همان چیزی را بفرستد که کاربر میبیند، نه یک شکل تازه. */
|
||||
it('sends the edited segments back on save', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderWithProviders(<ServiceSegmentsTab serviceUuid="s-1" canEdit />);
|
||||
|
||||
const name = await screen.findByDisplayValue('آمادهسازی');
|
||||
await user.clear(name);
|
||||
await user.type(name, 'ضدعفونی');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /ذخیرهٔ بخشها/ }));
|
||||
|
||||
await waitFor(() => expect(put).toHaveBeenCalled());
|
||||
|
||||
const [url, body] = put.mock.calls[0];
|
||||
expect(url).toBe('/api/v1/service-item/s-1/segments');
|
||||
expect(body.segments[0].name).toBe('ضدعفونی');
|
||||
});
|
||||
|
||||
/** بدون اجازهٔ ویرایش، فرم فقط خواندنی است — دکمهٔ ذخیره اصلاً نباید باشد. */
|
||||
it('renders read-only without the edit permission', async () => {
|
||||
renderWithProviders(<ServiceSegmentsTab serviceUuid="s-1" canEdit={false} />);
|
||||
|
||||
const name = await screen.findByDisplayValue('آمادهسازی');
|
||||
|
||||
expect(name).toBeDisabled();
|
||||
expect(screen.queryByRole('button', { name: /ذخیرهٔ بخشها/ })).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user