- A study share link can hide the owner's notes until a chosen time: the server sends only passages and discussion questions until then, with a banner in the shared view (new share_reveal column, migrated on start). - Outline: a session length with "N of M min planned, X min over" and a marker on the point where the plan runs past it. - Export > Slides: printable 16:9 slides (title, passage, outline points with their verse, discussion questions) for one session or all; light or dark. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
67 lines
3.4 KiB
JavaScript
67 lines
3.4 KiB
JavaScript
// @vitest-environment node
|
|
import { test, expect } from 'vitest';
|
|
import { checkShareAccess, expiryFromDays, hashPasscode } from './shareLinks.js';
|
|
|
|
const NOW = 1_800_000_000_000;
|
|
|
|
test('expiry is a number of days from now, or never', () => {
|
|
expect(expiryFromDays(null, NOW)).toBeNull();
|
|
expect(expiryFromDays(0, NOW)).toBeNull();
|
|
expect(expiryFromDays('x', NOW)).toBeNull();
|
|
expect(expiryFromDays(7, NOW)).toBe(NOW + 7 * 86_400_000);
|
|
expect(expiryFromDays(9999, NOW)).toBe(NOW + 365 * 86_400_000); // never more than a year
|
|
});
|
|
|
|
test('a link with no rules is open; an expired one says so', async () => {
|
|
expect((await checkShareAccess({}, NOW)).ok).toBe(true);
|
|
expect(await checkShareAccess({ expiresAt: NOW + 1000 }, NOW)).toEqual({ ok: true });
|
|
expect(await checkShareAccess({ expiresAt: NOW - 1 }, NOW)).toMatchObject({ ok: false, status: 410 });
|
|
});
|
|
|
|
test('a passcode is asked for, and only the right one opens the link', async () => {
|
|
const passHash = await hashPasscode(' moses ');
|
|
expect(passHash).not.toContain('moses');
|
|
expect(await checkShareAccess({ passHash }, NOW)).toMatchObject({ ok: false, status: 401, needsPasscode: true });
|
|
expect(await checkShareAccess({ passHash, supplied: 'aaron' }, NOW)).toMatchObject({ ok: false, status: 401, needsPasscode: true, error: expect.stringMatching(/not right/) });
|
|
expect(await checkShareAccess({ passHash, supplied: ' moses ' }, NOW)).toEqual({ ok: true });
|
|
expect(await hashPasscode(' ')).toBeNull();
|
|
});
|
|
|
|
test('an expired link is reported as expired even before the passcode is checked', async () => {
|
|
const passHash = await hashPasscode('x');
|
|
expect(await checkShareAccess({ passHash, expiresAt: NOW - 5, supplied: 'x' }, NOW)).toMatchObject({ status: 410 });
|
|
});
|
|
|
|
import { hideUntilReveal, revealFromInput } from './shareLinks.js';
|
|
|
|
const study = () => ({
|
|
id: 's1', title: 'John', translation: 'BSB', outline: [{ id: 'n', text: 'Point' }],
|
|
chapters: [{ book: 'John', bookAbbrev: 'JHN', chapter: '3', verses: [{ number: 16, text: 'For God so loved.' }],
|
|
chunks: [{ id: 'c', startVerse: 16, endVerse: 18, discussionQuestions: 'Why?', observation: 'secret', interpretation: 'secret', application: 'secret', finalScript: 'secret', generalNotes: 'secret', greekWords: [{ id: 'w' }], teachBack: 'secret', recalls: [{ text: 'secret' }] }] }],
|
|
});
|
|
|
|
test('before the reveal time a shared study keeps the passages and questions and drops every note', () => {
|
|
const now = 1_000;
|
|
const out = hideUntilReveal(study(), now + 5_000, now);
|
|
expect(out.hiddenUntil).toBe(now + 5_000);
|
|
expect(out.outline).toEqual([]);
|
|
expect(out.chapters[0].verses).toHaveLength(1);
|
|
expect(out.chapters[0].chunks[0]).toEqual({ id: 'c', startVerse: 16, endVerse: 18, discussionQuestions: 'Why?' });
|
|
expect(JSON.stringify(out)).not.toContain('secret');
|
|
});
|
|
|
|
test('after the reveal time, or with none set, the whole study is sent', () => {
|
|
const full = study();
|
|
expect(hideUntilReveal(full, 500, 1_000)).toBe(full);
|
|
expect(hideUntilReveal(full, null, 1_000)).toBe(full);
|
|
});
|
|
|
|
test('a reveal time must be in the future, and no more than a year away', () => {
|
|
const now = 1_000_000;
|
|
expect(revealFromInput(null, now)).toBeNull();
|
|
expect(revealFromInput(now - 1, now)).toBeNull();
|
|
expect(revealFromInput('nope', now)).toBeNull();
|
|
expect(revealFromInput(now + 60_000, now)).toBe(now + 60_000);
|
|
expect(revealFromInput(now + 10 * 365 * 86_400_000, now)).toBe(now + 365 * 86_400_000);
|
|
});
|