diff --git a/webclient-vue/src/components/feedback/AppErrorState.vue b/webclient-vue/src/components/feedback/AppErrorState.vue index 06feb9c..c268ce6 100644 --- a/webclient-vue/src/components/feedback/AppErrorState.vue +++ b/webclient-vue/src/components/feedback/AppErrorState.vue @@ -1,15 +1,31 @@ + + diff --git a/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js b/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js index ee3963f..92144b2 100644 --- a/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js +++ b/webclient-vue/src/components/feedback/__tests__/AppErrorState.spec.js @@ -53,4 +53,76 @@ expect(retry).toHaveBeenCalled(); }); + + it("renders error type, statusCode and errorAlias badges when error prop provided", () => { + const wrapper = mount(AppErrorState, { + props: { + title: "Failed", + error: { + type: "api_error", + message: "Invalid input", + statusCode: 422, + errorAlias: "VALIDATION_FAILED", + raw: { status: false }, + }, + }, + }); + + expect(wrapper.text()).toContain("api_error"); + expect(wrapper.text()).toContain("HTTP 422"); + expect(wrapper.text()).toContain("VALIDATION_FAILED"); + }); + + it("shows copy button when error prop is provided", () => { + const wrapper = mount(AppErrorState, { + props: { + title: "Failed", + error: { + type: "network_error", + message: "Connection refused", + }, + }, + }); + + const copyBtn = wrapper.find('button[aria-label="Copy error details"]'); + expect(copyBtn.exists()).toBe(true); + }); + + it("hides copy button when no error prop is provided", () => { + const wrapper = mount(AppErrorState, { + props: { + title: "Failed", + retry: null, + }, + }); + + const copyBtn = wrapper.find('button[aria-label="Copy error details"]'); + expect(copyBtn.exists()).toBe(false); + }); + + it("formats error details as JSON for copying", async () => { + Object.assign(navigator, { + clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, + }); + + const error = { + type: "api_error", + message: "Invalid input", + statusCode: 422, + raw: { status: false }, + }; + + const wrapper = mount(AppErrorState, { + props: { title: "Failed", error }, + }); + + const copyBtn = wrapper.find('button[aria-label="Copy error details"]'); + expect(copyBtn.exists()).toBe(true); + + await copyBtn.trigger("click"); + + expect(navigator.clipboard.writeText).toHaveBeenCalledWith( + JSON.stringify(error, null, 2) + ); + }); }); diff --git a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue index ba92f82..3d1cac2 100644 --- a/webclient-vue/src/features/areas/pages/AreaDetailPage.vue +++ b/webclient-vue/src/features/areas/pages/AreaDetailPage.vue @@ -5,14 +5,14 @@ diff --git a/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue b/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue index 5bbfaba..4fdf185 100644 --- a/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue +++ b/webclient-vue/src/features/areas/pages/AreaFavoritesPage.vue @@ -11,7 +11,7 @@ diff --git a/webclient-vue/src/features/areas/pages/AreaTreePage.vue b/webclient-vue/src/features/areas/pages/AreaTreePage.vue index d112571..d3a5db4 100644 --- a/webclient-vue/src/features/areas/pages/AreaTreePage.vue +++ b/webclient-vue/src/features/areas/pages/AreaTreePage.vue @@ -11,7 +11,7 @@ diff --git a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue index e6e935e..59b89d8 100644 --- a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue +++ b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue @@ -5,7 +5,7 @@ diff --git a/webclient-vue/src/features/devices/pages/DevicesListPage.vue b/webclient-vue/src/features/devices/pages/DevicesListPage.vue index 99576d4..278f69b 100644 --- a/webclient-vue/src/features/devices/pages/DevicesListPage.vue +++ b/webclient-vue/src/features/devices/pages/DevicesListPage.vue @@ -17,7 +17,7 @@ @@ -38,7 +38,7 @@ diff --git a/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue b/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue index 1610070..03f16eb 100644 --- a/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue +++ b/webclient-vue/src/features/devices/pages/DevicesScanningPage.vue @@ -37,7 +37,7 @@ diff --git a/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue b/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue index 8105059..e8d178d 100644 --- a/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue +++ b/webclient-vue/src/features/scripts/pages/ScriptDetailPage.vue @@ -5,7 +5,7 @@ @@ -104,7 +104,7 @@
diff --git a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue index b8e3043..6df139e 100644 --- a/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue +++ b/webclient-vue/src/features/scripts/pages/ScriptsActionsPage.vue @@ -11,7 +11,7 @@ diff --git a/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue index c2a0004..a667231 100644 --- a/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue +++ b/webclient-vue/src/features/scripts/pages/ScriptsRegularPage.vue @@ -11,7 +11,7 @@ diff --git a/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue b/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue index f950996..59276bf 100644 --- a/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue +++ b/webclient-vue/src/features/scripts/pages/ScriptsScopesPage.vue @@ -11,7 +11,7 @@