From 21b2b57d28e79cc41457bb93aa2568a5fbfa1a60 Mon Sep 17 00:00:00 2001 From: Wxw-Gu Date: Tue, 15 Sep 2026 11:20:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9A=90=E8=97=8F=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=87=8C=E7=9A=84=E9=98=B2=E6=92=A4=E5=9B=9E=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E5=8E=86=E5=8F=B2=E5=BC=80=E5=90=AF=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E4=B8=BA=20false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/services/settings-store.ts | 10 ++- .../settings/model/settingsNavigation.ts | 6 +- tests/e2e/app.spec.ts | 7 +- tests/e2e/visual.spec.ts | 32 +-------- tests/unit/settings-navigation.test.ts | 7 ++ .../settings-store-recall-migration.test.ts | 71 +++++++++++++++++++ 6 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 tests/unit/settings-store-recall-migration.test.ts diff --git a/src/main/services/settings-store.ts b/src/main/services/settings-store.ts index 993f8e9..8ef4ded 100644 --- a/src/main/services/settings-store.ts +++ b/src/main/services/settings-store.ts @@ -174,6 +174,12 @@ export function loadSettings(): AppSettings { if (cache.dbRoot) { cache.dbRoot = redirectLegacyWeChatFilesToXwechat(cache.dbRoot) } + // 防撤回已下线(设置入口已隐藏):历史版本可能把它持久化为 true。 + // 这里强制收敛为 false 并回写磁盘,确保旧的撤回监听与撤回日志不会继续运行。 + if (cache.recallProtectionEnabled) { + cache.recallProtectionEnabled = false + saveSettings(cache) + } return cache } } catch (error) { @@ -184,7 +190,9 @@ export function loadSettings(): AppSettings { } export function saveSettings(next: AppSettings): AppSettings { - cache = { ...next } + // 防撤回已下线:所有写入路径(含 settings:set 补丁)统一收敛为 false, + // 避免遗留入口或旧版本把它重新打开。 + cache = { ...next, recallProtectionEnabled: false } try { ensureDir() fs.writeJsonSync(SETTINGS_FILE, cache, { spaces: 2 }) diff --git a/src/renderer/src/features/settings/model/settingsNavigation.ts b/src/renderer/src/features/settings/model/settingsNavigation.ts index 5218a05..65e946a 100644 --- a/src/renderer/src/features/settings/model/settingsNavigation.ts +++ b/src/renderer/src/features/settings/model/settingsNavigation.ts @@ -35,7 +35,8 @@ export const SETTINGS_NAVIGATION: SettingsNavigationGroup[] = [ { label: '应用', items: [ - { id: 'recall-protection', label: '防撤回' }, + // “防撤回”已下线,不再在设置里暴露;保留 category/render case 以兼容已有页面状态。 + // { id: 'recall-protection', label: '防撤回' }, { id: 'appearance', label: '外观与行为' }, { id: 'advanced', label: '高级' }, { id: 'about', label: '关于' } @@ -48,5 +49,6 @@ export const SETTINGS_CATEGORY_LABELS = { SETTINGS_NAVIGATION.flatMap((group) => group.items.map((item) => [item.id, item.label])) ), 'personal-wechat-send': '微信发送', - 'storage-export': '存储与导出' + 'storage-export': '存储与导出', + 'recall-protection': '防撤回' } as Record diff --git a/tests/e2e/app.spec.ts b/tests/e2e/app.spec.ts index c5ef78f..63c6ea3 100644 --- a/tests/e2e/app.spec.ts +++ b/tests/e2e/app.spec.ts @@ -499,11 +499,8 @@ test('SETTINGS-02 basic settings controls fit the default desktop window and kee await debugSwitch.click() await expect(debugSwitch).toBeChecked() - await fixture.page.getByRole('button', { name: '防撤回' }).click() - const recallSwitch = fixture.page.getByRole('switch', { name: '开启防撤回' }) - await expect(recallSwitch).toBeVisible() - await recallSwitch.click() - await expect(recallSwitch).toBeChecked() + // 防撤回已下线:设置导航不再暴露该入口(与「存储与导出」同一处理方式)。 + await expect(fixture.page.getByRole('button', { name: '防撤回' })).toHaveCount(0) await fixture.page.getByRole('button', { name: '关于' }).click() await expect(fixture.page.getByRole('button', { name: '检查更新' })).toBeVisible() diff --git a/tests/e2e/visual.spec.ts b/tests/e2e/visual.spec.ts index c8374e9..5c6c4cd 100644 --- a/tests/e2e/visual.spec.ts +++ b/tests/e2e/visual.spec.ts @@ -661,36 +661,8 @@ test('SETTINGS-03 database key controls visual @visual', async () => { } }) -for (const appearanceTheme of ['light', 'dark'] as const) { - test(`SETTINGS-08 recall protection ${appearanceTheme} visual @visual`, async () => { - const fixture = await launchTestApp({ now: visualNow, appearanceTheme }) - const pageErrors: Error[] = [] - fixture.page.on('pageerror', (error) => pageErrors.push(error)) - try { - await fixture.page - .getByRole('navigation', { name: '一级导航' }) - .getByRole('button', { name: '设置' }) - .click() - await fixture.page.getByRole('button', { name: '防撤回' }).click() - await expect(fixture.page.getByRole('switch', { name: '开启防撤回' })).toBeVisible() - await expect(fixture.page.locator('html')).toHaveAttribute('data-theme', appearanceTheme) - expect( - await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) - ).toBe(true) - expect(pageErrors).toEqual([]) - await clearScreenshotFocus(fixture.page) - await expect(fixture.page).toHaveScreenshot( - `settings-recall-protection-${appearanceTheme}.png`, - { - animations: 'disabled', - caret: 'hide' - } - ) - } finally { - await fixture.close() - } - }) -} +// SETTINGS-08(防撤回页 light/dark 视觉)已移除:该设置入口已下线, +// 页面不再可达,对应的 settings-recall-protection-{light,dark}.png 基线同步删除。 for (const appearanceTheme of ['light', 'dark'] as const) { test(`SETTINGS-04 image decryption controls ${appearanceTheme} visual @visual`, async () => { diff --git a/tests/unit/settings-navigation.test.ts b/tests/unit/settings-navigation.test.ts index 0ade876..47a3106 100644 --- a/tests/unit/settings-navigation.test.ts +++ b/tests/unit/settings-navigation.test.ts @@ -22,4 +22,11 @@ describe('settings navigation', () => { ) expect(SETTINGS_CATEGORY_LABELS['storage-export']).toBe('存储与导出') }) + + it('hides recall protection from visible navigation', () => { + expect(SETTINGS_NAVIGATION.flatMap((group) => group.items)).not.toEqual( + expect.arrayContaining([{ id: 'recall-protection', label: '防撤回' }]) + ) + expect(SETTINGS_CATEGORY_LABELS['recall-protection']).toBe('防撤回') + }) }) diff --git a/tests/unit/settings-store-recall-migration.test.ts b/tests/unit/settings-store-recall-migration.test.ts new file mode 100644 index 0000000..2acd042 --- /dev/null +++ b/tests/unit/settings-store-recall-migration.test.ts @@ -0,0 +1,71 @@ +import { mkdtempSync, readFileSync, writeFileSync } from 'fs' +import { tmpdir } from 'os' +import { join } from 'path' +import { afterAll, describe, expect, it, vi } from 'vitest' + +vi.mock('electron', () => ({ app: { getPath: () => '/tmp/tracememo-settings-test-user-data' } })) + +/** + * settings-store 在模块加载时确定 settings.json 路径并缓存设置对象, + * 所以每个用例都要用独立的目录 + 重新加载模块。 + */ +async function importStore(directory: string): Promise< + typeof import('../../src/main/services/settings-store') +> { + process.env.WE_SETTINGS_DIR = directory + vi.resetModules() + return import('../../src/main/services/settings-store') +} + +function readSettingsFile(directory: string): Record { + return JSON.parse(readFileSync(join(directory, 'settings.json'), 'utf8')) as Record< + string, + unknown + > +} + +function createDirectory(): string { + return mkdtempSync(join(tmpdir(), 'tracememo-settings-recall-')) +} + +describe('recall protection retirement migration', () => { + afterAll(() => { + delete process.env.WE_SETTINGS_DIR + }) + + it('forces a previously enabled recall protection back to false on disk', async () => { + const directory = createDirectory() + writeFileSync( + join(directory, 'settings.json'), + JSON.stringify({ recallProtectionEnabled: true }), + 'utf8' + ) + + const store = await importStore(directory) + + expect(store.loadSettings().recallProtectionEnabled).toBe(false) + expect(readSettingsFile(directory).recallProtectionEnabled).toBe(false) + }) + + it('keeps recall protection off for every write path', async () => { + const directory = createDirectory() + const store = await importStore(directory) + + expect(store.updateSettings({ recallProtectionEnabled: true }).recallProtectionEnabled).toBe( + false + ) + expect(store.loadSettings().recallProtectionEnabled).toBe(false) + + expect( + store.saveSettings({ ...store.loadSettings(), recallProtectionEnabled: true }) + .recallProtectionEnabled + ).toBe(false) + expect(readSettingsFile(directory).recallProtectionEnabled).toBe(false) + }) + + it('defaults recall protection to false without a settings file', async () => { + const store = await importStore(createDirectory()) + + expect(store.loadSettings().recallProtectionEnabled).toBe(false) + }) +})