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 <noreply@anthropic.com>
This commit is contained in:
ClaudiaFang 2026-05-22 03:40:45 +00:00
parent fcb9447733
commit 1f71a43980
3 changed files with 92 additions and 131 deletions

View file

@ -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<App>;
let mockGitService: Mocked<GitServiceInterface>;
let mockAdapter: Mocked<DataAdapter>;
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<DataAdapter>;
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<App>;
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<GitServiceInterface>;
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);

View file

@ -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<App>;
let mockGitService: Mocked<GitServiceInterface>;
let mockAdapter: Mocked<DataAdapter>;
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<DataAdapter>;
mockApp = {
vault: {
read: vi.fn(),
modify: vi.fn(),
getFileByPath: vi.fn().mockReturnValue(null),
adapter: mockAdapter,
},
} as unknown as Mocked<App>;
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<GitServiceInterface>;
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');

View file

@ -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<App>;
mockGitService: Mocked<GitServiceInterface>;
mockAdapter: Mocked<DataAdapter>;
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<DataAdapter>;
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<App>;
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<GitServiceInterface>;
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;