Skip to content

Commit

Permalink
feat: adjust action logic (blib-la#73)
Browse files Browse the repository at this point in the history
## Motivation

<!-- List motivation and changes here -->

- adds several tests
- make actions more flexible
- allow function calling


## Issues closed

<!-- List closed issues here -->
  • Loading branch information
pixelass committed Mar 11, 2024
1 parent d91755c commit 9485760
Show file tree
Hide file tree
Showing 39 changed files with 847 additions and 321 deletions.
4 changes: 2 additions & 2 deletions electron-builder.yml
Expand Up @@ -10,7 +10,7 @@ files:
- package.json
- app
- resources/python/**/*
- resources/powershell/**/*
- resources/actions/**/*
- resources/7zip/**/*
publish: null
nsis:
Expand All @@ -20,5 +20,5 @@ nsis:
asar: true
asarUnpack:
- "resources/python/**/*"
- "resources/powershell/**/*"
- "resources/actions/**/*"
- "resources/7zip/**/*"
1 change: 1 addition & 0 deletions jest.config.client.ts
Expand Up @@ -36,6 +36,7 @@ const jestConfig = {
testEnvironment: "jsdom",
transformIgnorePatterns: ["/node_modules/"],
extensionsToTreatAsEsm: [".ts", ".tsx"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.client.ts"],
};

export default jestConfig;
7 changes: 7 additions & 0 deletions jest.setup.client.ts
@@ -0,0 +1,7 @@
import "@testing-library/jest-dom";

if (typeof CSS === "undefined") {
global.CSS = {
escape: (string_: string) => string_.replaceAll(/([()\\{}])/g, "\\$1"),
} as any; // Cast to 'any' to bypass TypeScript's type checking for the mock.
}
42 changes: 35 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -88,6 +88,7 @@
"dayjs": "1.11.10",
"dotenv": "^16.4.5",
"dotenv-cli": "^7.4.0",
"dot-prop": "^8.0.2",
"electron": "30.0.0-alpha.4",
"electron-builder": "^24.13.3",
"eslint": "^8.57.0",
Expand Down
2 changes: 1 addition & 1 deletion playwright/installer.test.ts
Expand Up @@ -31,7 +31,7 @@ test.afterAll(async () => {
test("Renders the installer page", async () => {
page = await electronApp.firstWindow();
const title = await page.title();
expect(title).toBe("Blibla");
expect(title).toBe("Captain");
});

test("Allows switching the language", async () => {
Expand Down
File renamed without changes.
@@ -1,8 +1,10 @@
---
id: action:window
id: userStore.set
label: Switch to Dark mode
icon: ./icons/moon.svg
action: set:color-mode:dark
icon: moon
action: function
parameters:
theme: dark
language: en
description: "Change your app's theme to Dark mode with one click."
---
Expand Down
@@ -1,8 +1,10 @@
---
id: action:window
id: userStore.set
label: Switch to Light mode
icon: ./icons/sun.svg
action: set:color-mode:light
icon: sun
action: function
parameters:
theme: light
language: en
description: "Change your app's theme to Light mode with one click."
---
Expand Down
@@ -1,9 +1,11 @@
---
id: action:user
id: userStore.set
label: Switch to German
icon: ./icons/de-flag.svg
action: set:language:de
language: en
icon: flagDe
action: function
parameters:
language: de
language: en
description: "Instantly switch the application's language to German for a seamless user experience."
---

Expand Down
@@ -1,8 +1,10 @@
---
id: action:user
id: userStore.set
label: Switch to English
icon: ./icons/english-flag.svg
action: set:language:en
icon: flagEn
action: function
parameters:
language: en
language: en
description: "Instantly switch the application's language to English for a seamless user experience."
---
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -10,21 +10,21 @@ This document serves as an index and overview of the various settings available

## Table of Contents

- [Color Mode](./color_mode.md) - Customize the visual theme of the application according to your preference or system default.
- [Language](./language.md) - Set your preferred language for the application interface.
- [OpenAI API Integration](./openai_api.md) - Enter your OpenAI API key to enable integration with OpenAI services.
- [Color Mode](color_mode.md) - Customize the visual theme of the application according to your preference or system default.
- [Language](language.md) - Set your preferred language for the application interface.
- [OpenAI API Integration](openai_api.md) - Enter your OpenAI API key to enable integration with OpenAI services.

## Detailed Settings Guides

The following guides provide detailed instructions on configuring each specific setting:

### [Color Mode Settings](./color_mode.md)
### [Color Mode Settings](color_mode.md)
Adjust the color theme of your application interface. Options include Light, Dark, or System themes.

### [Language Settings](./language.md)
### [Language Settings](language.md)
Change the language of the application interface. A variety of languages are supported to provide a comfortable user experience.

### [OpenAI API Settings](./openai_api.md)
### [OpenAI API Settings](openai_api.md)
Integrate OpenAI's powerful AI capabilities by entering your unique API key.

For more information on each setting, please refer to the individual markdown files linked above. If you need further assistance, please visit the Help section or contact support.
Expand Down
29 changes: 0 additions & 29 deletions resources/docs/create-action/readme.md

This file was deleted.

10 changes: 0 additions & 10 deletions src/client/apps/live-painting/index.tsx
Expand Up @@ -93,16 +93,6 @@ export function LivePainting() {
setImages(previousImages => [...previousImages, { id, dataUrl: image, url }]);
}, [image, setImages, writeFile]);

useEffect(() => {
const unsubscribe = window.ipc.on("language", async locale => {
await changeLanguage(locale);
});

return () => {
unsubscribe();
};
}, [changeLanguage]);

useEffect(() => {
async function handleSave(event: KeyboardEvent) {
if (event.key === "s" && event.ctrlKey) {
Expand Down
106 changes: 106 additions & 0 deletions src/client/ions/handlers/__tests__/action.test.ts
@@ -0,0 +1,106 @@
import { handleCaptainAction, performElementAction } from "../action";

import { buildKey } from "#/build-key";
import { ID } from "#/enums";
import type { VectorStoreResponse } from "#/types/vector-store";

jest.mock("#/build-key", () => ({
buildKey: jest.fn(),
}));

describe("handleCaptainAction", () => {
// Mocking window.ipc.send
const mockSend = jest.fn();
beforeAll(() => {
// Ensure window.ipc exists
global.window.ipc = { send: mockSend } as any;
});

beforeEach(() => {
jest.clearAllMocks();
});

it("should handle function actions correctly", () => {
const response: VectorStoreResponse = {
id: "1",
score: 0.5,
payload: {
id: "testFunction",
language: "en",
action: "function",
label: "Test Function",
},
};

(buildKey as jest.Mock).mockReturnValue("functionKey");

handleCaptainAction(response);

expect(buildKey).toHaveBeenCalledWith([ID.CAPTAIN_ACTION]);
expect(mockSend).toHaveBeenCalledWith("functionKey", {
action: response.payload.action,
payload: response.payload,
});
});

it("should handle non-function actions correctly", () => {
const response: VectorStoreResponse = {
id: "2",
score: 0.5,
payload: {
id: "testApp",
language: "en",
action: "open",
label: "Test App",
},
};

(buildKey as jest.Mock).mockReturnValue("appKey:open");

handleCaptainAction(response);

expect(buildKey).toHaveBeenCalledWith([ID.APP], { suffix: ":open" });
expect(mockSend).toHaveBeenCalledWith("appKey:open", {
appId: response.payload.id,
action: response.payload.action,
});
});
});

describe("performElementAction", () => {
// Setup a DOM element for testing
beforeAll(() => {
document.body.innerHTML = `<div data-captainid="test-element"></div>`;
});

it("executes the action on an element when found", () => {
const mockAction = jest.fn();
performElementAction("test-element", mockAction);
expect(mockAction).toHaveBeenCalledTimes(1);
expect(mockAction).toHaveBeenCalledWith(expect.any(HTMLElement));
});

it("does not execute the action when the element is not found", () => {
const mockAction = jest.fn();
performElementAction("nonexistent-element", mockAction);
expect(mockAction).not.toHaveBeenCalled();
});

it("logs an error when the action function throws", () => {
const mockAction = jest.fn().mockImplementation(() => {
throw new Error("Test error");
});
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});

performElementAction("test-element", mockAction);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
"Error performing action on element with captainId=test-element:"
),
expect.any(Error)
);

// Clean up
consoleErrorSpy.mockRestore();
});
});

0 comments on commit 9485760

Please sign in to comment.