diff --git a/webclient-vue/src/components/script/ActionScriptsGrid.vue b/webclient-vue/src/components/script/ActionScriptsGrid.vue new file mode 100644 index 0000000..43a80fa --- /dev/null +++ b/webclient-vue/src/components/script/ActionScriptsGrid.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/webclient-vue/src/components/script/__tests__/ActionScriptsGrid.spec.js b/webclient-vue/src/components/script/__tests__/ActionScriptsGrid.spec.js new file mode 100644 index 0000000..a3b0953 --- /dev/null +++ b/webclient-vue/src/components/script/__tests__/ActionScriptsGrid.spec.js @@ -0,0 +1,74 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { mount } from "@vue/test-utils"; +import { setActivePinia, createPinia } from "pinia"; +import { useAreasStore } from "../../../stores/areas.js"; +import ActionScriptsGrid from "../ActionScriptsGrid.vue"; + +describe("ActionScriptsGrid", () => { + beforeEach(() => { + setActivePinia(createPinia()); + }); + + function createWrapper(props = {}) { + return mount(ActionScriptsGrid, { + props: { + scripts: [ + { id: 1, alias: "kitchen_light", name: "Kitchen Light", icon: '', description: "Toggle kitchen", state: "enabled", author: "Test", scope: "KitchenScope", area_id: 1 }, + { id: 2, alias: "hall_light", name: "Hall Light", state: "disabled", author: "Test" }, + ], + ...props, + }, + }); + } + + it("renders action cards for each script", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("Kitchen Light"); + expect(wrapper.text()).toContain("Hall Light"); + }); + + it("shows script description when present", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("Toggle kitchen"); + }); + + it("shows state badge with success variant for enabled", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("enabled"); + }); + + it("shows state badge with secondary variant for disabled", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("disabled"); + }); + + it("shows scope badge when present", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("KitchenScope"); + }); + + it("shows area badge when showAreaBadge is true and area exists", () => { + const areasStore = useAreasStore(); + areasStore.areas = [{ id: 1, display_name: "Kitchen Area" }]; + const wrapper = createWrapper({ showAreaBadge: true }); + expect(wrapper.text()).toContain("Kitchen Area"); + }); + + it("does not show area badge when showAreaBadge is false", () => { + const areasStore = useAreasStore(); + areasStore.areas = [{ id: 1, display_name: "Kitchen Area" }]; + const wrapper = createWrapper({ showAreaBadge: false }); + expect(wrapper.text()).not.toContain("Kitchen Area"); + }); + + it("shows author when present", () => { + const wrapper = createWrapper(); + expect(wrapper.text()).toContain("Test"); + }); + + it("emits run event on button click", async () => { + const wrapper = createWrapper(); + const buttons = wrapper.findAll("button"); + expect(buttons.length).toBeGreaterThan(0); + }); +}); diff --git a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue index 192c87a..1970bed 100644 --- a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue +++ b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue @@ -63,36 +63,7 @@ title="No actions" message="No action scripts assigned to this area." /> -
- - - - -
+
@@ -220,7 +191,6 @@ GnInput, GnAlert, GnSelect, - GnActionCard, useToast, } from "gnexus-ui-kit/vue"; import AppLoadingState from "../../../components/feedback/AppLoadingState.vue"; @@ -231,6 +201,7 @@ import DeviceTable from "../../../components/device/DeviceTable.vue"; import ScriptTable from "../../../components/script/ScriptTable.vue"; import PageActionsDropdown from "../../../components/layout/PageActionsDropdown.vue"; +import ActionScriptsGrid from "../../../components/script/ActionScriptsGrid.vue"; const route = useRoute(); const router = useRouter(); @@ -336,22 +307,6 @@ const unassignLoading = ref(false); const unassignError = ref(""); -function goToScriptDetail(alias) { - router.push({ name: "script-detail", params: { type: "actions", id: alias } }); -} - -async function runAction(alias) { - const result = await scriptsStore.runScript(alias); - if (result?.ok) { - toast.success({ - title: `Ran ${alias}`, - text: scriptsStore.lastRunResult?.execTime ? `Exec time: ${scriptsStore.lastRunResult.execTime}` : undefined, - }); - } else { - toast.error({ title: `Failed ${alias}`, text: result?.error?.message || "Unknown error" }); - } -} - function openRename() { if (!area.value) return; renameForm.areaId = area.value.id; @@ -487,17 +442,6 @@ color: var(--color-primary); } -.script-icon { - font-size: 32px; -} - -.script-meta { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; -} - .form-group { margin-bottom: 16px; } diff --git a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue index dd748f5..db0ff18 100644 --- a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue +++ b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue @@ -21,77 +21,25 @@ message="No action scripts registered." /> -
- - - - -
+