UNPKG

datajunction-ui

Version:
340 lines (298 loc) 9.6 kB
import React from 'react'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { MyWorkspacePage } from '../index'; import * as useWorkspaceData from '../../../hooks/useWorkspaceData'; import * as UserProvider from '../../../providers/UserProvider'; // Mock CSS imports vi.mock('../MyWorkspacePage.css', () => ({})); vi.mock('../../../../styles/settings.css', () => ({})); // Mock child components to test integration vi.mock('../NotificationsSection', () => ({ NotificationsSection: ({ notifications, loading }) => ( <div data-testid="notifications-section"> {loading ? 'Loading...' : `${notifications.length} notifications`} </div> ), })); vi.mock('../NeedsAttentionSection', () => ({ NeedsAttentionSection: ({ loading }) => ( <div data-testid="needs-attention-section"> {loading ? 'Loading...' : 'Needs Attention'} </div> ), })); vi.mock('../MyNodesSection', () => ({ MyNodesSection: ({ ownedNodes, loading }) => ( <div data-testid="my-nodes-section"> {loading ? 'Loading...' : `${ownedNodes.length} owned nodes`} </div> ), })); vi.mock('../MaterializationsSection', () => ({ MaterializationsSection: ({ nodes, loading }) => ( <div data-testid="materializations-section"> {loading ? 'Loading...' : `${nodes.length} materializations`} </div> ), })); vi.mock('../ActiveBranchesSection', () => ({ ActiveBranchesSection: ({ loading }) => ( <div data-testid="active-branches-section"> {loading ? 'Loading...' : 'Active Branches'} </div> ), })); describe('<MyWorkspacePage />', () => { const mockCurrentUser = { username: 'test.user@example.com', email: 'test.user@example.com', }; const mockOwnedNodes = [ { name: 'default.test_metric', type: 'METRIC', current: { displayName: 'Test Metric', updatedAt: '2024-01-01' }, }, { name: 'default.test_dimension', type: 'DIMENSION', current: { displayName: 'Test Dimension', updatedAt: '2024-01-02' }, }, ]; const mockNotifications = [ { entity_name: 'default.test_metric', entity_type: 'node', activity_type: 'update', created_at: '2024-01-01T00:00:00Z', user: 'test.user@example.com', node_type: 'metric', display_name: 'Test Metric', }, ]; const mockMaterializations = [ { name: 'default.test_cube', type: 'CUBE', current: { displayName: 'Test Cube', availability: { validThroughTs: Date.now() - 1000 * 60 * 60, // 1 hour ago }, materializations: [{ name: 'mat1', schedule: '@daily' }], }, }, ]; const makeLoadingStates = (loading = false) => ({ myNodes: loading, collections: loading, notifications: loading, materializations: loading, needsAttention: loading, namespace: loading, }); const makeDashboardData = (overrides = {}) => ({ data: { ownedNodes: [], ownedHasMore: {}, recentlyEdited: [], editedHasMore: {}, watchedNodes: [], notifications: [], materializedNodes: [], needsAttention: { nodesMissingDescription: [], invalidNodes: [], staleDrafts: [], orphanedDimensions: [], }, hasPersonalNamespace: true, ...overrides, }, loadingStates: makeLoadingStates(false), }); beforeEach(() => { vi.clearAllMocks(); }); it('should render loading state', () => { vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: null, loading: true, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData(), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); expect(screen.getByText('Dashboard')).toBeInTheDocument(); // Sections are not shown while user is loading expect(screen.queryByTestId('my-nodes-section')).not.toBeInTheDocument(); }); it('should render all sections when data is loaded', () => { vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ ownedNodes: mockOwnedNodes, notifications: mockNotifications, materializedNodes: mockMaterializations, }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); expect(screen.getByTestId('my-nodes-section')).toHaveTextContent( '2 owned nodes', ); expect(screen.getByTestId('notifications-section')).toHaveTextContent( '1 notifications', ); expect(screen.getByTestId('materializations-section')).toHaveTextContent( '1 materializations', ); expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); expect(screen.getByTestId('active-branches-section')).toBeInTheDocument(); }); it('should pass correct props to sections', () => { vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ ownedNodes: mockOwnedNodes }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); expect(screen.getByTestId('my-nodes-section')).toBeInTheDocument(); expect(screen.getByTestId('notifications-section')).toBeInTheDocument(); expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); it('should filter out non-stale materializations from NeedsAttention', () => { const now = Date.now(); const freshNode = { name: 'default.fresh_cube', type: 'CUBE', current: { availability: { validThroughTs: now - 1000 * 60 * 60 * 10, // 10 hours ago (< 72h, not stale) }, }, }; vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ materializedNodes: [freshNode] }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); // Page renders without crash — stale filter ran without errors expect(screen.getByTestId('materializations-section')).toHaveTextContent( '1 materializations', ); expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); it('should exclude nodes with null validThroughTs from stale list', () => { const pendingNode = { name: 'default.pending_cube', type: 'CUBE', current: { availability: { validThroughTs: null, }, }, }; vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ materializedNodes: [pendingNode] }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); // Pending node (null validThroughTs) should not be passed as stale expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); it('should exclude nodes with no availability from stale list', () => { const noAvailabilityNode = { name: 'default.no_avail_cube', type: 'CUBE', current: {}, }; vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ materializedNodes: [noAvailabilityNode] }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); it('should derive personal namespace from username', () => { vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: { username: 'jane.doe@company.com' }, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData(), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); // NeedsAttentionSection mock receives personalNamespace="users.jane.doe" expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); it('should calculate stale materializations correctly', () => { const now = Date.now(); const staleNode = { name: 'default.stale_cube', type: 'CUBE', current: { displayName: 'Stale Cube', availability: { validThroughTs: now - 1000 * 60 * 60 * 80, // 80 hours ago (> 72 hours) }, }, }; vi.spyOn(UserProvider, 'useCurrentUser').mockReturnValue({ currentUser: mockCurrentUser, loading: false, }); vi.spyOn(useWorkspaceData, 'useWorkspaceDashboardData').mockReturnValue( makeDashboardData({ materializedNodes: [staleNode] }), ); render( <MemoryRouter> <MyWorkspacePage /> </MemoryRouter>, ); // The stale materialization should be passed to NeedsAttentionSection expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument(); }); });