diff --git a/src/types/current-user.model.ts b/src/types/current-user.model.ts index eda5e6eec..400f7a2fa 100644 --- a/src/types/current-user.model.ts +++ b/src/types/current-user.model.ts @@ -71,4 +71,5 @@ export interface IUser extends ICurrentUser { feedTokens: IFeedToken[]; authAuthorizedApplications: IApplicationToken[]; conversations: Paginate; + moderation: string; } diff --git a/src/views/Admin/AdminUserProfile.vue b/src/views/Admin/AdminUserProfile.vue index d8b142533..c7c3a829e 100644 --- a/src/views/Admin/AdminUserProfile.vue +++ b/src/views/Admin/AdminUserProfile.vue @@ -177,16 +177,22 @@
- {{ - t("Accept") - }} + {{ t("Accept") }}
- {{ - t("Ban") - }} + {{ t("Ban") }}
{{ t("The user has been banned") }}
- {{ - t("Unban") - }} + {{ t("Unban") }}
@@ -359,18 +368,18 @@ import { computed, inject, reactive, ref, watch } from "vue"; import { useHead } from "@/utils/head"; import { useI18n } from "vue-i18n"; import { formatDateTimeString } from "@/filters/datetime"; -import { useRouter } from "vue-router"; import { IPerson } from "@/types/actor"; import { Dialog } from "@/plugins/dialog"; const props = defineProps<{ id: string }>(); -const { result: userResult, loading: loadingUser } = useQuery<{ user: IUser }>( - GET_USER, - () => ({ - id: props.id, - }) -); +const { + result: userResult, + loading: loadingUser, + refetch: refetchUser, +} = useQuery<{ user: IUser }>(GET_USER, () => ({ + id: props.id, +})); const user = computed(() => userResult.value?.user); @@ -485,8 +494,6 @@ const roleName = (role: ICurrentUserRole): string => { } }; -const router = useRouter(); - const { mutate: deleteUserAccount } = useMutation< { deleteProfile: { id: string } }, { userId: string } @@ -512,7 +519,7 @@ const deleteAccount = async (): Promise => { deleteUserAccount({ userId: props.id, }); - return router.push({ name: RouteName.USERS }); + refetchUser(); }, }); }; @@ -530,7 +537,7 @@ const unbanAccount = async (): Promise => { unbanUserAccount({ userId: props.id, }); - return router.push({ name: RouteName.USERS }); + refetchUser(); }, }); }; @@ -542,7 +549,7 @@ const acceptAccount = async () => { role: ICurrentUserRole.USER, notify: true, }); - router.push({ name: RouteName.ADMIN_USER_PROFILE, id: props.id }); + refetchUser(); }; const profiles = computed((): IPerson[] | undefined => { @@ -556,7 +563,7 @@ const confirmUser = async () => { confirmed: true, notify: newUser.notify, }); - router.push({ name: RouteName.ADMIN_USER_PROFILE, id: props.id }); + refetchUser(); }; const updateUserRole = async () => { @@ -566,7 +573,7 @@ const updateUserRole = async () => { role: newUser.role, notify: newUser.notify, }); - router.push({ name: RouteName.ADMIN_USER_PROFILE, id: props.id }); + refetchUser(); }; const updateUserEmail = async () => { @@ -576,7 +583,7 @@ const updateUserEmail = async () => { email: newUser.email, notify: newUser.notify, }); - router.push({ name: RouteName.ADMIN_USER_PROFILE, id: props.id }); + refetchUser(); }; const { mutate: updateUser } = useMutation< diff --git a/tests/unit/specs/components/admin/__snapshots__/adminUsersProfileView.spec.ts.snap b/tests/unit/specs/components/admin/__snapshots__/adminUsersProfileView.spec.ts.snap index f5f0e4eb4..16f3ac362 100644 --- a/tests/unit/specs/components/admin/__snapshots__/adminUsersProfileView.spec.ts.snap +++ b/tests/unit/specs/components/admin/__snapshots__/adminUsersProfileView.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`UsersView > Show moderate list 1`] = ` +exports[`UsersView > Ban user 1`] = ` "
@@ -14,93 +14,10 @@ exports[`UsersView > Show moderate list 1`] = ` Email truc@mobilizon.test - - - - - - - Language - French - - - - Role - Pending - - - - - - Moderation - moderation text - - - - Confirmed - Saturday, August 30, 2025 at 11:56 AM - - - - - - Last sign-in - Unknown - - - - Last IP adress - Unknown - - - - -
- - - - - -
-

Actions

- - - - - -
-
- -
-
-
- -
-
-
- - - -" -`; - -exports[`UsersView > Show simple list 1`] = ` -"
- -
-

Details

-
-
-
-
- - - - - - @@ -111,9 +28,9 @@ exports[`UsersView > Show simple list 1`] = ` - + @@ -166,15 +83,716 @@ exports[`UsersView > Show simple list 1`] = ` + +
Emailtruc@mobilizon.test - - + +
Role User - -
Login status
-
- +
+
+
+ + + + + + + + + + + + + + + +
" +`; + +exports[`UsersView > Show moderate list 1`] = ` +"
+ +
+

Details

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Emailtruc@mobilizon.test + + +
LanguageFrench
RolePending + +
Moderationmoderation text
ConfirmedSaturday, August 30, 2025 at 11:56 AM + +
Last sign-inUnknown
Last IP adressUnknown
+
+
+
+
+
+ +
+

Actions

+ + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
" +`; + +exports[`UsersView > Show simple list 1`] = ` +"
+ +
+

Details

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Emailtruc@mobilizon.test + +
LanguageFrench
RoleUser
Login statusActivated
ConfirmedSaturday, August 30, 2025 at 11:56 AM + +
Last sign-inUnknown
Last IP adressUnknown
Total number of participations14
Uploaded media total size6,76 mégaoctets
+
+
+
+
+
+
+

Profiles

+
+ +
+
+
+

Actions

+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + +
" +`; + +exports[`UsersView > Unban user 1`] = ` +"
+ +
+

Details

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Emailtruc@mobilizon.test + + +
LanguageFrench
RoleUser + +
Login statusDisabled
ConfirmedSaturday, August 30, 2025 at 11:56 AM
Last sign-inUnknown
Last IP adressUnknown
Total number of participations14
Uploaded media total size6,76 mégaoctets
+
+
+
+
+
+
+

Profiles

+
+ +
+
+
+

Actions

+ + + +
+
+
- - - + + + + + + + + + + + + + + +
" `; diff --git a/tests/unit/specs/components/admin/adminUsersProfileView.spec.ts b/tests/unit/specs/components/admin/adminUsersProfileView.spec.ts index c2740956c..352e2d996 100644 --- a/tests/unit/specs/components/admin/adminUsersProfileView.spec.ts +++ b/tests/unit/specs/components/admin/adminUsersProfileView.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { DefaultApolloClient } from "@vue/apollo-composable"; -import { config, shallowMount } from "@vue/test-utils"; +import { config, mount } from "@vue/test-utils"; import buildCurrentUserResolver from "@/apollo/user"; import flushPromises from "flush-promises"; import { cache } from "@/apollo/memory"; @@ -10,16 +10,19 @@ import { RequestHandler, } from "mock-apollo-client"; import AdminUserProfile from "@/views/Admin/AdminUserProfile.vue"; -import { GET_USER } from "@/graphql/user"; +import { + DELETE_ACCOUNT_AS_MODERATOR, + GET_USER, + UNBAN_ACCOUNT_AS_MODERATOR, +} from "@/graphql/user"; import { ADMIN_UPDATE_USER, LANGUAGES_CODES } from "@/graphql/admin"; import { Oruga } from "@oruga-ui/oruga-next"; -import { nullMock } from "../../common"; +import { htmlRemoveId, nullMock } from "../../common"; import { createRouterMock, injectRouterMock, VueRouterMock, } from "vue-router-mock"; -import { SettingsRouteName } from "@/router/settings"; let mockClient: MockApolloClient | null; let requestHandlers: Record; @@ -78,6 +81,10 @@ const getUserMock = { }, }; +// A copy of the user that is banned +const getUserMockBan = structuredClone(getUserMock); +getUserMockBan.data.user.disabled = true; + const getModerateMock = { data: { user: { @@ -137,17 +144,27 @@ describe("UsersView", () => { }); requestHandlers = { languagecode: vi.fn().mockResolvedValue(languageCodeMock), - get_users: vi.fn().mockResolvedValue(currentUserMock), + get_user: vi.fn().mockResolvedValue(currentUserMock), update_user: vi.fn().mockResolvedValue(nullMock), + ban_user: vi.fn().mockResolvedValue(nullMock), + unban_user: vi.fn().mockResolvedValue(nullMock), }; mockClient.setRequestHandler(LANGUAGES_CODES, requestHandlers.languagecode); - mockClient.setRequestHandler(GET_USER, requestHandlers.get_users); + mockClient.setRequestHandler(GET_USER, requestHandlers.get_user); mockClient.setRequestHandler( ADMIN_UPDATE_USER, requestHandlers.update_user ); + mockClient.setRequestHandler( + DELETE_ACCOUNT_AS_MODERATOR, + requestHandlers.ban_user + ); + mockClient.setRequestHandler( + UNBAN_ACCOUNT_AS_MODERATOR, + requestHandlers.unban_user + ); - const wrapper = shallowMount(AdminUserProfile, { + const wrapper = mount(AdminUserProfile, { props: { id: "1234" }, stubs: ["router-link", "router-view"], global: { @@ -164,9 +181,9 @@ describe("UsersView", () => { await wrapper.vm.$nextTick(); await flushPromises(); expect(wrapper.exists()).toBe(true); - expect(wrapper.html()).toMatchSnapshot(); + expect(htmlRemoveId(wrapper.html())).toMatchSnapshot(); expect(requestHandlers.languagecode).toHaveBeenCalledTimes(1); - expect(requestHandlers.get_users).toHaveBeenCalledTimes(1); + expect(requestHandlers.get_user).toHaveBeenCalledTimes(1); expect(requestHandlers.update_user).toHaveBeenCalledTimes(0); }); @@ -177,27 +194,76 @@ describe("UsersView", () => { await wrapper.vm.$nextTick(); await flushPromises(); expect(wrapper.exists()).toBe(true); - expect(wrapper.html()).toMatchSnapshot(); + expect(htmlRemoveId(wrapper.html())).toMatchSnapshot(); expect(requestHandlers.languagecode).toHaveBeenCalledTimes(0); - expect(requestHandlers.get_users).toHaveBeenCalledTimes(1); + expect(requestHandlers.get_user).toHaveBeenCalledTimes(1); expect(requestHandlers.update_user).toHaveBeenCalledTimes(0); - const btn = wrapper.find('o-button-stub[variant="success"]'); + const btn = wrapper.find("#acceptAccount"); expect(btn.exists()).toBe(true); btn.trigger("click"); await flushPromises(); expect(requestHandlers.languagecode).toHaveBeenCalledTimes(0); - expect(requestHandlers.get_users).toHaveBeenCalledTimes(1); + // The user is refreshed after the update + expect(requestHandlers.get_user).toHaveBeenCalledTimes(2); expect(requestHandlers.update_user).toHaveBeenCalledTimes(1); expect(requestHandlers.update_user).toHaveBeenCalledWith({ id: "1234", notify: true, role: "USER", }); + }); + + it("Ban user", async () => { + const wrapper = generateWrapper(); + await wrapper.vm.$nextTick(); await flushPromises(); - await flushPromises(); - expect(wrapper.router.push).toHaveBeenCalledWith({ - name: SettingsRouteName.ADMIN_USER_PROFILE, - id: "1234", + expect(wrapper.exists()).toBe(true); + expect(htmlRemoveId(wrapper.html())).toMatchSnapshot(); + expect(requestHandlers.get_user).toHaveBeenCalledTimes(1); + expect(requestHandlers.ban_user).toHaveBeenCalledTimes(0); + + // Find ban button + const btn = wrapper.find("#deleteAccount"); + expect(btn.exists()).toBe(true); + btn.trigger("click"); + + // TODO The definitive button "Ban the account" is in a dialog pop-up outside of the component + // Sadly, we can't access this pop-up and click on the button to fully test + /* + // ban_user has been called + expect(requestHandlers.ban_user).toHaveBeenCalledTimes(1); + expect(requestHandlers.get_user).toHaveBeenCalledTimes(2); + expect(requestHandlers.ban_user).toHaveBeenCalledWith({ + userId: "1234", }); + */ + }); + + it("Unban user", async () => { + const wrapper = generateWrapper(getUserMockBan); + await wrapper.vm.$nextTick(); + await flushPromises(); + expect(wrapper.exists()).toBe(true); + expect(htmlRemoveId(wrapper.html())).toMatchSnapshot(); + expect(requestHandlers.get_user).toHaveBeenCalledTimes(1); + expect(requestHandlers.ban_user).toHaveBeenCalledTimes(0); + expect(requestHandlers.unban_user).toHaveBeenCalledTimes(0); + + // Find ban button + const btn = wrapper.find("#unbanAccount"); + expect(btn.exists()).toBe(true); + btn.trigger("click"); + + // TODO The definitive button "Unban the account" is in a dialog pop-up outside of the component + // Sadly, we can't access this pop-up and click on the button to fully test + /* + // unban_user has been called + expect(requestHandlers.get_user).toHaveBeenCalledTimes(2); + expect(requestHandlers.ban_user).toHaveBeenCalledTimes(0); + expect(requestHandlers.unban_user).toHaveBeenCalledTimes(1); + expect(requestHandlers.ban_user).toHaveBeenCalledWith({ + userId: "1234", + }); + */ }); });