= {};
+
+ for (const key in obj) {
+ if (isSerializable(obj[key])) {
+ result[key] = removeNonSerializableProperties(obj[key]);
+ }
+ }
+
+ return result as T;
+}
diff --git a/packages/renderer/src/lib/actions/ContributionActions.spec.ts b/packages/renderer/src/lib/actions/ContributionActions.spec.ts
new file mode 100644
index 00000000000..d2d1c27bfb5
--- /dev/null
+++ b/packages/renderer/src/lib/actions/ContributionActions.spec.ts
@@ -0,0 +1,110 @@
+import '@testing-library/jest-dom/vitest';
+import { beforeAll, test, expect, vi } from 'vitest';
+import { fireEvent, render, screen } from '@testing-library/svelte';
+import ContributionActions from '/@/lib/actions/ContributionActions.svelte';
+
+const executeCommand = vi.fn();
+
+beforeAll(() => {
+ (window as any).executeCommand = executeCommand;
+ executeCommand.mockImplementation(() => {});
+
+ (window.events as unknown) = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ receive: (_channel: string, func: any) => {
+ func();
+ },
+ };
+});
+
+test('Expect no ListItemButtonIcon', async () => {
+ render(ContributionActions, {
+ args: [],
+ contributions: [],
+ onError: () => {},
+ });
+ const imgs = screen.queryAllByRole('img');
+ expect(imgs).lengthOf(0);
+});
+
+test('Expect one ListItemButtonIcon', async () => {
+ render(ContributionActions, {
+ args: [],
+ contributions: [
+ {
+ command: 'dummy.command',
+ title: 'dummy-title',
+ },
+ ],
+ onError: () => {},
+ dropdownMenu: true,
+ });
+ const item = screen.getByText('dummy-title');
+ expect(item).toBeInTheDocument();
+});
+
+test('Expect executeCommand to be called', async () => {
+ render(ContributionActions, {
+ args: [],
+ contributions: [
+ {
+ command: 'dummy.command',
+ title: 'dummy-title',
+ },
+ ],
+ onError: () => {},
+ dropdownMenu: true,
+ });
+ const item = screen.getByText('dummy-title');
+
+ await fireEvent.click(item);
+ expect(executeCommand).toBeCalledWith('dummy.command');
+});
+
+test('Expect executeCommand to be called with sanitize object', async () => {
+ render(ContributionActions, {
+ args: [
+ {
+ nonSerializable: () => {},
+ serializable: 'hello',
+ },
+ ],
+ contributions: [
+ {
+ command: 'dummy.command',
+ title: 'dummy-title',
+ },
+ ],
+ onError: () => {},
+ dropdownMenu: true,
+ });
+ const item = screen.getByText('dummy-title');
+
+ await fireEvent.click(item);
+ expect(executeCommand).toBeCalledWith('dummy.command', { serializable: 'hello' });
+});
+
+test('Expect executeCommand to be called with sanitize object nested', async () => {
+ render(ContributionActions, {
+ args: [
+ {
+ parent: {
+ nonSerializable: () => {},
+ serializable: 'hello',
+ },
+ },
+ ],
+ contributions: [
+ {
+ command: 'dummy.command',
+ title: 'dummy-title',
+ },
+ ],
+ onError: () => {},
+ dropdownMenu: true,
+ });
+ const item = screen.getByText('dummy-title');
+
+ await fireEvent.click(item);
+ expect(executeCommand).toBeCalledWith('dummy.command', { parent: { serializable: 'hello' } });
+});
diff --git a/packages/renderer/src/lib/actions/ContributionActions.svelte b/packages/renderer/src/lib/actions/ContributionActions.svelte
new file mode 100644
index 00000000000..edc58e0f469
--- /dev/null
+++ b/packages/renderer/src/lib/actions/ContributionActions.svelte
@@ -0,0 +1,29 @@
+
+
+{#each contributions as menu}
+
+{/each}
diff --git a/packages/renderer/src/lib/compose/ComposeActions.svelte b/packages/renderer/src/lib/compose/ComposeActions.svelte
index eaeb8047a84..159deba87f2 100644
--- a/packages/renderer/src/lib/compose/ComposeActions.svelte
+++ b/packages/renderer/src/lib/compose/ComposeActions.svelte
@@ -6,10 +6,13 @@ import ListItemButtonIcon from '../ui/ListItemButtonIcon.svelte';
import DropdownMenu from '../ui/DropdownMenu.svelte';
import FlatMenu from '../ui/FlatMenu.svelte';
import type { ContainerInfoUI } from '../container/ContainerInfoUI';
+import type { Menu } from '../../../../main/src/plugin/menu-registry';
+import ContributionActions from '/@/lib/actions/ContributionActions.svelte';
export let compose: ComposeInfoUI;
export let dropdownMenu = false;
export let detailed = false;
+export let contributions: Menu[] = [];
export let inProgressCallback: (containers: ContainerInfoUI[], inProgress: boolean, state?: string) => void = () => {};
export let errorCallback: (erroMessage: string) => void = () => {};
@@ -124,4 +127,9 @@ if (dropdownMenu) {
menu="{dropdownMenu}"
detailed="{detailed}"
icon="{faArrowsRotate}" />
+
diff --git a/packages/renderer/src/lib/container/ContainerActions.svelte b/packages/renderer/src/lib/container/ContainerActions.svelte
index fae4d3a2e7a..16620171f89 100644
--- a/packages/renderer/src/lib/container/ContainerActions.svelte
+++ b/packages/renderer/src/lib/container/ContainerActions.svelte
@@ -16,9 +16,12 @@ import { router } from 'tinro';
import ListItemButtonIcon from '../ui/ListItemButtonIcon.svelte';
import DropdownMenu from '../ui/DropdownMenu.svelte';
import FlatMenu from '../ui/FlatMenu.svelte';
+import type { Menu } from '../../../../main/src/plugin/menu-registry';
+import ContributionActions from '/@/lib/actions/ContributionActions.svelte';
export let container: ContainerInfoUI;
export let dropdownMenu = false;
export let detailed = false;
+export let contributions: Menu[] = [];
export let inProgressCallback: (inProgress: boolean, state?: string) => void = () => {};
export let errorCallback: (erroMessage: string) => void = () => {};
@@ -173,4 +176,9 @@ if (dropdownMenu) {
menu="{dropdownMenu}"
detailed="{detailed}"
icon="{faArrowsRotate}" />
+
diff --git a/packages/renderer/src/lib/image/ImageActions.svelte b/packages/renderer/src/lib/image/ImageActions.svelte
index 8962b96ce5e..362b3ee4d95 100644
--- a/packages/renderer/src/lib/image/ImageActions.svelte
+++ b/packages/renderer/src/lib/image/ImageActions.svelte
@@ -1,12 +1,5 @@
@@ -109,13 +99,11 @@ if (dropdownMenu) {
icon="{faLayerGroup}" />
{/if}
- {#each contributions as menu}
-
- {/each}
+
{#if errorMessage}
diff --git a/packages/renderer/src/lib/pod/PodActions.svelte b/packages/renderer/src/lib/pod/PodActions.svelte
index ad06f7787a7..1cb6c1c3ed0 100644
--- a/packages/renderer/src/lib/pod/PodActions.svelte
+++ b/packages/renderer/src/lib/pod/PodActions.svelte
@@ -5,10 +5,13 @@ import { router } from 'tinro';
import ListItemButtonIcon from '../ui/ListItemButtonIcon.svelte';
import DropdownMenu from '../ui/DropdownMenu.svelte';
import FlatMenu from '../ui/FlatMenu.svelte';
+import type { Menu } from '../../../../main/src/plugin/menu-registry';
+import ContributionActions from '/@/lib/actions/ContributionActions.svelte';
export let pod: PodInfoUI;
export let dropdownMenu = false;
export let detailed = false;
+export let contributions: Menu[] = [];
export let inProgressCallback: (inProgress: boolean, state?: string) => void = () => {};
export let errorCallback: (erroMessage: string) => void = () => {};
@@ -126,4 +129,9 @@ if (dropdownMenu) {
detailed="{detailed}"
icon="{faArrowsRotate}" />
{/if}
+