From 1f71a43980cc01b547896a068c4944e692776847 Mon Sep 17 00:00:00 2001 From: ClaudiaFang Date: Fri, 22 May 2026 03:40:45 +0000 Subject: [PATCH] refactor(test): extract shared SyncManager mock setup to reduce duplication Move repeated beforeEach mock initialization into createSyncManagerMocks() helper to eliminate ~45 lines of copy-paste across binary and hidden test files. Co-Authored-By: Claude Sonnet 4.6 --- tests/logic/sync-manager-binary.test.ts | 68 +++--------------- tests/logic/sync-manager-hidden.test.ts | 91 +++++------------------- tests/logic/sync-manager-test-helpers.ts | 64 +++++++++++++++++ 3 files changed, 92 insertions(+), 131 deletions(-) create mode 100644 tests/logic/sync-manager-test-helpers.ts diff --git a/tests/logic/sync-manager-binary.test.ts b/tests/logic/sync-manager-binary.test.ts index fbee3d0..2b9bd46 100644 --- a/tests/logic/sync-manager-binary.test.ts +++ b/tests/logic/sync-manager-binary.test.ts @@ -1,72 +1,20 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import { describe, it, expect, vi, beforeEach, Mocked } from 'vitest'; -import { SyncManager } from '../../src/logic/sync-manager'; -import { App, DataAdapter } from 'obsidian'; -import { GitLabFilesPushSettings } from '../../src/settings'; -import { GitServiceInterface } from '../../src/services/git-service-interface'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createSyncManagerMocks, makeBuf, SyncManagerMocks } from './sync-manager-test-helpers'; vi.mock('obsidian'); describe('SyncManager – binary file handling', () => { - let manager: SyncManager; - let mockApp: Mocked; - let mockGitService: Mocked; - let mockAdapter: Mocked; - let mockSettings: GitLabFilesPushSettings; - - const makeBuf = (bytes: number[]) => new Uint8Array(bytes).buffer; + let mocks: SyncManagerMocks; beforeEach(() => { vi.clearAllMocks(); - - mockAdapter = { - exists: vi.fn(), - read: vi.fn(), - write: vi.fn(), - readBinary: vi.fn(), - writeBinary: vi.fn(), - mkdir: vi.fn().mockResolvedValue(undefined), - } as unknown as Mocked; - - mockApp = { - vault: { - read: vi.fn(), - readBinary: vi.fn(), - modify: vi.fn(), - modifyBinary: vi.fn(), - getFileByPath: vi.fn().mockReturnValue(null), - adapter: mockAdapter, - }, - } as unknown as Mocked; - - mockGitService = { - pushFile: vi.fn(), - getFile: vi.fn(), - testConnection: vi.fn(), - listFiles: vi.fn(), - deleteFile: vi.fn(), - getRepoGitignores: vi.fn(), - updateConfig: vi.fn(), - } as unknown as Mocked; - - mockSettings = { - serviceType: 'github', - githubToken: 'token', - githubOwner: 'owner', - githubRepo: 'repo', - branch: 'main', - syncMetadata: {}, - vaultFolder: '', - rootPath: '', - } as unknown as GitLabFilesPushSettings; - - manager = new SyncManager(mockApp, mockGitService, mockSettings); - // @ts-ignore - manager.saveSettings = vi.fn().mockResolvedValue(undefined); + mocks = createSyncManagerMocks(); }); describe('pushFile with binary path (string)', () => { it('reads via adapter.readBinary for binary extensions', async () => { + const { manager, mockAdapter, mockGitService } = mocks; const buf = makeBuf([137, 80, 78, 71]); vi.mocked(mockAdapter.exists).mockResolvedValue(true); vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf); @@ -83,6 +31,7 @@ describe('SyncManager – binary file handling', () => { }); it('skips push when binary content is already in sync', async () => { + const { manager, mockAdapter, mockGitService } = mocks; const buf = makeBuf([1, 2, 3, 4]); vi.mocked(mockAdapter.exists).mockResolvedValue(true); vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf); @@ -94,6 +43,7 @@ describe('SyncManager – binary file handling', () => { }); it('updates metadata when binary is already in sync', async () => { + const { manager, mockAdapter, mockGitService, mockSettings } = mocks; const buf = makeBuf([1, 2, 3]); vi.mocked(mockAdapter.exists).mockResolvedValue(true); vi.mocked(mockAdapter.readBinary).mockResolvedValue(buf); @@ -109,6 +59,7 @@ describe('SyncManager – binary file handling', () => { describe('pullFile with binary content', () => { it('writes via adapter.writeBinary when remote content is ArrayBuffer', async () => { + const { manager, mockAdapter, mockGitService } = mocks; const buf = makeBuf([137, 80, 78, 71]); vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); @@ -120,6 +71,7 @@ describe('SyncManager – binary file handling', () => { }); it('creates parent directory before writing binary', async () => { + const { manager, mockAdapter, mockGitService } = mocks; const buf = makeBuf([255, 216, 255]); vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); @@ -131,6 +83,7 @@ describe('SyncManager – binary file handling', () => { }); it('skips pull when binary content is already in sync', async () => { + const { manager, mockAdapter, mockGitService, mockSettings } = mocks; const buf = makeBuf([1, 2, 3]); vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf }); vi.mocked(mockAdapter.exists).mockResolvedValue(true); @@ -143,6 +96,7 @@ describe('SyncManager – binary file handling', () => { }); it('updates metadata after pulling binary file', async () => { + const { manager, mockAdapter, mockGitService, mockSettings } = mocks; const buf = makeBuf([0, 1, 2]); vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'bin-sha', content: buf }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); diff --git a/tests/logic/sync-manager-hidden.test.ts b/tests/logic/sync-manager-hidden.test.ts index b4d6eb8..2207aa5 100644 --- a/tests/logic/sync-manager-hidden.test.ts +++ b/tests/logic/sync-manager-hidden.test.ts @@ -1,71 +1,21 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import { describe, it, expect, vi, beforeEach, Mocked } from 'vitest'; -import { SyncManager } from '../../src/logic/sync-manager'; -import { App, DataAdapter } from 'obsidian'; -import { GitLabFilesPushSettings } from '../../src/settings'; -import { GitServiceInterface } from '../../src/services/git-service-interface'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createSyncManagerMocks, SyncManagerMocks } from './sync-manager-test-helpers'; vi.mock('obsidian'); describe('SyncManager – hidden file support', () => { - let manager: SyncManager; - let mockApp: Mocked; - let mockGitService: Mocked; - let mockAdapter: Mocked; - let mockSettings: GitLabFilesPushSettings; + let mocks: SyncManagerMocks; beforeEach(() => { vi.clearAllMocks(); - - mockAdapter = { - exists: vi.fn(), - read: vi.fn(), - write: vi.fn(), - writeBinary: vi.fn(), - mkdir: vi.fn().mockResolvedValue(undefined), - } as unknown as Mocked; - - mockApp = { - vault: { - read: vi.fn(), - modify: vi.fn(), - getFileByPath: vi.fn().mockReturnValue(null), - adapter: mockAdapter, - }, - } as unknown as Mocked; - - mockGitService = { - pushFile: vi.fn(), - getFile: vi.fn(), - testConnection: vi.fn(), - listFiles: vi.fn(), - deleteFile: vi.fn(), - getRepoGitignores: vi.fn(), - updateConfig: vi.fn(), - } as unknown as Mocked; - - mockSettings = { - serviceType: 'github', - githubToken: 'token', - githubOwner: 'owner', - githubRepo: 'repo', - branch: 'main', - syncMetadata: {}, - vaultFolder: '', - rootPath: '', - } as unknown as GitLabFilesPushSettings; - - manager = new SyncManager(mockApp, mockGitService, mockSettings); - // @ts-ignore - accessing private for test - manager.saveSettings = vi.fn().mockResolvedValue(undefined); + mocks = createSyncManagerMocks(); }); describe('pullFile with hidden paths', () => { it('creates single hidden parent directory on pull', async () => { - vi.mocked(mockGitService.getFile).mockResolvedValue({ - sha: 'abc123', - content: '{"key":"value"}', - }); + const { manager, mockAdapter, mockGitService } = mocks; + vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'abc123', content: '{"key":"value"}' }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); await manager.pullFile('.claude/settings.json'); @@ -75,10 +25,8 @@ describe('SyncManager – hidden file support', () => { }); it('creates all nested hidden parent directories on pull', async () => { - vi.mocked(mockGitService.getFile).mockResolvedValue({ - sha: 'def456', - content: 'nested content', - }); + const { manager, mockAdapter, mockGitService } = mocks; + vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'def456', content: 'nested content' }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); await manager.pullFile('.claude/memory/user.md'); @@ -89,10 +37,8 @@ describe('SyncManager – hidden file support', () => { }); it('does not fail if hidden directory already exists (mkdir throws)', async () => { - vi.mocked(mockGitService.getFile).mockResolvedValue({ - sha: 'abc123', - content: 'content', - }); + const { manager, mockAdapter, mockGitService } = mocks; + vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'abc123', content: 'content' }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); vi.mocked(mockAdapter.mkdir).mockRejectedValue(new Error('already exists')); @@ -101,10 +47,8 @@ describe('SyncManager – hidden file support', () => { }); it('updates metadata after pulling hidden file', async () => { - vi.mocked(mockGitService.getFile).mockResolvedValue({ - sha: 'sha-hidden', - content: 'file content', - }); + const { manager, mockAdapter, mockGitService, mockSettings } = mocks; + vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: 'sha-hidden', content: 'file content' }); vi.mocked(mockAdapter.exists).mockResolvedValue(false); await manager.pullFile('.claude/CLAUDE.md'); @@ -117,6 +61,7 @@ describe('SyncManager – hidden file support', () => { describe('pushFile with hidden paths', () => { it('pushes hidden file content via string path', async () => { + const { manager, mockAdapter, mockGitService } = mocks; vi.mocked(mockAdapter.exists).mockResolvedValue(true); vi.mocked(mockAdapter.read).mockResolvedValue('# Memory\n\nsome content'); vi.mocked(mockGitService.getFile).mockResolvedValue({ sha: '', content: '' }); @@ -125,15 +70,12 @@ describe('SyncManager – hidden file support', () => { await manager.pushFile('.claude/CLAUDE.md'); expect(mockGitService.pushFile).toHaveBeenCalledWith( - '.claude/CLAUDE.md', - '# Memory\n\nsome content', - 'main', - expect.any(String), - '' + '.claude/CLAUDE.md', '# Memory\n\nsome content', 'main', expect.any(String), '' ); }); it('skips push when hidden file is already in sync', async () => { + const { manager, mockAdapter, mockGitService } = mocks; const content = 'same content'; vi.mocked(mockAdapter.exists).mockResolvedValue(true); vi.mocked(mockAdapter.read).mockResolvedValue(content); @@ -144,7 +86,8 @@ describe('SyncManager – hidden file support', () => { expect(mockGitService.pushFile).not.toHaveBeenCalled(); }); - it('does not exist notice when hidden file is missing', async () => { + it('does not push when hidden file is missing from vault', async () => { + const { manager, mockAdapter, mockGitService } = mocks; vi.mocked(mockAdapter.exists).mockResolvedValue(false); await manager.pushFile('.claude/missing.json'); diff --git a/tests/logic/sync-manager-test-helpers.ts b/tests/logic/sync-manager-test-helpers.ts new file mode 100644 index 0000000..259b44e --- /dev/null +++ b/tests/logic/sync-manager-test-helpers.ts @@ -0,0 +1,64 @@ +import { vi, Mocked } from 'vitest'; +import { SyncManager } from '../../src/logic/sync-manager'; +import { App, DataAdapter } from 'obsidian'; +import { GitLabFilesPushSettings } from '../../src/settings'; +import { GitServiceInterface } from '../../src/services/git-service-interface'; + +export interface SyncManagerMocks { + manager: SyncManager; + mockApp: Mocked; + mockGitService: Mocked; + mockAdapter: Mocked; + mockSettings: GitLabFilesPushSettings; +} + +export function createSyncManagerMocks(): SyncManagerMocks { + const mockAdapter = { + exists: vi.fn(), + read: vi.fn(), + write: vi.fn(), + readBinary: vi.fn(), + writeBinary: vi.fn(), + mkdir: vi.fn().mockResolvedValue(undefined), + } as unknown as Mocked; + + const mockApp = { + vault: { + read: vi.fn(), + readBinary: vi.fn(), + modify: vi.fn(), + modifyBinary: vi.fn(), + getFileByPath: vi.fn().mockReturnValue(null), + adapter: mockAdapter, + }, + } as unknown as Mocked; + + const mockGitService = { + pushFile: vi.fn(), + getFile: vi.fn(), + testConnection: vi.fn(), + listFiles: vi.fn(), + deleteFile: vi.fn(), + getRepoGitignores: vi.fn(), + updateConfig: vi.fn(), + } as unknown as Mocked; + + const mockSettings = { + serviceType: 'github', + githubToken: 'token', + githubOwner: 'owner', + githubRepo: 'repo', + branch: 'main', + syncMetadata: {}, + vaultFolder: '', + rootPath: '', + } as unknown as GitLabFilesPushSettings; + + const manager = new SyncManager(mockApp, mockGitService, mockSettings); + // @ts-ignore - accessing private for test + manager.saveSettings = vi.fn().mockResolvedValue(undefined); + + return { manager, mockApp, mockGitService, mockAdapter, mockSettings }; +} + +export const makeBuf = (bytes: number[]) => new Uint8Array(bytes).buffer;