UNPKG

datajunction-ui

Version:
296 lines (245 loc) 8.68 kB
import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MaterializationsSection } from '../MaterializationsSection'; vi.mock('../MyWorkspacePage.css', () => ({})); describe('<MaterializationsSection />', () => { const now = Date.now(); const createMockNode = (name, hoursAgo, schedule = '@daily') => ({ name, type: 'CUBE', current: { displayName: name, availability: { validThroughTs: hoursAgo !== null ? now - hoursAgo * 60 * 60 * 1000 : null, table: 'warehouse.materialized_table', }, materializations: [{ name: 'mat1', schedule }], }, }); it('should render loading state', () => { render( <MemoryRouter> <MaterializationsSection nodes={[]} loading={true} /> </MemoryRouter>, ); expect( screen.queryByText('No materializations configured.'), ).not.toBeInTheDocument(); }); it('should render empty state when no materializations', () => { render( <MemoryRouter> <MaterializationsSection nodes={[]} loading={false} /> </MemoryRouter>, ); expect( screen.getByText('No materializations configured.'), ).toBeInTheDocument(); expect(screen.getByText('Materialize a node')).toBeInTheDocument(); expect( screen.getByText('Speed up queries with cached data'), ).toBeInTheDocument(); }); it('should render pending status for nodes without validThroughTs', () => { const pendingNode = createMockNode('pending_cube', null); render( <MemoryRouter> <MaterializationsSection nodes={[pendingNode]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('pending_cube')).toBeInTheDocument(); expect(screen.getByText('⏳ Pending')).toBeInTheDocument(); }); it('should render green status for recent materializations (< 24h)', () => { const freshNode = createMockNode('fresh_cube', 12); // 12 hours ago render( <MemoryRouter> <MaterializationsSection nodes={[freshNode]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('fresh_cube')).toBeInTheDocument(); expect(screen.getByText(/12h ago/)).toBeInTheDocument(); }); it('should render yellow status for stale materializations (24-72h)', () => { const staleNode = createMockNode('stale_cube', 48); // 48 hours ago render( <MemoryRouter> <MaterializationsSection nodes={[staleNode]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('stale_cube')).toBeInTheDocument(); expect(screen.getByText(/2d ago/)).toBeInTheDocument(); }); it('should render red status for very stale materializations (> 72h)', () => { const veryStaleNode = createMockNode('very_stale_cube', 168); // 7 days ago render( <MemoryRouter> <MaterializationsSection nodes={[veryStaleNode]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('very_stale_cube')).toBeInTheDocument(); expect(screen.getByText(/7d ago/)).toBeInTheDocument(); }); it('should sort nodes by validThroughTs (most recent first)', () => { const nodes = [ createMockNode('old_cube', 72), createMockNode('recent_cube', 12), createMockNode('middle_cube', 36), ]; render( <MemoryRouter> <MaterializationsSection nodes={nodes} loading={false} /> </MemoryRouter>, ); const allCubes = screen.getAllByText(/cube/); // Should be sorted: recent, middle, old expect(allCubes[0]).toHaveTextContent('recent_cube'); expect(allCubes[1]).toHaveTextContent('middle_cube'); expect(allCubes[2]).toHaveTextContent('old_cube'); }); it('should display materialization schedule', () => { const node = createMockNode('scheduled_cube', 12, '@hourly'); render( <MemoryRouter> <MaterializationsSection nodes={[node]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('🕐 @hourly')).toBeInTheDocument(); }); it('should display materialization table', () => { const node = createMockNode('cube_with_table', 12); render( <MemoryRouter> <MaterializationsSection nodes={[node]} loading={false} /> </MemoryRouter>, ); expect( screen.getByText('→ warehouse.materialized_table'), ).toBeInTheDocument(); }); it('should handle nodes with no schedule', () => { const nodeWithoutSchedule = { name: 'no_schedule_cube', type: 'CUBE', current: { displayName: 'no_schedule_cube', availability: { validThroughTs: now - 12 * 60 * 60 * 1000, }, materializations: [{ name: 'mat1' }], // No schedule }, }; render( <MemoryRouter> <MaterializationsSection nodes={[nodeWithoutSchedule]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('🕐 No schedule')).toBeInTheDocument(); }); it('should limit display to 5 nodes', () => { const manyNodes = Array.from({ length: 10 }, (_, i) => createMockNode(`cube_${i}`, 12), ); render( <MemoryRouter> <MaterializationsSection nodes={manyNodes} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('cube_0')).toBeInTheDocument(); expect(screen.getByText('cube_4')).toBeInTheDocument(); expect(screen.queryByText('cube_5')).not.toBeInTheDocument(); expect(screen.getByText('+5 more')).toBeInTheDocument(); }); it('should sort nodes with null validThroughTs after those with timestamps', () => { const pendingNode = createMockNode('pending_cube', null); const freshNode = createMockNode('fresh_cube', 12); render( <MemoryRouter> <MaterializationsSection nodes={[pendingNode, freshNode]} loading={false} /> </MemoryRouter>, ); const allCubes = screen.getAllByText(/cube/); // fresh (has timestamp) should appear before pending (null timestamp) expect(allCubes[0]).toHaveTextContent('fresh_cube'); expect(allCubes[1]).toHaveTextContent('pending_cube'); }); it('should keep relative order when both nodes have null validThroughTs', () => { const pending1 = createMockNode('pending_a', null); const pending2 = createMockNode('pending_b', null); render( <MemoryRouter> <MaterializationsSection nodes={[pending1, pending2]} loading={false} /> </MemoryRouter>, ); // Both pending — neither crashes, both appear expect(screen.getByText('pending_a')).toBeInTheDocument(); expect(screen.getByText('pending_b')).toBeInTheDocument(); }); it('should show "just now" for materializations updated less than 1 hour ago', () => { const justNowTs = now - 5 * 60 * 1000; // 5 minutes ago const justNowNode = { name: 'just_now_cube', type: 'CUBE', current: { displayName: 'just_now_cube', availability: { validThroughTs: justNowTs, }, materializations: [{ name: 'mat1', schedule: '@hourly' }], }, }; render( <MemoryRouter> <MaterializationsSection nodes={[justNowNode]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('🟢 just now')).toBeInTheDocument(); }); it('should handle undefined nodes gracefully', () => { render( <MemoryRouter> <MaterializationsSection nodes={undefined} loading={false} /> </MemoryRouter>, ); expect( screen.getByText('No materializations configured.'), ).toBeInTheDocument(); }); it('should handle nodes without availability', () => { const nodeWithoutAvailability = { name: 'no_availability_cube', type: 'CUBE', current: { displayName: 'no_availability_cube', materializations: [{ name: 'mat1', schedule: '@daily' }], }, }; render( <MemoryRouter> <MaterializationsSection nodes={[nodeWithoutAvailability]} loading={false} /> </MemoryRouter>, ); expect(screen.getByText('no_availability_cube')).toBeInTheDocument(); expect(screen.getByText('⏳ Pending')).toBeInTheDocument(); }); it('should link to nodes with materialization filter', () => { render( <MemoryRouter> <MaterializationsSection nodes={[]} loading={false} /> </MemoryRouter>, ); const viewAllLink = screen.getByText('View All →').closest('a'); expect(viewAllLink).toHaveAttribute('href', '/?hasMaterialization=true'); }); });