mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-03-25 09:25:47 +00:00
feat: Improve window title matching and error messages for URLs with ports
When users search for windows with URLs containing ports (e.g., 'http://example.com:8080'), the system now provides much better debugging information when the window isn't found. Key improvements: - Enhanced window not found errors now list all available window titles - Added specific guidance for URL-based searches (try without protocol) - New CaptureError.windowTitleNotFound with detailed debugging info - Comprehensive test coverage for colon parsing in app targets - Better error messages help users understand why matching failed Example improved error: "Window with title containing 'http://example.com:8080' not found in Google Chrome. Available windows: 'example.com:8080 - Google Chrome', 'New Tab - Google Chrome'. Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080')." This addresses the common issue where browsers display simplified URLs in window titles without the protocol, making it easier for users to find the correct matching pattern. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
822ea1cce7
commit
dd680eb638
5 changed files with 540 additions and 10 deletions
|
|
@ -302,7 +302,14 @@ struct ImageCommand: ParsableCommand {
|
|||
let targetWindow: WindowData
|
||||
if let windowTitle {
|
||||
guard let window = windows.first(where: { $0.title.contains(windowTitle) }) else {
|
||||
throw CaptureError.windowNotFound
|
||||
// Create detailed error message with available window titles for debugging
|
||||
let availableTitles = windows.map { "\"\($0.title)\"" }.joined(separator: ", ")
|
||||
let searchTerm = windowTitle
|
||||
let appName = targetApp.localizedName ?? "Unknown"
|
||||
|
||||
Logger.shared.debug("Window not found. Searched for '\(searchTerm)' in \(appName). Available windows: \(availableTitles)")
|
||||
|
||||
throw CaptureError.windowTitleNotFound(searchTerm, appName, availableTitles)
|
||||
}
|
||||
targetWindow = window
|
||||
} else if let windowIndex {
|
||||
|
|
@ -314,7 +321,9 @@ struct ImageCommand: ParsableCommand {
|
|||
targetWindow = windows[0] // frontmost window
|
||||
}
|
||||
|
||||
let fileName = FileNameGenerator.generateFileName(appName: targetApp.localizedName, windowTitle: targetWindow.title, format: format)
|
||||
let fileName = FileNameGenerator.generateFileName(
|
||||
appName: targetApp.localizedName, windowTitle: targetWindow.title, format: format
|
||||
)
|
||||
let filePath = OutputPathResolver.getOutputPath(basePath: path, fileName: fileName)
|
||||
|
||||
try captureWindow(targetWindow, to: filePath)
|
||||
|
|
@ -463,7 +472,6 @@ struct ImageCommand: ParsableCommand {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private func captureWindow(_ window: WindowData, to path: String) throws(CaptureError) {
|
||||
do {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
|
|
@ -494,11 +502,4 @@ struct ImageCommand: ParsableCommand {
|
|||
throw CaptureError.windowCaptureFailed(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ enum CaptureError: Error, LocalizedError {
|
|||
case invalidDisplayID
|
||||
case captureCreationFailed(Error?)
|
||||
case windowNotFound
|
||||
case windowTitleNotFound(String, String, String) // searchTerm, appName, availableTitles
|
||||
case windowCaptureFailed(Error?)
|
||||
case fileWriteError(String, Error?)
|
||||
case appNotFound(String)
|
||||
|
|
@ -135,6 +136,13 @@ enum CaptureError: Error, LocalizedError {
|
|||
return message
|
||||
case .windowNotFound:
|
||||
return "The specified window could not be found."
|
||||
case let .windowTitleNotFound(searchTerm, appName, availableTitles):
|
||||
var message = "Window with title containing '\(searchTerm)' not found in \(appName)."
|
||||
if !availableTitles.isEmpty {
|
||||
message += " Available windows: \(availableTitles)."
|
||||
}
|
||||
message += " Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080')."
|
||||
return message
|
||||
case let .windowCaptureFailed(underlyingError):
|
||||
var message = "Failed to capture the specified window."
|
||||
if let error = underlyingError {
|
||||
|
|
@ -182,6 +190,7 @@ enum CaptureError: Error, LocalizedError {
|
|||
case .invalidDisplayID: 13
|
||||
case .captureCreationFailed: 14
|
||||
case .windowNotFound: 15
|
||||
case .windowTitleNotFound: 21
|
||||
case .windowCaptureFailed: 16
|
||||
case .fileWriteError: 17
|
||||
case .appNotFound: 18
|
||||
|
|
|
|||
223
tests/unit/tools/improved-window-error-messages.test.ts
Normal file
223
tests/unit/tools/improved-window-error-messages.test.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { imageToolHandler } from "../../../src/tools/image";
|
||||
import { executeSwiftCli } from "../../../src/utils/peekaboo-cli";
|
||||
import { resolveImagePath } from "../../../src/utils/image-cli-args";
|
||||
import { pino } from "pino";
|
||||
|
||||
// Mock the Swift CLI utility
|
||||
vi.mock("../../../src/utils/peekaboo-cli");
|
||||
|
||||
// Mock image-cli-args module
|
||||
vi.mock("../../../src/utils/image-cli-args", async () => {
|
||||
const actual = await vi.importActual("../../../src/utils/image-cli-args");
|
||||
return {
|
||||
...actual,
|
||||
resolveImagePath: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockExecuteSwiftCli = executeSwiftCli as vi.MockedFunction<typeof executeSwiftCli>;
|
||||
const mockResolveImagePath = resolveImagePath as vi.MockedFunction<typeof resolveImagePath>;
|
||||
|
||||
const mockLogger = pino({ level: "silent" });
|
||||
const mockContext = { logger: mockLogger };
|
||||
|
||||
describe("Improved Window Error Messages", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockResolveImagePath.mockResolvedValue({
|
||||
effectivePath: "/tmp/test",
|
||||
tempDirUsed: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should provide helpful error message with available window titles when window not found", async () => {
|
||||
// Mock detailed window not found error with available titles
|
||||
const mockDetailedWindowNotFoundResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "Window with title containing 'http://example.com:8080' not found in Google Chrome. Available windows: \"example.com:8080 - Google Chrome\", \"New Tab - Google Chrome\". Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080').",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "Window title matching failed with suggested alternatives"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockDetailedWindowNotFoundResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:http://example.com:8080",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
// Should fail with detailed error message
|
||||
expect(result.isError).toBe(true);
|
||||
|
||||
const errorText = result.content[0].text;
|
||||
expect(errorText).toContain("Window with title containing 'http://example.com:8080' not found");
|
||||
expect(errorText).toContain("Available windows:");
|
||||
expect(errorText).toContain("example.com:8080 - Google Chrome");
|
||||
expect(errorText).toContain("New Tab - Google Chrome");
|
||||
expect(errorText).toContain("try without the protocol");
|
||||
expect(errorText).toContain("'example.com:8080' instead of 'http://example.com:8080'");
|
||||
});
|
||||
|
||||
it("should handle case where app has no windows matching title", async () => {
|
||||
const mockNoMatchingWindowsResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "Window with title containing 'nonexistent-page' not found in Safari. Available windows: \"Apple - Google Search - Safari\", \"GitHub - Safari\". Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080').",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "No windows match the specified title"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockNoMatchingWindowsResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Safari:WINDOW_TITLE:nonexistent-page",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
|
||||
const errorText = result.content[0].text;
|
||||
expect(errorText).toContain("Window with title containing 'nonexistent-page' not found in Safari");
|
||||
expect(errorText).toContain("Available windows:");
|
||||
expect(errorText).toContain("Apple - Google Search - Safari");
|
||||
expect(errorText).toContain("GitHub - Safari");
|
||||
});
|
||||
|
||||
it("should provide guidance for URL-based searches", async () => {
|
||||
const mockURLGuidanceResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "Window with title containing 'https://localhost:3000/app' not found in Firefox. Available windows: \"localhost:3000/app - Mozilla Firefox\", \"about:blank - Mozilla Firefox\". Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080').",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "URL matching guidance provided"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockURLGuidanceResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Firefox:WINDOW_TITLE:https://localhost:3000/app",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
|
||||
const errorText = result.content[0].text;
|
||||
expect(errorText).toContain("localhost:3000/app - Mozilla Firefox");
|
||||
expect(errorText).toContain("Note: For URLs, try without the protocol");
|
||||
});
|
||||
|
||||
it("should handle case where no similar windows exist", async () => {
|
||||
const mockNoSimilarWindowsResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "Window with title containing 'very-specific-search' not found in Code. Available windows: \"ImageCommand.swift - peekaboo\", \"main.swift - peekaboo\". Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080').",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "No similar windows found"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockNoSimilarWindowsResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Code:WINDOW_TITLE:very-specific-search",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
|
||||
const errorText = result.content[0].text;
|
||||
expect(errorText).toContain("Window with title containing 'very-specific-search' not found in Code");
|
||||
expect(errorText).toContain("ImageCommand.swift - peekaboo");
|
||||
expect(errorText).toContain("main.swift - peekaboo");
|
||||
});
|
||||
|
||||
it("should handle successful window matching after applying guidance", async () => {
|
||||
// Test successful case when user follows the guidance
|
||||
const mockSuccessfulMatchResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
saved_files: [
|
||||
{
|
||||
path: "/tmp/chrome_window.png",
|
||||
item_label: "Google Chrome",
|
||||
window_title: "example.com:8080 - Google Chrome",
|
||||
window_id: 12345,
|
||||
window_index: 0,
|
||||
mime_type: "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockSuccessfulMatchResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:example.com:8080",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.saved_files).toHaveLength(1);
|
||||
expect(result.saved_files?.[0].window_title).toBe("example.com:8080 - Google Chrome");
|
||||
});
|
||||
|
||||
it("should provide appropriate guidance for different URL patterns", async () => {
|
||||
const urlPatterns = [
|
||||
{
|
||||
input: "http://localhost:8080",
|
||||
suggestion: "localhost:8080"
|
||||
},
|
||||
{
|
||||
input: "https://api.example.com:443/v1",
|
||||
suggestion: "api.example.com:443/v1"
|
||||
},
|
||||
{
|
||||
input: "ftp://files.example.com:21",
|
||||
suggestion: "files.example.com:21"
|
||||
}
|
||||
];
|
||||
|
||||
for (const pattern of urlPatterns) {
|
||||
vi.clearAllMocks();
|
||||
|
||||
const mockResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: `Window with title containing '${pattern.input}' not found in Browser. Available windows: \"${pattern.suggestion} - Browser\". Note: For URLs, try without the protocol (e.g., 'example.com:8080' instead of 'http://example.com:8080').`,
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "URL pattern guidance"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
||||
|
||||
const input = {
|
||||
app_target: `Browser:WINDOW_TITLE:${pattern.input}`,
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain(`Window with title containing '${pattern.input}' not found`);
|
||||
expect(result.content[0].text).toContain(`${pattern.suggestion} - Browser`);
|
||||
expect(result.content[0].text).toContain("try without the protocol");
|
||||
}
|
||||
});
|
||||
});
|
||||
181
tests/unit/tools/window-title-matching.test.ts
Normal file
181
tests/unit/tools/window-title-matching.test.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { imageToolHandler } from "../../../src/tools/image";
|
||||
import { executeSwiftCli } from "../../../src/utils/peekaboo-cli";
|
||||
import { resolveImagePath } from "../../../src/utils/image-cli-args";
|
||||
import { pino } from "pino";
|
||||
|
||||
// Mock the Swift CLI utility
|
||||
vi.mock("../../../src/utils/peekaboo-cli");
|
||||
|
||||
// Mock image-cli-args module
|
||||
vi.mock("../../../src/utils/image-cli-args", async () => {
|
||||
const actual = await vi.importActual("../../../src/utils/image-cli-args");
|
||||
return {
|
||||
...actual,
|
||||
resolveImagePath: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockExecuteSwiftCli = executeSwiftCli as vi.MockedFunction<typeof executeSwiftCli>;
|
||||
const mockResolveImagePath = resolveImagePath as vi.MockedFunction<typeof resolveImagePath>;
|
||||
|
||||
const mockLogger = pino({ level: "silent" });
|
||||
const mockContext = { logger: mockLogger };
|
||||
|
||||
describe("Window Title Matching Issues", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockResolveImagePath.mockResolvedValue({
|
||||
effectivePath: "/tmp/test",
|
||||
tempDirUsed: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle window not found error for URL-based titles", async () => {
|
||||
// Mock the exact scenario from the issue - window not found
|
||||
const mockWindowNotFoundResponse = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "The specified window could not be found.",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "Window matching criteria was not found"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockWindowNotFoundResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:http://example.com:8080",
|
||||
path: "/tmp/multiple_colons.png",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
// Should fail with window not found error
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain("The specified window could not be found");
|
||||
expect(result._meta?.backend_error_code).toBe("WINDOW_NOT_FOUND");
|
||||
|
||||
// Verify correct arguments were passed to Swift CLI
|
||||
expect(mockExecuteSwiftCli).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
"--app", "Google Chrome",
|
||||
"--mode", "window",
|
||||
"--window-title", "http://example.com:8080"
|
||||
]),
|
||||
mockLogger,
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("should suggest debugging when window title matching fails", async () => {
|
||||
// Test that includes some debugging suggestions in the response
|
||||
const mockWindowNotFoundWithDetails = {
|
||||
success: false,
|
||||
error: {
|
||||
message: "The specified window could not be found.",
|
||||
code: "WINDOW_NOT_FOUND",
|
||||
details: "Window with title containing 'http://example.com:8080' not found in Google Chrome"
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockWindowNotFoundWithDetails);
|
||||
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:http://example.com:8080",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain("The specified window could not be found");
|
||||
});
|
||||
|
||||
it("should handle successful window matching with URLs", async () => {
|
||||
// Test successful case where the window IS found
|
||||
const mockSuccessResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
saved_files: [
|
||||
{
|
||||
path: "/tmp/chrome_window.png",
|
||||
item_label: "Google Chrome",
|
||||
window_title: "example.com:8080 - Google Chrome",
|
||||
window_id: 12345,
|
||||
window_index: 0,
|
||||
mime_type: "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockSuccessResponse);
|
||||
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:example.com:8080",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.saved_files).toHaveLength(1);
|
||||
expect(result.saved_files?.[0].window_title).toContain("example.com:8080");
|
||||
});
|
||||
|
||||
it("should demonstrate different URL formats that might appear in window titles", async () => {
|
||||
// Various formats Chrome might show in window titles
|
||||
const urlFormats = [
|
||||
"http://example.com:8080",
|
||||
"example.com:8080",
|
||||
"localhost:8080",
|
||||
"127.0.0.1:8080",
|
||||
"https://example.com:8443/path"
|
||||
];
|
||||
|
||||
for (const urlFormat of urlFormats) {
|
||||
vi.clearAllMocks();
|
||||
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
saved_files: [
|
||||
{
|
||||
path: `/tmp/window_${urlFormat.replace(/[:/]/g, '_')}.png`,
|
||||
item_label: "Browser",
|
||||
window_title: `${urlFormat} - Browser`,
|
||||
window_id: 123,
|
||||
window_index: 0,
|
||||
mime_type: "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
||||
|
||||
const input = {
|
||||
app_target: `Browser:WINDOW_TITLE:${urlFormat}`,
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const result = await imageToolHandler(input, mockContext);
|
||||
|
||||
// Should succeed for all URL formats
|
||||
expect(result.isError).toBeUndefined();
|
||||
|
||||
// Verify correct arguments were passed
|
||||
expect(mockExecuteSwiftCli).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
"--app", "Browser",
|
||||
"--window-title", urlFormat
|
||||
]),
|
||||
mockLogger,
|
||||
expect.any(Object)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
116
tests/unit/utils/colon-parsing.test.ts
Normal file
116
tests/unit/utils/colon-parsing.test.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { buildSwiftCliArgs } from "../../../src/utils/image-cli-args";
|
||||
|
||||
describe("App Target Colon Parsing", () => {
|
||||
it("should correctly parse window title with URLs containing ports", () => {
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_TITLE:http://example.com:8080",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
// Should contain the correct app name
|
||||
expect(args).toContain("--app");
|
||||
const appIndex = args.indexOf("--app");
|
||||
expect(args[appIndex + 1]).toBe("Google Chrome");
|
||||
|
||||
// Should be in window mode
|
||||
expect(args).toContain("--mode");
|
||||
const modeIndex = args.indexOf("--mode");
|
||||
expect(args[modeIndex + 1]).toBe("window");
|
||||
|
||||
// Should contain the window title argument with the full URL including port
|
||||
expect(args).toContain("--window-title");
|
||||
const titleIndex = args.indexOf("--window-title");
|
||||
expect(args[titleIndex + 1]).toBe("http://example.com:8080");
|
||||
});
|
||||
|
||||
it("should handle URLs with multiple colons correctly", () => {
|
||||
const input = {
|
||||
app_target: "Safari:WINDOW_TITLE:https://user:pass@example.com:8443/path?param=value",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
expect(args).toContain("--window-title");
|
||||
const titleIndex = args.indexOf("--window-title");
|
||||
expect(args[titleIndex + 1]).toBe("https://user:pass@example.com:8443/path?param=value");
|
||||
});
|
||||
|
||||
it("should handle window titles with colons in file paths", () => {
|
||||
const input = {
|
||||
app_target: "TextEdit:WINDOW_TITLE:C:\\Users\\test\\file.txt",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
expect(args).toContain("--window-title");
|
||||
const titleIndex = args.indexOf("--window-title");
|
||||
expect(args[titleIndex + 1]).toBe("C:\\Users\\test\\file.txt");
|
||||
});
|
||||
|
||||
it("should handle simple window titles without additional colons", () => {
|
||||
const input = {
|
||||
app_target: "TextEdit:WINDOW_TITLE:My Document.txt",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
expect(args).toContain("--window-title");
|
||||
const titleIndex = args.indexOf("--window-title");
|
||||
expect(args[titleIndex + 1]).toBe("My Document.txt");
|
||||
});
|
||||
|
||||
it("should handle window index correctly (no colons in value)", () => {
|
||||
const input = {
|
||||
app_target: "Google Chrome:WINDOW_INDEX:0",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
expect(args).toContain("--window-index");
|
||||
const indexIdx = args.indexOf("--window-index");
|
||||
expect(args[indexIdx + 1]).toBe("0");
|
||||
});
|
||||
|
||||
it("should handle colons in app names gracefully", () => {
|
||||
// Edge case: what if app name itself contains colons?
|
||||
const input = {
|
||||
app_target: "App:Name:WINDOW_TITLE:Title",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
// This case is ambiguous - current logic takes first part as app name
|
||||
expect(args).toContain("--app");
|
||||
const appIndex = args.indexOf("--app");
|
||||
expect(args[appIndex + 1]).toBe("App");
|
||||
|
||||
// "Name" is not a valid specifier, so no window-specific flags should be added
|
||||
// It should default to main window (no --window-title or --window-index flags)
|
||||
expect(args).not.toContain("--window-title");
|
||||
expect(args).not.toContain("--window-index");
|
||||
expect(args).toContain("--mode");
|
||||
const modeIndex = args.indexOf("--mode");
|
||||
expect(args[modeIndex + 1]).toBe("window");
|
||||
});
|
||||
|
||||
it("should handle timestamp-like patterns in titles", () => {
|
||||
const input = {
|
||||
app_target: "Log Viewer:WINDOW_TITLE:2023-01-01 12:30:45",
|
||||
format: "png" as const
|
||||
};
|
||||
|
||||
const args = buildSwiftCliArgs(input, "/tmp/test.png");
|
||||
|
||||
expect(args).toContain("--window-title");
|
||||
const titleIndex = args.indexOf("--window-title");
|
||||
expect(args[titleIndex + 1]).toBe("2023-01-01 12:30:45");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue