import { describe, it, expect, afterEach } from 'vitest' import { DatabaseSync } from 'node:sqlite' import { mkdtempSync, rmSync } from 'fs' import { join } from 'path' import { tmpdir } from 'os' import { migrate } from '../../src/main/db/migrate' import { ChapterRepository } from '../../src/main/db/repositories/chapter.repo' import { VolumeRepository } from '../../src/main/db/repositories/volume.repo' import { WritingLogRepository } from '../../src/main/db/repositories/writing-log.repo' import { GlobalSettingsService } from '../../src/main/services/global-settings' import { WritingLogService } from '../../src/main/services/writing-log.service' import { WritingSessionRepository } from '../../src/main/db/repositories/writing-session.repo' import { WritingSessionService } from '../../src/main/services/writing-session.service' import { ChapterWritingTracker } from '../../src/main/services/chapter-writing-tracker.service' import { addDays, localDateString } from '../../src/main/services/writing-date.util' import type { SqliteDb } from '../../src/main/db/types' describe('WritingLogService', () => { let userDir: string let settingsDir: string let settings: GlobalSettingsService let repo: WritingLogRepository let service: WritingLogService afterEach(() => { repo?.close() if (userDir) rmSync(userDir, { recursive: true, force: true }) if (settingsDir) rmSync(settingsDir, { recursive: true, force: true }) }) function setup(goal = 1000): void { userDir = mkdtempSync(join(tmpdir(), 'bilin-wlog-')) settingsDir = mkdtempSync(join(tmpdir(), 'bilin-wlog-settings-')) settings = new GlobalSettingsService(settingsDir) settings.update({ ...settings.get(), dailyWordGoal: goal }) repo = new WritingLogRepository(userDir) service = new WritingLogService(repo, settings) } it('UT-LOG-01: addToday accumulates +200', () => { setup() const bookId = 'book-1' expect(service.addToday(bookId, 200)).toBe(200) expect(service.addToday(bookId, 200)).toBe(400) expect(service.getStats(bookId).todayWords).toBe(400) }) it('UT-LOG-02: negative delta clamps at 0', () => { setup() const bookId = 'book-1' service.addToday(bookId, 100) expect(service.addToday(bookId, -200)).toBe(0) }) it('UT-LOG-04: streak counts consecutive goal-met days', () => { setup(100) const bookId = 'book-1' const today = localDateString() repo.addToday(bookId, 150, addDays(today, -2)) repo.addToday(bookId, 120, addDays(today, -1)) repo.addToday(bookId, 80, today) expect(service.getStats(bookId).streakDays).toBe(2) }) }) describe('ChapterWritingTracker', () => { let db: SqliteDb let userDir: string let settingsDir: string let settings: GlobalSettingsService let writingLogs: WritingLogService let repo: WritingLogRepository afterEach(() => { db?.close() repo?.close() if (userDir) rmSync(userDir, { recursive: true, force: true }) if (settingsDir) rmSync(settingsDir, { recursive: true, force: true }) }) it('UT-LOG-03: supplementOnFinish fills gap', () => { db = new DatabaseSync(':memory:') migrate(db) userDir = mkdtempSync(join(tmpdir(), 'bilin-tracker-')) settingsDir = mkdtempSync(join(tmpdir(), 'bilin-tracker-settings-')) settings = new GlobalSettingsService(settingsDir) repo = new WritingLogRepository(userDir) writingLogs = new WritingLogService(repo, settings) const sessionRepo = new WritingSessionRepository(repo.getDb()) const sessions = new WritingSessionService(sessionRepo) const bookId = 'book-1' const volId = new VolumeRepository(db).create('卷一', 0).id const chapter = new ChapterRepository(db).create(volId, '第一章', 0, '

一二三四五六七八九十

') const tracker = new ChapterWritingTracker(db, bookId, writingLogs, sessions) tracker.recordDelta(chapter.id, 5) tracker.supplementOnFinish(chapter.id, chapter.wordCount) expect(writingLogs.getStats(bookId).todayWords).toBe(chapter.wordCount) }) it('IT-LOG-01: recordDelta tracks chapter updates', () => { db = new DatabaseSync(':memory:') migrate(db) userDir = mkdtempSync(join(tmpdir(), 'bilin-itlog-')) settingsDir = mkdtempSync(join(tmpdir(), 'bilin-itlog-settings-')) settings = new GlobalSettingsService(settingsDir) repo = new WritingLogRepository(userDir) writingLogs = new WritingLogService(repo, settings) const sessionRepo = new WritingSessionRepository(repo.getDb()) const sessions = new WritingSessionService(sessionRepo) const bookId = 'book-1' const volId = new VolumeRepository(db).create('卷一', 0).id const chapterRepo = new ChapterRepository(db) const ch = chapterRepo.create(volId, 'A', 0, '

ab

') const tracker = new ChapterWritingTracker(db, bookId, writingLogs, sessions) const updated = chapterRepo.update(ch.id, { content: '

abcd

' }) tracker.recordDelta(ch.id, updated.wordCount) const updated2 = chapterRepo.update(ch.id, { content: '

abcdef

' }) tracker.recordDelta(ch.id, updated2.wordCount) expect(writingLogs.getStats(bookId).todayWords).toBe(updated2.wordCount) }) })