import {render, screen} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import {describe, expect, it, vi} from 'vitest'; import {localToday} from '../dates'; import SowSeedsModal from './SowSeedsModal'; vi.mock('./CropPicker', () => ({ default: ({onChoose}) => , })); vi.mock('./CropDetails', () => ({default: ({slug}) =>

Details of {slug}

})); vi.mock('./SeedPacket', () => ({default: ({packet}) =>

Packet {packet.name}

})); const nursery = {id: 1, name: 'Nursery'}; const gardens = [{id: 10, name: 'Front', slug: 'front'}, {id: 20, name: 'Back', slug: 'back'}]; const seeds = [{id: 3, crop_id: 5, crop_slug: 'tomato', name: 'Roma tomato', saved: '2024', quantity: 20, plant_before: null}]; function stubFetch(response = {ok: true, status: 201, data: {garden: {id: 1}}}) { const fetch = vi.fn(async () => ({ok: response.ok, status: response.status, json: async () => response.data || {}})); vi.stubGlobal('fetch', fetch); return fetch; } function setup(props = {}) { const onSown = vi.fn(); render(); return onSown; } const next = () => userEvent.click(screen.getByRole('button', {name: 'Next'})); describe('SowSeedsModal', () => { it('is titled Sow seeds', () => { setup(); expect(screen.getByRole('dialog', {name: 'Sow seeds'})).toBeInTheDocument(); }); it('starts with your own packets when you have some', () => { setup(); expect(screen.getByLabelText('From my seeds')).toBeChecked(); }); it('starts with new seeds when you have no packets', () => { setup({seeds: []}); expect(screen.getByLabelText('New seeds')).toBeChecked(); }); it('goes on to the next step on choosing a packet', async () => { setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); expect(screen.getByLabelText('How many seeds?')).toBeInTheDocument(); }); it('goes on to the next step on choosing a crop', async () => { setup({seeds: []}); await userEvent.click(screen.getByRole('button', {name: 'Pick kale'})); expect(screen.getByText('Details of kale')).toBeInTheDocument(); }); it('asks about seedlings, not seeds, for seedlings', async () => { setup(); await userEvent.click(screen.getByLabelText('Seedlings I got')); await userEvent.click(screen.getByRole('button', {name: 'Pick kale'})); expect(screen.getByLabelText('How many seedlings?')).toBeInTheDocument(); }); it('says what the packet will have left', async () => { setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await userEvent.clear(screen.getByLabelText('How many seeds?')); await userEvent.type(screen.getByLabelText('How many seeds?'), '5'); expect(screen.getByText('That packet goes from 20 to 15.')).toBeInTheDocument(); }); it('cannot go on with no seeds', async () => { setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await userEvent.clear(screen.getByLabelText('How many seeds?')); expect(screen.getByRole('button', {name: 'Next'})).toBeDisabled(); }); it('asks which garden when sowing straight into one', async () => { setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await userEvent.click(screen.getByLabelText('Straight into a garden')); expect(screen.getByLabelText('Which garden?')).toBeInTheDocument(); }); it('goes back to choose other seeds', async () => { setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await userEvent.click(screen.getByRole('button', {name: 'Change seeds'})); expect(screen.getByLabelText('Search your seeds')).toBeInTheDocument(); }); describe('sowing into the nursery', () => { async function sow() { await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await next(); await userEvent.click(screen.getByRole('button', {name: 'Sow into the nursery'})); } it('posts the planting, using the packet', async () => { const fetch = stubFetch(); setup(); await sow(); expect(JSON.parse(fetch.mock.calls[0][1].body)).toEqual({ planting: {garden_id: 1, crop_id: 5, quantity: 6, planted_from: 'seed', parent_seed_id: 3, planted_at: localToday()}, use_seeds: 1, }); }); it('hands the reply to onSown', async () => { stubFetch(); const onSown = setup(); await sow(); expect(onSown).toHaveBeenCalledWith({garden: {id: 1}}); }); it('includes the notes', async () => { const fetch = stubFetch(); setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await next(); await userEvent.type(screen.getByLabelText(/Do you have any notes/), 'Soaked first'); await userEvent.click(screen.getByRole('button', {name: 'Sow into the nursery'})); expect(JSON.parse(fetch.mock.calls[0][1].body).planting.description).toBe('Soaked first'); }); it('lists the server\'s reasons', async () => { stubFetch({ok: false, status: 422, data: {errors: {quantity: ['is too many']}}}); setup(); await sow(); expect(await screen.findByText('Quantity is too many')).toBeInTheDocument(); }); it('says when the server cannot be reached', async () => { vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline'))); setup(); await sow(); expect(await screen.findByText(/Couldn't reach the server/)).toBeInTheDocument(); }); }); describe('sowing and adding another', () => { it('says what was sown', async () => { stubFetch(); setup(); await userEvent.click(screen.getByRole('button', {name: /Roma tomato/})); await next(); await userEvent.click(screen.getByRole('button', {name: 'Sow and add another'})); expect(await screen.findByText(/Sowed 6 Roma tomato\./)).toBeInTheDocument(); }); }); describe('sowing straight into a garden', () => { it('offers to place it on the layout', async () => { stubFetch(); setup({seeds: []}); await userEvent.click(screen.getByRole('button', {name: 'Pick kale'})); await userEvent.click(screen.getByLabelText('Straight into a garden')); await next(); await userEvent.click(screen.getByRole('button', {name: 'Sow into Front'})); expect(await screen.findByRole('link', {name: 'Place on the garden layout'})).toHaveAttribute('href', '/gardens/front/layout'); }); }); });