Outline: per-chunk outlines, richer points, print and share
- One outline for the whole study plus one per chunk (picker at the top); chunk outlines can start from the chunk's verses, each carrying its words - Points can have a type (illustration, application, quote, question, transition), minutes (with a running total), a longer note, verse text fetched from a passage, and cross-references / word studies attached from the chunk - Share (system share sheet, or copy), Print / PDF, Copy as text, Word and Markdown downloads for any one outline - All of it included in the Markdown, HTML/PDF and Word study exports, with each chunk's outline under that chunk Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d273c8cb3e
commit
820886e3ab
12 files changed
+741
-103
No files matched your search
@@ -224,6 +224,77 @@ describe('Outline stage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Outline: per chunk, details, print and share', () => {
|
||||
async function openOutline() {
|
||||
vi.stubGlobal('fetch', vi.fn((url) => (url.includes('bible.helloao.org') ? json(200, chapterData) : Promise.resolve({ ok: false }))));
|
||||
const user = userEvent.setup();
|
||||
await renderApp();
|
||||
await user.click((await screen.findAllByRole('button', { name: /new project/i }))[0]);
|
||||
await user.click(await screen.findByRole('button', { name: /load chapter/i }));
|
||||
await screen.findByText(/Scripture & Chunks/i);
|
||||
const verses = within(screen.getByTestId('verse-list'));
|
||||
fireEvent.click(verses.getByRole('button', { name: /In the beginning/ }));
|
||||
fireEvent.click(verses.getByRole('button', { name: /without form/ }), { shiftKey: true });
|
||||
await screen.findByText('1 created');
|
||||
await user.click(screen.getByRole('button', { name: /^outline$/i }));
|
||||
await screen.findByRole('heading', { name: 'Outline' });
|
||||
return user;
|
||||
}
|
||||
|
||||
test('each chunk has its own outline, separate from the whole study\'s', async () => {
|
||||
const user = await openOutline();
|
||||
const picker = screen.getByRole('combobox', { name: 'Outline for' });
|
||||
await user.selectOptions(picker, picker.querySelectorAll('option')[1]); // the chunk
|
||||
expect(await screen.findByText(/no outline yet for/i)).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /start from the verses/i }));
|
||||
const list = await screen.findByRole('list', { name: 'Outline' });
|
||||
expect(within(list).getAllByRole('textbox')).toHaveLength(2); // one point per verse
|
||||
expect(within(list).getByText(/In the beginning God created/)).toBeInTheDocument(); // with its words
|
||||
await user.selectOptions(screen.getByRole('combobox', { name: 'Outline for' }), 'study');
|
||||
expect(await screen.findByText(/^No outline yet$/)).toBeInTheDocument(); // the whole study is untouched
|
||||
expect(screen.getByRole('option', { name: /· 2/ })).toBeInTheDocument(); // the chunk shows its count
|
||||
});
|
||||
|
||||
test('a point can be given a type and minutes, and the total shows', async () => {
|
||||
const user = await openOutline();
|
||||
await user.click(screen.getByRole('button', { name: /start blank/i }));
|
||||
await user.type(await screen.findByRole('textbox', { name: 'Point I.' }), 'The story');
|
||||
await user.click(screen.getByRole('button', { name: /details for i\./i }));
|
||||
await user.selectOptions(screen.getByLabelText('Type'), 'illustration');
|
||||
await user.type(screen.getByLabelText('Minutes'), '5');
|
||||
expect(await screen.findByText('About 5 minutes')).toBeInTheDocument();
|
||||
await user.type(screen.getByRole('textbox', { name: 'Notes for I.' }), 'Tell it slowly');
|
||||
await user.click(screen.getByRole('button', { name: /details for i\./i })); // close: the note shows as a line
|
||||
expect(await screen.findByText('Tell it slowly')).toBeInTheDocument();
|
||||
expect(screen.getByText('Illustration')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('"Add verse text" puts the passage\'s words into the point', async () => {
|
||||
const user = await openOutline();
|
||||
await user.click(screen.getByRole('button', { name: /start blank/i }));
|
||||
await user.click(await screen.findByRole('button', { name: /details for i\./i }));
|
||||
await user.type(screen.getByLabelText('Passage'), 'Titus 1:1');
|
||||
await user.click(screen.getByRole('button', { name: /add verse text/i }));
|
||||
expect(await screen.findByText(/In the beginning God created/)).toBeInTheDocument();
|
||||
await user.clear(screen.getByLabelText('Passage'));
|
||||
await user.type(screen.getByLabelText('Passage'), 'nonsense');
|
||||
await user.click(screen.getByRole('button', { name: /replace verse text/i }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(/type a passage/i);
|
||||
});
|
||||
|
||||
test('Share copies the outline when there is no share sheet; Print opens a page of it', async () => {
|
||||
const user = await openOutline();
|
||||
await user.click(screen.getByRole('button', { name: /start from my chunks/i }));
|
||||
await screen.findByRole('list', { name: 'Outline' });
|
||||
await user.click(screen.getByRole('button', { name: /^share$/i }));
|
||||
expect(await screen.findByText(/outline copied/i)).toBeInTheDocument();
|
||||
const doc = { write: vi.fn(), close: vi.fn() };
|
||||
vi.spyOn(window, 'open').mockReturnValue({ document: doc, focus: vi.fn(), print: vi.fn() });
|
||||
await user.click(screen.getByRole('button', { name: /print \/ pdf/i }));
|
||||
expect(doc.write.mock.calls[0][0]).toContain('class="outline"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Study cards on Home', () => {
|
||||
test('a long run of chapters and many tags stay short', async () => {
|
||||
const chapters = Array.from({ length: 24 }, (_, i) => `Luke ${i + 1}`).join(', ');
|
||||
|
||||
Reference in new issue
Block a user