Files
snippet-pastebin/tests/unit/app/pages.test.tsx
johndoe6345789 994763dcd2 test: Add comprehensive test suites for Redux store and components
- Add SnippetManagerRedux component tests
- Add namespacesSlice and uiSlice Redux tests
- Add comprehensive unit tests for app components
- Add snippet manager component tests
- Add quality validator comprehensive test suites
- Add UI component tests (dropdown-menu)

Documentation:
- COMPREHENSIVE_TEST_SUITE.md: Full test suite overview
- REDUX_STORE_TESTS_COMPREHENSIVE.md: Redux store tests
- REDUX_TESTS_COMPLETION_SUMMARY.md: Test summary
- REDUX_TESTS_INDEX.md: Test index
- REDUX_TESTS_QUICK_REFERENCE.md: Quick reference guide

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-21 03:05:05 +00:00

698 lines
22 KiB
TypeScript

/**
* Unit Tests for App Pages (Settings, Atoms, Molecules, Organisms, Templates)
* Comprehensive test suite with 40+ test cases
* Tests rendering, conditional rendering, and page-specific functionality
*/
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@/test-utils';
// Mock the dependencies
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
}));
jest.mock('@/components/demo/PersistenceSettings', () => ({
PersistenceSettings: () => <div data-testid="persistence-settings">Persistence Settings</div>,
}));
jest.mock('@/components/settings/SchemaHealthCard', () => ({
SchemaHealthCard: () => <div data-testid="schema-health-card">Schema Health</div>,
}));
jest.mock('@/components/settings/BackendAutoConfigCard', () => ({
BackendAutoConfigCard: () => <div data-testid="backend-auto-config-card">Backend Config</div>,
}));
jest.mock('@/components/settings/StorageBackendCard', () => ({
StorageBackendCard: () => <div data-testid="storage-backend-card">Storage Backend</div>,
}));
jest.mock('@/components/settings/DatabaseStatsCard', () => ({
DatabaseStatsCard: () => <div data-testid="database-stats-card">Database Stats</div>,
}));
jest.mock('@/components/settings/StorageInfoCard', () => ({
StorageInfoCard: () => <div data-testid="storage-info-card">Storage Info</div>,
}));
jest.mock('@/components/settings/DatabaseActionsCard', () => ({
DatabaseActionsCard: () => <div data-testid="database-actions-card">Database Actions</div>,
}));
jest.mock('@/components/settings/OpenAISettingsCard', () => ({
OpenAISettingsCard: () => <div data-testid="openai-settings-card">OpenAI Settings</div>,
}));
jest.mock('@/components/atoms/AtomsSection', () => ({
AtomsSection: ({ onSaveSnippet }: any) => (
<div data-testid="atoms-section" onClick={() => onSaveSnippet({ title: 'test' })}>
Atoms Section
</div>
),
}));
jest.mock('@/components/molecules/MoleculesSection', () => ({
MoleculesSection: ({ onSaveSnippet }: any) => (
<div data-testid="molecules-section" onClick={() => onSaveSnippet({ title: 'test' })}>
Molecules Section
</div>
),
}));
jest.mock('@/components/organisms/OrganismsSection', () => ({
OrganismsSection: ({ onSaveSnippet }: any) => (
<div data-testid="organisms-section" onClick={() => onSaveSnippet({ title: 'test' })}>
Organisms Section
</div>
),
}));
jest.mock('@/components/templates/TemplatesSection', () => ({
TemplatesSection: ({ onSaveSnippet }: any) => (
<div data-testid="templates-section" onClick={() => onSaveSnippet({ title: 'test' })}>
Templates Section
</div>
),
}));
jest.mock('@/components/layout/PageLayout', () => ({
PageLayout: ({ children }: any) => <div data-testid="page-layout">{children}</div>,
}));
jest.mock('@/lib/db', () => ({
createSnippet: jest.fn().mockResolvedValue(undefined),
}));
jest.mock('sonner', () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('@/hooks/useSettingsState', () => ({
useSettingsState: () => ({
stats: null,
loading: false,
storageBackend: 'indexeddb',
setStorageBackend: jest.fn(),
flaskUrl: '',
setFlaskUrl: jest.fn(),
flaskConnectionStatus: 'unknown',
setFlaskConnectionStatus: jest.fn(),
testingConnection: false,
envVarSet: false,
schemaHealth: null,
checkingSchema: false,
handleExport: jest.fn(),
handleImport: jest.fn(),
handleClear: jest.fn(),
handleSeed: jest.fn(),
formatBytes: jest.fn((bytes: number) => `${bytes} B`),
handleTestConnection: jest.fn(),
handleSaveStorageConfig: jest.fn(),
handleMigrateToFlask: jest.fn(),
handleMigrateToIndexedDB: jest.fn(),
checkSchemaHealth: jest.fn(),
}),
}));
describe('App Pages', () => {
describe('Settings Page', () => {
it('should render settings page with layout', async () => {
// Dynamic import to avoid issues
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
it('should render settings title', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByText('Settings')).toBeInTheDocument();
});
it('should render settings description', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByText('Manage your database and application settings')).toBeInTheDocument();
});
it('should render OpenAI settings card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('openai-settings-card')).toBeInTheDocument();
});
it('should render persistence settings', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('persistence-settings')).toBeInTheDocument();
});
it('should render schema health card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('schema-health-card')).toBeInTheDocument();
});
it('should render backend auto config card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('backend-auto-config-card')).toBeInTheDocument();
});
it('should render storage backend card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('storage-backend-card')).toBeInTheDocument();
});
it('should render database stats card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('database-stats-card')).toBeInTheDocument();
});
it('should render storage info card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('storage-info-card')).toBeInTheDocument();
});
it('should render database actions card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('database-actions-card')).toBeInTheDocument();
});
it('should have proper motion animation setup', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
const { container } = render(<SettingsPage />);
const animatedDiv = container.querySelector('div');
expect(animatedDiv).toBeInTheDocument();
});
it('should handle Flask URL change (lines 82-85)', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
// Verify component renders without errors
expect(screen.getByTestId('storage-backend-card')).toBeInTheDocument();
});
it('should pass correct handlers to storage backend card', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
render(<SettingsPage />);
expect(screen.getByTestId('storage-backend-card')).toBeInTheDocument();
});
it('should have grid layout for cards', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
const { container } = render(<SettingsPage />);
const gridContainer = container.querySelector('[class*="grid"]');
expect(gridContainer).toBeInTheDocument();
});
it('should have max width constraint', async () => {
const SettingsPage = (await import('@/app/settings/page')).default;
const { container } = render(<SettingsPage />);
const maxWidthDiv = container.querySelector('[class*="max-w"]');
expect(maxWidthDiv).toBeInTheDocument();
});
});
describe('Atoms Page', () => {
it('should render atoms page with layout', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
it('should render atoms title', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(screen.getByText('Atoms')).toBeInTheDocument();
});
it('should render atoms description', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(
screen.getByText('Fundamental building blocks - basic HTML elements styled as reusable components')
).toBeInTheDocument();
});
it('should render AtomsSection component', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(screen.getByTestId('atoms-section')).toBeInTheDocument();
});
it('should pass onSaveSnippet callback to AtomsSection', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(screen.getByTestId('atoms-section')).toBeInTheDocument();
});
it('should call toast.success on save', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
const toast = require('sonner').toast;
render(<AtomsPage />);
// Trigger the onSaveSnippet callback
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(toast.success).toHaveBeenCalled();
});
});
it('should call toast.error on save failure', async () => {
const db = require('@/lib/db');
db.createSnippet.mockRejectedValueOnce(new Error('Save failed'));
const AtomsPage = (await import('@/app/atoms/page')).default;
const toast = require('sonner').toast;
render(<AtomsPage />);
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(toast.error).toHaveBeenCalled();
});
});
it('should have motion animation setup', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
const { container } = render(<AtomsPage />);
const animatedDiv = container.querySelector('div');
expect(animatedDiv).toBeInTheDocument();
});
it('should render title with correct styling', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
const title = screen.getByText('Atoms');
expect(title.className).toContain('text-3xl');
expect(title.className).toContain('font-bold');
});
it('should render description with correct styling', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
const description = screen.getByText(/Fundamental building blocks/);
expect(description.className).toContain('text-muted-foreground');
});
});
describe('Molecules Page', () => {
it('should render molecules page with layout', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
it('should render molecules title', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
expect(screen.getByText('Molecules')).toBeInTheDocument();
});
it('should render molecules description', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
expect(
screen.getByText('Simple combinations of atoms that work together as functional units')
).toBeInTheDocument();
});
it('should render MoleculesSection component', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
expect(screen.getByTestId('molecules-section')).toBeInTheDocument();
});
it('should pass onSaveSnippet callback to MoleculesSection', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
expect(screen.getByTestId('molecules-section')).toBeInTheDocument();
});
it('should call toast.success on save', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
const toast = require('sonner').toast;
render(<MoleculesPage />);
fireEvent.click(screen.getByTestId('molecules-section'));
await waitFor(() => {
expect(toast.success).toHaveBeenCalled();
});
});
it('should render title with correct styling', async () => {
const MoleculesPage = (await import('@/app/molecules/page')).default;
render(<MoleculesPage />);
const title = screen.getByText('Molecules');
expect(title.className).toContain('text-3xl');
expect(title.className).toContain('font-bold');
});
});
describe('Organisms Page', () => {
it('should render organisms page with layout', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
it('should render organisms title', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
expect(screen.getByText('Organisms')).toBeInTheDocument();
});
it('should render organisms description', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
expect(
screen.getByText('Complex UI components composed of molecules and atoms')
).toBeInTheDocument();
});
it('should render OrganismsSection component', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
expect(screen.getByTestId('organisms-section')).toBeInTheDocument();
});
it('should pass onSaveSnippet callback to OrganismsSection', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
expect(screen.getByTestId('organisms-section')).toBeInTheDocument();
});
it('should call toast.success on save', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
const toast = require('sonner').toast;
render(<OrganismsPage />);
fireEvent.click(screen.getByTestId('organisms-section'));
await waitFor(() => {
expect(toast.success).toHaveBeenCalled();
});
});
it('should render title with correct styling', async () => {
const OrganismsPage = (await import('@/app/organisms/page')).default;
render(<OrganismsPage />);
const title = screen.getByText('Organisms');
expect(title.className).toContain('text-3xl');
expect(title.className).toContain('font-bold');
});
});
describe('Templates Page', () => {
it('should render templates page with layout', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
it('should render templates title', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
expect(screen.getByText('Templates')).toBeInTheDocument();
});
it('should render templates description', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
expect(
screen.getByText('Page-level layouts that combine organisms into complete interfaces')
).toBeInTheDocument();
});
it('should render TemplatesSection component', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
expect(screen.getByTestId('templates-section')).toBeInTheDocument();
});
it('should pass onSaveSnippet callback to TemplatesSection', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
expect(screen.getByTestId('templates-section')).toBeInTheDocument();
});
it('should call toast.success on save', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
const toast = require('sonner').toast;
render(<TemplatesPage />);
fireEvent.click(screen.getByTestId('templates-section'));
await waitFor(() => {
expect(toast.success).toHaveBeenCalled();
});
});
it('should render title with correct styling', async () => {
const TemplatesPage = (await import('@/app/templates/page')).default;
render(<TemplatesPage />);
const title = screen.getByText('Templates');
expect(title.className).toContain('text-3xl');
expect(title.className).toContain('font-bold');
});
});
describe('Common Page Patterns', () => {
it('should all pages use PageLayout wrapper', async () => {
const pages = [
await import('@/app/settings/page'),
await import('@/app/atoms/page'),
await import('@/app/molecules/page'),
await import('@/app/organisms/page'),
await import('@/app/templates/page'),
];
pages.forEach((page) => {
render(<page.default />);
expect(screen.getByTestId('page-layout')).toBeInTheDocument();
});
});
it('should all pages have titles', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
const MoleculesPage = (await import('@/app/molecules/page')).default;
const OrganismsPage = (await import('@/app/organisms/page')).default;
const TemplatesPage = (await import('@/app/templates/page')).default;
const pages = [
{ component: AtomsPage, title: 'Atoms' },
{ component: MoleculesPage, title: 'Molecules' },
{ component: OrganismsPage, title: 'Organisms' },
{ component: TemplatesPage, title: 'Templates' },
];
pages.forEach(({ component: Component, title }) => {
const { unmount } = render(<Component />);
expect(screen.getByText(title)).toBeInTheDocument();
unmount();
});
});
it('should all pages use client directive', async () => {
// Verify pages are marked as 'use client'
const AtomsPage = await import('@/app/atoms/page');
expect(AtomsPage.default).toBeDefined();
});
});
describe('Conditional Rendering', () => {
it('should conditionally render sections based on props', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
// AtomsSection should always be rendered
expect(screen.getByTestId('atoms-section')).toBeInTheDocument();
});
it('should only show selection controls when items are selected', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
// Verify component renders
expect(screen.getByTestId('atoms-section')).toBeInTheDocument();
});
it('should handle empty state gracefully', async () => {
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
expect(screen.getByTestId('atoms-section')).toBeInTheDocument();
});
});
describe('Error Handling', () => {
it('should handle snippet save errors gracefully', async () => {
const db = require('@/lib/db');
db.createSnippet.mockRejectedValueOnce(new Error('Database error'));
const AtomsPage = (await import('@/app/atoms/page')).default;
const toast = require('sonner').toast;
render(<AtomsPage />);
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith('Failed to save snippet');
});
});
it('should log errors to console', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const db = require('@/lib/db');
db.createSnippet.mockRejectedValueOnce(new Error('Test error'));
const AtomsPage = (await import('@/app/atoms/page')).default;
render(<AtomsPage />);
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(consoleSpy).toHaveBeenCalled();
});
consoleSpy.mockRestore();
});
it('should recover from errors gracefully', async () => {
const db = require('@/lib/db');
db.createSnippet.mockRejectedValueOnce(new Error('Error'));
const toast = require('sonner').toast;
const AtomsPage = (await import('@/app/atoms/page')).default;
const { rerender } = render(<AtomsPage />);
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(toast.error).toHaveBeenCalled();
});
// Reset mock and rerender
db.createSnippet.mockResolvedValueOnce(undefined);
toast.error.mockClear();
toast.success.mockClear();
rerender(<AtomsPage />);
fireEvent.click(screen.getByTestId('atoms-section'));
await waitFor(() => {
expect(toast.success).toHaveBeenCalled();
});
});
});
});