From 0301df260832e115452f19490ac0423fed780678 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Jun 2025 06:47:56 +0100 Subject: [PATCH] fix: Trim whitespace from app_target parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .trim() to app_target when passing to Swift CLI - Handles cases like " Spotify " correctly matching "Spotify" - Applies to all app name formats including window specifiers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/image-cli-args.ts | 6 +-- tests/unit/tools/image.test.ts | 89 +++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/utils/image-cli-args.ts b/src/utils/image-cli-args.ts index 27489dd..702af7b 100644 --- a/src/utils/image-cli-args.ts +++ b/src/utils/image-cli-args.ts @@ -96,7 +96,7 @@ export function buildSwiftCliArgs( const specifierType = parts[1]; const specifierValue = parts.slice(2).join(":"); // Handle colons in window titles - args.push("--app", appName); + args.push("--app", appName.trim()); args.push("--mode", "window"); if (specifierType === "WINDOW_TITLE") { @@ -115,12 +115,12 @@ export function buildSwiftCliArgs( { app_target: input.app_target }, "Malformed window specifier, treating as app name", ); - args.push("--app", input.app_target); + args.push("--app", input.app_target.trim()); args.push("--mode", "multi"); } } else { // 'AppName': All windows of that app - args.push("--app", input.app_target); + args.push("--app", input.app_target.trim()); args.push("--mode", "multi"); } diff --git a/tests/unit/tools/image.test.ts b/tests/unit/tools/image.test.ts index 39c4581..9dcf745 100644 --- a/tests/unit/tools/image.test.ts +++ b/tests/unit/tools/image.test.ts @@ -317,6 +317,64 @@ describe("Image Tool", () => { ); }); + it("should handle case-insensitive format values", async () => { + // Import schema to test preprocessing + const { imageToolSchema } = await import("../../../src/types/index.js"); + + // Mock resolveImagePath for minimal case + mockResolveImagePath.mockResolvedValue({ + effectivePath: "/tmp/test.png", + tempDirUsed: undefined, + }); + + const mockResponse = mockSwiftCli.captureImage("screen", { + path: "/tmp/test.png", + format: "png", + }); + mockExecuteSwiftCli.mockResolvedValue(mockResponse); + + // Test uppercase PNG - parse through schema first + const parsedInput = imageToolSchema.parse({ format: "PNG", path: "/tmp/test.png" }); + await imageToolHandler( + parsedInput, + mockContext, + ); + + expect(mockExecuteSwiftCli).toHaveBeenCalledWith( + expect.arrayContaining(["--format", "png"]), + mockLogger, + ); + }); + + it("should handle jpeg alias for jpg format", async () => { + // Import schema to test preprocessing + const { imageToolSchema } = await import("../../../src/types/index.js"); + + // Mock resolveImagePath for minimal case + mockResolveImagePath.mockResolvedValue({ + effectivePath: "/tmp/test.jpg", + tempDirUsed: undefined, + }); + + const mockResponse = mockSwiftCli.captureImage("screen", { + path: "/tmp/test.jpg", + format: "jpg", + }); + mockExecuteSwiftCli.mockResolvedValue(mockResponse); + + // Test jpeg alias - parse through schema first + const parsedInput = imageToolSchema.parse({ format: "jpeg", path: "/tmp/test.jpg" }); + await imageToolHandler( + parsedInput, + mockContext, + ); + + expect(mockExecuteSwiftCli).toHaveBeenCalledWith( + expect.arrayContaining(["--format", "jpg"]), + mockLogger, + ); + }); + it("should handle app_target: 'frontmost' with warning", async () => { // Mock resolveImagePath for minimal case mockResolveImagePath.mockResolvedValue({ @@ -1116,6 +1174,9 @@ describe("Image Tool", () => { }); it("should fall back to PNG when format is an invalid value", async () => { + // Import schema to test preprocessing + const { imageToolSchema } = await import("../../../src/types/index.js"); + // Mock resolveImagePath mockResolveImagePath.mockResolvedValue({ effectivePath: MOCK_TEMP_IMAGE_DIR, @@ -1129,8 +1190,9 @@ describe("Image Tool", () => { mockExecuteSwiftCli.mockResolvedValue(mockResponse); // Test with invalid format - schema should preprocess to 'png' + const parsedInput = imageToolSchema.parse({ format: "invalid" }); const result = await imageToolHandler( - { format: "invalid" as any }, + parsedInput, mockContext, ); @@ -1200,4 +1262,29 @@ describe("Image Tool", () => { expect(result.content[0].text).toBe("Image capture failed: Application not found"); }); }); + + describe("imageToolHandler - Whitespace trimming", () => { + it("should trim leading and trailing whitespace from app_target", async () => { + mockResolveImagePath.mockResolvedValue({ + effectivePath: MOCK_TEMP_IMAGE_DIR, + tempDirUsed: MOCK_TEMP_IMAGE_DIR, + }); + + const mockResponse = mockSwiftCli.captureImage("Spotify", { + path: MOCK_SAVED_FILE_PATH, + format: "png", + }); + mockExecuteSwiftCli.mockResolvedValue(mockResponse); + + await imageToolHandler( + { app_target: " Spotify " }, + mockContext, + ); + + // Check that the Swift CLI was called with trimmed app name + const callArgs = mockExecuteSwiftCli.mock.calls[0][0]; + const appIndex = callArgs.indexOf("--app"); + expect(callArgs[appIndex + 1]).toBe("Spotify"); // Should be trimmed + }); + }); }); \ No newline at end of file