import { render, screen, fireEvent, within } from '@testing-library/react'; import { vi, test, expect, beforeEach } from 'vitest'; import { getPlanSchedule, formatDayReadings } from '../utils/readingPlans.js'; const fn = () => vi.fn(); const ctx = {}; vi.mock('../context/AppContext.js', () => ({ useApp: () => ctx })); import ReadingPlansPage from './ReadingPlansPage.jsx'; const record = (over = {}) => ({ id: 'p1', planId: 'gospels-30', startedAt: Date.now(), completedDays: [0, 1], chaptersRead: {}, completedAt: null, lastActivityAt: Date.now(), translation: 'NLT', ...over, }); beforeEach(() => { Object.assign(ctx, { goHome: fn(), planProgress: [], customPlans: [], readingActivity: [], startPlan: fn(), toggleDay: fn(), restartPlan: fn(), removePlanProgress: fn(), recalibratePace: fn(), setPlanTranslation: fn(), addCustomPlan: fn(), removeCustomPlan: fn(), updateCustomPlan: fn(), openPlanReading: fn(), studyReadingPlanDay: fn(), studyBuildBusy: false, readingPlanSyncStatus: '', readingPlanSyncError: '', retryReadingPlanSync: fn(), readerTheme: 'paper', setReaderTheme: fn(), readerTranslation: 'BSB', }); }); test('with no plans, the catalog is shown and there is no Continue card', () => { render(); expect(screen.queryByText('Continue reading')).toBeNull(); expect(screen.getByRole('button', { name: /The Gospels in 30 Days/ })).toBeTruthy(); }); test('Continue reading opens the plan at its next unread day, in the plan translation', () => { ctx.planProgress = [record()]; render(); const day2 = getPlanSchedule('gospels-30')[2]; expect(screen.getAllByText(new RegExp(formatDayReadings(day2)))[0]).toBeTruthy(); expect(screen.getAllByText('NLT').length).toBeGreaterThan(0); fireEvent.click(screen.getByText('Continue reading')); expect(ctx.openPlanReading).toHaveBeenCalledWith('p1', 2); }); test('starting a catalog plan passes the chosen translation', () => { render(); fireEvent.click(screen.getByRole('button', { name: /The Gospels in 30 Days/ })); fireEvent.change(screen.getByRole('combobox'), { target: { value: 'CSB' } }); fireEvent.click(screen.getByRole('button', { name: 'Start plan' })); expect(ctx.startPlan).toHaveBeenCalledWith('gospels-30', 'CSB'); }); test('every reader translation can be chosen for a plan', () => { render(); fireEvent.click(screen.getByRole('button', { name: /The Gospels in 30 Days/ })); const options = within(screen.getByRole('combobox')).getAllByRole('option').map((o) => o.value); expect(options).toEqual(expect.arrayContaining(['BSB', 'ESV', 'NLT', 'CSB', 'NASB', 'NET'])); }); test('an in-progress plan: change its translation, jump into a specific day', () => { ctx.planProgress = [record()]; render(); fireEvent.click(screen.getAllByRole('button', { name: /The Gospels in 30 Days/ })[0]); fireEvent.change(screen.getByRole('combobox'), { target: { value: 'ESV' } }); expect(ctx.setPlanTranslation).toHaveBeenCalledWith('p1', 'ESV'); fireEvent.click(screen.getAllByText('Read')[3]); // "Read" on Day 4 expect(ctx.openPlanReading).toHaveBeenCalledWith('p1', 3); }); test('categories filter the catalog', () => { render(); fireEvent.click(screen.getByRole('button', { name: 'Old Testament' })); expect(screen.queryByRole('button', { name: /The Gospels in 30 Days/ })).toBeNull(); });