147 lines
6.2 KiB
TypeScript
147 lines
6.2 KiB
TypeScript
import type { ExtensionContext, Memento, Uri } from 'vscode'
|
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
import * as os from 'node:os'
|
|
import * as path from 'node:path'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { DEFAULT_COMMIT_PROFILE_NAME } from '../cc/commitProfile'
|
|
import { getEffectiveCommitProfilePath, getSettings, persistSeededProfiles, saveSettings, SETTINGS_KEY } from './store'
|
|
|
|
const tempDirs: string[] = []
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
while (tempDirs.length > 0) {
|
|
const dir = tempDirs.pop()
|
|
if (dir)
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
function tempDir(): string {
|
|
const dir = mkdtempSync(path.join(os.tmpdir(), 'settings-store-'))
|
|
tempDirs.push(dir)
|
|
return dir
|
|
}
|
|
|
|
function fakeCtx(dir: string, memento: Record<string, unknown> = {}): ExtensionContext {
|
|
return {
|
|
extensionPath: path.resolve(__dirname, '../..'),
|
|
globalStorageUri: { fsPath: dir } as Uri,
|
|
globalState: {
|
|
get: <T>(key: string): T | undefined => memento[key] as T | undefined,
|
|
update: async (key: string, value: unknown) => {
|
|
memento[key] = value
|
|
},
|
|
keys: () => Object.keys(memento),
|
|
} as unknown as Memento,
|
|
} as ExtensionContext
|
|
}
|
|
|
|
describe('saveSettings 落盘', () => {
|
|
it('目录配置落盘后才切换候选目录,随后通知问答刷新', async () => {
|
|
const ctx = fakeCtx(tempDir())
|
|
const profileModule = await import('../cc/profiles.js')
|
|
const setDirectory = vi.spyOn(profileModule, 'setProfilesDirOverride').mockImplementation(() => {
|
|
expect(getSettings(ctx).profilesDirectory).toBe('/new/profiles')
|
|
})
|
|
const { onDidChangeSettings } = await import('./store.js')
|
|
const subscription = onDidChangeSettings(ctx, () => {
|
|
expect(setDirectory).toHaveBeenCalledWith('/new/profiles')
|
|
})
|
|
try {
|
|
await saveSettings(ctx, { profilesDirectory: '/new/profiles' })
|
|
expect(setDirectory).toHaveBeenCalledOnce()
|
|
}
|
|
finally {
|
|
subscription.dispose()
|
|
setDirectory.mockRestore()
|
|
}
|
|
})
|
|
|
|
it('问答默认模型独立落盘,重启读取且不受旧的上次选择影响', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir, { 'superpowers.codeReader.askProfile': 'sonnet' })
|
|
expect(getSettings(ctx).defaultAskProfilePath).toBe('')
|
|
await saveSettings(ctx, { defaultAskProfilePath: '/p/dsflash.json' })
|
|
expect(JSON.parse(readFileSync(path.join(dir, 'settings.json'), 'utf8')).defaultAskProfilePath).toBe('/p/dsflash.json')
|
|
expect(getSettings(fakeCtx(dir)).defaultAskProfilePath).toBe('/p/dsflash.json')
|
|
await saveSettings(ctx, { webhookPort: 9000 })
|
|
expect(getSettings(ctx).defaultAskProfilePath).toBe('/p/dsflash.json')
|
|
await saveSettings(ctx, { defaultAskProfilePath: '' })
|
|
expect(getSettings(fakeCtx(dir)).defaultAskProfilePath).toBe('')
|
|
})
|
|
|
|
it('问答、重写注释的默认模型各存各的:没配时都是空(用默认),改一个不动另一个', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir)
|
|
expect(getSettings(ctx).defaultRewriteProfilePath).toBe('')
|
|
await saveSettings(ctx, { defaultAskProfilePath: '/p/sonnet.json', defaultRewriteProfilePath: ' /p/dsflash.json ' })
|
|
expect(getSettings(fakeCtx(dir))).toMatchObject({ defaultAskProfilePath: '/p/sonnet.json', defaultRewriteProfilePath: '/p/dsflash.json' })
|
|
await saveSettings(ctx, { defaultRewriteProfilePath: '' })
|
|
expect(getSettings(fakeCtx(dir))).toMatchObject({ defaultAskProfilePath: '/p/sonnet.json', defaultRewriteProfilePath: '' })
|
|
})
|
|
|
|
it('默认配置保存后通知订阅者,取消订阅后不再通知', async () => {
|
|
const ctx = fakeCtx(tempDir())
|
|
const { onDidChangeSettings } = await import('./store.js')
|
|
const listener = vi.fn(() => getSettings(ctx).defaultAskProfilePath)
|
|
const subscription = onDidChangeSettings(ctx, listener)
|
|
await saveSettings(ctx, { defaultAskProfilePath: '/p/sonnet.json' })
|
|
expect(listener).toHaveLastReturnedWith('/p/sonnet.json')
|
|
subscription.dispose()
|
|
await saveSettings(ctx, { defaultAskProfilePath: '' })
|
|
expect(listener).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('写入 globalStorage 的 settings.json,清空 memento 后仍能读到 commitProfilePath', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir)
|
|
await saveSettings(ctx, { commitProfilePath: '/p/deepseek-v4-pro.json' })
|
|
|
|
const file = path.join(dir, 'settings.json')
|
|
expect(existsSync(file)).toBe(true)
|
|
expect(JSON.parse(readFileSync(file, 'utf8')).commitProfilePath).toBe('/p/deepseek-v4-pro.json')
|
|
|
|
const reloaded = fakeCtx(dir, {})
|
|
expect(getSettings(reloaded).commitProfilePath).toBe('/p/deepseek-v4-pro.json')
|
|
expect(await getEffectiveCommitProfilePath(reloaded)).toBe('/p/deepseek-v4-pro.json')
|
|
})
|
|
})
|
|
|
|
describe('persistSeededProfiles', () => {
|
|
it('memento 里有旧设置、磁盘无文件时,把旧设置迁到 settings.json', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir, {
|
|
[SETTINGS_KEY]: { commitProfilePath: '/old/from-memento.json', webhookPort: 9000 },
|
|
})
|
|
await persistSeededProfiles(ctx)
|
|
|
|
const reloaded = fakeCtx(dir, {})
|
|
expect(getSettings(reloaded).commitProfilePath).toBe('/old/from-memento.json')
|
|
expect(getSettings(reloaded).webhookPort).toBe(9000)
|
|
})
|
|
|
|
it('key 缺失且本机有 deepseek-v4-pro 时,把 seed 写进 settings.json', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir)
|
|
vi.spyOn(await import('../cc/profiles.js'), 'listClaudeProfiles').mockResolvedValue([
|
|
{ name: DEFAULT_COMMIT_PROFILE_NAME, path: '/p/deepseek-v4-pro.json' },
|
|
])
|
|
await persistSeededProfiles(ctx)
|
|
|
|
const reloaded = fakeCtx(dir, {})
|
|
expect(getSettings(reloaded).commitProfilePath).toBe('/p/deepseek-v4-pro.json')
|
|
})
|
|
|
|
it('用户主动存了空串,不再被 seed 覆盖', async () => {
|
|
const dir = tempDir()
|
|
const ctx = fakeCtx(dir)
|
|
await saveSettings(ctx, { commitProfilePath: '' })
|
|
vi.spyOn(await import('../cc/profiles.js'), 'listClaudeProfiles').mockResolvedValue([
|
|
{ name: DEFAULT_COMMIT_PROFILE_NAME, path: '/p/deepseek-v4-pro.json' },
|
|
])
|
|
await persistSeededProfiles(ctx)
|
|
expect(getSettings(ctx).commitProfilePath).toBe('')
|
|
})
|
|
})
|