Files
study-bible-project/src/components/CoTeachers.test.jsx
T
nmemmertandClaude Sonnet 5.5 54cb1c217d
Build and Push Docker Image / build-and-push (push) Successful in 6m47s
Invite-only sign-up, present mode, series, handouts, templates, dictation, progress, sharing
- Admin: sign-up Open / Invite only / Closed with single-use invite links
- Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes)
- Series: group studies on the dashboard, filter by series, calendar file for a study's sessions
- Lesson handout: printable page per session (passage, outline, questions, writing lines)
- Save a chunk's notes as your own study template (synced)
- Voice dictation for notes and scripts (browser speech recognition)
- Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices
- Export highlights and bookmarks as Markdown or Word, grouped by named colour
- Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY)
- Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 10:26:43 -04:00

50 lines
2.5 KiB
React

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, test, expect, beforeEach } from 'vitest';
vi.mock('../syncService.js', () => ({ getCollaborators: vi.fn(), createEditInvite: vi.fn(), removeCollaborator: vi.fn() }));
import CoTeachers from './CoTeachers.jsx';
import { createEditInvite, getCollaborators, removeCollaborator } from '../syncService.js';
beforeEach(() => {
vi.clearAllMocks();
getCollaborators.mockResolvedValue({ ok: true, data: { collaborators: [{ userId: 'u2', email: 'sam@x.co' }], pendingInvites: 0 } });
createEditInvite.mockResolvedValue({ ok: true, data: { token: 'abc123', expiresAt: Date.now() + 7 * 86_400_000 } });
removeCollaborator.mockResolvedValue({ ok: true });
vi.spyOn(window, 'confirm').mockReturnValue(true);
});
test('the owner sees the co-teachers and can make a single-use invite link', async () => {
const user = userEvent.setup();
render(<CoTeachers projectId="p1" />);
expect(await screen.findByText('sam@x.co')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /invite a co-teacher/i }));
const link = await screen.findByLabelText('Invite link');
expect(link.value).toContain('?join=abc123');
expect(createEditInvite).toHaveBeenCalledWith('p1');
});
test('the owner can remove a co-teacher after confirming', async () => {
const user = userEvent.setup();
render(<CoTeachers projectId="p1" />);
await user.click(await screen.findByRole('button', { name: 'Remove' }));
expect(removeCollaborator).toHaveBeenCalledWith('p1', 'u2');
});
test('someone invited to a study sees who shared it and can leave', async () => {
const onLeave = vi.fn();
const user = userEvent.setup();
render(<CoTeachers projectId="p1" sharedBy="owner@x.co" onLeave={onLeave} />);
expect(screen.getByText(/owner@x.co invited you to edit/)).toBeInTheDocument();
expect(getCollaborators).not.toHaveBeenCalled(); // only owners can list co-teachers
await user.click(screen.getByRole('button', { name: /leave this study/i }));
expect(onLeave).toHaveBeenCalled();
});
test('when the server can\'t be reached, it says so instead of offering a link', async () => {
getCollaborators.mockResolvedValue({ ok: false, status: 404, error: 'Project not found.' });
render(<CoTeachers projectId="p1" />);
await waitFor(() => expect(screen.getByText(/Invites need your account/)).toBeInTheDocument());
expect(screen.queryByRole('button', { name: /invite a co-teacher/i })).toBeNull();
});