mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 06:54:27 +00:00
- Conflict modal (#42): resize to min(1100px,92vw) x min(85vh,800px) flex layout, remove the 280px content height cap, and add a Diff/Local/Remote tab switcher on narrow screens (defaults to Diff). - Settings connection status (#41): show a persistent Connected/Not connected/Checking badge in the settings tab, auto-tested on open and after an 800ms debounce on token/branch/URL/owner/repo edits, updated in place (no full re-render, so typing focus is preserved). - Local ignore patterns (#40): new "Ignore patterns" setting (.gitignore- style, multi-line) applied in GitignoreManager.isIgnored() in addition to the repo's own .gitignore, covering push/pull/refresh uniformly. Closes #42, #41, #40. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYCTyZw7gUmJ7oh1VTmAqh
269 lines
6.1 KiB
TypeScript
269 lines
6.1 KiB
TypeScript
import { vi } from 'vitest';
|
|
|
|
// Mock global browser environment if not in JSDOM
|
|
if (typeof document === 'undefined') {
|
|
(globalThis as unknown as { document: unknown }).document = {
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
createElement: vi.fn(() => ({
|
|
classList: { add: vi.fn(), contains: vi.fn(), toggle: vi.fn() },
|
|
appendChild: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
setAttribute: vi.fn(),
|
|
textContent: '',
|
|
type: 'text',
|
|
empty() {},
|
|
})),
|
|
};
|
|
}
|
|
if (typeof window === 'undefined') {
|
|
(globalThis as unknown as { window: unknown }).window = {
|
|
setInterval: vi.fn(),
|
|
clearInterval: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createButtonElement(): HTMLButtonElement {
|
|
return document.createElement('button');
|
|
}
|
|
|
|
export const Plugin = class {};
|
|
export const PluginSettingTab = class {
|
|
app: unknown;
|
|
plugin: unknown;
|
|
containerEl: HTMLElement;
|
|
|
|
constructor(app?: unknown, plugin?: unknown) {
|
|
this.app = app;
|
|
this.plugin = plugin;
|
|
this.containerEl = document.createElement('div') as HTMLElement;
|
|
}
|
|
};
|
|
|
|
export const TextComponent = class {
|
|
inputEl: HTMLInputElement;
|
|
private changeHandler?: (value: string) => void;
|
|
|
|
constructor(containerEl?: HTMLElement) {
|
|
this.inputEl = document.createElement('input');
|
|
containerEl?.appendChild(this.inputEl);
|
|
}
|
|
|
|
setPlaceholder(value: string) {
|
|
this.inputEl.placeholder = value;
|
|
return this;
|
|
}
|
|
|
|
setValue(value: string) {
|
|
this.inputEl.value = value;
|
|
return this;
|
|
}
|
|
|
|
onChange(handler: (value: string) => void) {
|
|
this.changeHandler = handler;
|
|
return this;
|
|
}
|
|
|
|
triggerChange(value: string) {
|
|
this.inputEl.value = value;
|
|
this.changeHandler?.(value);
|
|
}
|
|
};
|
|
|
|
export const TextAreaComponent = class {
|
|
inputEl: HTMLTextAreaElement;
|
|
private changeHandler?: (value: string) => void;
|
|
|
|
constructor(containerEl?: HTMLElement) {
|
|
this.inputEl = document.createElement('textarea');
|
|
containerEl?.appendChild(this.inputEl);
|
|
}
|
|
|
|
setPlaceholder(value: string) {
|
|
this.inputEl.placeholder = value;
|
|
return this;
|
|
}
|
|
|
|
setValue(value: string) {
|
|
this.inputEl.value = value;
|
|
return this;
|
|
}
|
|
|
|
onChange(handler: (value: string) => void) {
|
|
this.changeHandler = handler;
|
|
return this;
|
|
}
|
|
|
|
triggerChange(value: string) {
|
|
this.inputEl.value = value;
|
|
this.changeHandler?.(value);
|
|
}
|
|
};
|
|
|
|
export const DropdownComponent = class {
|
|
private changeHandler?: (value: string) => void;
|
|
|
|
addOption() { return this; }
|
|
setValue() { return this; }
|
|
onChange(handler: (value: string) => void) {
|
|
this.changeHandler = handler;
|
|
return this;
|
|
}
|
|
triggerChange(value: string) {
|
|
this.changeHandler?.(value);
|
|
}
|
|
};
|
|
|
|
export const ButtonComponent = class {
|
|
buttonEl: HTMLButtonElement;
|
|
|
|
constructor(containerEl?: HTMLElement) {
|
|
this.buttonEl = createButtonElement();
|
|
containerEl?.appendChild(this.buttonEl);
|
|
}
|
|
|
|
setButtonText(text: string) {
|
|
this.buttonEl.textContent = text;
|
|
return this;
|
|
}
|
|
|
|
setTooltip(text: string) {
|
|
this.buttonEl.title = text;
|
|
return this;
|
|
}
|
|
|
|
setCta() {
|
|
this.buttonEl.classList.add('mod-cta');
|
|
return this;
|
|
}
|
|
|
|
setWarning() {
|
|
this.buttonEl.classList.add('mod-warning');
|
|
return this;
|
|
}
|
|
|
|
setDestructive() {
|
|
this.buttonEl.classList.add('mod-destructive');
|
|
return this;
|
|
}
|
|
|
|
setIcon(icon: string) {
|
|
this.buttonEl.dataset.icon = icon;
|
|
return this;
|
|
}
|
|
|
|
onClick(handler: () => void) {
|
|
this.buttonEl.addEventListener('click', handler);
|
|
return this;
|
|
}
|
|
};
|
|
|
|
export const ExtraButtonComponent = class extends ButtonComponent {};
|
|
|
|
export const Setting = class {
|
|
containerEl?: HTMLElement;
|
|
|
|
constructor(containerEl?: HTMLElement) {
|
|
this.containerEl = containerEl;
|
|
}
|
|
|
|
setName() { return this; }
|
|
setDesc() { return this; }
|
|
setHeading() { return this; }
|
|
addToggle() { return this; }
|
|
addText(callback?: (component: InstanceType<typeof TextComponent>) => void) {
|
|
if (callback) callback(new TextComponent(this.containerEl));
|
|
return this;
|
|
}
|
|
addTextArea(callback?: (component: InstanceType<typeof TextAreaComponent>) => void) {
|
|
if (callback) callback(new TextAreaComponent(this.containerEl));
|
|
return this;
|
|
}
|
|
addButton(callback?: (component: InstanceType<typeof ButtonComponent>) => void) {
|
|
if (callback) callback(new ButtonComponent(this.containerEl));
|
|
return this;
|
|
}
|
|
addExtraButton(callback?: (component: InstanceType<typeof ExtraButtonComponent>) => void) {
|
|
if (callback) callback(new ExtraButtonComponent(this.containerEl));
|
|
return this;
|
|
}
|
|
addDropdown(callback?: (component: InstanceType<typeof DropdownComponent>) => void) {
|
|
if (callback) callback(new DropdownComponent());
|
|
return this;
|
|
}
|
|
};
|
|
|
|
export const Notice = class {
|
|
constructor() {}
|
|
setMessage() {}
|
|
hide() {}
|
|
};
|
|
|
|
export const Modal = class {
|
|
app: unknown;
|
|
contentEl: HTMLElement;
|
|
|
|
constructor(app?: unknown) {
|
|
this.app = app;
|
|
this.contentEl = document.createElement('div') as HTMLElement;
|
|
}
|
|
|
|
open() {
|
|
const withOnOpen = this as unknown as { onOpen?: () => void };
|
|
if (typeof withOnOpen.onOpen === 'function') {
|
|
withOnOpen.onOpen();
|
|
}
|
|
}
|
|
|
|
close() {
|
|
const withOnClose = this as unknown as { onClose?: () => void };
|
|
if (typeof withOnClose.onClose === 'function') {
|
|
withOnClose.onClose();
|
|
}
|
|
}
|
|
};
|
|
|
|
export const MarkdownView = class {};
|
|
export const Editor = class {};
|
|
export const App = class {
|
|
workspace = {
|
|
getActiveViewOfType: vi.fn(),
|
|
on: vi.fn(),
|
|
};
|
|
vault = {
|
|
read: vi.fn(),
|
|
modify: vi.fn(),
|
|
getFileByPath: vi.fn(),
|
|
on: vi.fn(),
|
|
configDir: 'mock-config-dir',
|
|
adapter: {
|
|
getBasePath: vi.fn().mockReturnValue('/mock/path'),
|
|
},
|
|
};
|
|
};
|
|
|
|
export const TFile = class {};
|
|
export const requestUrl = vi.fn();
|
|
export const setTooltip = vi.fn();
|
|
export const setIcon = vi.fn();
|
|
|
|
vi.mock('obsidian', () => ({
|
|
Plugin,
|
|
PluginSettingTab,
|
|
Setting,
|
|
Notice,
|
|
Modal,
|
|
MarkdownView,
|
|
Editor,
|
|
App,
|
|
TFile,
|
|
TextComponent,
|
|
TextAreaComponent,
|
|
DropdownComponent,
|
|
ButtonComponent,
|
|
ExtraButtonComponent,
|
|
requestUrl,
|
|
setTooltip,
|
|
setIcon,
|
|
}));
|