feat: ship v0.9.0 with pomodoro, achievements, and injection history

Add pomodoro timer with goal notifications, writing streak milestones, and knowledge injection logging with UI previews across AI writing flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 19:15:52 +08:00
parent 255f1a99a0
commit d4122c8f95
53 changed files with 1679 additions and 80 deletions
+75
View File
@@ -0,0 +1,75 @@
import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest'
vi.mock('electron', () => ({
BrowserWindow: {
getAllWindows: () => []
},
Notification: {
isSupported: () => false
}
}))
import { mkdtempSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { WritingLogRepository } from '../../src/main/db/repositories/writing-log.repo'
import { PomodoroDailyRepository } from '../../src/main/db/repositories/pomodoro-daily.repo'
import { GlobalSettingsService } from '../../src/main/services/global-settings'
import { WritingLogService } from '../../src/main/services/writing-log.service'
import { GoalNotificationService } from '../../src/main/services/goal-notification.service'
import { PomodoroService } from '../../src/main/services/pomodoro.service'
describe('PomodoroService', () => {
let userDir: string
let settingsDir: string
let settings: GlobalSettingsService
let writingRepo: WritingLogRepository
let writingLogs: WritingLogService
let pomodoroDaily: PomodoroDailyRepository
let notify: GoalNotificationService
let pomodoro: PomodoroService
beforeEach(() => {
vi.useFakeTimers()
process.env.BILIN_E2E = '1'
process.env.POMODORO_TEST_SEC = '3'
userDir = mkdtempSync(join(tmpdir(), 'bilin-pom-'))
settingsDir = mkdtempSync(join(tmpdir(), 'bilin-pom-settings-'))
settings = new GlobalSettingsService(settingsDir)
writingRepo = new WritingLogRepository(userDir)
writingLogs = new WritingLogService(writingRepo, settings)
pomodoroDaily = new PomodoroDailyRepository(writingRepo.getDb())
notify = new GoalNotificationService(settings)
vi.spyOn(notify, 'notifyPomodoroComplete')
vi.spyOn(notify, 'notifyPomodoroBookSwitch')
pomodoro = new PomodoroService(writingLogs, pomodoroDaily, settings, notify)
})
afterEach(() => {
vi.useRealTimers()
delete process.env.POMODORO_TEST_SEC
writingRepo?.close()
if (userDir) rmSync(userDir, { recursive: true, force: true })
if (settingsDir) rmSync(settingsDir, { recursive: true, force: true })
})
it('UT-POM-01: complete records word delta in pomodoro_daily', () => {
const bookId = 'book-1'
writingLogs.addToday(bookId, 100)
pomodoro.start(bookId)
writingLogs.addToday(bookId, 50)
vi.advanceTimersByTime(3000)
expect(pomodoroDaily.getTodayCount(bookId)).toBe(1)
expect(notify.notifyPomodoroComplete).toHaveBeenCalledWith(50)
expect(pomodoro.getState().running).toBe(false)
})
it('UT-POM-02: start different book cancels previous', () => {
pomodoro.start('book-a')
expect(pomodoro.getState().bookId).toBe('book-a')
pomodoro.start('book-b')
expect(notify.notifyPomodoroBookSwitch).toHaveBeenCalled()
expect(pomodoro.getState().bookId).toBe('book-b')
expect(pomodoro.getState().running).toBe(true)
})
})