Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
305 changes: 304 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,299 @@ 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("moves top-insert cursor when final content prepends text", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue("Title\nCaptured\nBody");

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: "Body",
newFileContent: "Captured\nBody",
captureContent: "Captured",
});

await engine.run();

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

it("keeps original cursor when final content is unchanged", async () => {
const app = createApp();
vi.mocked(app.vault.read).mockResolvedValue("cba\ncba");

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: "cb",
newFileContent: "cba\ncba",
captureContent: "a\ncba",
});

await engine.run();

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

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