- 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>
57 lines
3.0 KiB
JavaScript
57 lines
3.0 KiB
JavaScript
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||
import { outlineShareText, printOutline, shareOutline } from './outlineExport.js';
|
||
import { buildOutlineHtml } from './bibleUtils.jsx';
|
||
|
||
const outline = {
|
||
title: 'Titus 1 — Elders', subtitle: 'Titus 1:5–9',
|
||
tree: [{ id: 'a', text: 'Appoint elders', ref: 'Titus 1:5', kind: 'application', minutes: 4, note: 'Why it matters', children: [{ id: 'b', text: 'Blameless', ref: '', children: [] }] }],
|
||
};
|
||
beforeEach(() => { vi.stubGlobal('navigator', { ...navigator, share: undefined, clipboard: { writeText: vi.fn(() => Promise.resolve()) } }); });
|
||
afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); });
|
||
|
||
test('the share text has the title, passage and the outline', () => {
|
||
const text = outlineShareText(outline);
|
||
expect(text.startsWith('Titus 1 — Elders\nTitus 1:5–9\n\nI. [Application] Appoint elders (Titus 1:5) · 4 min')).toBe(true);
|
||
expect(text).toContain('A. Blameless');
|
||
});
|
||
|
||
test('uses the system share sheet when there is one', async () => {
|
||
const share = vi.fn(() => Promise.resolve());
|
||
vi.stubGlobal('navigator', { share, clipboard: { writeText: vi.fn() } });
|
||
expect(await shareOutline(outline)).toBe('shared');
|
||
expect(share).toHaveBeenCalledWith(expect.objectContaining({ title: 'Titus 1 — Elders — Outline' }));
|
||
});
|
||
|
||
test('closing the share sheet is not an error, and a broken one falls back to copying', async () => {
|
||
vi.stubGlobal('navigator', { share: vi.fn(() => Promise.reject(Object.assign(new Error('x'), { name: 'AbortError' }))), clipboard: { writeText: vi.fn() } });
|
||
expect(await shareOutline(outline)).toBe('cancelled');
|
||
const writeText = vi.fn(() => Promise.resolve());
|
||
vi.stubGlobal('navigator', { share: vi.fn(() => Promise.reject(new Error('nope'))), clipboard: { writeText } });
|
||
expect(await shareOutline(outline)).toBe('copied');
|
||
expect(writeText).toHaveBeenCalled();
|
||
});
|
||
|
||
test('without a share sheet it copies, and says so when even that fails', async () => {
|
||
expect(await shareOutline(outline)).toBe('copied');
|
||
vi.stubGlobal('navigator', { clipboard: { writeText: vi.fn(() => Promise.reject(new Error('denied'))) } });
|
||
expect(await shareOutline(outline)).toBe('failed');
|
||
});
|
||
|
||
test('print writes a page of just the outline, and reports a blocked window', () => {
|
||
const doc = { write: vi.fn(), close: vi.fn() };
|
||
vi.spyOn(window, 'open').mockReturnValue({ document: doc, focus: vi.fn(), print: vi.fn() });
|
||
expect(printOutline(outline)).toBe(true);
|
||
const html = doc.write.mock.calls[0][0];
|
||
expect(html).toContain('Appoint elders');
|
||
expect(html).toContain('About 4 minutes');
|
||
window.open.mockReturnValue(null);
|
||
expect(printOutline(outline)).toBe(false);
|
||
});
|
||
|
||
test('the printable page escapes text and shows notes', () => {
|
||
const html = buildOutlineHtml({ title: 'A <b>', tree: [{ id: 'x', text: '<i>hi</i>', ref: '', note: 'line', children: [] }] });
|
||
expect(html).toContain('A <b>');
|
||
expect(html).toContain('<i>hi</i>');
|
||
expect(html).toContain('class="o-note"');
|
||
});
|