9c4d92f638
* feat(collab): establish shared protocol and core contracts * feat(collab): implement LAN collaboration services * feat(collab): add agent runtime and provider integration * feat(collab): add collaboration user interface * feat(chat): add linked content and Collab references * feat(collab): wire plugin lifecycle and build tooling * test(collab): cover collaboration workflows * docs(collab): document collaboration architecture * fix: support locked Obsidian types in settings * fix: build protocol before linting * test: make Collab suites portable in CI * fix: isolate conflict merge identity * fix(collab): harden protocol and Git portability * ci: harden cross-platform and protocol verification * ci: make release workflow shell-safe * fix(collab): make Windows Git smoke portable * test(collab): expose safe Git smoke diagnostics * fix(collab): bind receive hook Git through PATH * test(collab): surface receive-pack failure context * test(collab): label Git HTTP failure stages * fix(collab): trust LAN CA with Windows Schannel * fix(collab): summarize Git failures safely * fix(collab): skip CRL checks for pinned LAN CA * fix(collab): select Schannel for Windows LAN Git * chore: scope cross-platform smoke to relevant PRs * fix: reopen reviews from expanded file rows * test: keep Collab style check compatible with target * refactor: enforce collab state ownership and bounded reads * refactor: harden collab lifecycle and state ownership * fix: reconnect Collab members from stale endpoints
167 lines
5.3 KiB
TypeScript
167 lines
5.3 KiB
TypeScript
import { TextDecoder, TextEncoder } from 'node:util';
|
|
|
|
type TestWindow = typeof globalThis & {
|
|
cancelAnimationFrame?: (handle: number) => void;
|
|
requestAnimationFrame?: (callback: FrameRequestCallback) => number;
|
|
};
|
|
|
|
const testWindow = globalThis as TestWindow;
|
|
|
|
if (!globalThis.TextEncoder) {
|
|
Object.assign(globalThis, { TextDecoder, TextEncoder });
|
|
}
|
|
|
|
if (!testWindow.requestAnimationFrame) {
|
|
testWindow.requestAnimationFrame = (callback: FrameRequestCallback): number => (
|
|
Number(setTimeout(() => callback(Date.now()), 0))
|
|
);
|
|
}
|
|
|
|
if (!testWindow.cancelAnimationFrame) {
|
|
testWindow.cancelAnimationFrame = (handle: number): void => {
|
|
clearTimeout(handle);
|
|
};
|
|
}
|
|
|
|
if (globalThis.Range && !Reflect.has(globalThis.Range.prototype, 'getClientRects')) {
|
|
Object.defineProperty(globalThis.Range.prototype, 'getClientRects', {
|
|
configurable: true,
|
|
value: () => [],
|
|
});
|
|
}
|
|
|
|
if (!('window' in globalThis)) {
|
|
Object.defineProperty(globalThis, 'window', {
|
|
configurable: true,
|
|
value: testWindow,
|
|
writable: true,
|
|
});
|
|
}
|
|
|
|
// Polyfill Obsidian DOM helpers for jsdom-based tests.
|
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
|
function applyDomElementInfo(el: Element, info: unknown): void {
|
|
if (!info) return;
|
|
if (typeof info === 'string') {
|
|
el.classList.add(...info.split(/\s+/).filter(Boolean));
|
|
return;
|
|
}
|
|
const opts = info as Record<string, unknown>;
|
|
if (opts.cls) {
|
|
const classes = Array.isArray(opts.cls) ? opts.cls : String(opts.cls).split(/\s+/);
|
|
el.classList.add(...classes.filter(Boolean) as string[]);
|
|
}
|
|
if (opts.text && 'textContent' in el) {
|
|
(el as HTMLElement).textContent = String(opts.text);
|
|
}
|
|
if (opts.attr) {
|
|
for (const [key, value] of Object.entries(opts.attr as Record<string, unknown>)) {
|
|
if (value === null || value === undefined) continue;
|
|
el.setAttribute(key, String(value));
|
|
}
|
|
}
|
|
if (opts.title && 'setAttribute' in el) {
|
|
el.setAttribute('title', String(opts.title));
|
|
}
|
|
}
|
|
|
|
(globalThis as typeof globalThis & { createDiv?: typeof createDiv }).createDiv = function createDiv(
|
|
info?: unknown,
|
|
callback?: (el: HTMLDivElement) => void,
|
|
): HTMLDivElement {
|
|
const el = document.createElement('div');
|
|
applyDomElementInfo(el, info);
|
|
if (callback) callback(el);
|
|
return el;
|
|
};
|
|
|
|
(globalThis as typeof globalThis & { createEl?: typeof createEl }).createEl = function createEl<K extends keyof HTMLElementTagNameMap>(
|
|
tag: K,
|
|
info?: unknown,
|
|
callback?: (el: HTMLElementTagNameMap[K]) => void,
|
|
): HTMLElementTagNameMap[K] {
|
|
const el = document.createElement(tag);
|
|
applyDomElementInfo(el, info);
|
|
if (callback) callback(el);
|
|
return el;
|
|
};
|
|
|
|
(globalThis as typeof globalThis & { createSpan?: typeof createSpan }).createSpan = function createSpan(
|
|
info?: unknown,
|
|
callback?: (el: HTMLSpanElement) => void,
|
|
): HTMLSpanElement {
|
|
const el = document.createElement('span');
|
|
applyDomElementInfo(el, info);
|
|
if (callback) callback(el);
|
|
return el;
|
|
};
|
|
|
|
(globalThis as typeof globalThis & { createSvg?: typeof createSvg }).createSvg = function createSvg<K extends keyof SVGElementTagNameMap>(
|
|
tag: K,
|
|
info?: unknown,
|
|
callback?: (el: SVGElementTagNameMap[K]) => void,
|
|
): SVGElementTagNameMap[K] {
|
|
const el = document.createElementNS(SVG_NS, tag) as SVGElementTagNameMap[K];
|
|
applyDomElementInfo(el, info);
|
|
if (callback) callback(el);
|
|
return el;
|
|
};
|
|
|
|
(globalThis as typeof globalThis & { createFragment?: typeof createFragment }).createFragment = function createFragment(
|
|
callback?: (el: DocumentFragment) => void,
|
|
): DocumentFragment {
|
|
const el = document.createDocumentFragment();
|
|
if (callback) callback(el);
|
|
return el;
|
|
};
|
|
|
|
if (globalThis.Node && !Reflect.has(globalThis.Node.prototype, 'win')) {
|
|
Object.defineProperty(globalThis.Node.prototype, 'win', {
|
|
configurable: true,
|
|
get(this: Node): Window {
|
|
const owner = this.nodeType === Node.DOCUMENT_NODE
|
|
? this as Document
|
|
: this.ownerDocument;
|
|
return owner?.defaultView ?? globalThis.window;
|
|
},
|
|
});
|
|
}
|
|
|
|
if (globalThis.HTMLElement && !Reflect.has(globalThis.HTMLElement.prototype, 'createDiv')) {
|
|
globalThis.HTMLElement.prototype.createDiv = function (this: HTMLElement, info?, callback?) {
|
|
const el = createDiv(info, callback);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
globalThis.HTMLElement.prototype.createEl = function (this: HTMLElement, tag, info?, callback?) {
|
|
const el = createEl(tag, info, callback);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
globalThis.HTMLElement.prototype.createSpan = function (this: HTMLElement, info?, callback?) {
|
|
const el = createSpan(info, callback);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
globalThis.HTMLElement.prototype.createSvg = function (this: HTMLElement, tag, info?, callback?) {
|
|
const el = createSvg(tag, info, callback);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
}
|
|
|
|
if (globalThis.HTMLElement && !Reflect.has(globalThis.HTMLElement.prototype, 'setText')) {
|
|
globalThis.HTMLElement.prototype.setText = function (this: HTMLElement, value: string) {
|
|
this.textContent = value;
|
|
};
|
|
}
|
|
|
|
if (globalThis.SVGElement && !Reflect.has(globalThis.SVGElement.prototype, 'createSvg')) {
|
|
globalThis.SVGElement.prototype.createSvg = function (this: SVGElement, tag, info?, callback?) {
|
|
const el = createSvg(tag, info, callback);
|
|
this.appendChild(el);
|
|
return el;
|
|
};
|
|
}
|