fix: export/import — отмена пользователем не показывает error toast

Bug: при отмене save-dialog или open-dialog DataCard показывал тост
«Не удалось сохранить» / «Файл не подошёл». Но cancel — это не ошибка.

Расширил IPC возврат: { ok, canceled, path?, error? }. UI теперь
различает: ok → success toast, !ok && canceled → молча, !ok && !canceled
→ error toast.

+9 тестов на validateSettingsPatch для voicePromptsEnabled,
meetingAutoPause, lastSeenVersion (semver-regex / null-сброс /
malformed). Итого 159 → 168 тестов.

Settings → About теперь показывает текущую версию приложения
(раньше была только кнопка «Что нового»). Загружается через
IPC.getAppVersion при mount.
This commit is contained in:
AnRil
2026-05-22 23:26:11 +07:00
parent 0c813c3ac8
commit 2b7eb412c7
4 changed files with 84 additions and 12 deletions

View File

@@ -352,12 +352,16 @@ export function registerIpc(): void {
defaultPath,
filters: [{ name: 'JSON', extensions: ['json'] }]
})
if (result.canceled || !result.filePath) return { ok: false, path: null }
// Cancel — это не ошибка. Возвращаем canceled=true чтобы UI мог
// ничего не показывать (без error toast).
if (result.canceled || !result.filePath) {
return { ok: false, canceled: true, path: null }
}
try {
writeFileSync(result.filePath, exportState(), 'utf-8')
return { ok: true, path: result.filePath }
return { ok: true, canceled: false, path: result.filePath }
} catch (e) {
return { ok: false, path: null, error: String(e) }
return { ok: false, canceled: false, path: null, error: String(e) }
}
})
@@ -369,7 +373,7 @@ export function registerIpc(): void {
filters: [{ name: 'JSON', extensions: ['json'] }]
})
if (result.canceled || result.filePaths.length === 0) {
return { ok: false }
return { ok: false, canceled: true }
}
try {
const raw = readFileSync(result.filePaths[0], 'utf-8')
@@ -378,9 +382,9 @@ export function registerIpc(): void {
broadcastState()
broadcastHistoryChanged()
}
return { ok }
return { ok, canceled: false }
} catch (e) {
return { ok: false, error: String(e) }
return { ok: false, canceled: false, error: String(e) }
}
})
}

View File

@@ -266,6 +266,65 @@ describe('validateSettingsPatch', () => {
expect(validateSettingsPatch({ snoozeMinutes: -5 })).toBeNull()
})
it('accepts voicePromptsEnabled boolean', () => {
expect(validateSettingsPatch({ voicePromptsEnabled: true })).toEqual({
voicePromptsEnabled: true
})
expect(validateSettingsPatch({ voicePromptsEnabled: false })).toEqual({
voicePromptsEnabled: false
})
})
it('rejects non-boolean voicePromptsEnabled in patch', () => {
expect(validateSettingsPatch({ voicePromptsEnabled: 'yes' })).toBeNull()
expect(validateSettingsPatch({ voicePromptsEnabled: 1 })).toBeNull()
})
it('accepts meetingAutoPause boolean', () => {
expect(validateSettingsPatch({ meetingAutoPause: true })).toEqual({
meetingAutoPause: true
})
})
it('rejects non-boolean meetingAutoPause', () => {
expect(validateSettingsPatch({ meetingAutoPause: 'yes' })).toBeNull()
})
describe('lastSeenVersion', () => {
it('accepts valid semver', () => {
const r = validateSettingsPatch({ lastSeenVersion: '0.5.7' })
expect(r?.lastSeenVersion).toBe('0.5.7')
expect(validateSettingsPatch({ lastSeenVersion: '10.20.30' })).toEqual({
lastSeenVersion: '10.20.30'
})
})
it('accepts pre-release suffix', () => {
const r = validateSettingsPatch({ lastSeenVersion: '0.5.7-beta.1' })
expect(r?.lastSeenVersion).toBe('0.5.7-beta.1')
})
it('treats null/undefined as reset to undefined', () => {
const r1 = validateSettingsPatch({ lastSeenVersion: null })
expect(r1).toEqual({ lastSeenVersion: undefined })
const r2 = validateSettingsPatch({ lastSeenVersion: undefined })
// 'lastSeenVersion' is `in raw` even if undefined — both treated reset.
expect(r2).toEqual({ lastSeenVersion: undefined })
})
it('rejects malformed strings', () => {
expect(validateSettingsPatch({ lastSeenVersion: '0.5' })).toBeNull()
expect(validateSettingsPatch({ lastSeenVersion: 'v0.5.7' })).toBeNull()
expect(validateSettingsPatch({ lastSeenVersion: 'beta' })).toBeNull()
expect(validateSettingsPatch({ lastSeenVersion: '' })).toBeNull()
})
it('rejects non-strings', () => {
expect(validateSettingsPatch({ lastSeenVersion: 42 })).toBeNull()
expect(validateSettingsPatch({ lastSeenVersion: ['1', '0', '0'] })).toBeNull()
})
})
describe('quietHours subobject', () => {
const baseQh = {
enabled: true,