Files
growstuff/app/javascript/components/SeedLibrary.test.jsx
T

259 lines
9.4 KiB
React

import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {beforeEach, describe, expect, it, vi} from 'vitest';
import SeedLibrary from './SeedLibrary';
vi.mock('../islands', () => ({mountIslands: vi.fn(), unmountIslands: vi.fn()}));
vi.mock('./CropPicker', () => ({
default: ({label, onChoose}) => <button type="button" onClick={() => onChoose({slug: 'kale'})}>{label}</button>,
}));
const props = {
tabs: '<nav><a href="/seeds?tab=mine">Mine</a></nav>',
html: '<p>First results</p><ul class="pagination"><li><a href="/seeds?page=2">2</a></li></ul>',
groups: [['crop', 'Crop'], ['year', 'Year']],
filters: [
{
label: 'Source',
chips: [
{label: 'All', params: {from: null, heirloom: null}},
{label: 'Bought', params: {from: 'bought'}},
{label: 'Heirloom', params: {heirloom: 'heirloom'}},
],
},
{label: 'Planted', chips: [{label: 'Not planted yet', params: {planted: 'no'}}]},
],
months: [[1, 'January'], [9, 'September']],
sow_now: 'Sow now (November)',
gardeners: ['Amy', 'Zed'],
clear: {from: null, heirloom: null, sow_month: null, crop: null},
crop: null,
crop_search: true,
placeholder: 'Find a crop',
};
function stubFetch(body = {tabs: '<nav />', results: '<p>Next results</p>', crop: null}) {
const fetch = vi.fn(async () => ({ok: true, status: 200, json: async () => body}));
vi.stubGlobal('fetch', fetch);
return fetch;
}
function setup(overrides = {}) {
render(<SeedLibrary {...props} {...overrides} />);
}
describe('SeedLibrary', () => {
beforeEach(() => {
window.history.replaceState({}, '', '/seeds');
});
it('shows the tabs Rails rendered', () => {
setup();
expect(screen.getByRole('link', {name: 'Mine'})).toBeInTheDocument();
});
it('shows the results Rails rendered', () => {
setup();
expect(screen.getByText('First results')).toBeInTheDocument();
});
it('offers to group', () => {
setup();
expect(screen.getByRole('link', {name: 'Year'})).toHaveAttribute('href', '/seeds?group=year');
});
it('groups by crop to start with', () => {
setup();
expect(screen.getByRole('link', {name: 'Crop'})).toHaveAttribute('aria-current', 'true');
});
it('shows cards to start with', () => {
setup();
expect(screen.getByRole('link', {name: 'Cards'})).toHaveAttribute('aria-current', 'true');
});
it('offers the list view', () => {
setup();
expect(screen.getByRole('link', {name: 'List'})).toHaveAttribute('href', '/seeds?view=list');
});
it('fetches the new results when a chip is clicked', async () => {
const fetch = stubFetch();
setup();
await userEvent.click(screen.getByRole('link', {name: 'Heirloom'}));
expect(fetch).toHaveBeenCalledWith('/seeds?heirloom=heirloom', expect.objectContaining({headers: expect.objectContaining({'X-Seed-Library': 'results'})}));
});
it('shows the new results without leaving the page', async () => {
stubFetch();
setup();
await userEvent.click(screen.getByRole('link', {name: 'Heirloom'}));
expect(await screen.findByText('Next results')).toBeInTheDocument();
});
it('changes the address', async () => {
stubFetch();
setup();
await userEvent.click(screen.getByRole('link', {name: 'Heirloom'}));
await screen.findByText('Next results');
expect(window.location.search).toBe('?heirloom=heirloom');
});
it('marks the chip that is chosen', async () => {
stubFetch();
setup();
await userEvent.click(screen.getByRole('link', {name: 'Heirloom'}));
await screen.findByText('Next results');
expect(screen.getByRole('link', {name: 'Heirloom'})).toHaveClass('active');
});
it('follows links in the results that stay on the page', async () => {
const fetch = stubFetch();
setup();
window.HTMLElement.prototype.scrollIntoView = vi.fn();
await userEvent.click(screen.getByRole('link', {name: '2'}));
expect(fetch).toHaveBeenCalledWith('/seeds?page=2', expect.anything());
});
it('searches by the crop chosen', async () => {
const fetch = stubFetch();
setup();
await userEvent.click(screen.getByRole('button', {name: 'Search seeds by crop'}));
expect(fetch).toHaveBeenCalledWith('/seeds?crop=kale', expect.anything());
});
describe('with a crop chosen', () => {
it('shows the crop in place of the search', () => {
setup({crop: {name: 'Kale'}});
expect(screen.getByText('Kale')).toBeInTheDocument();
});
it('can clear it', async () => {
const fetch = stubFetch();
setup({crop: {name: 'Kale'}});
await userEvent.click(screen.getByRole('link', {name: /Clear crop/}));
expect(fetch).toHaveBeenCalledWith('/seeds', expect.anything());
});
});
describe('without a crop search', () => {
it('leaves out the search', () => {
setup({crop_search: false});
expect(screen.queryByRole('button', {name: 'Search seeds by crop'})).not.toBeInTheDocument();
});
});
describe('filters', () => {
it('labels the crop filter', () => {
setup();
expect(screen.getByText('Crop', {selector: '.seed-field-label'})).toBeInTheDocument();
});
it('labels the source filter', () => {
setup();
expect(screen.getByText('Source')).toBeInTheDocument();
});
it('labels the month to sow filter', () => {
setup();
expect(screen.getByLabelText('Month to sow')).toBeInTheDocument();
});
it('labels the planted filter', () => {
setup();
expect(screen.getByText('Planted', {selector: '.seed-field-label'})).toBeInTheDocument();
});
it('adds the planted filter to the others in force', () => {
window.history.replaceState({}, '', '/seeds?from=bought');
setup();
expect(screen.getByRole('link', {name: 'Not planted yet'})).toHaveAttribute('href', '/seeds?from=bought&planted=no');
});
it('offers sow now among the months', () => {
setup();
expect(screen.getByRole('option', {name: 'Sow now (November)'})).toHaveValue('now');
});
it('fetches the seeds sown now with the other filters kept', async () => {
const fetch = stubFetch();
window.history.replaceState({}, '', '/seeds?from=bought');
setup();
await userEvent.selectOptions(screen.getByLabelText('Month to sow'), 'Sow now (November)');
expect(fetch).toHaveBeenCalledWith('/seeds?from=bought&sow_month=now', expect.anything());
});
it('suggests the gardeners given', () => {
setup();
expect(screen.getByLabelText('Gardener')).toHaveAttribute('list', 'seed-gardeners');
});
it('fetches a gardener typed and entered, keeping the other filters', async () => {
const fetch = stubFetch();
window.history.replaceState({}, '', '/seeds?from=bought');
setup();
await userEvent.type(screen.getByLabelText('Gardener'), 'Amy{Enter}');
expect(fetch).toHaveBeenCalledWith('/seeds?from=bought&gardener=Amy', expect.anything());
});
it('takes the gardener off when the name is cleared', async () => {
const fetch = stubFetch();
window.history.replaceState({}, '', '/seeds?gardener=Amy');
setup();
await userEvent.clear(screen.getByLabelText('Gardener'));
await userEvent.tab();
expect(fetch).toHaveBeenCalledWith('/seeds', expect.anything());
});
it('leaves out the gardener search on a member\'s own seeds', () => {
setup({gardeners: null});
expect(screen.queryByLabelText('Gardener')).not.toBeInTheDocument();
});
it('keeps the filters in force when another is chosen', () => {
window.history.replaceState({}, '', '/seeds?from=bought&group=year');
setup();
expect(screen.getByRole('link', {name: 'Heirloom'})).toHaveAttribute('href', '/seeds?from=bought&group=year&heirloom=heirloom');
});
it('takes a chosen chip off when it is chosen again', () => {
window.history.replaceState({}, '', '/seeds?from=bought&heirloom=heirloom');
setup();
expect(screen.getByRole('link', {name: 'Heirloom'})).toHaveAttribute('href', '/seeds?from=bought');
});
it('takes the source off with all', () => {
window.history.replaceState({}, '', '/seeds?from=bought&heirloom=heirloom&group=year');
setup();
expect(screen.getByRole('link', {name: 'All'})).toHaveAttribute('href', '/seeds?group=year');
});
it('keeps the filters when grouping', () => {
window.history.replaceState({}, '', '/seeds?from=bought');
setup();
expect(screen.getByRole('link', {name: 'Year'})).toHaveAttribute('href', '/seeds?from=bought&group=year');
});
it('offers to clear all the filters when one is in force', () => {
window.history.replaceState({}, '', '/seeds?from=bought&group=year');
setup();
expect(screen.getByRole('link', {name: 'Clear all filters'})).toHaveAttribute('href', '/seeds?group=year');
});
it('does not offer to clear when nothing is filtered', () => {
window.history.replaceState({}, '', '/seeds?group=year');
setup();
expect(screen.queryByRole('link', {name: 'Clear all filters'})).not.toBeInTheDocument();
});
it('fetches with the month chosen and the other filters kept', async () => {
const fetch = stubFetch();
window.history.replaceState({}, '', '/seeds?from=bought');
setup();
await userEvent.selectOptions(screen.getByLabelText('Month to sow'), 'September');
expect(fetch).toHaveBeenCalledWith('/seeds?from=bought&sow_month=9', expect.anything());
});
});
});