SplitSettingsCreator.test.tsx4.8 KBView on GitHub
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { SplitSettingsCreator, type SplitLabelOption } from '@/modules/threads/components/SplitSettingsCreator';

describe('SplitSettingsCreator', () => {
  it('saves form values without manual query inputs', async () => {
    const onSave = jest.fn();

    render(<SplitSettingsCreator onBack={() => {}} onSave={onSave} />);

    fireEvent.change(screen.getByPlaceholderText('Important'), {
      target: { value: 'Team' },
    });
    fireEvent.click(screen.getByText('Save split'));

    expect(onSave).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'Team',
        query: 'label:INBOX',
        alsoShowInImportant: false,
      }),
    );
  });

  /**
   * The label query helpers (labelToken / appendLabelToQuery / removeLabelFromQuery)
   * are module-private; we exercise them through the component's "Add label" and
   * per-label "Delete" affordances, asserting the resulting query that flows to onSave.
   */
  describe('label query surgery', () => {
    const LABELS: SplitLabelOption[] = [
      { id: 'l1', name: 'Receipts' },
      { id: 'l2', name: 'My Clients' }, // whitespace → must be quoted
      { id: 'l3', name: 'Newsletters' },
    ];

    function saveAndGetQuery(onSave: jest.Mock): string {
      fireEvent.click(screen.getByText('Save split'));
      return onSave.mock.calls.at(-1)![0].query as string;
    }

    it('quotes label names that contain whitespace', () => {
      const onSave = jest.fn();
      render(
        <SplitSettingsCreator onBack={() => {}} onSave={onSave} labels={LABELS} />,
      );
      fireEvent.change(screen.getByPlaceholderText('Important'), { target: { value: 'X' } });

      fireEvent.click(screen.getByText('Add label'));
      fireEvent.click(screen.getByText('My Clients'));

      // No prior query → the quoted token replaces the throwaway default.
      expect(saveAndGetQuery(onSave)).toBe('label:"My Clients"');
    });

    it('replaces the throwaway DEFAULT_QUERY rather than appending to it', () => {
      const onSave = jest.fn();
      render(
        <SplitSettingsCreator
          onBack={() => {}}
          onSave={onSave}
          labels={LABELS}
          draft={{ query: 'label:INBOX' }}
        />,
      );
      fireEvent.change(screen.getByPlaceholderText('Important'), { target: { value: 'X' } });

      fireEvent.click(screen.getByText('Add label'));
      fireEvent.click(screen.getByText('Receipts'));

      // Starts from the default 'label:INBOX' → replaced wholesale, not AND'd.
      expect(saveAndGetQuery(onSave)).toBe('label:Receipts');
    });

    it('dedupes a label that is already present in the query', () => {
      const onSave = jest.fn();
      render(
        <SplitSettingsCreator
          onBack={() => {}}
          onSave={onSave}
          labels={LABELS}
          draft={{ query: 'is:unread label:Receipts' }}
        />,
      );
      fireEvent.change(screen.getByPlaceholderText('Important'), { target: { value: 'X' } });

      fireEvent.click(screen.getByText('Add label'));
      fireEvent.click(screen.getByText('Receipts'));

      // Already present → no duplicate token appended.
      expect(saveAndGetQuery(onSave)).toBe('is:unread label:Receipts');
    });

    it('appends a new label to an existing non-default query', () => {
      const onSave = jest.fn();
      render(
        <SplitSettingsCreator
          onBack={() => {}}
          onSave={onSave}
          labels={LABELS}
          draft={{ query: 'from:<email>' }}
        />,
      );
      fireEvent.change(screen.getByPlaceholderText('Important'), { target: { value: 'X' } });

      fireEvent.click(screen.getByText('Add label'));
      fireEvent.click(screen.getByText('Newsletters'));

      expect(saveAndGetQuery(onSave)).toBe('from:<email> label:Newsletters');
    });

    it('removes a label embedded mid-query when the label is deleted', async () => {
      const onSave = jest.fn();
      const onDeleteLabel = jest.fn().mockResolvedValue(undefined);
      render(
        <SplitSettingsCreator
          onBack={() => {}}
          onSave={onSave}
          labels={LABELS}
          onDeleteLabel={onDeleteLabel}
          draft={{ query: 'is:unread label:Receipts from:<email>' }}
        />,
      );
      fireEvent.change(screen.getByPlaceholderText('Important'), { target: { value: 'X' } });

      fireEvent.click(screen.getByText('Add label'));
      // Click the per-label delete affordance, then confirm.
      fireEvent.click(screen.getByLabelText('Delete Receipts'));
      fireEvent.click(screen.getByText('Delete'));

      await waitFor(() => expect(onDeleteLabel).toHaveBeenCalled());

      // The mid-query `label:Receipts` clause is excised; surrounding terms remain.
      expect(saveAndGetQuery(onSave)).toBe('is:unread from:<email>');
    });
  });
});