feat: initial release v0.3.0

This commit is contained in:
saturn
2026-03-08 03:15:27 +08:00
commit 881ed44996
1311 changed files with 225407 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest'
import { resolveSelectedEpisodeId } from '@/app/[locale]/workspace/[projectId]/episode-selection'
describe('resolveSelectedEpisodeId', () => {
it('returns null when episodes list is empty', () => {
expect(resolveSelectedEpisodeId([], null)).toBeNull()
expect(resolveSelectedEpisodeId([], 'ep-1')).toBeNull()
})
it('uses url episode id when it exists in list', () => {
const episodes = [{ id: 'ep-1' }, { id: 'ep-2' }]
expect(resolveSelectedEpisodeId(episodes, 'ep-2')).toBe('ep-2')
})
it('falls back to first episode when url episode id is missing', () => {
const episodes = [{ id: 'ep-1' }, { id: 'ep-2' }]
expect(resolveSelectedEpisodeId(episodes, null)).toBe('ep-1')
})
it('falls back to first episode when url episode id is invalid', () => {
const episodes = [{ id: 'ep-1' }, { id: 'ep-2' }]
expect(resolveSelectedEpisodeId(episodes, 'ep-404')).toBe('ep-1')
})
})

View File

@@ -0,0 +1,99 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
useStateMock,
useRefMock,
useCallbackMock,
useMemoMock,
setShowRebuildConfirmMock,
setRebuildConfirmContextMock,
setPendingActionTypeMock,
} = vi.hoisted(() => ({
useStateMock: vi.fn(),
useRefMock: vi.fn(() => ({ current: null })),
useCallbackMock: vi.fn((fn: unknown) => fn),
useMemoMock: vi.fn((factory: () => unknown) => factory()),
setShowRebuildConfirmMock: vi.fn(),
setRebuildConfirmContextMock: vi.fn(),
setPendingActionTypeMock: vi.fn(),
}))
vi.mock('react', async () => {
const actual = await vi.importActual<typeof import('react')>('react')
return {
...actual,
useState: useStateMock,
useRef: useRefMock,
useCallback: useCallbackMock,
useMemo: useMemoMock,
}
})
import {
hasDownstreamStoryboardData,
useRebuildConfirm,
} from '@/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useRebuildConfirm'
function createDeferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>((res) => {
resolve = res
})
return { promise, resolve }
}
describe('useRebuildConfirm', () => {
beforeEach(() => {
useStateMock.mockReset()
useRefMock.mockReset()
useCallbackMock.mockClear()
useMemoMock.mockClear()
setShowRebuildConfirmMock.mockReset()
setRebuildConfirmContextMock.mockReset()
setPendingActionTypeMock.mockReset()
useRefMock.mockReturnValue({ current: null })
useStateMock
.mockImplementationOnce(() => [false, setShowRebuildConfirmMock])
.mockImplementationOnce(() => [null, setRebuildConfirmContextMock])
.mockImplementationOnce(() => [null, setPendingActionTypeMock])
})
it('clicking story to script -> sets pending action before downstream check resolves', async () => {
const deferred = createDeferred<{ storyboardCount: number; panelCount: number }>()
const getProjectStoryboardStats = vi.fn(() => deferred.promise)
const action = vi.fn(async () => undefined)
const hook = useRebuildConfirm({
episodeId: 'episode-1',
episodeStoryboards: [],
getProjectStoryboardStats,
t: (key: string) => key,
})
const pendingRun = hook.runWithRebuildConfirm('storyToScript', action)
expect(setPendingActionTypeMock).toHaveBeenCalledWith('storyToScript')
expect(getProjectStoryboardStats).toHaveBeenCalledWith('episode-1')
expect(action).not.toHaveBeenCalled()
deferred.resolve({ storyboardCount: 0, panelCount: 0 })
await pendingRun
expect(action).toHaveBeenCalledTimes(1)
})
})
describe('hasDownstreamStoryboardData', () => {
it('storyboard and panel counts are both zero -> returns false', () => {
expect(hasDownstreamStoryboardData({ storyboardCount: 0, panelCount: 0 })).toBe(false)
})
it('storyboard count is greater than zero -> returns true', () => {
expect(hasDownstreamStoryboardData({ storyboardCount: 1, panelCount: 0 })).toBe(true)
})
it('panel count is greater than zero -> returns true', () => {
expect(hasDownstreamStoryboardData({ storyboardCount: 0, panelCount: 2 })).toBe(true)
})
})