From 69b9191feeb845db565b4bc1f0d99922798fad04 Mon Sep 17 00:00:00 2001 From: Vinicius Aquino Date: Mon, 27 Jan 2025 14:38:24 -0300 Subject: [PATCH] feat(ui): add 'Add Container' button and dialog This feature is still not functional, as the script (for agent installation in containers) is not implemented yet. Signed-off-by: Vinicius Aquino --- ui/src/components/Containers/ContainerAdd.vue | 103 ++++++++++ ui/src/views/Containers.vue | 3 + .../Containers/ContainerAdd.spec.ts | 194 ++++++++++++++++++ .../__snapshots__/ContainerAdd.spec.ts.snap | 9 + 4 files changed, 309 insertions(+) create mode 100644 ui/src/components/Containers/ContainerAdd.vue create mode 100644 ui/tests/components/Containers/ContainerAdd.spec.ts create mode 100644 ui/tests/components/Containers/__snapshots__/ContainerAdd.spec.ts.snap diff --git a/ui/src/components/Containers/ContainerAdd.vue b/ui/src/components/Containers/ContainerAdd.vue new file mode 100644 index 00000000000..79a2c1921b4 --- /dev/null +++ b/ui/src/components/Containers/ContainerAdd.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/ui/src/views/Containers.vue b/ui/src/views/Containers.vue index e66cc484ab5..43466636ae1 100644 --- a/ui/src/views/Containers.vue +++ b/ui/src/views/Containers.vue @@ -22,7 +22,9 @@
+
+
@@ -51,6 +53,7 @@ import Containers from "../components/Containers/Container.vue"; import TagSelector from "../components/Tags/TagSelector.vue"; import BoxMessage from "../components/Box/BoxMessage.vue"; import handleError from "@/utils/handleError"; +import ContainerAdd from "../components/Containers/ContainerAdd.vue"; const store = useStore(); const router = useRouter(); diff --git a/ui/tests/components/Containers/ContainerAdd.spec.ts b/ui/tests/components/Containers/ContainerAdd.spec.ts new file mode 100644 index 00000000000..01da0da3555 --- /dev/null +++ b/ui/tests/components/Containers/ContainerAdd.spec.ts @@ -0,0 +1,194 @@ +import { DOMWrapper, mount, VueWrapper } from "@vue/test-utils"; +import { createVuetify } from "vuetify"; +import MockAdapter from "axios-mock-adapter"; +import { expect, describe, it, beforeEach, vi } from "vitest"; +import { store, key } from "@/store"; +import ContainerAdd from "@/components/Containers/ContainerAdd.vue"; +import { router } from "@/router"; +import { namespacesApi, billingApi, devicesApi } from "@/api/http"; +import { SnackbarPlugin } from "@/plugins/snackbar"; + +const node = document.createElement("div"); +node.setAttribute("id", "app"); +document.body.appendChild(node); + +const devices = [ + { + uid: "a582b47a42d", + name: "39-5e-2a", + identity: { + mac: "00:00:00:00:00:00", + }, + info: { + id: "linuxmint", + pretty_name: "Linux Mint 19.3", + version: "", + }, + public_key: "----- PUBLIC KEY -----", + tenant_id: "fake-tenant-data", + last_seen: "2020-05-20T18:58:53.276Z", + online: false, + namespace: "user", + status: "accepted", + }, + { + uid: "a582b47a42e", + name: "39-5e-2b", + identity: { + mac: "00:00:00:00:00:00", + }, + info: { + id: "linuxmint", + pretty_name: "Linux Mint 19.3", + version: "", + }, + public_key: "----- PUBLIC KEY -----", + tenant_id: "fake-tenant-data", + last_seen: "2020-05-20T19:58:53.276Z", + online: true, + namespace: "user", + status: "accepted", + }, +]; + +const members = [ + { + id: "xxxxxxxx", + username: "test", + role: "owner", + }, +]; + +const billingData = { + active: false, + status: "canceled", + customer_id: "cus_test", + subscription_id: "sub_test", + current_period_end: 2068385820, + created_at: "", + updated_at: "", + invoices: [], +}; + +const namespaceData = { + name: "test", + owner: "xxxxxxxx", + tenant_id: "fake-tenant-data", + members, + max_devices: 3, + devices_count: 3, + devices: 2, + created_at: "", + billing: billingData, +}; + +const authData = { + status: "", + token: "", + user: "test", + name: "test", + tenant: "fake-tenant-data", + email: "test@test.com", + id: "xxxxxxxx", + role: "owner", +}; + +const customerData = { + id: "cus_test", + name: "test", + email: "test@test.com", + payment_methods: [ + { + id: "test_id", + number: "xxxxxxxxxxxx4242", + brand: "visa", + exp_month: 3, + exp_year: 2029, + cvc: "", + default: true, + }, + ], +}; + +const stats = { + registered_devices: 3, + online_devices: 1, + active_sessions: 0, + pending_devices: 0, + rejected_devices: 0, +}; + +describe("ContainerAdd", () => { + let wrapper: VueWrapper>; + + const vuetify = createVuetify(); + + let mockNamespace: MockAdapter; + let mockBilling: MockAdapter; + let mockDevices: MockAdapter; + + beforeEach(async () => { + const el = document.createElement("div"); + document.body.appendChild(el); + + vi.useFakeTimers(); + localStorage.setItem("tenant", "fake-tenant-data"); + + mockBilling = new MockAdapter(billingApi.getAxios()); + mockNamespace = new MockAdapter(namespacesApi.getAxios()); + mockDevices = new MockAdapter(devicesApi.getAxios()); + + mockNamespace.onGet("http://localhost:3000/api/namespaces/fake-tenant-data").reply(200, namespaceData); + mockBilling.onGet("http://localhost:3000/api/billing/customer").reply(200, customerData); + mockBilling.onGet("http://localhost:3000/api/billing/subscription").reply(200, billingData); + mockBilling.onGet("http://localhost:3000/api/billing/devices-most-used").reply(200, devices); + mockDevices.onGet("http://localhost:3000/api/devices?filter=&page=1&per_page=10&status=accepted").reply(200, devices); + mockDevices.onGet("http://localhost:3000/api/stats").reply(200, stats); + + store.commit("auth/authSuccess", authData); + store.commit("namespaces/setNamespace", namespaceData); + store.commit("billing/setSubscription", billingData); + store.commit("customer/setCustomer", customerData); + store.commit("devices/setDeviceChooserStatus", true); + + wrapper = mount(ContainerAdd, { + global: { + plugins: [[store, key], vuetify, router, SnackbarPlugin], + }, + attachTo: el, + }); + }); + + it("Is a Vue instance", () => { + expect(wrapper.vm).toBeTruthy(); + }); + + it("Renders the component", () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + + it("Data is defined", () => { + expect(wrapper.vm.$data).toBeDefined(); + }); + + it("Renders the component data table", async () => { + expect(wrapper.find('[data-test="device-add-btn"]').exists()).toBe(true); + await wrapper.findComponent('[data-test="device-add-btn"]').trigger("click"); + const dialog = new DOMWrapper(document.body); + expect(dialog.find('[data-test="dialog"]').exists()).toBe(true); + expect(dialog.find('[data-test="dialog-title"]').exists()).toBe(true); + expect(dialog.find('[data-test="dialog-text"]').exists()).toBe(true); + expect(dialog.find('[data-test="command-field"]').exists()).toBe(true); + expect(dialog.find('[data-test="documentation-link"]').exists()).toBe(true); + expect(dialog.find('[data-test="close-btn"]').exists()).toBe(true); + }); + + it("Opens the dialog when add device button is clicked", async () => { + const dialog = new DOMWrapper(document.body); + expect(dialog.find('[data-test="dialog"]').exists()).toBe(true); + + await wrapper.find('[data-test="device-add-btn"]').trigger("click"); + + expect(dialog.find('[data-test="dialog"]').exists()).toBe(true); + }); +}); diff --git a/ui/tests/components/Containers/__snapshots__/ContainerAdd.spec.ts.snap b/ui/tests/components/Containers/__snapshots__/ContainerAdd.spec.ts.snap new file mode 100644 index 00000000000..14b456246c7 --- /dev/null +++ b/ui/tests/components/Containers/__snapshots__/ContainerAdd.spec.ts.snap @@ -0,0 +1,9 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ContainerAdd > Renders the component 1`] = ` +"" +`;