- Add useLoadingState hook with data/loading/error state management - Add useSimpleLoading hook for basic boolean loading state - Add useMultipleLoadingStates hook for tracking multiple items - Create Skeleton component with shimmer animation for placeholders - Create specialized skeletons: SkeletonText, SkeletonAvatar, SkeletonCard, SkeletonListItem, SkeletonBeneficiaryCard, SkeletonSensorCard - Create LoadingOverlay components: modal, inline, and card variants - Create ScreenLoading wrapper with loading/error/content states - Create RefreshableScreen with built-in pull-to-refresh - Create EmptyState and LoadingButtonState utility components - Add comprehensive tests for all components and hooks (61 tests passing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
169 lines
4.7 KiB
TypeScript
169 lines
4.7 KiB
TypeScript
import React from 'react';
|
|
import { Text, View } from 'react-native';
|
|
import { render, fireEvent } from '@testing-library/react-native';
|
|
import {
|
|
ScreenLoading,
|
|
RefreshableScreen,
|
|
EmptyState,
|
|
LoadingButtonState,
|
|
} from '@/components/ui/ScreenLoading';
|
|
|
|
describe('ScreenLoading', () => {
|
|
it('renders children when not loading and no error', () => {
|
|
const { getByText } = render(
|
|
<ScreenLoading isLoading={false} error={null}>
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(getByText('Content')).toBeTruthy();
|
|
});
|
|
|
|
it('renders loading state when isLoading is true', () => {
|
|
const { getByText } = render(
|
|
<ScreenLoading isLoading={true} error={null}>
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(getByText('Loading...')).toBeTruthy();
|
|
});
|
|
|
|
it('renders custom loading message', () => {
|
|
const { getByText } = render(
|
|
<ScreenLoading
|
|
isLoading={true}
|
|
error={null}
|
|
loadingMessage="Fetching data..."
|
|
>
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(getByText('Fetching data...')).toBeTruthy();
|
|
});
|
|
|
|
it('renders error state when error is provided', () => {
|
|
const { getByText } = render(
|
|
<ScreenLoading isLoading={false} error="Network error">
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(getByText('Something went wrong')).toBeTruthy();
|
|
expect(getByText('Network error')).toBeTruthy();
|
|
});
|
|
|
|
it('renders retry button when onRetry is provided', () => {
|
|
const onRetry = jest.fn();
|
|
const { getByText } = render(
|
|
<ScreenLoading isLoading={false} error="Error" onRetry={onRetry}>
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(getByText('Try Again')).toBeTruthy();
|
|
});
|
|
|
|
it('calls onRetry when retry button is pressed', () => {
|
|
const onRetry = jest.fn();
|
|
const { getByText } = render(
|
|
<ScreenLoading isLoading={false} error="Error" onRetry={onRetry}>
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
fireEvent.press(getByText('Try Again'));
|
|
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not render retry button when onRetry is not provided', () => {
|
|
const { queryByText } = render(
|
|
<ScreenLoading isLoading={false} error="Error">
|
|
<Text>Content</Text>
|
|
</ScreenLoading>
|
|
);
|
|
expect(queryByText('Try Again')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('RefreshableScreen', () => {
|
|
it('renders children', () => {
|
|
const onRefresh = jest.fn();
|
|
const { getByText } = render(
|
|
<RefreshableScreen isRefreshing={false} onRefresh={onRefresh}>
|
|
<Text>Content</Text>
|
|
</RefreshableScreen>
|
|
);
|
|
expect(getByText('Content')).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('EmptyState', () => {
|
|
it('renders title and description', () => {
|
|
const { getByText } = render(
|
|
<EmptyState
|
|
title="No items"
|
|
description="Add some items to get started"
|
|
/>
|
|
);
|
|
expect(getByText('No items')).toBeTruthy();
|
|
expect(getByText('Add some items to get started')).toBeTruthy();
|
|
});
|
|
|
|
it('renders action button when provided', () => {
|
|
const onAction = jest.fn();
|
|
const { getByText } = render(
|
|
<EmptyState
|
|
title="No items"
|
|
actionLabel="Add Item"
|
|
onAction={onAction}
|
|
/>
|
|
);
|
|
expect(getByText('Add Item')).toBeTruthy();
|
|
});
|
|
|
|
it('calls onAction when action button is pressed', () => {
|
|
const onAction = jest.fn();
|
|
const { getByText } = render(
|
|
<EmptyState
|
|
title="No items"
|
|
actionLabel="Add Item"
|
|
onAction={onAction}
|
|
/>
|
|
);
|
|
fireEvent.press(getByText('Add Item'));
|
|
expect(onAction).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not render action button when onAction is not provided', () => {
|
|
const { queryByText } = render(
|
|
<EmptyState title="No items" actionLabel="Add Item" />
|
|
);
|
|
expect(queryByText('Add Item')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('LoadingButtonState', () => {
|
|
it('renders children when not loading', () => {
|
|
const { getByText } = render(
|
|
<LoadingButtonState isLoading={false}>
|
|
<Text>Submit</Text>
|
|
</LoadingButtonState>
|
|
);
|
|
expect(getByText('Submit')).toBeTruthy();
|
|
});
|
|
|
|
it('renders loading state when isLoading is true', () => {
|
|
const { getByText } = render(
|
|
<LoadingButtonState isLoading={true}>
|
|
<Text>Submit</Text>
|
|
</LoadingButtonState>
|
|
);
|
|
expect(getByText('Loading...')).toBeTruthy();
|
|
});
|
|
|
|
it('renders custom loading text', () => {
|
|
const { getByText } = render(
|
|
<LoadingButtonState isLoading={true} loadingText="Saving...">
|
|
<Text>Submit</Text>
|
|
</LoadingButtonState>
|
|
);
|
|
expect(getByText('Saving...')).toBeTruthy();
|
|
});
|
|
});
|