import React from 'react'; import { configure, render, screen, fireEvent, within } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; configure({ asyncUtilTimeout: 5000 }); /** * Rider (miler) create and edit forms. The hooks are mocked; the server rules * (duplicate phone, hub in another city, block holding) are covered by * doormile_backend's routes_miler_account_pg_test.go. */ beforeAll(() => { global.ResizeObserver = global.ResizeObserver || class { observe() {} unobserve() {} disconnect() {} }; // Radix Select needs these in jsdom. window.HTMLElement.prototype.hasPointerCapture = window.HTMLElement.prototype.hasPointerCapture || (() => false); window.HTMLElement.prototype.releasePointerCapture = window.HTMLElement.prototype.releasePointerCapture || (() => {}); window.HTMLElement.prototype.scrollIntoView = window.HTMLElement.prototype.scrollIntoView || (() => {}); }); jest.mock('lucide-react', () => new Proxy({}, { get: (_t, prop) => (prop === '__esModule' ? true : (props) => ) }) ); const mockCreate = jest.fn(); const mockUpdate = jest.fn(); const HUBS = [ { hubid: 11, hubname: 'Coimbatore Neptune Hub', applocationid: 1 }, { hubid: 21, hubname: 'Hyderabad Mars Hub', applocationid: 2 }, ]; const CITIES = [ { applocationid: 1, locationname: 'Coimbatore' }, { applocationid: 2, locationname: 'Hyderabad' }, ]; let mockRider = null; jest.mock('@/lib/doormileHooks', () => ({ useTenants: () => ({ data: [{ tenantid: 5, tenantname: "Sai's Kitchen" }] }), useHubs: () => ({ data: HUBS }), useAppLocations: () => ({ data: CITIES }), useCreateMiler: () => ({ mutate: mockCreate, isPending: false }), useUpdateMiler: () => ({ mutate: mockUpdate, isPending: false }), useMiler: () => ({ data: mockRider, isLoading: false }), })); import CreateRider from '@/pages/doormile/riders/CreateRider'; import EditRider from '@/pages/doormile/riders/EditRider'; /** Opens a Radix select by its trigger and returns the visible option labels. */ const openSelect = (trigger) => { fireEvent.keyDown(trigger, { key: 'Enter' }); return screen.getAllByRole('option').map((o) => o.textContent); }; const pick = (trigger, label) => { openSelect(trigger); fireEvent.click(screen.getByRole('option', { name: label })); }; beforeEach(() => { jest.clearAllMocks(); localStorage.clear(); mockRider = null; }); describe('Create miler', () => { const fill = () => { fireEvent.change(screen.getByPlaceholderText('9876543210'), { target: { value: '9876500001' } }); fireEvent.change(screen.getByPlaceholderText('murali@example.com'), { target: { value: 'ravi@r.test' } }); const inputs = screen.getAllByRole('textbox'); // Display name and login name are the first two text inputs. fireEvent.change(inputs[0], { target: { value: 'Ravi K' } }); fireEvent.change(inputs[1], { target: { value: 'ravi' } }); }; it('asks for no password: the rider sets their own MPIN in the app', () => { render(); expect(screen.queryByText('Initial password')).not.toBeInTheDocument(); expect(screen.getByText(/sets their own MPIN/)).toBeInTheDocument(); }); it('offers only the hubs of the chosen city', () => { render(); // Staff form: vehicle, client, city, hub. const [, , city, hub] = screen.getAllByRole('combobox'); pick(city, 'Coimbatore'); const options = openSelect(hub); expect(options).toContain('Coimbatore Neptune Hub'); expect(options).not.toContain('Hyderabad Mars Hub'); }); it("creates a rider in Doormile's own pool when no client is chosen", () => { render(); fill(); pick(screen.getAllByRole('combobox')[2], 'Coimbatore'); fireEvent.click(screen.getByRole('button', { name: /create miler/i })); expect(mockCreate).toHaveBeenCalledTimes(1); const payload = mockCreate.mock.calls[0][0]; expect(payload).toMatchObject({ tenantid: 0, applocationid: 1, contactno: '9876500001' }); expect(payload).not.toHaveProperty('password'); }); }); describe('Edit miler', () => { const RIDER = { milerprofileid: 7, displayname: 'Nagalakshmi', phone: '9876500004', email: 'naga@r.test', tenantid: 5, applocationid: 1, hubid: 11, defaultvehicletype: 'Scooter', }; const renderEdit = () => render( ); it('saves the changed phone (and not the unchanged hub) — and shows email, client and city read-only', () => { mockRider = RIDER; renderEdit(); expect(screen.getByText('naga@r.test')).toBeInTheDocument(); expect(screen.getByText("Sai's Kitchen")).toBeInTheDocument(); expect(screen.getAllByText(/Can't be changed here/)).toHaveLength(3); fireEvent.change(screen.getByDisplayValue('9876500004'), { target: { value: '9876500007' } }); fireEvent.click(screen.getByRole('button', { name: /save changes/i })); expect(mockUpdate).toHaveBeenCalledTimes(1); const { id, data } = mockUpdate.mock.calls[0][0]; expect(id).toBe(7); // Only what changed: re-sending the old hub could fail a legacy rider's save. expect(data).toEqual({ displayname: 'Nagalakshmi', contactno: '9876500007', defaultvehicletype: 'Scooter' }); }); it('a rename alone does not resend the login phone', () => { mockRider = RIDER; renderEdit(); fireEvent.change(screen.getByDisplayValue('Nagalakshmi'), { target: { value: 'Nagalakshmi R' } }); fireEvent.click(screen.getByRole('button', { name: /save changes/i })); expect(mockUpdate.mock.calls[0][0].data).toEqual({ displayname: 'Nagalakshmi R', defaultvehicletype: 'Scooter' }); }); it("offers only hubs in the rider's city", () => { mockRider = RIDER; renderEdit(); const hubTrigger = screen.getAllByRole('combobox')[1]; const options = openSelect(hubTrigger); expect(options).toContain('Coimbatore Neptune Hub'); expect(options).not.toContain('Hyderabad Mars Hub'); }); it('refuses a mobile number that is not 10 digits', () => { mockRider = RIDER; renderEdit(); fireEvent.change(screen.getByDisplayValue('9876500004'), { target: { value: '98765' } }); fireEvent.click(screen.getByRole('button', { name: /save changes/i })); expect(mockUpdate).not.toHaveBeenCalled(); expect(within(document.body).getByText('Enter a 10-digit mobile number')).toBeInTheDocument(); }); });