Add sharing

This commit is contained in:
Joey Yakimowich-Payne 2026-01-16 08:49:21 -07:00
commit 8a11275849
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
16 changed files with 1996 additions and 10 deletions

View file

@ -10,6 +10,7 @@ const createMockQuiz = (overrides?: Partial<QuizListItem>): QuizListItem => ({
title: 'Test Quiz',
source: 'manual',
questionCount: 5,
isShared: false,
createdAt: '2024-01-15T10:00:00.000Z',
updatedAt: '2024-01-15T10:00:00.000Z',
...overrides,
@ -23,6 +24,7 @@ describe('QuizLibrary', () => {
loading: false,
loadingQuizId: null,
deletingQuizId: null,
sharingQuizId: null,
exporting: false,
error: null,
onLoadQuiz: vi.fn(),
@ -30,6 +32,8 @@ describe('QuizLibrary', () => {
onExportQuizzes: vi.fn(),
onImportClick: vi.fn(),
onRetry: vi.fn(),
onShareQuiz: vi.fn().mockResolvedValue('mock-token'),
onUnshareQuiz: vi.fn().mockResolvedValue(undefined),
};
beforeEach(() => {
@ -519,4 +523,119 @@ describe('QuizLibrary', () => {
expect(defaultProps.onRetry).toHaveBeenCalled();
});
});
describe('share functionality', () => {
const mockWriteText = vi.fn().mockResolvedValue(undefined);
beforeEach(() => {
Object.defineProperty(navigator, 'clipboard', {
value: {
writeText: mockWriteText,
},
writable: true,
configurable: true,
});
mockWriteText.mockClear();
});
it('shows share button for non-shared quiz', () => {
render(<QuizLibrary {...defaultProps} />);
expect(screen.getByTitle('Share quiz')).toBeInTheDocument();
});
it('shows copy link button and stop sharing button for shared quiz', () => {
const quizzes = [createMockQuiz({ isShared: true, shareToken: 'test-token' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
expect(screen.getByTitle('Copy share link')).toBeInTheDocument();
expect(screen.getByTitle('Stop sharing')).toBeInTheDocument();
});
it('shows Shared badge for shared quiz', () => {
const quizzes = [createMockQuiz({ isShared: true, shareToken: 'test-token' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
expect(screen.getByText('Shared')).toBeInTheDocument();
});
it('calls onShareQuiz when share button clicked', async () => {
const user = userEvent.setup();
render(<QuizLibrary {...defaultProps} />);
await user.click(screen.getByTitle('Share quiz'));
expect(defaultProps.onShareQuiz).toHaveBeenCalledWith('quiz-1');
});
it('does not call onShareQuiz for already shared quiz', async () => {
const user = userEvent.setup();
const quizzes = [createMockQuiz({ isShared: true, shareToken: 'existing-token' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
await user.click(screen.getByTitle('Copy share link'));
expect(defaultProps.onShareQuiz).not.toHaveBeenCalled();
});
it('calls onUnshareQuiz when stop sharing button clicked', async () => {
const user = userEvent.setup();
const quizzes = [createMockQuiz({ isShared: true, shareToken: 'test-token' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
await user.click(screen.getByTitle('Stop sharing'));
expect(defaultProps.onUnshareQuiz).toHaveBeenCalledWith('quiz-1');
});
it('hides share buttons in selection mode', async () => {
const user = userEvent.setup();
render(<QuizLibrary {...defaultProps} />);
expect(screen.getByTitle('Share quiz')).toBeInTheDocument();
await user.click(screen.getByTitle('Select for export'));
expect(screen.queryByTitle('Share quiz')).not.toBeInTheDocument();
});
it('shows loading state when sharing quiz', () => {
const quizzes = [createMockQuiz({ id: 'quiz-1' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} sharingQuizId="quiz-1" />);
const quizCard = screen.getByText('Test Quiz').closest('[class*="rounded-2xl"]')!;
expect(quizCard).toHaveClass('opacity-70');
});
it('share click does not trigger quiz load', async () => {
const user = userEvent.setup();
render(<QuizLibrary {...defaultProps} />);
await user.click(screen.getByTitle('Share quiz'));
expect(defaultProps.onLoadQuiz).not.toHaveBeenCalled();
});
it('unshare click does not trigger quiz load', async () => {
const user = userEvent.setup();
const quizzes = [createMockQuiz({ isShared: true, shareToken: 'test-token' })];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
await user.click(screen.getByTitle('Stop sharing'));
expect(defaultProps.onLoadQuiz).not.toHaveBeenCalled();
});
it('displays multiple shared and non-shared quizzes correctly', () => {
const quizzes = [
createMockQuiz({ id: '1', title: 'Shared Quiz', isShared: true, shareToken: 'token1' }),
createMockQuiz({ id: '2', title: 'Private Quiz', isShared: false }),
];
render(<QuizLibrary {...defaultProps} quizzes={quizzes} />);
expect(screen.getByText('Shared')).toBeInTheDocument();
expect(screen.getAllByTitle('Share quiz')).toHaveLength(1);
expect(screen.getAllByTitle('Copy share link')).toHaveLength(1);
expect(screen.getAllByTitle('Stop sharing')).toHaveLength(1);
});
});
});