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
+33
-2
@@ -580,15 +580,46 @@ describe('Home page', () => {
|
||||
expect(await screen.findByDisplayValue('Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Continue reading opens the Reader inside the plan, in the plan\'s own translation', async () => {
|
||||
seedProject();
|
||||
localStorage.setItem('readingPlanProgress', JSON.stringify({
|
||||
progress: [{ id: 'rp1', planId: 'gospels-30', startedAt: Date.now(), completedDays: [], chaptersRead: {}, completedAt: null, lastActivityAt: Date.now(), translation: 'ESV' }],
|
||||
customPlans: [], activity: [],
|
||||
}));
|
||||
const esvChapter = { translation: 'ESV', verses: [{ number: 1, text: 'In the beginning was the Word.' }], copyright: '' };
|
||||
const fetchMock = vi.fn((url) => {
|
||||
if (String(url).startsWith('/api/bible/ESV/')) return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve(esvChapter) });
|
||||
return buildFetchMock()(url);
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
await user.click(screen.getByRole('button', { name: /reading plans/i }));
|
||||
await user.click(await screen.findByRole('button', { name: /continue reading/i }));
|
||||
// The plan strip is up, on the plan's translation, and the chapter came from the ESV route
|
||||
expect(await screen.findByText(/In the beginning was the Word/)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/The Gospels in 30 Days · Day 1/).length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('ESV')).toBeInTheDocument(); // plan strip shows the plan's translation
|
||||
expect(fetchMock.mock.calls.some(([u]) => String(u).startsWith('/api/bible/ESV/MAT/1'))).toBe(true);
|
||||
// Leaving the reader ends the plan context: the reader's own translation applies again
|
||||
await user.click(screen.getByRole('button', { name: /back to studies/i }));
|
||||
await user.click(screen.getByRole('button', { name: /read bible/i }));
|
||||
expect(await screen.findByText(/Matthew 1 · BSB/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('a reading plan can be started from the Reading Plans page', async () => {
|
||||
seedProject(); // the nav button shows once there's at least one project
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
await user.click(screen.getByRole('button', { name: /reading plans/i }));
|
||||
await user.click((await screen.findAllByRole('button', { name: /^start plan$/i }))[0]);
|
||||
// Tap a plan in the catalog, pick a translation for it, and start it.
|
||||
await user.click(await screen.findByRole('button', { name: /The Gospels in 30 Days/ }));
|
||||
await user.selectOptions(screen.getByRole('combobox'), 'ESV');
|
||||
await user.click(screen.getByRole('button', { name: /^start plan$/i }));
|
||||
await waitFor(() => {
|
||||
const stored = JSON.parse(localStorage.getItem('readingPlanProgress'));
|
||||
expect(stored?.progress?.[0]?.planId).toBeTruthy();
|
||||
expect(stored?.progress?.[0]?.planId).toBe('gospels-30');
|
||||
expect(stored?.progress?.[0]?.translation).toBe('ESV');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user