diff --git a/ui/admin/src/components/Namespace/NamespaceDelete.vue b/ui/admin/src/components/Namespace/NamespaceDelete.vue new file mode 100644 index 00000000000..54c706ad480 --- /dev/null +++ b/ui/admin/src/components/Namespace/NamespaceDelete.vue @@ -0,0 +1,69 @@ + + + diff --git a/ui/admin/src/components/Namespace/NamespaceEdit.vue b/ui/admin/src/components/Namespace/NamespaceEdit.vue index 06b4bf39947..279408b2bd6 100644 --- a/ui/admin/src/components/Namespace/NamespaceEdit.vue +++ b/ui/admin/src/components/Namespace/NamespaceEdit.vue @@ -1,23 +1,4 @@ + + + + @@ -66,7 +124,8 @@ import useNamespacesStore from "@admin/store/modules/namespaces"; import { IAdminNamespace } from "@admin/interfaces/INamespace"; import useSnackbar from "@/helpers/snackbar"; import DataTable from "@/components/Tables/DataTable.vue"; -import NamespaceEdit from "./NamespaceEdit.vue"; +import NamespaceEdit from "@admin/components/Namespace/NamespaceEdit.vue"; +import NamespaceDelete from "@admin/components/Namespace/NamespaceDelete.vue"; import handleError from "@/utils/handleError"; const snackbar = useSnackbar(); @@ -74,9 +133,15 @@ const namespacesStore = useNamespacesStore(); const namespaces = computed(() => namespacesStore.namespaces); const namespaceCount = computed(() => namespacesStore.namespaceCount); const router = useRouter(); + +const namespaceEdit = ref(false); +const namespaceDelete = ref(false); +const selectedNamespace = ref(null); + const loading = ref(false); const page = ref(1); const itemsPerPage = ref(10); + const headers = ref([ { text: "Name", @@ -94,10 +159,6 @@ const headers = ref([ text: "Owner", value: "owner", }, - { - text: "Session Record", - value: "settings", - }, { text: "Actions", value: "actions", @@ -120,12 +181,37 @@ const fetchNamespaces = async () => { }; const sumDevicesCount = (namespace: IAdminNamespace) => { - const { devices_accepted_count: acceptedCount, devices_pending_count: pendingCount, devices_rejected_count: rejectedCount } = namespace; + const { + devices_accepted_count: acceptedCount, + devices_pending_count: pendingCount, + devices_rejected_count: rejectedCount, + } = namespace; return (acceptedCount + pendingCount + rejectedCount) || 0; }; -const goToNamespace = async (namespace: string) => { - await router.push({ name: "namespaceDetails", params: { id: namespace } }); +const getOwnerEmail = (namespace: IAdminNamespace) => { + const owner = namespace.members?.find( + (member) => member.id === namespace.owner, + ); + return owner?.email || null; +}; + +const goToNamespace = async (tenantId: string) => { + await router.push({ name: "namespaceDetails", params: { id: tenantId } }); +}; + +const goToUser = async (userId: string) => { + await router.push({ name: "userDetails", params: { id: userId } }); +}; + +const openEditNamespace = (ns: IAdminNamespace) => { + selectedNamespace.value = ns; + namespaceEdit.value = true; +}; + +const openDeleteNamespace = (ns: IAdminNamespace) => { + selectedNamespace.value = ns; + namespaceDelete.value = true; }; watch([itemsPerPage, page], async () => { @@ -136,3 +222,9 @@ onMounted(async () => { await fetchNamespaces(); }); + + diff --git a/ui/admin/src/store/api/namespaces.ts b/ui/admin/src/store/api/namespaces.ts index 6a9960f1aa3..5b0e5ede76a 100644 --- a/ui/admin/src/store/api/namespaces.ts +++ b/ui/admin/src/store/api/namespaces.ts @@ -11,6 +11,8 @@ export const exportNamespaces = async (filter: string) => adminApi.exportNamespa export const getNamespace = async (id: string) => adminApi.getNamespaceAdmin(id); +export const deleteNamespace = async (tenant: string) => adminApi.deleteNamespaceAdmin(tenant); + export const updateNamespace = async ( data: IAdminNamespace, ) => adminApi.editNamespaceAdmin(data.tenant_id, { diff --git a/ui/admin/src/store/modules/namespaces.ts b/ui/admin/src/store/modules/namespaces.ts index 9d456cbbf13..a4a04848e39 100644 --- a/ui/admin/src/store/modules/namespaces.ts +++ b/ui/admin/src/store/modules/namespaces.ts @@ -32,6 +32,10 @@ const useNamespacesStore = defineStore("adminNamespaces", () => { return data; }; + const deleteNamespace = async (tenant: string) => { + await namespacesApi.deleteNamespace(tenant); + }; + const updateNamespace = async (data: IAdminNamespace) => { await namespacesApi.updateNamespace(data); }; @@ -44,6 +48,7 @@ const useNamespacesStore = defineStore("adminNamespaces", () => { fetchNamespaceList, fetchNamespaceById, exportNamespacesToCsv, + deleteNamespace, updateNamespace, }; }); diff --git a/ui/admin/src/views/NamespaceDetails.vue b/ui/admin/src/views/NamespaceDetails.vue index e3b495989cc..1bc0d2e713e 100644 --- a/ui/admin/src/views/NamespaceDetails.vue +++ b/ui/admin/src/views/NamespaceDetails.vue @@ -2,125 +2,320 @@

Namespace Details

- - -
-

- Name: -

-

- {{ namespace.name }} -

-
+ + + + {{ namespace.name || 'Namespace' }} + + {{ namespace.type }} + + -
-

- Devices: -

-

- {{ sumDevicesCount(namespace) }} -

-
+ + -
-

- Owner: -

-

- {{ namespace.owner }} -

-
+ +
+ mdi-pencil + Edit namespace +
+
-
-

- Tenant Id: -

-

- {{ namespace.tenant_id }} -

-
+ +
+ mdi-delete + Delete namespace +
+
+ +
+ + + + + + + + + +
+
Name:
+

{{ namespace.name }}

+
+ +
+
Tenant ID:
+

+ {{ namespace.tenant_id }} +

+
+ +
+
Owner:
+

+ {{ getOwnerEmail(namespace) || namespace.owner }} +

+
+ +
+
Total Devices:
+

{{ sumDevicesCount(namespace) }}

+
+ +
+
+
Accepted:
+

{{ namespace.devices_accepted_count || 0 }}

+
+
+
Pending:
+

{{ namespace.devices_pending_count || 0 }}

+
+
+
Rejected:
+

{{ namespace.devices_rejected_count || 0 }}

+
+
+
+ + +
+
Created:
+

{{ formatFullDateTime(namespace.created_at) }}

+
+ +
+
Max Devices:
+

{{ namespace.max_devices === -1 ? 'Unlimited' : namespace.max_devices }}

+
+ +
+
Session Record:
+ + {{ namespace.settings.session_record ? 'Enabled' : 'Disabled' }} + +
+ +
+
Connection Announcement:
+

{{ namespace.settings.connection_announcement }}

+
+
+
-
-

- Members: + + +
+

+ Members ({{ namespace.members?.length || 0 }})

-
    -
  • -
    - {{ name }}: + {{ value }} -
    -
    + data-test="namespace-member-email" + @click="goToUser(member.id)" + @keyup.enter="goToUser(member.id)" + > + {{ member.email }} + + + + {{ getRoleLabel(member.role) }} + + + {{ member.status }} + + + + {{ name }}: - {{ value }} -
    -
  • -
-
+ class="text-caption" + data-test="namespace-member-id" + > + ID: {{ member.id }} + + + Added: {{ formatFullDateTime(member.added_at) }} + + + Expires: {{ formatFullDateTime(member.expires_at) }} + + + + -
-

- Session Record: -

-

- {{ namespace.settings.session_record }} +

+ No members found

+ + +

Something is wrong, try again!

+
+ + + + + + diff --git a/ui/admin/tests/unit/components/Namespaces/NamespaceDelete/index.spec.ts b/ui/admin/tests/unit/components/Namespaces/NamespaceDelete/index.spec.ts new file mode 100644 index 00000000000..086351c53a2 --- /dev/null +++ b/ui/admin/tests/unit/components/Namespaces/NamespaceDelete/index.spec.ts @@ -0,0 +1,193 @@ +// admin/tests/unit/components/Namespaces/NamespaceDelete/index.spec.ts +import { createVuetify } from "vuetify"; +import { flushPromises, mount, VueWrapper } from "@vue/test-utils"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { Mock } from "vitest"; +import { createPinia, setActivePinia } from "pinia"; +import useNamespacesStore from "@admin/store/modules/namespaces"; +import NamespaceDelete from "@admin/components/Namespace/NamespaceDelete.vue"; +import { SnackbarInjectionKey } from "@/plugins/snackbar"; +import handleError from "@/utils/handleError"; + +// --- Mocks --- + +const mockRouter = { + push: vi.fn(), +}; + +const mockRoute = { + name: "namespaceDetails" as string, +}; + +vi.mock("vue-router", async () => { + const actual = await vi.importActual("vue-router"); + return { + ...actual, + useRouter: () => mockRouter, + useRoute: () => mockRoute, + }; +}); + +vi.mock("@/utils/handleError", () => ({ + __esModule: true, + default: vi.fn(), +})); + +const mockSnackbar = { + showSuccess: vi.fn(), + showError: vi.fn(), +}; + +// --- Test setup --- + +describe("Namespace Delete", () => { + let wrapper: VueWrapper>; + let namespacesStore: ReturnType; + let updateModelValue: ReturnType; + let pinia: ReturnType; + + const tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + const namespaceName = "namespace"; // < 10 chars → no truncation + + const mountComponent = () => { + updateModelValue = vi.fn(); + + wrapper = mount(NamespaceDelete, { + global: { + plugins: [createVuetify(), pinia], + provide: { + [SnackbarInjectionKey]: mockSnackbar, + }, + stubs: { + // Stub MessageDialog to avoid dealing with v-dialog + teleport + MessageDialog: { + template: ` +
+ +
+ `, + props: [ + "modelValue", + "title", + "icon", + "iconColor", + "confirmColor", + "confirmText", + "confirmLoading", + "cancelText", + "confirmDataTest", + "cancelDataTest", + ], + }, + }, + }, + props: { + tenant: tenantId, + name: namespaceName, + modelValue: true, + "onUpdate:modelValue": updateModelValue, + onUpdate: vi.fn(), + }, + }); + }; + + beforeEach(() => { + // Single shared Pinia instance + pinia = createPinia(); + setActivePinia(pinia); + + namespacesStore = useNamespacesStore(); + namespacesStore.deleteNamespace = vi.fn().mockResolvedValue(undefined); + + mockRouter.push.mockReset(); + mockRoute.name = "namespaceDetails"; + + mockSnackbar.showSuccess.mockReset(); + mockSnackbar.showError.mockReset(); + (handleError as Mock).mockReset(); + + mountComponent(); + }); + + it("Renders the dialog content with the namespace name", () => { + const content = wrapper.get('[data-test="content-text"]'); + + expect(content.text()).toContain("This action cannot be undone."); + expect(content.text()).toContain(namespaceName); + }); + + it("Deletes namespace, shows success, redirects and closes when on namespaceDetails route", async () => { + const removeButton = wrapper.get('[data-test="remove-btn"]'); + await removeButton.trigger("click"); + await flushPromises(); + + expect(namespacesStore.deleteNamespace).toHaveBeenCalledTimes(1); + expect(namespacesStore.deleteNamespace).toHaveBeenCalledWith(tenantId); + + expect(mockSnackbar.showSuccess).toHaveBeenCalledWith("Namespace deleted successfully."); + expect(mockSnackbar.showError).not.toHaveBeenCalled(); + + expect(mockRouter.push).toHaveBeenCalledWith({ name: "namespaces" }); + + expect(updateModelValue).toHaveBeenCalledWith(false); + + expect(wrapper.vm.isLoading).toBe(false); + + expect(wrapper.emitted("update")).toBeUndefined(); + }); + + it("Deletes namespace, shows success and emits update when not on namespaceDetails route", async () => { + mockRoute.name = "namespaces"; + + mountComponent(); + + const removeButton = wrapper.get('[data-test="remove-btn"]'); + await removeButton.trigger("click"); + await flushPromises(); + + expect(namespacesStore.deleteNamespace).toHaveBeenCalledTimes(1); + expect(namespacesStore.deleteNamespace).toHaveBeenCalledWith(tenantId); + + expect(mockSnackbar.showSuccess).toHaveBeenCalledWith("Namespace deleted successfully."); + expect(mockSnackbar.showError).not.toHaveBeenCalled(); + + expect(mockRouter.push).not.toHaveBeenCalled(); + + const updateEvents = wrapper.emitted("update"); + expect(updateEvents).toBeTruthy(); + expect(updateEvents?.length).toBe(1); + + expect(updateModelValue).toHaveBeenCalledWith(false); + + expect(wrapper.vm.isLoading).toBe(false); + }); + + it("Shows error snackbar, calls handleError and keeps dialog open when deletion fails", async () => { + (namespacesStore.deleteNamespace as Mock).mockRejectedValueOnce( + new Error("delete failed"), + ); + + const removeButton = wrapper.get('[data-test="remove-btn"]'); + await removeButton.trigger("click"); + await flushPromises(); + + expect(namespacesStore.deleteNamespace).toHaveBeenCalledTimes(1); + + expect(mockSnackbar.showError).toHaveBeenCalledWith( + "An error occurred while deleting the namespace.", + ); + expect(mockSnackbar.showSuccess).not.toHaveBeenCalled(); + + expect(handleError).toHaveBeenCalledTimes(1); + + expect(mockRouter.push).not.toHaveBeenCalled(); + + expect(wrapper.emitted("update")).toBeUndefined(); + + expect(updateModelValue).not.toHaveBeenCalledWith(false); + + expect(wrapper.vm.isLoading).toBe(false); + }); +}); diff --git a/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/__snapshots__/index.spec.ts.snap b/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/__snapshots__/index.spec.ts.snap index 24471c94d1e..9419e4f5f27 100644 --- a/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/__snapshots__/index.spec.ts.snap +++ b/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/__snapshots__/index.spec.ts.snap @@ -1,20 +1,231 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Namespace Edit > Renders the component 1`] = ` -" - +exports[`Namespace Edit > Renders the component and dialog 1`] = ` +" " `; -exports[`Namespace Edit > Renders the component 2`] = ` +exports[`Namespace Edit > Renders the component and dialog 2`] = ` "
- diff --git a/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/index.spec.ts b/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/index.spec.ts index f96efbcea2e..a8886a6a51e 100644 --- a/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/index.spec.ts +++ b/ui/admin/tests/unit/components/Namespaces/NamespaceEdit/index.spec.ts @@ -1,13 +1,14 @@ import { createVuetify } from "vuetify"; -import { DOMWrapper, flushPromises, mount } from "@vue/test-utils"; -import { describe, expect, it, vi } from "vitest"; +import { DOMWrapper, flushPromises, mount, VueWrapper } from "@vue/test-utils"; +import { describe, expect, it, vi, beforeEach } from "vitest"; +import type { Mock } from "vitest"; import { createPinia, setActivePinia } from "pinia"; import useNamespacesStore from "@admin/store/modules/namespaces"; import NamespaceEdit from "@admin/components/Namespace/NamespaceEdit.vue"; import { IAdminNamespace } from "@admin/interfaces/INamespace"; import { SnackbarInjectionKey } from "@/plugins/snackbar"; -const namespace = { +const namespace: IAdminNamespace = { billing: { active: true, current_period_end: "", @@ -27,7 +28,7 @@ const namespace = { members: [ { id: "", - role: "owner" as const, + role: "owner", }, ], name: "ossystems", @@ -36,7 +37,7 @@ const namespace = { session_record: true, }, tenant_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", -}; +} as IAdminNamespace; const mockSnackbar = { showSuccess: vi.fn(), @@ -44,21 +45,34 @@ const mockSnackbar = { }; describe("Namespace Edit", () => { - setActivePinia(createPinia()); - const namespacesStore = useNamespacesStore(); - namespacesStore.updateNamespace = vi.fn(); - namespacesStore.fetchNamespaceList = vi.fn(); - - const wrapper = mount(NamespaceEdit, { - global: { - plugins: [createVuetify()], - provide: { [SnackbarInjectionKey]: mockSnackbar }, - }, - props: { namespace: namespace as IAdminNamespace }, + let wrapper: VueWrapper>; + let namespacesStore: ReturnType; + + beforeEach(() => { + setActivePinia(createPinia()); + namespacesStore = useNamespacesStore(); + + namespacesStore.updateNamespace = vi.fn().mockResolvedValue(undefined); + namespacesStore.fetchNamespaceList = vi.fn().mockResolvedValue(undefined); + + mockSnackbar.showSuccess.mockReset(); + mockSnackbar.showError.mockReset(); + + wrapper = mount(NamespaceEdit, { + global: { + plugins: [createVuetify()], + provide: { [SnackbarInjectionKey]: mockSnackbar }, + }, + props: { + namespace, + modelValue: true, + }, + }); }); - it("Renders the component", () => { + it("Renders the component and dialog", () => { expect(wrapper.html()).toMatchSnapshot(); + const dialog = new DOMWrapper(document.body); expect(dialog.html()).toMatchSnapshot(); }); @@ -67,13 +81,53 @@ describe("Namespace Edit", () => { expect(wrapper.vm.name).toBe(namespace.name); expect(wrapper.vm.maxDevices).toBe(namespace.max_devices); expect(wrapper.vm.sessionRecord).toBe(namespace.settings.session_record); + + const dialog = new DOMWrapper(document.body); + + const nameInput = dialog.get('[data-test="name-text"] input'); + expect(nameInput.element.value).toBe(namespace.name); + + const maxDevicesInput = dialog.get('[data-test="maxDevices-text"] input'); + expect(maxDevicesInput.element.value).toBe(String(namespace.max_devices)); }); - it("Calls namespace store and snackbar on form submission", async () => { - wrapper.vm.submitForm(); + it("Calls namespace store and snackbar on form submission with updated values", async () => { + wrapper.vm.name = "updated-namespace"; + wrapper.vm.maxDevices = 42; + wrapper.vm.sessionRecord = false; + + await wrapper.vm.submitForm(); await flushPromises(); - expect(namespacesStore.updateNamespace).toHaveBeenCalled(); - expect(namespacesStore.fetchNamespaceList).toHaveBeenCalled(); + + expect(namespacesStore.updateNamespace).toHaveBeenCalledTimes(1); + + const updateNamespaceMock = namespacesStore.updateNamespace as Mock; + const payload = updateNamespaceMock.mock.calls[0][0]; + + expect(payload).toMatchObject({ + name: "updated-namespace", + max_devices: 42, + settings: { + ...namespace.settings, + session_record: false, + }, + }); + + expect(namespacesStore.fetchNamespaceList).toHaveBeenCalledTimes(1); expect(mockSnackbar.showSuccess).toHaveBeenCalledWith("Namespace updated successfully."); + expect(mockSnackbar.showError).not.toHaveBeenCalled(); + }); + + it("Shows error snackbar when updateNamespace fails", async () => { + const updateNamespaceMock = namespacesStore.updateNamespace as Mock; + updateNamespaceMock.mockRejectedValueOnce(new Error("update failed")); + + await wrapper.vm.submitForm(); + await flushPromises(); + + expect(namespacesStore.updateNamespace).toHaveBeenCalledTimes(1); + expect(namespacesStore.fetchNamespaceList).not.toHaveBeenCalled(); + expect(mockSnackbar.showError).toHaveBeenCalledWith("Failed to update namespace."); + expect(mockSnackbar.showSuccess).not.toHaveBeenCalled(); }); }); diff --git a/ui/admin/tests/unit/components/Namespaces/NamespaceList/__snapshots__/index.spec.ts.snap b/ui/admin/tests/unit/components/Namespaces/NamespaceList/__snapshots__/index.spec.ts.snap index 8f3670f07e4..8e113a31bce 100644 --- a/ui/admin/tests/unit/components/Namespaces/NamespaceList/__snapshots__/index.spec.ts.snap +++ b/ui/admin/tests/unit/components/Namespaces/NamespaceList/__snapshots__/index.spec.ts.snap @@ -1,8 +1,8 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Namespace List > Renders the component 1`] = ` -"
-
+"
+
@@ -13,43 +13,36 @@ exports[`Namespace List > Renders the component 1`] = ` Devices Tenant ID Owner - Session Record Actions - - ossystems - 2 - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - ossystems - -
true
- - + + ossystems + 2 + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ossystems + + + - + - - - - dev - 12 - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - dev - -
true
- - + + dev + 12 + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + dev + + + - + - - @@ -63,7 +56,7 @@ exports[`Namespace List > Renders the component 1`] = `
- + +
" `; diff --git a/ui/admin/tests/unit/views/NamespaceDetails/__snapshots__/index.spec.ts.snap b/ui/admin/tests/unit/views/NamespaceDetails/__snapshots__/index.spec.ts.snap index f4e38cc377b..72ff4c49199 100644 --- a/ui/admin/tests/unit/views/NamespaceDetails/__snapshots__/index.spec.ts.snap +++ b/ui/admin/tests/unit/views/NamespaceDetails/__snapshots__/index.spec.ts.snap @@ -1,10 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Namespace Details > Renders the component 1`] = ` -"
-

Namespace Details

+"
+

Namespace Details

-
+
-
-
-

Name:

-

dev

-
-
-

Devices:

-

1

-
-
-

Owner:

-

6256b739302b50b6cc5eafcc

-
-
-

Tenant Id:

-

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

-
-
-

Members:

-
    -
  • -
    id:6256b739302b50b6cc5eafcc
    -
  • -
  • -
    role:owner
    -
  • -
-
    -
  • -
    id:7326b239302b50b6cc5eafdd
    -
  • -
  • -
    role:administrator
    -
  • -
+
dev + + +
team
+ +
+ + +
+ +
+
+
+
+
Name:
+

dev

+
+
+
Tenant ID:
+

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

+
+
+
Owner:
+

owner@example.com

+
+
+
Total Devices:
+

1

+
+
+
+
Accepted:
+

1

+
+
+
Pending:
+

0

+
+
+
Rejected:
+

0

+
+
+
+
+
+
Created:
+

Wednesday, April 13th 2022, 11:43:24 am

+
+
+
Max Devices:
+

10

+
+
+
Session Record:
+ + +
Enabled
+ +
+
+
+
Connection Announcement:
+

New connection

+
+
-
-

Session Record:

-

true

+ +
+

Members (2)

+
+
+ + +
+ + +
owner@example.com + + +
owner
+ +
+ + +
active
+ +
+
+
ID: 6256b739302b50b6cc5eafccAdded: Wednesday, April 13th 2022, 11:43:24 am + +
+
+ +
+
+ + +
+ + +
admin@example.com + + +
admin
+ +
+ + +
pending
+ +
+
+
ID: 7326b239302b50b6cc5eafddAdded: Thursday, April 14th 2022, 11:43:24 am + +
+
+ +
+
diff --git a/ui/admin/tests/unit/views/NamespaceDetails/index.spec.ts b/ui/admin/tests/unit/views/NamespaceDetails/index.spec.ts index 4c52373caee..214e42a12c8 100644 --- a/ui/admin/tests/unit/views/NamespaceDetails/index.spec.ts +++ b/ui/admin/tests/unit/views/NamespaceDetails/index.spec.ts @@ -14,20 +14,30 @@ const namespaceDetail: IAdminNamespace = { name: "dev", owner: "6256b739302b50b6cc5eafcc", tenant_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + type: "team", members: [ { id: "6256b739302b50b6cc5eafcc", role: "owner", + email: "owner@example.com", + status: "active", + added_at: "2022-04-13T11:43:24.668Z", + expires_at: "0001-01-01T00:00:00Z", }, { id: "7326b239302b50b6cc5eafdd", role: "administrator", + email: "admin@example.com", + status: "pending", + added_at: "2022-04-14T11:43:24.668Z", + expires_at: "0001-01-01T00:00:00Z", }, ], settings: { session_record: true, + connection_announcement: "New connection", }, - max_devices: 0, + max_devices: 10, devices_accepted_count: 1, devices_pending_count: 0, devices_rejected_count: 0, @@ -81,23 +91,64 @@ describe("Namespace Details", () => { }); it("Should render the props of the Namespace on screen", () => { - expect(wrapper.find(`[data-test='${namespaceDetail.name}']`).text()).toContain(namespaceDetail.name); - expect(wrapper.find('[data-test="namespace-devices-count"').text()).toContain(devicesCount); - expect(wrapper.find(`[data-test='${namespaceDetail.owner}']`).text()).toContain(namespaceDetail.owner); - expect(wrapper.find(`[data-test='${namespaceDetail.tenant_id}']`).text()).toContain(namespaceDetail.tenant_id); - expect(wrapper.find(`[data-test='${namespaceDetail.settings.session_record}']`).text()) - .toContain(String(namespaceDetail.settings.session_record)); + const nameField = wrapper.get('[data-test="namespace-name-field"]'); + expect(nameField.text()).toContain(namespaceDetail.name); + + const tenantField = wrapper.get('[data-test="namespace-tenant-id-field"]'); + expect(tenantField.text()).toContain(namespaceDetail.tenant_id); + + const ownerField = wrapper.get('[data-test="namespace-owner-field"]'); + expect(ownerField.text()).toContain(namespaceDetail.members[0].email); + + const devicesField = wrapper.get('[data-test="namespace-devices-field"]'); + expect(devicesField.text()).toContain(String(devicesCount)); + + const breakdown = wrapper.get('[data-test="namespace-devices-breakdown"]'); + expect( + breakdown.get('[data-test="namespace-devices-accepted"]').text(), + ).toContain(String(namespaceDetail.devices_accepted_count)); + expect( + breakdown.get('[data-test="namespace-devices-pending"]').text(), + ).toContain(String(namespaceDetail.devices_pending_count)); + expect( + breakdown.get('[data-test="namespace-devices-rejected"]').text(), + ).toContain(String(namespaceDetail.devices_rejected_count)); + + const maxDevicesField = wrapper.get('[data-test="namespace-max-devices-field"]'); + expect(maxDevicesField.text()).toContain(String(namespaceDetail.max_devices)); + + const sessionRecordField = wrapper.get('[data-test="namespace-session-record-field"]'); + expect(sessionRecordField.text()).toContain("Enabled"); }); - it("Should render the correct members list", () => { - wrapper.findAll("ul").forEach((ul) => { - ul.findAll("li").forEach((li) => { - const fieldName = li.find("[data-test$='-item']"); - const fieldValue = li.find("[data-test$='-value']"); - expect(fieldName.exists()).toBeTruthy(); - expect(fieldValue.exists()).toBeTruthy(); - }); - }); - expect(wrapper.findAll("ul").length).toEqual(namespaceDetail.members.length); + it("Should render the props of the Namespace on screen", () => { + const nameField = wrapper.get('[data-test="namespace-name-field"]'); + expect(nameField.text()).toContain(namespaceDetail.name); + + const tenantField = wrapper.get('[data-test="namespace-tenant-id-field"]'); + expect(tenantField.text()).toContain(namespaceDetail.tenant_id); + + const ownerField = wrapper.get('[data-test="namespace-owner-field"]'); + expect(ownerField.text()).toContain(namespaceDetail.members[0].email); + + const devicesField = wrapper.get('[data-test="namespace-devices-field"]'); + expect(devicesField.text()).toContain(String(devicesCount)); + + const breakdown = wrapper.get('[data-test="namespace-devices-breakdown"]'); + expect( + breakdown.get('[data-test="namespace-devices-accepted"]').text(), + ).toContain(String(namespaceDetail.devices_accepted_count)); + expect( + breakdown.get('[data-test="namespace-devices-pending"]').text(), + ).toContain(String(namespaceDetail.devices_pending_count)); + expect( + breakdown.get('[data-test="namespace-devices-rejected"]').text(), + ).toContain(String(namespaceDetail.devices_rejected_count)); + + const maxDevicesField = wrapper.get('[data-test="namespace-max-devices-field"]'); + expect(maxDevicesField.text()).toContain(String(namespaceDetail.max_devices)); + + const sessionRecordField = wrapper.get('[data-test="namespace-session-record-field"]'); + expect(sessionRecordField.text()).toContain("Enabled"); }); }); diff --git a/ui/admin/tests/unit/views/Namespaces/__snapshots__/index.spec.ts.snap b/ui/admin/tests/unit/views/Namespaces/__snapshots__/index.spec.ts.snap index b22e70b9bc8..ddab321a3ff 100644 --- a/ui/admin/tests/unit/views/Namespaces/__snapshots__/index.spec.ts.snap +++ b/ui/admin/tests/unit/views/Namespaces/__snapshots__/index.spec.ts.snap @@ -52,8 +52,8 @@ exports[`Namespaces > Renders the component 1`] = `
-
-
+
+
@@ -64,13 +64,12 @@ exports[`Namespaces > Renders the component 1`] = ` Devices Tenant ID Owner - Session Record Actions - No data available + No data available @@ -137,5 +136,7 @@ exports[`Namespaces > Renders the component 1`] = `
+ +
" `;