feat: remove cover page
This commit is contained in:
@@ -10,15 +10,22 @@ function mockGetBook(data: TeachingBook, id = 'b1'): void {
|
||||
vi.mocked(booksApi.getBook).mockResolvedValue({ id, name: '示例整本', updatedAt: data.updatedAt, data })
|
||||
}
|
||||
|
||||
function createBookWithDesign(filename = '1.md'): { data: TeachingBook; design: ReturnType<typeof createEmptyTeachingDesign> } {
|
||||
const data = createEmptyBook()
|
||||
const design = createEmptyTeachingDesign(filename)
|
||||
data.designs.push(design)
|
||||
data.selectedId = design.id
|
||||
return { data, design }
|
||||
}
|
||||
|
||||
describe('useTeachingBook', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
it('loads the book from the API', async () => {
|
||||
const data = createEmptyBook()
|
||||
data.cover.courseName = 'Web 前端开发'
|
||||
it('loads the book from the API without cover state', async () => {
|
||||
const { data } = createBookWithDesign()
|
||||
mockGetBook(data)
|
||||
|
||||
const store = useTeachingBook('b1')
|
||||
@@ -26,7 +33,8 @@ describe('useTeachingBook', () => {
|
||||
|
||||
expect(booksApi.getBook).toHaveBeenCalledWith('b1')
|
||||
expect(store.loadStatus.value).toBe('loaded')
|
||||
expect(store.book.value.cover.courseName).toBe('Web 前端开发')
|
||||
expect(store.book.value).not.toHaveProperty('cover')
|
||||
expect(store.book.value.selectedId).toBe(data.selectedId)
|
||||
})
|
||||
|
||||
it('sets loadStatus to error when loading fails', async () => {
|
||||
@@ -82,13 +90,16 @@ describe('useTeachingBook', () => {
|
||||
})
|
||||
|
||||
it('autosaves the book via the API after the debounce delay', async () => {
|
||||
mockGetBook(createEmptyBook())
|
||||
const { data, design } = createBookWithDesign()
|
||||
mockGetBook(data)
|
||||
vi.mocked(booksApi.updateBook).mockResolvedValue({ id: 'b1', name: '示例整本', updatedAt: 'later' })
|
||||
|
||||
const store = useTeachingBook('b1')
|
||||
await flushPromises()
|
||||
|
||||
store.updateCover({ courseName: '新课程名' })
|
||||
store.updateDesign(design.id, (current) => {
|
||||
current.title = '新课程名'
|
||||
})
|
||||
await vi.advanceTimersByTimeAsync(300)
|
||||
|
||||
expect(booksApi.updateBook).toHaveBeenCalledWith('b1', store.book.value)
|
||||
@@ -96,13 +107,16 @@ describe('useTeachingBook', () => {
|
||||
})
|
||||
|
||||
it('sets saveStatus to error when autosave fails', async () => {
|
||||
mockGetBook(createEmptyBook())
|
||||
const { data, design } = createBookWithDesign()
|
||||
mockGetBook(data)
|
||||
vi.mocked(booksApi.updateBook).mockRejectedValue(new Error('保存失败。'))
|
||||
|
||||
const store = useTeachingBook('b1')
|
||||
await flushPromises()
|
||||
|
||||
store.updateCover({ courseName: '新课程名' })
|
||||
store.updateDesign(design.id, (current) => {
|
||||
current.title = '新课程名'
|
||||
})
|
||||
await vi.advanceTimersByTimeAsync(300)
|
||||
|
||||
expect(store.saveStatus.value).toBe('error')
|
||||
@@ -139,10 +153,8 @@ describe('useTeachingBook', () => {
|
||||
expect(store.book.value.designs).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('clearBook empties designs but keeps the cover', async () => {
|
||||
const data = createEmptyBook()
|
||||
data.cover.courseName = 'Web 前端开发'
|
||||
data.designs.push(createEmptyTeachingDesign('1.md'))
|
||||
it('clearBook empties designs and clears selection', async () => {
|
||||
const { data } = createBookWithDesign()
|
||||
mockGetBook(data)
|
||||
|
||||
const store = useTeachingBook('b1')
|
||||
@@ -151,7 +163,22 @@ describe('useTeachingBook', () => {
|
||||
store.clearBook()
|
||||
|
||||
expect(store.book.value.designs).toEqual([])
|
||||
expect(store.book.value.cover.courseName).toBe('Web 前端开发')
|
||||
expect(store.book.value.selectedId).toBe('cover')
|
||||
expect(store.book.value).not.toHaveProperty('cover')
|
||||
expect(store.book.value.selectedId).toBeNull()
|
||||
})
|
||||
|
||||
it('selects null after removing the last selected lesson', async () => {
|
||||
const { data, design } = createBookWithDesign()
|
||||
mockGetBook(data)
|
||||
|
||||
const store = useTeachingBook('b1')
|
||||
await flushPromises()
|
||||
|
||||
store.removeDesign(design.id)
|
||||
await flushPromises()
|
||||
|
||||
expect(store.book.value.designs).toEqual([])
|
||||
expect(store.book.value.selectedId).toBeNull()
|
||||
expect(store.selectedDesign.value).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { nextTick, ref, watch, type Ref } from 'vue'
|
||||
import {
|
||||
createEmptyBook,
|
||||
type BookCover,
|
||||
type DesignId,
|
||||
type TeachingBook,
|
||||
type TeachingDesign,
|
||||
@@ -38,10 +37,9 @@ export interface TeachingBookStore {
|
||||
warningCount: Ref<number>
|
||||
importFiles: (files: readonly File[], strategy: DuplicateStrategy) => Promise<ImportResult>
|
||||
detectDuplicates: (files: readonly File[]) => string[]
|
||||
selectPage: (id: 'cover' | DesignId) => void
|
||||
selectPage: (id: DesignId) => void
|
||||
moveDesign: (from: number, to: number) => void
|
||||
removeDesign: (id: DesignId) => void
|
||||
updateCover: (patch: Partial<BookCover>) => void
|
||||
updateDesign: (id: DesignId, updater: (design: TeachingDesign) => void) => void
|
||||
clearBook: () => void
|
||||
generateLesson: (topic: string) => Promise<GenerateLessonResult>
|
||||
@@ -67,7 +65,7 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
const current = book.value
|
||||
hasDesigns.value = current.designs.length > 0
|
||||
selectedDesign.value =
|
||||
current.selectedId === 'cover'
|
||||
current.selectedId === null
|
||||
? null
|
||||
: current.designs.find((design) => design.id === current.selectedId) ?? null
|
||||
warningCount.value = current.designs.reduce(
|
||||
@@ -176,7 +174,7 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
}
|
||||
}
|
||||
|
||||
if (imported > 0 && book.value.selectedId === 'cover' && book.value.designs.length > 0) {
|
||||
if (imported > 0 && book.value.selectedId === null && book.value.designs.length > 0) {
|
||||
book.value.selectedId = book.value.designs[0]!.id
|
||||
}
|
||||
|
||||
@@ -187,7 +185,7 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
return { imported, failed, duplicates }
|
||||
}
|
||||
|
||||
function selectPage(id: 'cover' | DesignId): void {
|
||||
function selectPage(id: DesignId): void {
|
||||
book.value.selectedId = id
|
||||
}
|
||||
|
||||
@@ -210,17 +208,12 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
designs.splice(index, 1)
|
||||
|
||||
if (book.value.selectedId === id) {
|
||||
book.value.selectedId = designs[index]?.id ?? designs[index - 1]?.id ?? 'cover'
|
||||
book.value.selectedId = designs[index]?.id ?? designs[index - 1]?.id ?? null
|
||||
}
|
||||
|
||||
touch()
|
||||
}
|
||||
|
||||
function updateCover(patch: Partial<BookCover>): void {
|
||||
Object.assign(book.value.cover, patch)
|
||||
touch()
|
||||
}
|
||||
|
||||
function updateDesign(id: DesignId, updater: (design: TeachingDesign) => void): void {
|
||||
const design = book.value.designs.find((candidate) => candidate.id === id)
|
||||
if (!design) {
|
||||
@@ -232,7 +225,7 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
|
||||
function clearBook(): void {
|
||||
book.value.designs = []
|
||||
book.value.selectedId = 'cover'
|
||||
book.value.selectedId = null
|
||||
touch()
|
||||
}
|
||||
|
||||
@@ -286,7 +279,6 @@ export function useTeachingBook(bookId: string): TeachingBookStore {
|
||||
selectPage,
|
||||
moveDesign,
|
||||
removeDesign,
|
||||
updateCover,
|
||||
updateDesign,
|
||||
clearBook,
|
||||
generateLesson,
|
||||
|
||||
Reference in New Issue
Block a user