UNPKG

datajunction-ui

Version:
432 lines (376 loc) 12.3 kB
import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MyNodesSection } from '../MyNodesSection'; vi.mock('../MyWorkspacePage.css', () => ({})); vi.mock('../NodeList', () => ({ NodeList: ({ nodes, showUpdatedAt }) => ( <div data-testid="node-list"> {nodes.map(node => ( <div key={node.name}>{node.name}</div> ))} </div> ), })); vi.mock('../TypeGroupGrid', () => ({ TypeGroupGrid: ({ groupedData }) => ( <div data-testid="type-group-grid"> {groupedData.map(group => ( <div key={group.type}> {group.type}: {group.nodes.length} nodes </div> ))} </div> ), })); describe('<MyNodesSection />', () => { beforeEach(() => { // Default to list view for most tests; Group by Type tests manage localStorage themselves localStorage.setItem('workspace_groupByType', 'false'); }); const mockOwnedNodes = [ { name: 'default.owned_metric', type: 'METRIC', current: { displayName: 'Owned Metric', updatedAt: '2024-01-01' }, }, ]; const mockWatchedNodes = [ { name: 'default.watched_metric', type: 'METRIC', current: { displayName: 'Watched Metric', updatedAt: '2024-01-02' }, }, { name: 'default.owned_metric', // Also owned, should be filtered out type: 'METRIC', current: { displayName: 'Owned Metric', updatedAt: '2024-01-01' }, }, ]; const mockRecentlyEdited = [ { name: 'default.edited_metric', type: 'METRIC', current: { displayName: 'Edited Metric', updatedAt: '2024-01-03' }, }, ]; it('should render loading state', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={[]} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={true} /> </MemoryRouter>, ); expect(screen.queryByText('Owned (0)')).not.toBeInTheDocument(); }); it('should render empty state when no nodes', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={[]} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); expect(screen.getByText('No nodes yet.')).toBeInTheDocument(); expect(screen.getByText('Create a node')).toBeInTheDocument(); expect(screen.getByText('Claim ownership')).toBeInTheDocument(); }); it('should render tabs with node counts', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={mockWatchedNodes} recentlyEdited={mockRecentlyEdited} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); expect(screen.getByText('Owned')).toBeInTheDocument(); // Should only count watched nodes that aren't owned (1 watched-only) expect(screen.getByText('Watched (1)')).toBeInTheDocument(); expect(screen.getByText('Recent Edits')).toBeInTheDocument(); }); it('should show owned nodes by default', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={mockWatchedNodes} recentlyEdited={mockRecentlyEdited} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); expect(screen.getByText('default.owned_metric')).toBeInTheDocument(); expect( screen.queryByText('default.watched_metric'), ).not.toBeInTheDocument(); }); it('should switch to watched tab', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={mockWatchedNodes} recentlyEdited={mockRecentlyEdited} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); fireEvent.click(screen.getByText('Watched (1)')); // Should show watched-only nodes (not owned) expect(screen.getByText('default.watched_metric')).toBeInTheDocument(); expect(screen.queryByText('default.owned_metric')).not.toBeInTheDocument(); }); it('should switch to recent edits tab', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={mockWatchedNodes} recentlyEdited={mockRecentlyEdited} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); fireEvent.click(screen.getByText('Recent Edits')); expect(screen.getByText('default.edited_metric')).toBeInTheDocument(); }); it('should show empty state for active tab with no nodes', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); fireEvent.click(screen.getByText('Watched (0)')); expect(screen.getByText('No watched nodes')).toBeInTheDocument(); }); it('should limit displayed nodes to 8 in list view', () => { const manyNodes = Array.from({ length: 15 }, (_, i) => ({ name: `default.metric_${i}`, type: 'METRIC', current: { displayName: `Metric ${i}`, updatedAt: '2024-01-01' }, })); render( <MemoryRouter> <MyNodesSection ownedNodes={manyNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // Should show "+7 more" text expect(screen.getByText('+7 more')).toBeInTheDocument(); }); it('should link to browse page with username filter', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // The "View All →" link should have the username filter const viewAllLink = screen.getByText('View All →').closest('a'); expect(viewAllLink).toHaveAttribute( 'href', '/?ownedBy=test.user@example.com', ); }); it('should auto-switch to edited tab when no owned nodes but has recently edited', async () => { const { waitFor } = await import('@testing-library/react'); render( <MemoryRouter> <MyNodesSection ownedNodes={[]} watchedNodes={[]} recentlyEdited={mockRecentlyEdited} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // After effect fires, should auto-switch to edited tab await waitFor(() => { expect(screen.getByText('default.edited_metric')).toBeInTheDocument(); }); }); it('should show "No recent edits" when on edited tab with no recent edits', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); fireEvent.click(screen.getByText('Recent Edits')); expect(screen.getByText('No recent edits')).toBeInTheDocument(); }); it('should filter watched nodes to exclude owned nodes', () => { const ownedNodes = [ { name: 'node1', type: 'METRIC', current: {} }, { name: 'node2', type: 'METRIC', current: {} }, ]; const watchedNodes = [ { name: 'node1', type: 'METRIC', current: {} }, // Also owned { name: 'node3', type: 'METRIC', current: {} }, // Watched only ]; render( <MemoryRouter> <MyNodesSection ownedNodes={ownedNodes} watchedNodes={watchedNodes} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // Watched tab should only show 1 (watched-only nodes) expect(screen.getByText('Watched (1)')).toBeInTheDocument(); }); describe('Group by Type feature', () => { beforeEach(() => { localStorage.clear(); }); it('should show group by type toggle when nodes exist', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); expect(screen.getByLabelText('Group by Type')).toBeInTheDocument(); }); it('should not show toggle when no nodes', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={[]} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); expect(screen.queryByLabelText('Group by Type')).not.toBeInTheDocument(); }); it('should toggle between grouped and list view', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // Initially should show grouped view (default is true) expect(screen.getByTestId('type-group-grid')).toBeInTheDocument(); expect(screen.queryByTestId('node-list')).not.toBeInTheDocument(); // Click toggle to switch to list view const toggle = screen.getByLabelText('Group by Type'); fireEvent.click(toggle); // Should now show list view expect(screen.queryByTestId('type-group-grid')).not.toBeInTheDocument(); expect(screen.getByTestId('node-list')).toBeInTheDocument(); }); it('should persist toggle state to localStorage', () => { render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); const toggle = screen.getByLabelText('Group by Type'); fireEvent.click(toggle); expect(localStorage.getItem('workspace_groupByType')).toBe('false'); fireEvent.click(toggle); expect(localStorage.getItem('workspace_groupByType')).toBe('true'); }); it('should load toggle state from localStorage', () => { localStorage.setItem('workspace_groupByType', 'true'); render( <MemoryRouter> <MyNodesSection ownedNodes={mockOwnedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // Should show grouped view on mount expect(screen.getByTestId('type-group-grid')).toBeInTheDocument(); expect(screen.getByLabelText('Group by Type')).toBeChecked(); }); it('should group nodes by type correctly', () => { const mixedNodes = [ { name: 'metric1', type: 'metric', current: {} }, { name: 'metric2', type: 'metric', current: {} }, { name: 'dim1', type: 'dimension', current: {} }, { name: 'source1', type: 'source', current: {} }, ]; render( <MemoryRouter> <MyNodesSection ownedNodes={mixedNodes} watchedNodes={[]} recentlyEdited={[]} username="test.user@example.com" loading={false} /> </MemoryRouter>, ); // Default is grouped view (localStorage cleared by beforeEach → null → true) expect(screen.getByText('metric: 2 nodes')).toBeInTheDocument(); expect(screen.getByText('dimension: 1 nodes')).toBeInTheDocument(); expect(screen.getByText('source: 1 nodes')).toBeInTheDocument(); }); }); });