Files
study-bible-project/src/utils/outlineExport.test.js
T
nmemmertandClaude Sonnet 5.5 820886e3ab 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>
2026-10-01 14:24:28 -04:00

57 lines
3.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 &lt;b&gt;');
expect(html).toContain('&lt;i&gt;hi&lt;/i&gt;');
expect(html).toContain('class="o-note"');
});