Skip to content

Commit 7a008b4

Browse files
luizhf42otavio
authored andcommitted
fix(ui): fix UserDelete dialog button behavior
1 parent 8ad7ab7 commit 7a008b4

File tree

6 files changed

+124
-129
lines changed

6 files changed

+124
-129
lines changed

ui/admin/src/components/User/UserDelete.vue

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
<template>
2+
<v-tooltip
3+
location="bottom"
4+
:disabled="!showTooltip"
5+
text="Remove"
6+
>
7+
<template #activator="{ props: tooltipProps }">
8+
<span
9+
v-bind="tooltipProps"
10+
role="button"
11+
>
12+
<slot :open-dialog="openDialog" />
13+
</span>
14+
</template>
15+
</v-tooltip>
16+
217
<MessageDialog
318
v-model="showDialog"
419
title="Are you sure?"
@@ -15,6 +30,7 @@
1530
</template>
1631

1732
<script setup lang="ts">
33+
import { ref } from "vue";
1834
import { useRouter } from "vue-router";
1935
import useUsersStore from "@admin/store/modules/users";
2036
import useSnackbar from "@/helpers/snackbar";
@@ -24,14 +40,17 @@ import handleError from "@/utils/handleError";
2440
const props = defineProps<{
2541
id: string;
2642
redirect?: boolean;
43+
showTooltip?: boolean;
2744
}>();
2845
2946
const emit = defineEmits(["update"]);
30-
const showDialog = defineModel<boolean>({ required: true });
47+
const showDialog = ref(false);
3148
const router = useRouter();
3249
const snackbar = useSnackbar();
3350
const usersStore = useUsersStore();
3451
52+
const openDialog = () => { showDialog.value = true; };
53+
3554
const remove = async () => {
3655
try {
3756
await usersStore.deleteUser(props.id);
@@ -46,5 +65,5 @@ const remove = async () => {
4665
}
4766
};
4867
49-
defineExpose({ showDialog });
68+
defineExpose({ showDialog, openDialog, remove });
5069
</script>

ui/admin/src/components/User/UserList.vue

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,15 @@
7474

7575
<UserDelete
7676
:id="item.id"
77-
v-model="deleteDialog"
78-
/>
79-
<v-tooltip
80-
bottom
81-
anchor="bottom"
77+
v-slot="{ openDialog }"
78+
:show-tooltip="true"
8279
>
83-
<template #activator="{ props }">
84-
<v-icon
85-
tag="a"
86-
dark
87-
v-bind="props"
88-
icon="mdi-delete"
89-
@click="deleteDialog = true"
90-
/>
91-
</template>
92-
<span>Remove</span>
93-
</v-tooltip>
80+
<v-icon
81+
icon="mdi-delete"
82+
tag="button"
83+
@click="openDialog"
84+
/>
85+
</UserDelete>
9486
</td>
9587
</tr>
9688
</template>
@@ -120,7 +112,6 @@ const itemsPerPage = ref(10);
120112
const loading = ref(false);
121113
const users = computed(() => usersStore.users as IAdminUser[]);
122114
const userCount = computed(() => usersStore.usersCount);
123-
const deleteDialog = ref(false);
124115
const headers = [
125116
{
126117
text: "Name",

ui/admin/src/views/UserDetails.vue

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<h1>User Details</h1>
44
</div>
55
<v-card
6-
v-if="user && userId"
6+
v-if="user.id"
77
class="mt-2 border rounded bg-background"
88
elevation="0"
99
>
@@ -57,15 +57,23 @@
5757
</div>
5858
</v-list-item>
5959

60-
<v-list-item
61-
data-test="user-delete-btn"
62-
@click="deleteDialog = true"
60+
<UserDelete
61+
:id="userId"
62+
v-slot="{ openDialog }"
63+
redirect
6364
>
64-
<div class="d-flex align-center">
65-
<v-icon class="mr-2">mdi-delete</v-icon>
66-
<v-list-item-title>Delete this user</v-list-item-title>
65+
<div>
66+
<v-list-item
67+
data-test="user-delete-btn"
68+
@click="openDialog"
69+
>
70+
<div class="d-flex align-center">
71+
<v-icon class="mr-2">mdi-delete</v-icon>
72+
<v-list-item-title>Delete this user</v-list-item-title>
73+
</div>
74+
</v-list-item>
6775
</div>
68-
</v-list-item>
76+
</UserDelete>
6977

7078
<div class="px-2 py-1" />
7179
</v-list>
@@ -229,12 +237,6 @@
229237
>
230238
<p class="text-center">Something is wrong, try again!</p>
231239
</v-card>
232-
233-
<UserDelete
234-
:id="userId"
235-
v-model="deleteDialog"
236-
redirect
237-
/>
238240
</template>
239241

240242
<script setup lang="ts">
@@ -255,7 +257,6 @@ const authStore = useAuthStore();
255257
256258
const userId = computed(() => route.params.id as string);
257259
const user = ref({} as IAdminUser);
258-
const deleteDialog = ref(false);
259260
260261
const lastLoginText = computed(() => {
261262
const value = user.value?.last_login;

ui/admin/tests/unit/components/User/UserDelete/__snapshots__/index.spec.ts.snap

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,95 @@
1+
import { nextTick } from "vue";
12
import { createVuetify } from "vuetify";
2-
import { DOMWrapper, mount } from "@vue/test-utils";
3-
import { describe, expect, it, vi } from "vitest";
3+
import { flushPromises, mount, VueWrapper } from "@vue/test-utils";
4+
import { afterEach, describe, expect, it, vi } from "vitest";
45
import { createPinia, setActivePinia } from "pinia";
56
import useUsersStore from "@admin/store/modules/users";
67
import UserDelete from "@admin/components/User/UserDelete.vue";
78
import routes from "@admin/router";
89
import { SnackbarPlugin } from "@/plugins/snackbar";
910

1011
describe("User Delete", () => {
12+
let wrapper: VueWrapper<InstanceType<typeof UserDelete>>;
1113
setActivePinia(createPinia());
1214
const usersStore = useUsersStore();
1315
usersStore.deleteUser = vi.fn();
1416
usersStore.fetchUsersList = vi.fn();
1517

16-
const wrapper = mount(UserDelete, {
17-
props: {
18-
id: "6256d9e3ea6f26bc595130fa",
19-
redirect: false,
20-
modelValue: true,
21-
},
22-
global: { plugins: [createVuetify(), routes, SnackbarPlugin] },
18+
const createWrapper = (props = {}, slots = {}) => {
19+
return mount(UserDelete, {
20+
props: {
21+
id: "test-id",
22+
...props,
23+
},
24+
slots: {
25+
default: "<button>Delete</button>",
26+
...slots,
27+
},
28+
global: { plugins: [createVuetify(), routes, SnackbarPlugin] },
29+
});
30+
};
31+
32+
afterEach(() => { wrapper.unmount(); });
33+
34+
it("Exposes openDialog via slot props", () => {
35+
const slotMock = vi.fn();
36+
wrapper = createWrapper({}, { default: slotMock });
37+
38+
expect(slotMock).toHaveBeenCalled();
39+
const slotProps = slotMock.mock.calls[0][0];
40+
expect(slotProps.openDialog).toBeInstanceOf(Function);
41+
});
42+
43+
it("Shows tooltip text when enabled", () => {
44+
wrapper = createWrapper({ showTooltip: true });
45+
46+
const tooltip = wrapper.findComponent({ name: "VTooltip" });
47+
expect(tooltip.props("text")).toBe("Remove");
48+
});
49+
50+
it("Opens dialog when openDialog is called", async () => {
51+
const slotMock = vi.fn();
52+
wrapper = createWrapper({}, { default: slotMock });
53+
54+
const slotProps = slotMock.mock.calls[0][0];
55+
56+
slotProps.openDialog();
57+
await nextTick();
58+
59+
const messageDialog = wrapper.findComponent({ name: "MessageDialog" });
60+
expect(messageDialog.props("modelValue")).toBe(true);
61+
});
62+
63+
it("Deletes user on confirm", async () => {
64+
wrapper = createWrapper();
65+
66+
const messageDialog = wrapper.findComponent({ name: "MessageDialog" });
67+
await messageDialog.vm.$emit("confirm");
68+
await flushPromises();
69+
70+
expect(usersStore.deleteUser).toHaveBeenCalledWith("test-id");
71+
expect(usersStore.fetchUsersList).toHaveBeenCalled();
2372
});
2473

25-
it("Renders the component", () => {
26-
expect(wrapper.html()).toMatchSnapshot();
27-
const dialog = new DOMWrapper(document.body);
28-
expect(dialog.html()).toMatchSnapshot();
74+
it("Redirects after delete when redirect prop is true", async () => {
75+
const pushSpy = vi.spyOn(routes, "push");
76+
77+
wrapper = createWrapper({ redirect: true });
78+
79+
const messageDialog = wrapper.findComponent({ name: "MessageDialog" });
80+
await messageDialog.vm.$emit("confirm");
81+
await flushPromises();
82+
83+
expect(pushSpy).toHaveBeenCalledWith("/users");
2984
});
3085

31-
it("Receives props correctly", () => {
32-
expect(wrapper.vm.id).toBe("6256d9e3ea6f26bc595130fa");
33-
expect(wrapper.vm.redirect).toBe(false);
86+
it("Emits update event after successful deletion", async () => {
87+
wrapper = createWrapper();
88+
89+
const messageDialog = wrapper.findComponent({ name: "MessageDialog" });
90+
await messageDialog.vm.$emit("confirm");
91+
await flushPromises();
92+
93+
expect(wrapper.emitted("update")).toBeTruthy();
3494
});
3595
});

ui/admin/tests/unit/components/User/UserList/__snapshots__/index.spec.ts.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ exports[`UserList > Renders the component 1`] = `
4141
<!--teleport start-->
4242
<!--teleport end-->
4343
<!---->
44-
<!---->
45-
<!---->
46-
<!----><a class="mdi-delete mdi v-icon notranslate v-theme--light v-icon--size-default v-icon--clickable" role="button" aria-hidden="false" tabindex="0" dark="" aria-describedby="v-tooltip-v-6"></a>
44+
<!----><span aria-describedby="v-tooltip-v-6" role="button"><button class="mdi-delete mdi v-icon notranslate v-theme--light v-icon--size-default v-icon--clickable" role="button" aria-hidden="false" tabindex="0"></button></span>
4745
<!--teleport start-->
4846
<!--teleport end-->
47+
<!---->
48+
<!---->
4949
</td>
5050
</tr>
5151
</tbody>

0 commit comments

Comments
 (0)