timeline-block.test.tsx2.5 KBView on GitHub
/**
 * Tests for the `timeline` dashboard block — the only net-new piece of the
 * Follow-up SLA report. Two concerns:
 *   1. The grammar validates a spec containing a `timeline` block (and still
 *      rejects an unknown block type).
 *   2. `TimelineView` adapts mock `{ kind, at, title }` rows into the shape
 *      `ConversationActivityOverview` consumes and renders one dot per event.
 */

import { render, screen } from '@testing-library/react';
import { TooltipProvider } from '@/components/ui/tooltip';
import { parseDashboardSpec, type DashboardSpec } from '@/modules/dashboards/types/dashboard';
import { LayoutNode } from '@/modules/dashboards/components/blocks';
import { SourcesProvider } from '@/modules/dashboards/components/context';

describe('timeline dashboard block — validation', () => {
  it('accepts a spec containing a timeline block', () => {
    const raw = {
      spec_version: '2.0',
      id: 'sla_timeline',
      dataSources: {
        ev: {
          mock: [
            { kind: 'inbound_email', at: '2026-03-01T09:00:00Z', title: 'Intro' },
            { kind: 'outbound_email', at: '2026-03-01T10:00:00Z', title: 'Recap + CTA' },
          ],
        },
      },
      layout: { type: 'timeline', source: 'ev', title: 'Won deal — tight cadence' },
    };
    const spec = parseDashboardSpec(raw);
    expect(spec).not.toBeNull();
    expect((spec as DashboardSpec).layout.type).toBe('timeline');
  });

  it('rejects an unknown block type', () => {
    const raw = {
      spec_version: '2.0',
      id: 'bogus',
      dataSources: {},
      layout: { type: 'not-a-real-block' },
    };
    expect(parseDashboardSpec(raw)).toBeNull();
  });
});

describe('timeline dashboard block — render', () => {
  it('renders one dot per mock event', () => {
    const events = [
      { kind: 'inbound_email', at: '2026-03-01T09:00:00Z', title: 'Intro' },
      { kind: 'outbound_email', at: '2026-03-03T10:00:00Z', title: 'Recap + CTA' },
      { kind: 'meeting', at: '2026-03-10T15:00:00Z', title: 'Demo' },
    ];
    render(
      <TooltipProvider>
        <SourcesProvider value={{ ev: { rows: events, isLoading: false } }}>
          <LayoutNode node={{ type: 'timeline', source: 'ev', title: 'Won deal' }} />
        </SourcesProvider>
      </TooltipProvider>,
    );
    // Each event dot is a <button>; the "now" indicator is a div, so the button
    // count equals the number of events.
    expect(screen.getAllByRole('button')).toHaveLength(events.length);
    expect(screen.getByText('Won deal')).toBeInTheDocument();
  });
});