diff --git a/src/app/screens/HomeScreen.tsx b/src/app/screens/HomeScreen.tsx
index 62c9782..b2af104 100644
--- a/src/app/screens/HomeScreen.tsx
+++ b/src/app/screens/HomeScreen.tsx
@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react';
import { View, StyleSheet } from 'react-native';
+import { theme } from '../../theme';
import { PageHeader } from '../../components/layout/PageHeader';
import { TabBar, HomeTab } from '../../components/layout/TabBar';
import { ScreenContainer } from '../../components/layout/ScreenContainer';
@@ -25,7 +26,7 @@ export default function HomeScreen() {
{activeTab === 'myWork' ? (
-
+
) : (
)}
@@ -37,5 +38,6 @@ export default function HomeScreen() {
const styles = StyleSheet.create({
content: {
flex: 1,
+ backgroundColor: theme.colors.background,
},
});
diff --git a/src/app/tabs/MyWorkTab.test.tsx b/src/app/tabs/MyWorkTab.test.tsx
new file mode 100644
index 0000000..af1a757
--- /dev/null
+++ b/src/app/tabs/MyWorkTab.test.tsx
@@ -0,0 +1,131 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react-native';
+import { MyWorkTab } from './MyWorkTab';
+import { MyWorkChapter } from '../../types/db/types';
+
+jest.mock('@react-navigation/native', () => ({
+ useNavigation: () => ({
+ navigate: jest.fn(),
+ }),
+}));
+
+jest.mock('react-native-svg', () => {
+ const MockReact = require('react');
+ const { View } = require('react-native');
+ const MockSvg = ({ children }: { children?: unknown }) =>
+ MockReact.createElement(View, null, children);
+ return {
+ __esModule: true,
+ default: MockSvg,
+ Circle: MockSvg,
+ };
+});
+
+jest.mock('lucide-react-native', () => {
+ const MockReact = require('react');
+ const { View } = require('react-native');
+ const MockIcon = () => MockReact.createElement(View);
+ return {
+ BookOpen: MockIcon,
+ ListChecks: MockIcon,
+ Cloud: MockIcon,
+ CloudCheck: MockIcon,
+ CloudUpload: MockIcon,
+ Circle: MockIcon,
+ Mic: MockIcon,
+ UserCheck: MockIcon,
+ ChevronRight: MockIcon,
+ };
+});
+
+jest.mock('../../hooks/useMyWorkChapters', () => ({
+ useMyWorkChapters: jest.fn(),
+}));
+
+const { useMyWorkChapters } = jest.requireMock(
+ '../../hooks/useMyWorkChapters',
+) as {
+ useMyWorkChapters: jest.Mock;
+};
+
+const sampleChapter: MyWorkChapter = {
+ id: 10,
+ displayLabel: 'Luke 4',
+ bookName: 'Luke',
+ chapterNumber: 4,
+ workflowStage: 'draft',
+ syncState: 'synced',
+ completedVerses: 3,
+ totalVerses: 5,
+ downloadedVerses: 5,
+ lastActivityLabel: 'Jun 1, 2024',
+ projectName: 'Gospel of Luke',
+ targetLanguageName: 'Baka',
+};
+
+const notStartedChapter: MyWorkChapter = {
+ id: 11,
+ displayLabel: 'Luke 16',
+ bookName: 'Luke',
+ chapterNumber: 16,
+ workflowStage: 'not_started',
+ syncState: 'none',
+ completedVerses: 0,
+ totalVerses: 5,
+ downloadedVerses: 0,
+ projectName: 'Gospel of Luke',
+ targetLanguageName: 'Baka',
+};
+
+describe('MyWorkTab', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders empty state when there are no chapters', async () => {
+ useMyWorkChapters.mockReturnValue({
+ chapters: [],
+ loading: false,
+ refreshing: false,
+ refresh: jest.fn(),
+ });
+
+ render();
+
+ expect(
+ await screen.findByText(
+ "You don't have any chapters to work on right now. Check the Projects tab to find available work.",
+ ),
+ ).toBeTruthy();
+ });
+
+ it('renders chapter title, badge, and activity date', async () => {
+ useMyWorkChapters.mockReturnValue({
+ chapters: [sampleChapter],
+ loading: false,
+ refreshing: false,
+ refresh: jest.fn(),
+ });
+
+ render();
+
+ expect(await screen.findByText('Luke 4')).toBeTruthy();
+ expect(await screen.findByText('Draft')).toBeTruthy();
+ expect(await screen.findByText('Jun 1, 2024')).toBeTruthy();
+ });
+
+ it('renders not started badge when source is not downloaded', async () => {
+ useMyWorkChapters.mockReturnValue({
+ chapters: [notStartedChapter],
+ loading: false,
+ refreshing: false,
+ refresh: jest.fn(),
+ });
+
+ render();
+
+ expect(await screen.findByText('Luke 16')).toBeTruthy();
+ expect(await screen.findByText('Not Started')).toBeTruthy();
+ expect(screen.queryByText('Source not downloaded')).toBeNull();
+ });
+});
diff --git a/src/app/tabs/MyWorkTab.tsx b/src/app/tabs/MyWorkTab.tsx
index ffd6c1a..350780e 100644
--- a/src/app/tabs/MyWorkTab.tsx
+++ b/src/app/tabs/MyWorkTab.tsx
@@ -1,7 +1,63 @@
import React from 'react';
-import { EmptyState } from '../../components/ui/EmptyState';
+import { FlatList, StyleSheet } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { StackNavigationProp } from '@react-navigation/stack';
import { MY_WORK_EMPTY_MESSAGE } from '../../constants/messages';
+import { useMyWorkChapters } from '../../hooks/useMyWorkChapters';
+import { RootStackParamList } from '../../types/navigation/types';
+import { EmptyState } from '../../components/ui/EmptyState';
+import { LoadingSpinner } from '../../components/ui/LoadingSpinner';
+import { MyWorkRow } from '../../components/ui/MyWorkRow';
+import { theme } from '../../theme';
+
+type Nav = StackNavigationProp;
-export function MyWorkTab() {
- return ;
+interface MyWorkTabProps {
+ refreshKey?: number;
+ isSyncing?: boolean;
}
+
+export function MyWorkTab({
+ refreshKey = 0,
+ isSyncing = false,
+}: MyWorkTabProps) {
+ const navigation = useNavigation