Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 220 additions & 1 deletion src/engine/CaptureChoiceEngine.selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { App } from "obsidian";
import { CaptureChoiceEngine } from "./CaptureChoiceEngine";
import type ICaptureChoice from "../types/choices/ICaptureChoice";
import type { IChoiceExecutor } from "../IChoiceExecutor";
import { isFolder, openFile } from "../utilityObsidian";
import {
isFolder,
jumpToNextTemplaterCursorIfPossible,
openExistingFileTab,
openFile,
} from "../utilityObsidian";

const { setUseSelectionAsCaptureValueMock } = vi.hoisted(() => ({
setUseSelectionAsCaptureValueMock: vi.fn(),
Expand Down Expand Up @@ -75,6 +80,8 @@ const createApp = () =>
exists: vi.fn(async () => false),
},
getAbstractFileByPath: vi.fn(() => null),
modify: vi.fn(async () => {}),
read: vi.fn(async () => ""),
},
workspace: {
getActiveFile: vi.fn(() => null),
Expand Down Expand Up @@ -131,6 +138,8 @@ describe("CaptureChoiceEngine selection-as-value resolution", () => {
beforeEach(() => {
setUseSelectionAsCaptureValueMock.mockClear();
vi.mocked(openFile).mockClear();
vi.mocked(openExistingFileTab).mockClear();
vi.mocked(jumpToNextTemplaterCursorIfPossible).mockClear();
});

it("uses global setting when no override is set", async () => {
Expand Down Expand Up @@ -192,6 +201,7 @@ describe("CaptureChoiceEngine selection-as-value resolution", () => {
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "content",
newFileContent: "content",
captureContent: "content",
});
Expand All @@ -209,6 +219,215 @@ describe("CaptureChoiceEngine selection-as-value resolution", () => {
}),
);
});

it("moves cursor to inserted capture location after opening a new leaf", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue("Header\nBody\nCaptured");

const choice = createChoice({
openFile: true,
captureToActiveFile: false,
});
const engine = new CaptureChoiceEngine(
app,
{ settings: { useSelectionAsCaptureValue: true } } as any,
choice,
createExecutor(),
);

const setCursor = vi.fn();
const openedLeaf = {
view: {
editor: { setCursor },
},
} as any;
const file = { path: "Test.md", basename: "Test" } as any;

vi.mocked(openExistingFileTab).mockReturnValue(null);
vi.mocked(openFile).mockResolvedValue(openedLeaf);

(engine as any).getFormattedPathToCaptureTo = vi
.fn()
.mockResolvedValue("Test.md");
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "Header\nBody",
newFileContent: "Header\nBody\nCaptured",
captureContent: "Captured",
});

await engine.run();

expect(setCursor).toHaveBeenCalledWith({ line: 2, ch: 0 });
expect(jumpToNextTemplaterCursorIfPossible).toHaveBeenCalledWith(
expect.anything(),
file,
);
});

it("moves cursor when reusing an already-open tab", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue("Header\nCaptured");

const choice = createChoice({
openFile: true,
captureToActiveFile: false,
});
const engine = new CaptureChoiceEngine(
app,
{ settings: { useSelectionAsCaptureValue: true } } as any,
choice,
createExecutor(),
);

const setCursor = vi.fn();
const existingLeaf = {
view: {
editor: { setCursor },
},
} as any;
const file = { path: "Test.md", basename: "Test" } as any;

vi.mocked(openExistingFileTab).mockReturnValue(existingLeaf);

(engine as any).getFormattedPathToCaptureTo = vi
.fn()
.mockResolvedValue("Test.md");
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "Header",
newFileContent: "Header\nCaptured",
captureContent: "Captured",
});

await engine.run();

expect(openFile).not.toHaveBeenCalled();
expect(setCursor).toHaveBeenCalledWith({ line: 1, ch: 0 });
});

it("recomputes cursor from final file content after post-capture rewrites", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue("Header\n\nCaptured");

const choice = createChoice({
openFile: true,
captureToActiveFile: false,
templater: {
afterCapture: "wholeFile",
},
});
const engine = new CaptureChoiceEngine(
app,
{ settings: { useSelectionAsCaptureValue: true } } as any,
choice,
createExecutor(),
);

const setCursor = vi.fn();
const openedLeaf = {
view: {
editor: { setCursor },
},
} as any;
const file = { path: "Test.md", basename: "Test" } as any;

vi.mocked(openExistingFileTab).mockReturnValue(null);
vi.mocked(openFile).mockResolvedValue(openedLeaf);

(engine as any).getFormattedPathToCaptureTo = vi
.fn()
.mockResolvedValue("Test.md");
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "Header",
newFileContent: "Header\nCaptured",
captureContent: "Captured",
});

await engine.run();

// Old behavior used newFileContent and would place line 1.
// We should now use the final post-processed file content.
expect(setCursor).toHaveBeenCalledWith({ line: 2, ch: 0 });
});

it("keeps cursor on capture when unrelated earlier sections are rewritten", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue(
"Title: updated\nBody\nCaptured",
);

const choice = createChoice({
openFile: true,
captureToActiveFile: false,
});
const engine = new CaptureChoiceEngine(
app,
{ settings: { useSelectionAsCaptureValue: true } } as any,
choice,
createExecutor(),
);

const setCursor = vi.fn();
const openedLeaf = {
view: {
editor: { setCursor },
},
} as any;
const file = { path: "Test.md", basename: "Test" } as any;

vi.mocked(openExistingFileTab).mockReturnValue(null);
vi.mocked(openFile).mockResolvedValue(openedLeaf);

(engine as any).getFormattedPathToCaptureTo = vi
.fn()
.mockResolvedValue("Test.md");
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "Title: old\nBody",
newFileContent: "Title: old\nBody\nCaptured",
captureContent: "Captured",
});

await engine.run();

expect(setCursor).toHaveBeenCalledWith({ line: 2, ch: 0 });
});

it("skips final cursor recomputation when openFile is disabled", async () => {
const app = createApp();
const choice = createChoice({
openFile: false,
captureToActiveFile: false,
});
const engine = new CaptureChoiceEngine(
app,
{ settings: { useSelectionAsCaptureValue: true } } as any,
choice,
createExecutor(),
);
const file = { path: "Test.md", basename: "Test" } as any;

(engine as any).getFormattedPathToCaptureTo = vi
.fn()
.mockResolvedValue("Test.md");
(engine as any).fileExists = vi.fn().mockResolvedValue(true);
(engine as any).onFileExists = vi.fn().mockResolvedValue({
file,
existingFileContent: "Body",
newFileContent: "Body\nCaptured",
captureContent: "Captured",
});

await engine.run();

expect(app.vault.read).not.toHaveBeenCalled();
});
});

describe("CaptureChoiceEngine capture target resolution", () => {
Expand Down
Loading