mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-11-03 23:08:18 +00:00
Own browser UI sessions and shared navigation
This commit is contained in:
1 parent
cfdc698775
commit
273079e827
28 files changed
+1948
-121
No files matched your search
+184
-5
@@ -106,11 +106,7 @@ describe("ConflictResolveModal result lifecycle", () => {
|
||||
});
|
||||
|
||||
it("closes and completes its result when the plug-in unloads", async () => {
|
||||
const modal = new ConflictResolveModal(
|
||||
{} as never,
|
||||
"open-during-unload.md" as FilePathWithPrefix,
|
||||
conflict
|
||||
);
|
||||
const modal = new ConflictResolveModal({} as never, "open-during-unload.md" as FilePathWithPrefix, conflict);
|
||||
modal.onOpen();
|
||||
|
||||
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
|
||||
@@ -165,3 +161,186 @@ describe("ConflictResolveModal result lifecycle", () => {
|
||||
expect(previousState).toBe("still-open");
|
||||
});
|
||||
});
|
||||
|
||||
type NavigationTarget = {
|
||||
classList: { add: ReturnType<typeof vi.fn>; remove: ReturnType<typeof vi.fn> };
|
||||
scrollIntoView: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
function createDiffView(count: number) {
|
||||
const focused: { current: NavigationTarget | undefined } = { current: undefined };
|
||||
const createTargets = (targetCount: number) =>
|
||||
Array.from({ length: targetCount }, () => {
|
||||
const target: NavigationTarget = {
|
||||
classList: {
|
||||
add: vi.fn(() => {
|
||||
focused.current = target;
|
||||
}),
|
||||
remove: vi.fn(() => {
|
||||
if (focused.current === target) focused.current = undefined;
|
||||
}),
|
||||
},
|
||||
scrollIntoView: vi.fn(),
|
||||
};
|
||||
return target;
|
||||
});
|
||||
let targets = createTargets(count);
|
||||
const diffView = {
|
||||
querySelectorAll: vi.fn(() => targets),
|
||||
querySelector: vi.fn(() => (targets.includes(focused.current as NavigationTarget) ? focused.current : null)),
|
||||
};
|
||||
return {
|
||||
diffView,
|
||||
targets,
|
||||
replaceTargets(targetCount: number) {
|
||||
focused.current = undefined;
|
||||
targets = createTargets(targetCount);
|
||||
return targets;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("ConflictResolveModal cyclic navigation", () => {
|
||||
it("commits the selected index before scrolling, including when scrolling fails", () => {
|
||||
const modal = Object.create(ConflictResolveModal.prototype) as ConflictResolveModal;
|
||||
const view = createDiffView(2);
|
||||
const indicator = { setText: vi.fn() };
|
||||
const failure = new Error("fixture scroll failed");
|
||||
Object.assign(modal, { diffView: view.diffView, currentDiffIndex: -1, diffNavIndicator: indicator });
|
||||
view.targets[0].scrollIntoView.mockImplementation(() => {
|
||||
expect(modal.currentDiffIndex).toBe(0);
|
||||
throw failure;
|
||||
});
|
||||
expect(() => modal.navigateDiff("next")).toThrow(failure);
|
||||
expect(modal.currentDiffIndex).toBe(0);
|
||||
expect(indicator.setText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts at the first diff for next and at the last diff for previous", () => {
|
||||
const nextModal = new ConflictResolveModal({} as never, "next.md" as FilePathWithPrefix, conflict);
|
||||
const nextView = createDiffView(3);
|
||||
const nextIndicator = { setText: vi.fn() };
|
||||
Object.assign(nextModal, {
|
||||
diffView: nextView.diffView,
|
||||
currentDiffIndex: -1,
|
||||
diffNavIndicator: nextIndicator,
|
||||
});
|
||||
|
||||
nextModal.navigateDiff("next");
|
||||
|
||||
expect(nextModal.currentDiffIndex).toBe(0);
|
||||
expect(nextView.targets[0].classList.add).toHaveBeenCalledWith("diff-focused");
|
||||
expect(nextView.targets[0].scrollIntoView).toHaveBeenCalledWith({ behavior: "smooth", block: "center" });
|
||||
expect(nextIndicator.setText).toHaveBeenCalledWith("1/3");
|
||||
|
||||
const previousModal = new ConflictResolveModal({} as never, "previous.md" as FilePathWithPrefix, conflict);
|
||||
const previousView = createDiffView(3);
|
||||
const previousIndicator = { setText: vi.fn() };
|
||||
Object.assign(previousModal, {
|
||||
diffView: previousView.diffView,
|
||||
currentDiffIndex: -1,
|
||||
diffNavIndicator: previousIndicator,
|
||||
});
|
||||
|
||||
previousModal.navigateDiff("prev");
|
||||
|
||||
expect(previousModal.currentDiffIndex).toBe(2);
|
||||
expect(previousView.targets[2].classList.add).toHaveBeenCalledWith("diff-focused");
|
||||
expect(previousView.targets[2].scrollIntoView).toHaveBeenCalledWith({ behavior: "smooth", block: "center" });
|
||||
expect(previousIndicator.setText).toHaveBeenCalledWith("3/3");
|
||||
});
|
||||
|
||||
it("wraps in both directions while moving focus, scrolling, and updating the indicator", () => {
|
||||
const modal = new ConflictResolveModal({} as never, "wrap.md" as FilePathWithPrefix, conflict);
|
||||
const { diffView, targets } = createDiffView(3);
|
||||
const indicator = { setText: vi.fn() };
|
||||
Object.assign(modal, { diffView, currentDiffIndex: -1, diffNavIndicator: indicator });
|
||||
|
||||
modal.navigateDiff("next");
|
||||
modal.navigateDiff("next");
|
||||
modal.navigateDiff("next");
|
||||
modal.navigateDiff("next");
|
||||
modal.navigateDiff("prev");
|
||||
|
||||
expect(modal.currentDiffIndex).toBe(2);
|
||||
expect(targets.map((target) => target.classList.add.mock.calls.length)).toEqual([2, 1, 2]);
|
||||
expect(targets.map((target) => target.classList.remove.mock.calls.length)).toEqual([2, 1, 1]);
|
||||
expect(targets.map((target) => target.scrollIntoView.mock.calls.length)).toEqual([2, 1, 2]);
|
||||
expect(indicator.setText.mock.calls.map(([text]) => text)).toEqual(["1/3", "2/3", "3/3", "1/3", "3/3"]);
|
||||
});
|
||||
|
||||
it("leaves the current navigation index and diff view untouched when no diff exists", () => {
|
||||
const modal = new ConflictResolveModal({} as never, "empty.md" as FilePathWithPrefix, conflict);
|
||||
const { diffView } = createDiffView(0);
|
||||
const indicator = { setText: vi.fn() };
|
||||
Object.assign(modal, { diffView, currentDiffIndex: 1, diffNavIndicator: indicator });
|
||||
|
||||
modal.navigateDiff("next");
|
||||
|
||||
expect(modal.currentDiffIndex).toBe(1);
|
||||
expect(diffView.querySelector).not.toHaveBeenCalled();
|
||||
expect(indicator.setText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("looks up diff nodes again after the diff view is replaced", () => {
|
||||
const modal = new ConflictResolveModal({} as never, "replace.md" as FilePathWithPrefix, conflict);
|
||||
const view = createDiffView(2);
|
||||
const indicator = { setText: vi.fn() };
|
||||
Object.assign(modal, { diffView: view.diffView, currentDiffIndex: -1, diffNavIndicator: indicator });
|
||||
|
||||
modal.navigateDiff("next");
|
||||
const replacementTargets = view.replaceTargets(3);
|
||||
modal.navigateDiff("next");
|
||||
|
||||
expect(modal.currentDiffIndex).toBe(1);
|
||||
expect(view.diffView.querySelectorAll).toHaveBeenCalledTimes(2);
|
||||
expect(view.diffView.querySelector).toHaveBeenCalledTimes(2);
|
||||
expect(replacementTargets[1].classList.add).toHaveBeenCalledWith("diff-focused");
|
||||
expect(replacementTargets[1].scrollIntoView).toHaveBeenCalledWith({ behavior: "smooth", block: "center" });
|
||||
expect(indicator.setText.mock.calls.map(([text]) => text)).toEqual(["1/2", "2/3"]);
|
||||
});
|
||||
|
||||
it("keeps navigation indices and focus independent between modal instances", () => {
|
||||
const firstModal = new ConflictResolveModal({} as never, "first.md" as FilePathWithPrefix, conflict);
|
||||
const secondModal = new ConflictResolveModal({} as never, "second.md" as FilePathWithPrefix, conflict);
|
||||
const firstView = createDiffView(2);
|
||||
const secondView = createDiffView(2);
|
||||
Object.assign(firstModal, {
|
||||
diffView: firstView.diffView,
|
||||
currentDiffIndex: -1,
|
||||
diffNavIndicator: { setText: vi.fn() },
|
||||
});
|
||||
Object.assign(secondModal, {
|
||||
diffView: secondView.diffView,
|
||||
currentDiffIndex: -1,
|
||||
diffNavIndicator: { setText: vi.fn() },
|
||||
});
|
||||
|
||||
firstModal.navigateDiff("next");
|
||||
secondModal.navigateDiff("next");
|
||||
firstModal.navigateDiff("next");
|
||||
|
||||
expect(firstModal.currentDiffIndex).toBe(1);
|
||||
expect(secondModal.currentDiffIndex).toBe(0);
|
||||
expect(firstView.targets[0].classList.remove).toHaveBeenCalledWith("diff-focused");
|
||||
expect(secondView.targets[0].classList.remove).not.toHaveBeenCalled();
|
||||
expect(secondView.targets[0].classList.add).toHaveBeenCalledWith("diff-focused");
|
||||
});
|
||||
|
||||
it("always resets the indicator using the current diff count", () => {
|
||||
const modal = new ConflictResolveModal({} as never, "reset.md" as FilePathWithPrefix, conflict);
|
||||
const view = createDiffView(2);
|
||||
const indicator = { setText: vi.fn() };
|
||||
Object.assign(modal, { diffView: view.diffView, currentDiffIndex: 1, diffNavIndicator: indicator });
|
||||
|
||||
modal.resetDiffNavigation();
|
||||
|
||||
expect(modal.currentDiffIndex).toBe(-1);
|
||||
expect(view.diffView.querySelectorAll).toHaveBeenCalledWith(".added, .deleted");
|
||||
expect(indicator.setText).toHaveBeenLastCalledWith("0/2");
|
||||
|
||||
view.replaceTargets(0);
|
||||
modal.resetDiffNavigation();
|
||||
expect(indicator.setText).toHaveBeenLastCalledWith("\u2014");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user