testing added and done
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import Community from './Community.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({ push: (...args) => mockPush(...args) }))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockFetchPublishedDecks = vi.fn()
|
||||
const mockCopyDeckToUser = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchPublishedDecks: (...args) => mockFetchPublishedDecks(...args),
|
||||
copyDeckToUser: (...args) => mockCopyDeckToUser(...args),
|
||||
getMyDeckRating: vi.fn(),
|
||||
submitDeckRating: vi.fn(),
|
||||
}))
|
||||
|
||||
describe('Community', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockFetchPublishedDecks.mockResolvedValue([])
|
||||
})
|
||||
|
||||
it('shows loading then empty message when no decks', async () => {
|
||||
render(Community)
|
||||
expect(screen.getByText(/Loading/)).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByText(/No published decks yet/)).toBeInTheDocument())
|
||||
expect(mockFetchPublishedDecks).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows error when fetch fails', async () => {
|
||||
mockFetchPublishedDecks.mockRejectedValue(new Error('Network error'))
|
||||
render(Community)
|
||||
await waitFor(() => expect(screen.getByText('Network error')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('renders deck list and goPreview on card click', async () => {
|
||||
mockFetchPublishedDecks.mockResolvedValue([
|
||||
{
|
||||
id: 'd1',
|
||||
title: 'Cool Deck',
|
||||
description: 'A deck',
|
||||
owner_id: 'o1',
|
||||
owner_display_name: 'Alice',
|
||||
owner_email: '[email protected]',
|
||||
question_count: 5,
|
||||
average_rating: 4.5,
|
||||
rating_count: 2,
|
||||
user_has_this: false,
|
||||
},
|
||||
])
|
||||
render(Community)
|
||||
await waitFor(() => expect(screen.getByText('Cool Deck')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: /Cool Deck/ }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/decks/d1/preview')
|
||||
})
|
||||
|
||||
it('handleUse without userId shows sign in error', async () => {
|
||||
auth.set({ user: null, loading: false, error: null })
|
||||
mockFetchPublishedDecks.mockResolvedValue([
|
||||
{ id: 'd1', title: 'Deck', owner_id: 'o1', owner_display_name: 'A', question_count: 1, user_has_this: false },
|
||||
])
|
||||
render(Community)
|
||||
await waitFor(() => expect(screen.getByText('Deck')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Add' }))
|
||||
expect(screen.getByText(/Sign in to add this deck/)).toBeInTheDocument()
|
||||
expect(mockCopyDeckToUser).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('handleUse with userId calls copyDeckToUser and reloads', async () => {
|
||||
mockFetchPublishedDecks
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'd1', title: 'Deck', owner_id: 'o1', owner_display_name: 'A', question_count: 1, user_has_this: false },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'd1', title: 'Deck', owner_id: 'o1', owner_display_name: 'A', question_count: 1, user_has_this: true },
|
||||
])
|
||||
mockCopyDeckToUser.mockResolvedValue(undefined)
|
||||
render(Community)
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Add' }))
|
||||
await waitFor(() => expect(mockCopyDeckToUser).toHaveBeenCalledWith('d1', 'u1'))
|
||||
await waitFor(() => expect(screen.getByText('In My decks')).toBeInTheDocument())
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import CommunityUser from './CommunityUser.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({
|
||||
push: (...args) => mockPush(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockFetchPublishedDecksByOwner = vi.fn()
|
||||
const mockCopyDeckToUser = vi.fn()
|
||||
const mockGetMyDeckRating = vi.fn()
|
||||
const mockSubmitDeckRating = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchPublishedDecksByOwner: (...args) => mockFetchPublishedDecksByOwner(...args),
|
||||
copyDeckToUser: (...args) => mockCopyDeckToUser(...args),
|
||||
getMyDeckRating: (...args) => mockGetMyDeckRating(...args),
|
||||
submitDeckRating: (...args) => mockSubmitDeckRating(...args),
|
||||
}))
|
||||
|
||||
describe('CommunityUser', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockFetchPublishedDecksByOwner.mockResolvedValue({
|
||||
decks: [],
|
||||
owner_display_name: 'Test User',
|
||||
owner_email: '[email protected]',
|
||||
})
|
||||
})
|
||||
|
||||
it('loads and shows owner name when no decks', async () => {
|
||||
render(CommunityUser, { props: { params: { id: 'owner1' } } })
|
||||
expect(screen.getByText('Loading…')).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByText(/Decks by Test User/)).toBeInTheDocument())
|
||||
expect(mockFetchPublishedDecksByOwner).toHaveBeenCalledWith('owner1', 'u1')
|
||||
})
|
||||
|
||||
it('shows error when fetch fails', async () => {
|
||||
mockFetchPublishedDecksByOwner.mockRejectedValue(new Error('Not found'))
|
||||
render(CommunityUser, { props: { params: { id: 'owner1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Not found')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('goCommunity button calls push to /community', async () => {
|
||||
render(CommunityUser, { props: { params: { id: 'owner1' } } })
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: /Community/ })).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: /Community/ }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/community')
|
||||
})
|
||||
|
||||
it('renders deck list when decks returned', async () => {
|
||||
mockFetchPublishedDecksByOwner.mockResolvedValue({
|
||||
decks: [
|
||||
{ id: 'd1', title: 'Deck One', owner_id: 'owner1', question_count: 3, user_has_this: false },
|
||||
],
|
||||
owner_display_name: 'Alice',
|
||||
owner_email: '[email protected]',
|
||||
})
|
||||
render(CommunityUser, { props: { params: { id: 'owner1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Deck One')).toBeInTheDocument())
|
||||
expect(screen.getByText(/Decks by Alice/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,107 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import CreateDeck from './CreateDeck.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({
|
||||
push: (...args) => mockPush(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockCreateDeck = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
createDeck: (...args) => mockCreateDeck(...args),
|
||||
}))
|
||||
|
||||
describe('CreateDeck', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockCreateDeck.mockResolvedValue('new-deck-id')
|
||||
})
|
||||
|
||||
it('shows form when user is logged in', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
expect(screen.getByRole('button', { name: 'Save deck' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows sign in message when user is null', () => {
|
||||
auth.set({ user: null, loading: false, error: null })
|
||||
render(CreateDeck)
|
||||
expect(screen.getByText('Sign in to create a deck.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('Cancel calls push to home', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/')
|
||||
})
|
||||
|
||||
it('loadFromJson sets error for invalid JSON', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
const textarea = screen.getByPlaceholderText(/Paste deck JSON/)
|
||||
await fireEvent.input(textarea, { target: { value: 'not json' } })
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Load from JSON' }))
|
||||
expect(screen.getByText(/Invalid JSON/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('loadFromJson sets error when title missing', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
const textarea = screen.getByPlaceholderText(/Paste deck JSON/)
|
||||
await fireEvent.input(textarea, { target: { value: '{"description":"x","questions":[{"prompt":"Q","answers":["A"],"correctAnswerIndices":[0]}]}' } })
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Load from JSON' }))
|
||||
expect(screen.getByText(/must include a "title"/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('loadFromJson populates title and questions from valid JSON', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
const validJson = JSON.stringify({
|
||||
title: 'Imported Deck',
|
||||
description: 'Desc',
|
||||
questions: [{ prompt: 'Question?', answers: ['A', 'B'], correctAnswerIndices: [0] }],
|
||||
})
|
||||
const textarea = screen.getByPlaceholderText(/Paste deck JSON/)
|
||||
await fireEvent.input(textarea, { target: { value: validJson } })
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Load from JSON' }))
|
||||
expect(screen.getByDisplayValue('Imported Deck')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('Question?')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('save with empty title shows error', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Save deck' }))
|
||||
expect(screen.getByText('Title is required')).toBeInTheDocument()
|
||||
expect(mockCreateDeck).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('save with title and question calls createDeck and pushes home', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByPlaceholderText('Deck title')).toBeInTheDocument())
|
||||
await fireEvent.input(screen.getByPlaceholderText('Deck title'), { target: { value: 'My Deck' } })
|
||||
await fireEvent.input(screen.getByPlaceholderText('Prompt'), { target: { value: 'First question?' } })
|
||||
await fireEvent.input(screen.getByPlaceholderText('Answer 1'), { target: { value: 'Yes' } })
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Save deck' }))
|
||||
await waitFor(() => expect(mockCreateDeck).toHaveBeenCalled())
|
||||
expect(mockCreateDeck).toHaveBeenCalledWith('u1', expect.objectContaining({ title: 'My Deck' }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/')
|
||||
})
|
||||
|
||||
it('Add question button adds a question editor', async () => {
|
||||
render(CreateDeck)
|
||||
await waitFor(() => expect(screen.getByText('Question 1')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: '+ Add question' }))
|
||||
expect(screen.getByText('Question 2')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import DeckPreview from './DeckPreview.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({
|
||||
push: (...args) => mockPush(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockFetchDeckWithQuestions = vi.fn()
|
||||
const mockUserHasDeck = vi.fn()
|
||||
const mockCopyDeckToUser = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchDeckWithQuestions: (...args) => mockFetchDeckWithQuestions(...args),
|
||||
userHasDeck: (...args) => mockUserHasDeck(...args),
|
||||
copyDeckToUser: (...args) => mockCopyDeckToUser(...args),
|
||||
}))
|
||||
|
||||
describe('DeckPreview', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'Preview Deck',
|
||||
description: 'Desc',
|
||||
published: true,
|
||||
owner_id: 'o1',
|
||||
copied_from_deck_id: null,
|
||||
questions: [{ prompt: 'Q?', answers: ['A'], correct_answer_indices: [0] }],
|
||||
})
|
||||
mockUserHasDeck.mockResolvedValue(false)
|
||||
})
|
||||
|
||||
it('loads and shows deck title', async () => {
|
||||
render(DeckPreview, { props: { params: { id: 'd1' } } })
|
||||
expect(screen.getByText('Loading deck…')).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByText('Preview Deck')).toBeInTheDocument())
|
||||
expect(mockFetchDeckWithQuestions).toHaveBeenCalledWith('d1')
|
||||
})
|
||||
|
||||
it('shows error when deck not available', async () => {
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'Private',
|
||||
published: false,
|
||||
owner_id: 'o1',
|
||||
questions: [],
|
||||
})
|
||||
render(DeckPreview, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('This deck is not available.')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('goBack pushes to community for published deck', async () => {
|
||||
render(DeckPreview, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Preview Deck')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: /Community/ }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/community')
|
||||
})
|
||||
|
||||
it('addToMyDecks calls copyDeckToUser', async () => {
|
||||
render(DeckPreview, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Preview Deck')).toBeInTheDocument())
|
||||
const addBtn = screen.getByRole('button', { name: /Add to My decks/i })
|
||||
mockCopyDeckToUser.mockResolvedValue(undefined)
|
||||
await fireEvent.click(addBtn)
|
||||
await waitFor(() => expect(mockCopyDeckToUser).toHaveBeenCalledWith('d1', 'u1'))
|
||||
})
|
||||
|
||||
it('addToMyDecks without userId shows sign in error', async () => {
|
||||
auth.set({ user: null, loading: false, error: null })
|
||||
render(DeckPreview, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Preview Deck')).toBeInTheDocument())
|
||||
const addBtn = screen.getByRole('button', { name: /Add to My decks/i })
|
||||
await fireEvent.click(addBtn)
|
||||
expect(screen.getByText(/Sign in to add this deck/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import DeckReviews from './DeckReviews.svelte'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({
|
||||
push: (...args) => mockPush(...args),
|
||||
}))
|
||||
|
||||
const mockFetchDeckWithQuestions = vi.fn()
|
||||
const mockGetDeckReviews = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchDeckWithQuestions: (...args) => mockFetchDeckWithQuestions(...args),
|
||||
getDeckReviews: (...args) => mockGetDeckReviews(...args),
|
||||
}))
|
||||
|
||||
describe('DeckReviews', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'Test Deck',
|
||||
published: true,
|
||||
questions: [],
|
||||
})
|
||||
mockGetDeckReviews.mockResolvedValue([])
|
||||
})
|
||||
|
||||
it('shows loading then deck title and no reviews', async () => {
|
||||
render(DeckReviews, { props: { params: { id: 'd1' } } })
|
||||
expect(screen.getByText('Loading…')).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByText('Test Deck')).toBeInTheDocument())
|
||||
expect(screen.getByText('No reviews yet.')).toBeInTheDocument()
|
||||
expect(mockFetchDeckWithQuestions).toHaveBeenCalledWith('d1')
|
||||
expect(mockGetDeckReviews).toHaveBeenCalledWith('d1')
|
||||
})
|
||||
|
||||
it('shows error when deck fetch fails', async () => {
|
||||
mockFetchDeckWithQuestions.mockRejectedValue(new Error('Network error'))
|
||||
render(DeckReviews, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Network error')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('shows error when deck is not published', async () => {
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'Private',
|
||||
published: false,
|
||||
questions: [],
|
||||
})
|
||||
render(DeckReviews, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('This deck is not available.')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('renders reviews list and goBack calls push', async () => {
|
||||
mockGetDeckReviews.mockResolvedValue([
|
||||
{
|
||||
user_id: 'u1',
|
||||
rating: 5,
|
||||
comment: 'Great!',
|
||||
display_name: 'Alice',
|
||||
email: '[email protected]',
|
||||
avatar_url: null,
|
||||
},
|
||||
])
|
||||
render(DeckReviews, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByText('Alice')).toBeInTheDocument())
|
||||
expect(screen.getByText('Great!')).toBeInTheDocument()
|
||||
await fireEvent.click(screen.getByRole('button', { name: /Back to deck/i }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/decks/d1/preview')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import EditDeck from './EditDeck.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({ push: (...args) => mockPush(...args) }))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockFetchDeckWithQuestions = vi.fn()
|
||||
const mockUpdateDeck = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchDeckWithQuestions: (...args) => mockFetchDeckWithQuestions(...args),
|
||||
updateDeck: (...args) => mockUpdateDeck(...args),
|
||||
togglePublished: vi.fn(),
|
||||
deleteDeck: vi.fn(),
|
||||
}))
|
||||
|
||||
describe('EditDeck', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'My Deck',
|
||||
description: 'Desc',
|
||||
owner_id: 'u1',
|
||||
published: false,
|
||||
copied_from_deck_id: null,
|
||||
config: {},
|
||||
questions: [{ prompt: 'Q?', explanation: '', answers: ['A'], correct_answer_indices: [0] }],
|
||||
})
|
||||
})
|
||||
|
||||
it('shows loading then form when deck owned by user', async () => {
|
||||
render(EditDeck, { props: { params: { id: 'd1' } } })
|
||||
expect(screen.getByText(/Loading deck/)).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByDisplayValue('My Deck')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('redirects when deck owner is not current user', async () => {
|
||||
mockFetchDeckWithQuestions.mockResolvedValue({
|
||||
id: 'd1',
|
||||
title: 'Other',
|
||||
owner_id: 'other-user',
|
||||
questions: [],
|
||||
})
|
||||
render(EditDeck, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(mockPush).toHaveBeenCalledWith('/'))
|
||||
})
|
||||
|
||||
it('cancel calls push to home', async () => {
|
||||
render(EditDeck, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByDisplayValue('My Deck')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/')
|
||||
})
|
||||
|
||||
it('save calls updateDeck and pushes home', async () => {
|
||||
mockUpdateDeck.mockResolvedValue(undefined)
|
||||
render(EditDeck, { props: { params: { id: 'd1' } } })
|
||||
await waitFor(() => expect(screen.getByDisplayValue('My Deck')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() => expect(mockUpdateDeck).toHaveBeenCalledWith('d1', expect.any(Object)))
|
||||
expect(mockPush).toHaveBeenCalledWith('/')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import MyDecks from './MyDecks.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({ push: (...args) => mockPush(...args) }))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockFetchMyDecks = vi.fn()
|
||||
const mockTogglePublished = vi.fn()
|
||||
const mockGetMyDeckRating = vi.fn()
|
||||
const mockSubmitDeckRating = vi.fn()
|
||||
const mockGetSourceUpdatePreview = vi.fn()
|
||||
const mockApplySourceUpdate = vi.fn()
|
||||
const mockDeleteDeck = vi.fn()
|
||||
vi.mock('../lib/api/decks.js', () => ({
|
||||
fetchMyDecks: (...args) => mockFetchMyDecks(...args),
|
||||
togglePublished: (...args) => mockTogglePublished(...args),
|
||||
getMyDeckRating: (...args) => mockGetMyDeckRating(...args),
|
||||
submitDeckRating: (...args) => mockSubmitDeckRating(...args),
|
||||
getSourceUpdatePreview: (...args) => mockGetSourceUpdatePreview(...args),
|
||||
applySourceUpdate: (...args) => mockApplySourceUpdate(...args),
|
||||
deleteDeck: (...args) => mockDeleteDeck(...args),
|
||||
}))
|
||||
|
||||
describe('MyDecks', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockFetchMyDecks.mockResolvedValue([])
|
||||
})
|
||||
|
||||
it('shows sign in message when no user', () => {
|
||||
auth.set({ user: null, loading: false, error: null })
|
||||
render(MyDecks)
|
||||
expect(screen.getByText(/Sign in to create and manage your decks/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows loading then empty message when no decks', async () => {
|
||||
render(MyDecks)
|
||||
expect(screen.getByText(/Loading/)).toBeInTheDocument()
|
||||
await waitFor(() => expect(screen.getByText(/No decks yet/)).toBeInTheDocument())
|
||||
expect(mockFetchMyDecks).toHaveBeenCalledWith('u1')
|
||||
})
|
||||
|
||||
it('shows error when fetch fails', async () => {
|
||||
mockFetchMyDecks.mockRejectedValue(new Error('Failed to load'))
|
||||
render(MyDecks)
|
||||
await waitFor(() => expect(screen.getByText('Failed to load')).toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('renders deck list and goEdit navigates to edit', async () => {
|
||||
mockFetchMyDecks.mockResolvedValue([
|
||||
{ id: 'd1', title: 'Deck One', description: '', question_count: 2, copied_from_deck_id: null },
|
||||
])
|
||||
render(MyDecks)
|
||||
await waitFor(() => expect(screen.getByText('Deck One')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByTitle('Edit deck'))
|
||||
expect(mockPush).toHaveBeenCalledWith('/decks/d1/edit')
|
||||
})
|
||||
|
||||
it('clicking card goes to preview', async () => {
|
||||
mockFetchMyDecks.mockResolvedValue([
|
||||
{ id: 'd1', title: 'Deck One', description: '', question_count: 2, copied_from_deck_id: null },
|
||||
])
|
||||
render(MyDecks)
|
||||
await waitFor(() => expect(screen.getByText('Deck One')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: /Deck One/ }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/decks/d1/preview')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'
|
||||
import Settings from './Settings.svelte'
|
||||
import { auth } from '../lib/stores/auth.js'
|
||||
|
||||
const mockPush = vi.fn()
|
||||
vi.mock('svelte-spa-router', () => ({
|
||||
push: (...args) => mockPush(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../lib/stores/auth.js', () => {
|
||||
const { writable } = require('svelte/store')
|
||||
const store = writable({ user: { id: 'u1' }, loading: false, error: null })
|
||||
return { auth: { subscribe: store.subscribe, set: store.set } }
|
||||
})
|
||||
|
||||
const mockGetProfile = vi.fn()
|
||||
const mockUpdateProfile = vi.fn()
|
||||
const mockUploadAvatar = vi.fn()
|
||||
vi.mock('../lib/api/profile.js', () => ({
|
||||
getProfile: (...args) => mockGetProfile(...args),
|
||||
updateProfile: (...args) => mockUpdateProfile(...args),
|
||||
uploadAvatar: (...args) => mockUploadAvatar(...args),
|
||||
}))
|
||||
|
||||
describe('Settings', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
auth.set({ user: { id: 'u1' }, loading: false, error: null })
|
||||
mockGetProfile.mockResolvedValue({
|
||||
id: 'u1',
|
||||
display_name: 'Test User',
|
||||
email: '[email protected]',
|
||||
avatar_url: null,
|
||||
})
|
||||
mockUpdateProfile.mockResolvedValue({})
|
||||
})
|
||||
|
||||
it('loads profile and shows display name', async () => {
|
||||
render(Settings)
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Test User')).toBeInTheDocument())
|
||||
expect(mockGetProfile).toHaveBeenCalledWith('u1')
|
||||
})
|
||||
|
||||
it('Cancel button calls push to home', async () => {
|
||||
render(Settings)
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Test User')).toBeInTheDocument())
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
expect(mockPush).toHaveBeenCalledWith('/')
|
||||
})
|
||||
|
||||
it('shows error for non-image file', async () => {
|
||||
render(Settings)
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Test User')).toBeInTheDocument())
|
||||
const input = document.querySelector('input[type="file"]')
|
||||
expect(input).toBeTruthy()
|
||||
const file = new File(['x'], 'doc.pdf', { type: 'application/pdf' })
|
||||
await fireEvent.change(input, { target: { files: [file] } })
|
||||
expect(screen.getByText(/Please choose an image file/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('save updates profile and shows success', async () => {
|
||||
mockGetProfile
|
||||
.mockResolvedValueOnce({ id: 'u1', display_name: 'Test User', email: '[email protected]', avatar_url: null })
|
||||
.mockResolvedValueOnce({ id: 'u1', display_name: 'New Name', email: '[email protected]', avatar_url: null })
|
||||
render(Settings)
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Test User')).toBeInTheDocument())
|
||||
const nameInput = screen.getByDisplayValue('Test User')
|
||||
await fireEvent.input(nameInput, { target: { value: 'New Name' } })
|
||||
await fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() =>
|
||||
expect(mockUpdateProfile).toHaveBeenCalledWith('u1', { display_name: 'New Name', avatar_url: null })
|
||||
)
|
||||
await waitFor(() => expect(screen.getByText('Settings saved.')).toBeInTheDocument())
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user