Fix and Expand Tests. Start Policy Status Modal

This commit is contained in:
Josh Krawczyk
2020-05-19 14:17:55 -04:00
parent 0d6ec9a83d
commit 05a5ce1235
11 changed files with 376 additions and 137 deletions

View File

@@ -3,4 +3,6 @@ module.exports = {
moduleNameMapper: {
quasar: "quasar/dist/quasar.umd.min.js"
},
//"collectCoverage": true,
//"collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"]
}

24
package-lock.json generated
View File

@@ -2100,9 +2100,9 @@
"dev": true
},
"@vue/test-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.0.2.tgz",
"integrity": "sha512-pnRWJbb0cLqjSJIKRpqoSISeYtufEn8D16VmhlCrDWIVt4iAY4Og4JpOPmFytvtQVz96p6n7T6ERI55ue6n0Ew==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.0.3.tgz",
"integrity": "sha512-mmsKXZSGfvd0bH05l4SNuczZ2MqlJH2DWhiul5wJXFxbf/gRRd2UL4QZgozEMQ30mRi9i4/+p4JJat8S4Js64Q==",
"dev": true,
"requires": {
"dom-event-types": "^1.0.0",
@@ -5941,6 +5941,12 @@
"locate-path": "^2.0.0"
}
},
"flush-promises": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz",
"integrity": "sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==",
"dev": true
},
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -13178,9 +13184,9 @@
}
},
"vue-router": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz",
"integrity": "sha512-GYhn2ynaZlysZMkFE5oCHRUTqE8BWs/a9YbKpNLi0i7xD6KG1EzDqpHQmv1F5gXjr8kL5iIVS8EOtRaVUEXTqA=="
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.2.0.tgz",
"integrity": "sha512-khkrcUIzMcI1rDcNtqkvLwfRFzB97GmJEsPAQdj7t/VvpGhmXLOkUfhc+Ah8CvpSXGXwuWuQO+x8Sy/xDhXZIA=="
},
"vue-style-loader": {
"version": "4.1.2",
@@ -13217,9 +13223,9 @@
"dev": true
},
"vuex": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.3.0.tgz",
"integrity": "sha512-1MfcBt+YFd20DPwKe0ThhYm1UEXZya4gVKUvCy7AtS11YAOUR+9a6u4fsv1Rr6ePZCDNxW/M1zuIaswp6nNv8Q=="
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.4.0.tgz",
"integrity": "sha512-ajtqwEW/QhnrBZQsZxCLHThZZaa+Db45c92Asf46ZDXu6uHXgbfVuBaJ4gzD2r4UX0oMJHstFwd2r2HM4l8umg=="
},
"w3c-hr-time": {
"version": "1.0.2",

View File

@@ -13,8 +13,8 @@
"core-js": "^3.6.5",
"quasar": "^1.11.3",
"vue": "^2.6.11",
"vue-router": "^3.1.6",
"vuex": "^3.3.0"
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.3.1",
@@ -22,8 +22,9 @@
"@vue/cli-plugin-unit-jest": "^4.3.1",
"@vue/cli-plugin-vuex": "~4.3.1",
"@vue/cli-service": "~4.3.1",
"@vue/test-utils": "~1.0.2",
"@vue/test-utils": "^1.0.3",
"babel-plugin-transform-imports": "1.5.0",
"flush-promises": "^1.0.2",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"vue-cli-plugin-quasar": "^2.0.2",

View File

@@ -21,30 +21,6 @@
icon="add"
@click="showAddPolicyModal"
/>
<q-btn
ref="edit"
label="Edit"
:disable="selectedRow === null"
dense
flat
push
unelevated
no-caps
icon="edit"
@click="showPolicyFormModal = true"
/>
<q-btn
ref="delete"
label="Delete"
:disable="selectedRow === null"
dense
flat
push
unelevated
no-caps
icon="delete"
@click="deletePolicy"
/>
<q-btn
ref="overview"
label="Policy Overview"
@@ -79,6 +55,53 @@
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" @click="props.selected = true">
<!-- context menu -->
<q-menu context-menu>
<q-list dense style="min-width: 200px">
<q-item
clickable
v-close-popup
@click="showEditPolicyModal(props.row.id)"
id="context-edit"
>
<q-item-section side>
<q-icon name="edit" />
</q-item-section>
<q-item-section>Edit</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="deletePolicy(props.row.id)"
id="context-delete"
>
<q-item-section side>
<q-icon name="delete" />
</q-item-section>
<q-item-section>Delete</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item
clickable
v-close-popup
@click="showRelationsModal(props.row)"
id="context-relation"
>
<q-item-section side>
<q-icon name="account_tree" />
</q-item-section>
<q-item-section>Show Relations</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item clickable v-close-popup>
<q-item-section>Close</q-item-section>
</q-item>
</q-list>
</q-menu>
<q-td>{{ props.row.name }}</q-td>
<q-td>{{ props.row.desc }}</q-td>
<q-td>{{ props.row.active }}</q-td>
@@ -104,13 +127,19 @@
</q-card-section>
</q-card>
<q-dialog v-model="showPolicyFormModal">
<PolicyForm :pk="selectedRow" @close="showPolicyFormModal = false" @refresh="clearRow" />
<PolicyForm
:pk="editPolicyId"
@close="closeEditPolicyModal"
@refresh="clearRow" />
</q-dialog>
<q-dialog v-model="showPolicyOverviewModal">
<PolicyOverview @close="showPolicyOverviewModal = false" />
<PolicyOverview
@close="showPolicyOverviewModal = false" />
</q-dialog>
<q-dialog v-model="showRelationsViewModal">
<RelationsView :policy="policy" @close="closeRelationsModal" />
<RelationsView
:policy="policy"
@close="closeRelationsModal" />
</q-dialog>
</div>
</template>
@@ -133,6 +162,7 @@ export default {
showPolicyOverviewModal: false,
showRelationsViewModal: false,
policy: null,
editPolicyId: null,
selected: [],
pagination: {
rowsPerPage: 0,
@@ -189,7 +219,7 @@ export default {
this.$store.commit("automation/setPolicyChecks", {});
this.$store.commit("automation/setPolicyAutomatedTasks", {});
},
deletePolicy() {
deletePolicy(id) {
this.$q
.dialog({
title: "Delete policy?",
@@ -198,7 +228,7 @@ export default {
})
.onOk(() => {
this.$store
.dispatch("automation/deletePolicy", this.selectedRow)
.dispatch("automation/deletePolicy", id)
.then(response => {
this.notifySuccess(`Policy was deleted!`);
})
@@ -215,8 +245,15 @@ export default {
this.policy = null;
this.showRelationsViewModal = false;
},
showEditPolicyModal(id) {
this.editPolicyId = id;
this.showPolicyFormModal = true;
},
closeEditPolicyModal() {
this.showPolicyFormModal = false;
this.editPolicyId = null;
},
showAddPolicyModal() {
this.clearRow();
this.showPolicyFormModal = true;
}
},
@@ -227,7 +264,7 @@ export default {
})
},
mounted() {
this.getPolicies();
this.clearRow();
}
};
</script>

View File

@@ -99,7 +99,22 @@
</q-item-section>
<q-item-section>Delete</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item
clickable
v-close-popup
@click="showPolicyCheckStatusModal(props.row)"
>
<q-item-section side>
<q-icon name="remove_red_eye" />
</q-item-section>
<q-item-section>Status</q-item-section>
</q-item>
<q-separator></q-separator>
<q-item clickable v-close-popup>
<q-item-section>Close</q-item-section>
</q-item>
@@ -120,13 +135,6 @@
v-model="props.row.email_alert"
/>
</q-td>
<q-td v-if="props.row.status === 'pending'"></q-td>
<q-td v-else-if="props.row.status === 'passing'">
<q-icon style="font-size: 1.3rem;" color="positive" name="check_circle" />
</q-td>
<q-td v-else-if="props.row.status === 'failing'">
<q-icon style="font-size: 1.3rem;" color="negative" name="error" />
</q-td>
<q-td
v-if="props.row.check_type === 'diskspace'"
>Disk Space Drive {{ props.row.disk }} > {{props.row.threshold }}%</q-td>
@@ -145,27 +153,18 @@
<q-td
v-else-if="props.row.check_type === 'winsvc'"
>Service Check - {{ props.row.svc_display_name }}</q-td>
<q-td v-if="props.row.status === 'pending'">Awaiting First Synchronization</q-td>
<q-td v-else-if="props.row.status === 'passing'">
<q-badge color="positive">Passing</q-badge>
<q-td>
<q-btn
label="See Status"
color="primary"
dense
flat
unelevated
no-caps
@click="showPolicyCheckStatusModal(props.row)"
size="sm"
/>
</q-td>
<q-td v-else-if="props.row.status === 'failing'">
<q-badge color="negative">Failing</q-badge>
</q-td>
<q-td v-if="props.row.check_type === 'ping'">
<span
style="cursor:pointer;color:blue;text-decoration:underline"
@click="moreInfo('Ping', props.row.more_info)"
>output</span>
</q-td>
<q-td v-else-if="props.row.check_type === 'script'">
<span
style="cursor:pointer;color:blue;text-decoration:underline"
@click="scriptMoreInfo(props.row)"
>output</span>
</q-td>
<q-td v-else>{{ props.row.more_info }}</q-td>
<q-td>{{ props.row.last_run }}</q-td>
</q-tr>
</template>
</q-table>
@@ -202,7 +201,6 @@
:policypk="checks.id"
/>
</q-dialog>
<q-dialog v-model="showAddMemCheck">
<AddMemCheck @close="showAddMemCheck = false" :policypk="checks.id" />
</q-dialog>
@@ -213,7 +211,6 @@
:policypk="checks.id"
/>
</q-dialog>
<q-dialog v-model="showAddWinSvcCheck">
<AddWinSvcCheck @close="showAddWinSvcCheck = false" :policypk="checks.id" />
</q-dialog>
@@ -235,6 +232,12 @@
:policypk="checks.id"
/>
</q-dialog>
<q-dialog v-model="showPolicyCheckStatus">
<PolicyCheckStatus
:check="statusCheck"
@close="closePolicyCheckStatusModal"
/>
</q-dialog>
</div>
</template>
@@ -254,6 +257,7 @@ import AddWinSvcCheck from "@/components/modals/checks/AddWinSvcCheck";
import EditWinSvcCheck from "@/components/modals/checks/EditWinSvcCheck";
import AddScriptCheck from "@/components/modals/checks/AddScriptCheck";
import EditScriptCheck from "@/components/modals/checks/EditScriptCheck";
import PolicyCheckStatus from "@/components/automation/modals/PolicyCheckStatus";
export default {
name: "PolicyChecksTab",
@@ -270,7 +274,8 @@ export default {
AddWinSvcCheck,
EditWinSvcCheck,
AddScriptCheck,
EditScriptCheck
EditScriptCheck,
PolicyCheckStatus
},
mixins: [mixins],
data() {
@@ -287,26 +292,14 @@ export default {
showEditWinSvcCheck: false,
showAddScriptCheck: false,
showEditScriptCheck: false,
showPolicyCheckStatus: false,
editCheckPK: null,
scriptInfo: {},
statusCheck: {},
columns: [
{ name: "smsalert", field: "text_alert", align: "left" },
{ name: "emailalert", field: "email_alert", align: "left" },
{ name: "statusicon", align: "left" },
{ name: "desc", label: "Description", align: "left" },
{ name: "status", label: "Status", field: "status", align: "left" },
{
name: "moreinfo",
label: "More Info",
field: "more_info",
align: "left"
},
{
name: "datetime",
label: "Date / Time",
field: "last_run",
align: "left"
}
{ name: "status", label: "Status", field: "status", align: "left" }
],
pagination: {
rowsPerPage: 9999
@@ -334,19 +327,6 @@ export default {
onRefresh(id) {
this.$store.dispatch("automation/loadPolicyChecks", id);
},
moreInfo(name, output) {
this.$q.dialog({
title: `${name} output`,
style: "width: 35vw; max-width: 50vw",
message: `<pre>${output}</pre>`,
html: true,
dark: true
});
},
scriptMoreInfo(props) {
this.scriptInfo = props;
this.showScriptOutput = true;
},
editCheck(category) {
switch (category) {
case "diskspace":
@@ -389,6 +369,14 @@ export default {
})
.catch(e => this.notifyError(e.response.data.error));
});
},
showPolicyCheckStatusModal(check) {
this.statusCheck = check;
this.showPolicyCheckStatus = true;
},
closePolicyCheckStatusModal() {
this.showPolicyCheckStatus = false;
this.statusCheck = {};
}
},
computed: {

View File

@@ -0,0 +1,33 @@
<template>
<q-card style="width: 60vw" >
<q-card-section class="row items-center">
<div class="text-h6">Policy Name Status</div>
<div class="text-subtitle1">{{ this.check.desc }}</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<q-list bordered separator>
<q-item>
<q-item-section>Single line item</q-item-section>
</q-item>
</q-list>
</q-card-section>
</q-card>
</template>
<script>
export default {
name: "PolicyCheckStatus",
props: {
check: {
required: true,
type: Object
}
},
data() {
return {
}
},
}
</script>

View File

@@ -205,7 +205,7 @@ export default {
}
},
mounted() {
//If pk prop is set that means we are editting
// If pk prop is set that means we are editting
if (this.pk) {
this.getPolicy();
}

View File

@@ -17,9 +17,9 @@
narrow-indicator
no-caps
>
<q-tab name="clients" label="Clients" />
<q-tab name="sites" label="Sites" />
<q-tab name="agents" label="Agents" />
<q-tab name="clients" label="Clients" ref="clients" />
<q-tab name="sites" label="Sites" ref="sites" />
<q-tab name="agents" label="Agents" ref="agents" />
</q-tabs>
<q-separator />
@@ -27,7 +27,7 @@
<q-tab-panels v-model="tab" :animated="false">
<q-tab-panel name="clients">
<div class="text-h6">Clients</div>
<q-list separator padding >
<q-list separator padding>
<q-item :key="item.id" v-for="item in related.clients">
<q-item-section>
<q-item-label>{{ item.client }}</q-item-label>
@@ -38,7 +38,7 @@
<q-tab-panel name="sites">
<div class="text-h6">Sites</div>
<q-list separator padding >
<q-list separator padding>
<q-item :key="item.id" v-for="item in related.sites">
<q-item-section>
<q-item-label>{{ item.site }}</q-item-label>
@@ -54,7 +54,7 @@
<q-item :key="item.pk" v-for="item in related.agents">
<q-item-section>
<q-item-label>{{ item.hostname }}</q-item-label>
<q-item-label caption>{{ item.client }} {{ item.site }}</q-item-label>
<q-item-label caption><b>{{ item.client }}</b> {{ item.site }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
@@ -80,7 +80,6 @@ export default {
}
},
mounted() {
this.$q.loading.show();
this.$store

View File

@@ -75,7 +75,8 @@ describe("AutomationManager.vue", () => {
localVue,
stubs: [
"PolicySubTableTabs",
"PolicyForm"
"PolicyForm",
"RelationsView"
],
});
@@ -86,7 +87,9 @@ describe("AutomationManager.vue", () => {
// This is needed to remove q-dialogs since body doesn't rerender
afterEach(() => {
const dialogs = document.querySelectorAll(".q-dialog");
const menus = document.querySelectorAll(".q-menu");
dialogs.forEach(x => x.remove());
menus.forEach(x => x.remove());
});
@@ -116,17 +119,17 @@ describe("AutomationManager.vue", () => {
});
it("shows edit policy modal on edit button press", async () => {
const button = wrapper.findComponent({ ref: "edit" });
it("shows edit policy modal on edit context menu button press", async () => {
expect(bodyWrapper.find(".q-dialog").exists()).toBe(false);
await button.trigger("click")
expect(bodyWrapper.find(".q-dialog").exists()).toBe(false);
expect(bodyWrapper.find(".q-menu").exists()).toBe(false);
//Select Row
await wrapper.find("tbody > tr.q-tr").trigger("click");
await button.trigger("click");
// Right Click on Row
await wrapper.find("tbody > tr.q-tr").trigger("contextmenu");
expect(bodyWrapper.find(".q-menu").exists()).toBe(true);
await bodyWrapper.find("#context-edit").trigger("click");
expect(wrapper.vm.editPolicyId).toBe(1);
expect(bodyWrapper.find(".q-dialog").exists()).toBe(true);
});
@@ -137,19 +140,21 @@ describe("AutomationManager.vue", () => {
expect(bodyWrapper.find(".q-dialog").exists()).toBe(false);
await button.trigger("click");
expect(wrapper.vm.editPolicyId).toBe(null);
expect(bodyWrapper.find(".q-dialog").exists()).toBe(true);
});
it("deletes selected policy", async () => {
const button = wrapper.findComponent({ ref: "delete" });
expect(bodyWrapper.find(".q-dialog").exists()).toBe(false);
// Select Row
await wrapper.find("tbody > tr.q-tr").trigger("click");
await button.trigger("click");
expect(bodyWrapper.find(".q-dialog").exists()).toBe(true);
expect(bodyWrapper.find(".q-menu").exists()).toBe(false);
// Right Click on Row
await wrapper.find("tbody > tr.q-tr").trigger("contextmenu");
expect(bodyWrapper.find(".q-menu").exists()).toBe(true);
await bodyWrapper.find("#context-delete").trigger("click");
//Get OK button and click it
bodyWrapper.findAll(".q-btn").wrappers[1].trigger("click");
@@ -179,4 +184,19 @@ describe("AutomationManager.vue", () => {
});
it("shows relation modal on context menu button press", async () => {
expect(bodyWrapper.find(".q-dialog").exists()).toBe(false);
expect(bodyWrapper.find(".q-menu").exists()).toBe(false);
// Right Click on Row
await wrapper.find("tbody > tr.q-tr").trigger("contextmenu");
expect(bodyWrapper.find(".q-menu").exists()).toBe(true);
await bodyWrapper.find("#context-relation").trigger("click");
expect(wrapper.vm.policy).toBe(policiesData[0]);
expect(bodyWrapper.find(".q-dialog").exists()).toBe(true);
});
});

View File

@@ -1,4 +1,5 @@
import { mount, createLocalVue } from "@vue/test-utils";
import { mount, createLocalVue, createWrapper } from "@vue/test-utils";
import flushPromises from "flush-promises";
import Vuex from "vuex";
import PolicyForm from "@/components/automation/modals/PolicyForm";
import "@/quasar.js"
@@ -8,17 +9,40 @@ localVue.use(Vuex);
describe("PolicyForm.vue", () => {
const clients = [];
const sites = [];
const agents = [];
const clients = [
{
id: 1,
client: "Test Client"
},
{
id: 2,
client: "Test Client2"
},
{
id: 3,
client: "Test Client3"
}
];
const sites = [
{
id: 1,
site: "Site Name",
client_name: "Test Client"
},
{
id: 2,
site: "Site Name2",
client_name: "Test Client2"
}
];
const policy = {
id: 1,
name: "Test Policy",
active: true,
clients: [{id: 1, client: "Test Name"}],
sites: [{id: 1, site: "Test Name"}],
agents: [{pk: 1, hostname: "Test Name"}]
clients: [],
sites: [],
agents: []
};
let methods;
@@ -34,7 +58,6 @@ describe("PolicyForm.vue", () => {
rootActions = {
loadClients: jest.fn(() => new Promise(res => res({ data: clients }))),
loadSites: jest.fn(() => new Promise(res => res({ data: sites }))),
loadAgents: jest.fn(() => new Promise(res => res({ data: agents }))),
};
actions = {
@@ -65,7 +88,6 @@ describe("PolicyForm.vue", () => {
expect(rootActions.loadClients).toHaveBeenCalled();
expect(rootActions.loadSites).toHaveBeenCalled();
expect(rootActions.loadAgents).toHaveBeenCalled();
// Not called unless pk prop is set
expect(actions.loadPolicy).not.toHaveBeenCalled();
@@ -74,7 +96,7 @@ describe("PolicyForm.vue", () => {
it("calls vuex actions on mount with pk prop set", () => {
const wrapper = mount(PolicyForm, {
mount(PolicyForm, {
localVue,
store,
propsData: {
@@ -84,14 +106,24 @@ describe("PolicyForm.vue", () => {
expect(rootActions.loadClients).toHaveBeenCalled();
expect(rootActions.loadSites).toHaveBeenCalled();
expect(rootActions.loadAgents).toHaveBeenCalled();
expect(actions.loadPolicy).toHaveBeenCalled();
});
/*it("renders the client, site, and agent dropdowns correctly", async () => {
it("Sets client and site options correctly", async () => {
})*/
const wrapper = mount(PolicyForm, {
localVue,
store
});
// Make sure the promises are resolved
await flushPromises();
expect(wrapper.vm.clientOptions).toHaveLength(3);
expect(wrapper.vm.siteOptions).toHaveLength(2);
});
it("sends the correct add action on submit", async () => {

View File

@@ -0,0 +1,121 @@
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import RelationsView from "@/components/automation/modals/RelationsView";
import "@/quasar.js"
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Relations.vue", () => {
const policy = {
id: 1,
name: "Test Policy",
active: true,
clients: [{id: 1, client: "Test Name"}],
sites: [{id: 1, site: "Test Name"}],
agents: []
};
const related = {
agents: [
{
pk: 1,
hostname: "Test Name",
site: "Test Site",
client: "Test Client"},
{
pk: 2,
site: "Test Site",
hostname: "Test Name2",
site: "Test Site",
client: "Test Client"
}
],
sites: [
{
id: 1,
client_name: "Test Name",
site: "Test Name"
}
],
clients: [
{
id: 1,
client: "Test Name"
},
{
id: 2,
client: "Test Name2"
},
{
id: 3,
client: "Test Name3"
}
]
};
let wrapper, actions, store;
// Runs before every test
beforeEach(() => {
actions = {
getRelated: jest.fn(() => new Promise(res => res({ data: related }))),
};
store = new Vuex.Store({
modules: {
automation: {
namespaced: true,
actions,
}
}
});
wrapper = mount(RelationsView, {
localVue,
store,
propsData: {
policy: policy
}
});
});
// The Tests
it("calls vuex actions on mount", () => {
expect(actions.getRelated).toHaveBeenCalledWith(expect.anything(), policy.id);
});
it("Checks the correct number of list items are rendered in clients tab", async () => {
await wrapper.findComponent({ref: "clients"}).trigger("click");
const list = wrapper.findAll(".q-item");
expect(list.length).toBeGreaterThanOrEqual(related.clients.length);
});
it("Checks the correct number of list items are rendered in sites tab", async () => {
await wrapper.findComponent({ref: "sites"}).trigger("click");
const list = wrapper.findAll(".q-item");
expect(list.length).toBeGreaterThanOrEqual(related.sites.length);
});
it("Checks the correct number of list items are rendered in agents tab", async () => {
await wrapper.findComponent({ref: "agents"}).trigger("click");
const list = wrapper.findAll(".q-item");
expect(list.length).toBeGreaterThanOrEqual(related.agents.length);
});
});