mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
Adds a "what's new" modal shown once after the plugin updates to a new version, so users don't miss what changed: - New src/changelog.ts: a hand-curated CHANGELOG array (distinct from the auto-generated CHANGELOG.md, which lists every commit) where entries can be marked `notable` so they're called out separately from minor fixes, per the issue's clarification comment. - New src/utils/version.ts: compareVersions() does numeric per-segment comparison (so "1.10.0" sorts after "1.9.0", unlike a plain string compare). - getUnseenReleases() filters+sorts the changelog to what's newer than a given "last seen" version, extracted as a pure/testable function rather than inlined in main.ts (which isn't unit-tested in this repo). - New settings field lastSeenVersion, persisted the same way as other settings. On a fresh install (empty lastSeenVersion) it's just recorded silently — no modal, since there's nothing to compare against. On an actual version bump, WhatsNewModal shows the unseen releases' highlights, with a "View full changelog" link out to CHANGELOG.md and a "Got it" dismiss; the version is recorded either way so the tip only ever shows once per upgrade. - The whole check is wrapped in try/catch so a malformed version string can never break plugin startup. Closes #39
125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { SyncManager } from '../../src/logic/sync-manager';
|
|
|
|
import { App, TFile } from 'obsidian';
|
|
import type { GitServiceInterface } from '../../src/services/git-service-interface';
|
|
import type { GitLabFilesPushSettings } from '../../src/settings';
|
|
|
|
vi.mock('obsidian', () => ({
|
|
Notice: vi.fn(),
|
|
TFile: class {
|
|
path: string = '';
|
|
name: string = '';
|
|
},
|
|
App: class {},
|
|
}));
|
|
|
|
const mockApp = {
|
|
vault: {
|
|
read: vi.fn(),
|
|
modify: vi.fn(),
|
|
getFileByPath: vi.fn(),
|
|
getAbstractFileByPath: vi.fn(),
|
|
createFolder: vi.fn(),
|
|
adapter: {
|
|
exists: vi.fn(),
|
|
read: vi.fn(),
|
|
write: vi.fn(),
|
|
}
|
|
}
|
|
} as unknown as App;
|
|
|
|
const mockGetFile = vi.fn();
|
|
const mockPushFile = vi.fn();
|
|
const mockGitService = {
|
|
getFile: mockGetFile,
|
|
pushFile: mockPushFile,
|
|
} as unknown as GitServiceInterface;
|
|
|
|
const mockSettings: GitLabFilesPushSettings = {
|
|
serviceType: 'gitlab',
|
|
gitlabToken: '',
|
|
gitlabBaseUrl: 'https://gitlab.com',
|
|
projectId: '',
|
|
githubToken: '',
|
|
githubOwner: '',
|
|
githubRepo: '',
|
|
giteaToken: '',
|
|
giteaBaseUrl: '',
|
|
giteaOwner: '',
|
|
giteaRepo: '',
|
|
branch: 'main',
|
|
rootPath: 'notes',
|
|
vaultFolder: 'Work',
|
|
syncMetadata: {},
|
|
symlinkHandling: 'real',
|
|
ignorePatterns: '',
|
|
lastSeenVersion: '',
|
|
};
|
|
|
|
describe('SyncManager Mapping', () => {
|
|
let manager: SyncManager;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockSettings.syncMetadata = {};
|
|
manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
|
});
|
|
|
|
it('should strip vaultFolder when pushing', async () => {
|
|
const vaultPath = 'Work/test.md';
|
|
const mockFile = Object.assign(new TFile(), { path: vaultPath, name: 'test.md' });
|
|
|
|
const getFileByPathSpy = vi.spyOn(mockApp.vault, 'getFileByPath');
|
|
const readSpy = vi.spyOn(mockApp.vault, 'read');
|
|
getFileByPathSpy.mockReturnValue(mockFile);
|
|
readSpy.mockResolvedValue('content');
|
|
|
|
vi.mocked(mockGetFile).mockResolvedValue({ content: '', sha: '' });
|
|
vi.mocked(mockPushFile).mockResolvedValue({ path: 'notes/test.md' });
|
|
|
|
await manager.pushFile(mockFile);
|
|
|
|
expect(mockGetFile).toHaveBeenCalledWith('test.md', 'main');
|
|
expect(mockPushFile).toHaveBeenCalledWith(
|
|
'test.md',
|
|
'content',
|
|
'main',
|
|
'Update test.md from Obsidian',
|
|
''
|
|
);
|
|
});
|
|
|
|
it('should map back to vaultFolder when pulling', async () => {
|
|
const vaultPath = 'Work/remote.md';
|
|
vi.mocked(mockGetFile).mockResolvedValue({ content: 'remote content', sha: 'sha' });
|
|
const existsSpy = vi.spyOn(mockApp.vault.adapter, 'exists');
|
|
const writeSpy = vi.spyOn(mockApp.vault.adapter, 'write');
|
|
existsSpy.mockResolvedValue(false);
|
|
writeSpy.mockResolvedValue(undefined);
|
|
|
|
await manager.pullFile(vaultPath);
|
|
|
|
expect(mockGetFile).toHaveBeenCalledWith('remote.md', 'main');
|
|
expect(writeSpy).toHaveBeenCalledWith(vaultPath, 'remote content');
|
|
});
|
|
|
|
it('should handle root-level files correctly when no vaultFolder', async () => {
|
|
mockSettings.vaultFolder = '';
|
|
manager = new SyncManager(mockApp, mockGitService, mockSettings);
|
|
|
|
const path = 'root.md';
|
|
const mockFile = Object.assign(new TFile(), { path, name: 'root.md' });
|
|
|
|
const getFileByPathSpy = vi.spyOn(mockApp.vault, 'getFileByPath');
|
|
const readSpy = vi.spyOn(mockApp.vault, 'read');
|
|
getFileByPathSpy.mockReturnValue(mockFile);
|
|
readSpy.mockResolvedValue('content');
|
|
|
|
vi.mocked(mockGetFile).mockResolvedValue({ content: '', sha: '' });
|
|
|
|
await manager.pushFile(mockFile);
|
|
|
|
expect(mockGetFile).toHaveBeenCalledWith('root.md', 'main');
|
|
});
|
|
});
|