diff --git a/src/graphql/user.ts b/src/graphql/user.ts
index 22e476b1b..9de34aa8e 100644
--- a/src/graphql/user.ts
+++ b/src/graphql/user.ts
@@ -2,8 +2,18 @@ import gql from "graphql-tag";
import { ACTOR_FRAGMENT } from "./actor";
export const CREATE_USER = gql`
- mutation CreateUser($email: String!, $password: String!, $locale: String) {
- createUser(email: $email, password: $password, locale: $locale) {
+ mutation CreateUser(
+ $email: String!
+ $password: String!
+ $moderation: String!
+ $locale: String
+ ) {
+ createUser(
+ email: $email
+ password: $password
+ moderation: $moderation
+ locale: $locale
+ ) {
email
confirmationSentAt
}
diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json
index f6ae1ba5f..3ab645f21 100644
--- a/src/i18n/en_US.json
+++ b/src/i18n/en_US.json
@@ -976,6 +976,7 @@
"Registration is closed.": "Registration is closed.",
"Registration is currently closed.": "Registration is currently closed.",
"Registration is moderated, new user must be validated.": "Registration is moderated, new user must be validated.",
+ "Registration is subject to moderation, indicate your motivation.": "Registration is subject to moderation, indicate your motivation.",
"Registrations": "Registrations",
"Registrations are restricted by allowlisting.": "Registrations are restricted by allowlisting.",
"Reject": "Reject",
diff --git a/src/views/User/RegisterView.vue b/src/views/User/RegisterView.vue
index f8136f7e8..42d9ddfd6 100644
--- a/src/views/User/RegisterView.vue
+++ b/src/views/User/RegisterView.vue
@@ -107,7 +107,7 @@
@@ -123,6 +123,28 @@
/>
+
+
+
+
{{ t("Login") }}({
email: typeof route.query.email === "string" ? route.query.email : "",
password:
typeof route.query.password === "string" ? route.query.password : "",
+ moderation:
+ typeof route.query.moderation === "string" ? route.query.moderation : "",
locale: "en",
});
const emailErrors = ref
([]);
const passwordErrors = ref([]);
+const moderationError = ref([]);
const sendingForm = ref(false);
@@ -298,6 +329,12 @@ onError((error) => {
message: message[0] as string,
});
break;
+ case "moderation":
+ moderationError.value.push({
+ type: "danger" as errorType,
+ message: message[0] as string,
+ });
+ break;
default:
}
}
@@ -311,6 +348,7 @@ const submit = async (): Promise => {
try {
emailErrors.value = [];
passwordErrors.value = [];
+ moderationError.value = [];
mutate(credentials);
} catch (error: any) {
@@ -343,11 +381,14 @@ const maxErrorType = (errors: errorMessage[]): errorType | undefined => {
const errorEmailType = computed((): errorType | undefined => {
return maxErrorType(emailErrors.value);
});
-
const errorPasswordType = computed((): errorType | undefined => {
return maxErrorType(passwordErrors.value);
});
+const errorModerationType = computed((): errorType | undefined => {
+ return maxErrorType(moderationError.value);
+});
+
const errorEmailMessage = computed((): string => {
return emailErrors.value.map(({ message }) => message).join(" ");
});
@@ -355,4 +396,8 @@ const errorEmailMessage = computed((): string => {
const errorPasswordMessage = computed((): string => {
return passwordErrors.value?.map(({ message }) => message).join(" ");
});
+
+const errorModerationMessage = computed((): string => {
+ return moderationError.value?.map(({ message }) => message).join(" ");
+});
diff --git a/tests/unit/specs/components/User/RegisterView.spec.ts b/tests/unit/specs/components/User/RegisterView.spec.ts
new file mode 100644
index 000000000..6eb618369
--- /dev/null
+++ b/tests/unit/specs/components/User/RegisterView.spec.ts
@@ -0,0 +1,185 @@
+import { config, mount } from "@vue/test-utils";
+import RegisterView from "@/views/User/RegisterView.vue";
+import { createMockClient, RequestHandler } from "mock-apollo-client";
+import flushPromises from "flush-promises";
+import { configMock } from "../../mocks/config";
+import { CONFIG } from "@/graphql/config";
+import { CREATE_USER } from "@/graphql/user";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { DefaultApolloClient } from "@vue/apollo-composable";
+import Oruga from "@oruga-ui/oruga-next";
+import {
+ VueRouterMock,
+ createRouterMock,
+ injectRouterMock,
+} from "vue-router-mock";
+import { nullMock } from "../../common";
+
+config.global.plugins.push(Oruga);
+config.plugins.VueWrapper.install(VueRouterMock);
+
+let requestHandlers: Record;
+
+const generateWrapper = (
+ customRegModeration: boolean = false,
+ customRequestHandlers: Record = {}
+) => {
+ const mockClient = createMockClient();
+
+ const config_value = {
+ ...configMock,
+ };
+ if (customRegModeration) {
+ config_value.data.config.registrationsModeration = true;
+ }
+
+ requestHandlers = {
+ configQueryHandler: vi.fn().mockResolvedValue(config_value),
+ createUserHandler: vi.fn().mockResolvedValue(nullMock),
+ ...customRequestHandlers,
+ };
+
+ mockClient.setRequestHandler(CONFIG, requestHandlers.configQueryHandler);
+ mockClient.setRequestHandler(CREATE_USER, requestHandlers.createUserHandler);
+
+ return mount(RegisterView, {
+ global: {
+ stubs: ["router-link", "router-view"],
+ provide: {
+ [DefaultApolloClient]: mockClient,
+ },
+ },
+ });
+};
+
+describe("Register page", () => {
+ const router = createRouterMock({
+ spy: {
+ create: (fn) => vi.fn(fn),
+ reset: (spy) => spy.mockReset(),
+ },
+ });
+ beforeEach(() => {
+ // inject it globally to ensure `useRoute()`, `$route`, etc work
+ // properly and give you access to test specific functions
+ injectRouterMock(router);
+ });
+
+ it("register without moderation", async () => {
+ const wrapper = generateWrapper();
+ expect(wrapper.router).toBe(router);
+ await flushPromises();
+ expect(wrapper.html()).toMatchSnapshot();
+ expect(wrapper.find("form").exists()).toBe(true);
+ wrapper.find('form input[type="email"]').setValue("some@email.tld");
+ wrapper.find('form input[type="password"]').setValue("somepassword");
+ wrapper.find("form").trigger("submit");
+ await wrapper.vm.$nextTick();
+ expect(requestHandlers.createUserHandler).toHaveBeenCalledWith({
+ email: "some@email.tld",
+ locale: "en_US",
+ moderation: "",
+ password: "somepassword",
+ });
+ await flushPromises();
+ expect(wrapper.find("form").exists()).toBe(false);
+ });
+
+ it("shows error without moderation email", async () => {
+ const wrapper = generateWrapper(false, {
+ createUserHandler: vi.fn().mockResolvedValue({
+ errors: [{ field: "email", message: ["Bad email."] }],
+ }),
+ });
+ expect(wrapper.find("form").exists()).toBe(true);
+ wrapper
+ .findAll('input[type="password"')
+ .forEach((inputField) => inputField.setValue("my password"));
+ wrapper.find("form").trigger("submit");
+ await wrapper.vm.$nextTick();
+ expect(requestHandlers.createUserHandler).toBeCalledTimes(1);
+ expect(requestHandlers.createUserHandler).toHaveBeenCalledWith({
+ email: "",
+ locale: "en_US",
+ moderation: "",
+ password: "my password",
+ });
+ await flushPromises();
+ expect(wrapper.find("form").exists()).toBe(true);
+ expect(wrapper.find(".o-field__message-danger").text()).toContain(
+ "Bad email."
+ );
+ });
+
+ it("shows error without moderation password", async () => {
+ const wrapper = generateWrapper(false, {
+ createUserHandler: vi.fn().mockResolvedValue({
+ errors: [{ field: "password", message: ["Bad password."] }],
+ }),
+ });
+ expect(wrapper.find("form").exists()).toBe(true);
+ wrapper
+ .findAll('input[type="password"')
+ .forEach((inputField) => inputField.setValue("my password"));
+ wrapper.find("form").trigger("submit");
+ await wrapper.vm.$nextTick();
+ expect(requestHandlers.createUserHandler).toBeCalledTimes(1);
+ expect(requestHandlers.createUserHandler).toHaveBeenCalledWith({
+ email: "",
+ locale: "en_US",
+ moderation: "",
+ password: "my password",
+ });
+ await flushPromises();
+ expect(wrapper.find("form").exists()).toBe(true);
+ expect(wrapper.find(".o-field__message-danger").text()).toContain(
+ "Bad password."
+ );
+ });
+
+ it("register with moderation", async () => {
+ const wrapper = generateWrapper(true);
+ expect(wrapper.router).toBe(router);
+ await flushPromises();
+ expect(wrapper.html()).toMatchSnapshot();
+ expect(wrapper.find("form").exists()).toBe(true);
+ wrapper.find('form input[type="email"]').setValue("some@email.tld");
+ wrapper.find('form input[type="password"]').setValue("somepassword");
+ wrapper.find("form").trigger("submit");
+ await wrapper.vm.$nextTick();
+ expect(requestHandlers.createUserHandler).toHaveBeenCalledWith({
+ email: "some@email.tld",
+ locale: "en_US",
+ moderation: "",
+ password: "somepassword",
+ });
+ await flushPromises();
+ expect(wrapper.find("form").exists()).toBe(false);
+ });
+
+ it("shows error with moderation", async () => {
+ const wrapper = generateWrapper(true, {
+ createUserHandler: vi.fn().mockResolvedValue({
+ errors: [{ field: "moderation", message: ["Bad moderation."] }],
+ }),
+ });
+ expect(wrapper.find("form").exists()).toBe(true);
+ wrapper
+ .findAll('input[type="password"')
+ .forEach((inputField) => inputField.setValue("my password"));
+ wrapper.find("form").trigger("submit");
+ await wrapper.vm.$nextTick();
+ expect(requestHandlers.createUserHandler).toBeCalledTimes(1);
+ expect(requestHandlers.createUserHandler).toHaveBeenCalledWith({
+ email: "",
+ locale: "en_US",
+ moderation: "",
+ password: "my password",
+ });
+ await flushPromises();
+ expect(wrapper.find("form").exists()).toBe(true);
+ expect(wrapper.find(".o-field__message-danger").text()).toContain(
+ "Bad moderation."
+ );
+ });
+});
diff --git a/tests/unit/specs/components/User/__snapshots__/RegisterView.spec.ts.snap b/tests/unit/specs/components/User/__snapshots__/RegisterView.spec.ts.snap
new file mode 100644
index 000000000..067399f39
--- /dev/null
+++ b/tests/unit/specs/components/User/__snapshots__/RegisterView.spec.ts.snap
@@ -0,0 +1,158 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Register page > register with moderation 1`] = `
+"
+
+ Register an account on Mobilizon!
+ Mobilizon is an instance of the Mobilizon software.
+
+
+
+
+
Why create an account?
+
+
+ - To create and manage your events
+ - To create and manage multiples identities from a same account
+ - To register for an event by choosing one of your identities
+ - To create or join an group and start organizing with other people
+ - To follow groups and be informed of their latest events
+
+
+
+
+
+
+
About Mobilizon
+
Mobilizon.fr est l'instance Mobilizon de Framasoft.
+
Please read the published by Mobilizon's administrators.
+
+
+
+
+
"
+`;
+
+exports[`Register page > register without moderation 1`] = `
+"
+
+ Register an account on Mobilizon!
+ Mobilizon is an instance of the Mobilizon software.
+
+
+
+
+
Why create an account?
+
+
+ - To create and manage your events
+ - To create and manage multiples identities from a same account
+ - To register for an event by choosing one of your identities
+ - To create or join an group and start organizing with other people
+ - To follow groups and be informed of their latest events
+
+
+
+
+
+
+
About Mobilizon
+
Mobilizon.fr est l'instance Mobilizon de Framasoft.
+
Please read the published by Mobilizon's administrators.
+
+
+
+
+
+
+
+
"
+`;