feat: ship v0.5.0 with auto and wizard writing modes
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { parseScenePlanJson } from '../../src/main/services/auto-parse.service'
|
||||
|
||||
describe('parseScenePlanJson', () => {
|
||||
it('parses valid scene plan', () => {
|
||||
const json = JSON.stringify({
|
||||
scenes: [
|
||||
{
|
||||
label: '开场',
|
||||
summary: '林远步入演武场,众人围观,测灵石前气氛紧张,他感受到体内灵力涌动。'
|
||||
},
|
||||
{
|
||||
label: '异变',
|
||||
summary: '测灵石突然碎裂,长老震惊,同门哗然,林远意识到自己的天赋远超预期。'
|
||||
}
|
||||
],
|
||||
estimatedWords: 3000
|
||||
})
|
||||
const plan = parseScenePlanJson(json)
|
||||
expect(plan.scenes).toHaveLength(2)
|
||||
expect(plan.estimatedWords).toBe(3000)
|
||||
})
|
||||
|
||||
it('throws on invalid JSON', () => {
|
||||
expect(() => parseScenePlanJson('not json')).toThrow()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { migrate } from '../../src/main/db/migrate'
|
||||
import { VolumeRepository } from '../../src/main/db/repositories/volume.repo'
|
||||
import { AiSessionRepository } from '../../src/main/db/repositories/ai-session.repo'
|
||||
import { InteractiveRepository } from '../../src/main/db/repositories/interactive.repo'
|
||||
import { SnapshotRepository } from '../../src/main/db/repositories/snapshot.repo'
|
||||
import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo'
|
||||
import { ChapterFinishService } from '../../src/main/services/chapter-finish.service'
|
||||
import { AutoWritingService } from '../../src/main/services/auto-writing.service'
|
||||
import { handoffToInteractive } from '../../src/main/services/writing-flow-handoff.service'
|
||||
import type { SqliteDb } from '../../src/main/db/types'
|
||||
|
||||
describe('ChapterFinishService', () => {
|
||||
let db: SqliteDb
|
||||
let volId: string
|
||||
let flowId: string
|
||||
|
||||
beforeEach(() => {
|
||||
db = new DatabaseSync(':memory:')
|
||||
migrate(db)
|
||||
volId = new VolumeRepository(db).create('卷一', 0).id
|
||||
const session = new AiSessionRepository(db).create('t', 'm')
|
||||
flowId = new InteractiveRepository(db).create(session.id).id
|
||||
new InteractiveRepository(db).addScene(flowId, '<p>场景</p>', 'A')
|
||||
})
|
||||
|
||||
afterEach(() => db.close())
|
||||
|
||||
it('IT-FIN-02: creates ai snapshot on interactive finish', () => {
|
||||
const service = new ChapterFinishService(db)
|
||||
const { chapterId } = service.finishFromFlow(flowId, volId, '交互初稿', 'interactive', '交互写作成章')
|
||||
const snaps = new SnapshotRepository(db).list(chapterId)
|
||||
expect(snaps.some((s) => s.type === 'ai' && s.name.includes('交互'))).toBe(true)
|
||||
})
|
||||
|
||||
it('IT-FIN-01: auto origin and snapshot label', () => {
|
||||
const service = new ChapterFinishService(db)
|
||||
const { chapterId } = service.finishFromFlow(flowId, volId, '自动初稿', 'auto', '自动写作成章')
|
||||
const ch = new ChapterRepository(db).get(chapterId)!
|
||||
expect(ch.origin).toBe('auto')
|
||||
const snaps = new SnapshotRepository(db).list(chapterId)
|
||||
expect(snaps.some((s) => s.name.includes('自动'))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('auto handoff', () => {
|
||||
it('IT-AUTO-04: handoff sets flow_mode interactive', () => {
|
||||
const db = new DatabaseSync(':memory:')
|
||||
migrate(db)
|
||||
const session = new AiSessionRepository(db).create('t', 'm')
|
||||
const repo = new InteractiveRepository(db)
|
||||
const flow = repo.create(session.id, 'auto', 'auto_generate')
|
||||
repo.addScene(flow.id, '<p>已生成</p>', '场景1')
|
||||
const result = handoffToInteractive(repo, flow.id)
|
||||
expect(result.flowMode).toBe('interactive')
|
||||
expect(result.step).toBe('plot_suggest')
|
||||
db.close()
|
||||
})
|
||||
})
|
||||
|
||||
describe('AutoWritingService.setGoal', () => {
|
||||
it('advances to scene_plan', () => {
|
||||
const db = new DatabaseSync(':memory:')
|
||||
migrate(db)
|
||||
const session = new AiSessionRepository(db).create('t', 'm')
|
||||
const service = new AutoWritingService(db, { chat: async () => '' } as never)
|
||||
const flow = service.start(session.id)
|
||||
const updated = service.setGoal(flow.id, { chapterGoal: '林远通过入门考核', wordMin: 2000, wordMax: 4000 })
|
||||
expect(updated.step).toBe('scene_plan')
|
||||
db.close()
|
||||
})
|
||||
})
|
||||
@@ -30,7 +30,7 @@ describe('migrate v2', () => {
|
||||
expect(tableExists(db, 'search_fts')).toBe(true)
|
||||
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
})
|
||||
|
||||
it('migrates existing v1 database to v2', () => {
|
||||
@@ -47,7 +47,7 @@ describe('migrate v2', () => {
|
||||
|
||||
expect(tableExists(db, 'outline_items')).toBe(true)
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
|
||||
const cols = db.prepare('PRAGMA table_info(chapters)').all() as { name: string }[]
|
||||
const colNames = cols.map((c) => c.name)
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('migrate v3', () => {
|
||||
expect(tableExists(db, 'ai_messages')).toBe(true)
|
||||
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
})
|
||||
|
||||
it('migrates existing v2 database to v3', () => {
|
||||
@@ -49,6 +49,6 @@ describe('migrate v3', () => {
|
||||
expect(tables.map((t) => t.name)).toContain('ai_messages')
|
||||
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('migrate v4', () => {
|
||||
migrate(db)
|
||||
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
expect(tableExists(db, 'interactive_flows')).toBe(true)
|
||||
expect(tableExists(db, 'interactive_scenes')).toBe(true)
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('migrate v4', () => {
|
||||
migrate(db)
|
||||
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(4)
|
||||
expect(version.v).toBe(5)
|
||||
expect(tableExists(db, 'interactive_flows')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest'
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { migrate } from '../../src/main/db/migrate'
|
||||
import schemaV1 from '../../src/main/db/schema-v1.sql?raw'
|
||||
import schemaV2 from '../../src/main/db/schema-v2.sql?raw'
|
||||
import schemaV3 from '../../src/main/db/schema-v3.sql?raw'
|
||||
import schemaV4 from '../../src/main/db/schema-v4.sql?raw'
|
||||
import type { SqliteDb } from '../../src/main/db/types'
|
||||
|
||||
describe('migrate v5', () => {
|
||||
let db: SqliteDb
|
||||
afterEach(() => db?.close())
|
||||
|
||||
it('UT-MIG-05: applies v5 on fresh database', () => {
|
||||
db = new DatabaseSync(':memory:')
|
||||
migrate(db)
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(5)
|
||||
const cols = db.prepare('PRAGMA table_info(interactive_flows)').all() as { name: string }[]
|
||||
expect(cols.some((c) => c.name === 'flow_mode')).toBe(true)
|
||||
expect(cols.some((c) => c.name === 'mode_config_json')).toBe(true)
|
||||
})
|
||||
|
||||
it('migrates v4 database to v5', () => {
|
||||
db = new DatabaseSync(':memory:')
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (
|
||||
version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')), description TEXT
|
||||
)`)
|
||||
db.exec(schemaV1)
|
||||
db.exec(schemaV2)
|
||||
db.exec(schemaV3)
|
||||
db.prepare('INSERT INTO schema_version (version) VALUES (1),(2),(3)').run()
|
||||
try {
|
||||
db.exec("ALTER TABLE chapters ADD COLUMN origin TEXT NOT NULL DEFAULT 'manual'")
|
||||
} catch {
|
||||
/* duplicate ok */
|
||||
}
|
||||
db.exec(schemaV4)
|
||||
db.prepare('INSERT INTO schema_version (version) VALUES (4)').run()
|
||||
migrate(db)
|
||||
const version = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number }
|
||||
expect(version.v).toBe(5)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user