mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 06:54:27 +00:00
Move connection testing/state from the settings tab into a shared plugin-level store (GitLabFilesPush.connectionStatus, onConnectionStatusChange, testConnection) so a new status bar item can reflect the same in-flight test instead of racing a second one. The settings tab badge now subscribes to the shared state and unsubscribes on hide(). Status bar shows service name + state, tooltip carries the error detail, and clicking retests the connection.
122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-deprecated -- exercising the Obsidian < 1.13 display() compat fallback intentionally */
|
|
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { App } from 'obsidian';
|
|
import { DEFAULT_SETTINGS, GitLabSyncSettingTab } from '../../src/settings';
|
|
import GitLabFilesPush from '../../src/main';
|
|
import type { ConnectionTestResult } from '../../src/services/git-service-base';
|
|
import { createContainer, setupObsidianDOM } from './setup-dom';
|
|
|
|
vi.mock('../../src/main', () => ({
|
|
default: class {},
|
|
}));
|
|
|
|
beforeAll(() => { setupObsidianDOM(); });
|
|
|
|
// Mirrors the connection-status pub/sub that main.ts owns (onConnectionStatusChange
|
|
// / testConnection), since src/main is mocked out above and the settings tab now
|
|
// delegates to the plugin instead of running its own connection test.
|
|
function createPluginStub(testConnection: () => Promise<ConnectionTestResult>): GitLabFilesPush {
|
|
let connectionStatus: { state: string; detail?: string } = { state: 'checking' };
|
|
const listeners = new Set<(status: typeof connectionStatus) => void>();
|
|
|
|
return {
|
|
settings: { ...DEFAULT_SETTINGS },
|
|
saveSettings: vi.fn().mockResolvedValue(undefined),
|
|
initializeGitService: vi.fn(),
|
|
gitService: { testConnection },
|
|
onConnectionStatusChange: vi.fn((listener: (status: typeof connectionStatus) => void) => {
|
|
listeners.add(listener);
|
|
listener(connectionStatus);
|
|
return () => listeners.delete(listener);
|
|
}),
|
|
testConnection: vi.fn(async () => {
|
|
connectionStatus = { state: 'checking' };
|
|
for (const listener of listeners) listener(connectionStatus);
|
|
|
|
const result = await testConnection();
|
|
if (!result.repoOk) {
|
|
connectionStatus = { state: 'disconnected', detail: result.error ?? 'unreachable' };
|
|
} else if (!result.branchOk) {
|
|
connectionStatus = { state: 'disconnected', detail: 'branch not found' };
|
|
} else {
|
|
connectionStatus = { state: 'connected' };
|
|
}
|
|
for (const listener of listeners) listener(connectionStatus);
|
|
return result;
|
|
}),
|
|
} as unknown as GitLabFilesPush;
|
|
}
|
|
|
|
function scheduleConnectionTest(tab: GitLabSyncSettingTab): void {
|
|
(tab as unknown as { scheduleConnectionTest: () => void }).scheduleConnectionTest();
|
|
}
|
|
|
|
describe('GitLabSyncSettingTab connection status badge', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
it('shows checking then connected after opening the tab', async () => {
|
|
const testConnection = vi.fn().mockResolvedValue({ repoOk: true, branchOk: true });
|
|
const tab = new GitLabSyncSettingTab(new App(), createPluginStub(testConnection));
|
|
tab.containerEl = createContainer();
|
|
|
|
tab.display();
|
|
|
|
const badge = tab.containerEl.querySelector('.gfs-connection-status') as HTMLElement;
|
|
expect(badge.classList.contains('is-checking')).toBe(true);
|
|
|
|
await vi.advanceTimersByTimeAsync(800);
|
|
|
|
expect(testConnection).toHaveBeenCalledTimes(1);
|
|
expect(badge.classList.contains('is-connected')).toBe(true);
|
|
expect(badge.textContent).toBe('Connected');
|
|
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('debounces repeated field edits into a single connection test', async () => {
|
|
const testConnection = vi.fn().mockResolvedValue({ repoOk: false, branchOk: false, error: 'bad token' });
|
|
const plugin = createPluginStub(testConnection);
|
|
const tab = new GitLabSyncSettingTab(new App(), plugin);
|
|
tab.containerEl = createContainer();
|
|
|
|
tab.display();
|
|
await vi.advanceTimersByTimeAsync(800);
|
|
testConnection.mockClear();
|
|
|
|
// Simulate rapid keystrokes in the token field via the debounced hook directly.
|
|
scheduleConnectionTest(tab);
|
|
scheduleConnectionTest(tab);
|
|
scheduleConnectionTest(tab);
|
|
|
|
await vi.advanceTimersByTimeAsync(799);
|
|
expect(testConnection).not.toHaveBeenCalled();
|
|
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
expect(testConnection).toHaveBeenCalledTimes(1);
|
|
|
|
const badge = tab.containerEl.querySelector('.gfs-connection-status') as HTMLElement;
|
|
expect(badge.classList.contains('is-disconnected')).toBe(true);
|
|
expect(badge.textContent).toContain('bad token');
|
|
|
|
vi.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe('GitLabSyncSettingTab ignore patterns setting', () => {
|
|
it('renders a textarea seeded with the saved ignorePatterns value', async () => {
|
|
const plugin = createPluginStub(vi.fn().mockResolvedValue({ repoOk: true, branchOk: true }));
|
|
plugin.settings.ignorePatterns = 'draft/\n*.tmp';
|
|
const tab = new GitLabSyncSettingTab(new App(), plugin);
|
|
tab.containerEl = createContainer();
|
|
|
|
vi.useFakeTimers();
|
|
tab.display();
|
|
await vi.advanceTimersByTimeAsync(800);
|
|
vi.useRealTimers();
|
|
|
|
const textarea = tab.containerEl.querySelector('textarea') as HTMLTextAreaElement;
|
|
expect(textarea.value).toBe('draft/\n*.tmp');
|
|
});
|
|
});
|