Reading plans: integrated into the Reader, redesigned, with per-plan translation
Build and Push Docker Image / build-and-push (push) Failing after 19s
Build and Push Docker Image / build-and-push (push) Failing after 19s
Reader: plan strip with a dot per chapter, plan sheet (day picker, chapter ticks, translation, streak, other plans), end-of-day card that steps through the day and rolls into the next, and chapter-level auto-marking. Each plan remembers its own translation (BSB, ESV, NLT, CSB, NASB, NET) and the Reader follows it while a plan is open. Plans home redesigned in the reader's themes with a Continue card, in-progress plans, a filterable catalog, and sheets for details and the custom-plan builder. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c11b6a60bf
commit
b1bc1c8ea4
17 files changed
+1205
-863
No files matched your search
@@ -0,0 +1,71 @@
|
||||
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(<ReadingPlansPage />);
|
||||
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(<ReadingPlansPage />);
|
||||
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(<ReadingPlansPage />);
|
||||
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(<ReadingPlansPage />);
|
||||
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(<ReadingPlansPage />);
|
||||
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(<ReadingPlansPage />);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Old Testament' }));
|
||||
expect(screen.queryByRole('button', { name: /The Gospels in 30 Days/ })).toBeNull();
|
||||
});
|
||||
Reference in new issue
Block a user