[Adminitration] Allow registrations: 3 modes = "close", "allowed", "moderate" - #877

This commit is contained in:
Laurent GAY
2025-09-03 21:07:33 +02:00
parent 8f8aa0ffbe
commit 3e4899c8e4
28 changed files with 526 additions and 19 deletions

View File

@@ -0,0 +1,129 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`SettingsView > Show and save settings 1`] = `
"<div data-v-a8bb2abf="">
<breadcrumbs-nav data-v-a8bb2abf="" links="[object Object],[object Object]"></breadcrumbs-nav>
<section data-v-a8bb2abf="">
<form data-v-a8bb2abf="">
<o-field data-v-a8bb2abf="" label="Instance Name" label-for="instance-name">
<o-input data-v-a8bb2abf="" modelvalue="Mobilizon.test" id="instance-name" expanded=""></o-input>
</o-field>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-description">Instance Short Description</label><small data-v-a8bb2abf="">Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.</small>
<o-input data-v-a8bb2abf="" type="textarea" modelvalue="Welcome to Mobilizon" rows="2" id="instance-description"></o-input>
</div>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-slogan">Instance Slogan</label><small data-v-a8bb2abf="">A short tagline for your instance homepage. Defaults to "Gather ⋅ Organize ⋅ Mobilize"</small>
<o-input data-v-a8bb2abf="" modelvalue="Long life to Mobilizon" placeholder="Gather ⋅ Organize ⋅ Mobilize" id="instance-slogan"></o-input>
</div>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-contact">Contact</label><small data-v-a8bb2abf="">Can be an email or a link, or just plain text.</small>
<o-input data-v-a8bb2abf="" modelvalue="info@mobilizon.test" id="instance-contact"></o-input>
</div><label data-v-a8bb2abf="" class="field flex flex-col">
<p data-v-a8bb2abf="">Logo</p><small data-v-a8bb2abf="">Logo of the instance. Defaults to the upstream Mobilizon logo.</small>
<picture-upload-stub data-v-a8bb2abf="" textfallback="Logo" accept="image/gif,image/png,image/jpeg,image/webp" maxsize="10485760"></picture-upload-stub>
</label><label data-v-a8bb2abf="" class="field flex flex-col">
<p data-v-a8bb2abf="">Favicon</p><small data-v-a8bb2abf="">Browser tab icon and PWA icon of the instance. Defaults to the upstream Mobilizon icon.</small>
<picture-upload-stub data-v-a8bb2abf="" textfallback="Favicon" accept="image/gif,image/png,image/jpeg,image/webp" maxsize="10485760"></picture-upload-stub>
</label><label data-v-a8bb2abf="" class="field flex flex-col">
<p data-v-a8bb2abf="">Default Picture</p><small data-v-a8bb2abf="">Default picture when an event or group doesn't have one.</small>
<picture-upload-stub data-v-a8bb2abf="" textfallback="Default Picture" accept="image/gif,image/png,image/jpeg,image/webp" maxsize="10485760"></picture-upload-stub>
</label>
<!-- piece of code to manage instance colors
<div class="field flex flex-col">
<label class="" for="primary-color">{{ t("Primary Color") }}</label>
<o-input
type="color"
v-model="settingsToWrite.primaryColor"
id="primary-color"
/>
</div>
<div class="field flex flex-col">
<label class="" for="secondary-color">{{
t("Secondary Color")
}}</label>
<o-input
type="color"
v-model="settingsToWrite.secondaryColor"
id="secondary-color"
/>
</div>
-->
<o-field data-v-a8bb2abf="" label="Allow registrations">
<fieldset data-v-a8bb2abf="">
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="OPEN" name="registrationsModeType" native-value="CLOSE">Registration is closed.</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="OPEN" name="registrationsModeType" native-value="OPEN">Registration is allowed, anyone can register.</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="OPEN" name="registrationsModeType" native-value="MODERATED">Registration is moderated, new user must be validated.</o-radio>
</o-field>
</fieldset>
</o-field>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-languages">Instance languages</label><small data-v-a8bb2abf="">Main languages you/your moderators speak</small>
<o-taginput data-v-a8bb2abf="" modelvalue="" data="" allow-autocomplete="" open-on-focus="true" field="name" icon="label" placeholder="Select languages" id="instance-languages"></o-taginput>
</div>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-long-description">Instance Long Description</label><small data-v-a8bb2abf="">A place to explain who you are and the things that set your instance apart. You can use HTML tags.</small>
<o-input data-v-a8bb2abf="" type="textarea" modelvalue="Mobilizon instance." rows="4" id="instance-long-description"></o-input>
</div>
<div data-v-a8bb2abf="" class="field flex flex-col"><label data-v-a8bb2abf="" class="" for="instance-rules">Instance Rules</label><small data-v-a8bb2abf="">A place for your code of conduct, rules or guidelines. You can use HTML tags.</small>
<o-input data-v-a8bb2abf="" type="textarea" id="instance-rules"></o-input>
</div>
<o-field data-v-a8bb2abf="" label="Instance Terms Source">
<div data-v-a8bb2abf="" class="">
<div data-v-a8bb2abf="" class="">
<fieldset data-v-a8bb2abf="">
<legend data-v-a8bb2abf="">Choose the source of the instance's Terms</legend>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instanceTermsType" native-value="DEFAULT">Default Mobilizon terms</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instanceTermsType" native-value="URL">Custom URL</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instanceTermsType" native-value="CUSTOM">Custom text</o-radio>
</o-field>
</fieldset>
</div>
<div data-v-a8bb2abf="" class="">
<o-notification data-v-a8bb2abf="" class="bg-slate-700"><b data-v-a8bb2abf="">Default</b>
<i18n-t-stub data-v-a8bb2abf="" tag="p" keypath="The {default_terms} will be used. They will be translated in the user's language." scope="parent" class="prose dark:prose-invert"></i18n-t-stub><b data-v-a8bb2abf="">NOTE! The default terms have not been checked over by a lawyer and thus are unlikely to provide full legal protection for all situations for an instance admin using them. They are also not specific to all countries and jurisdictions. If you are unsure, please check with a lawyer.</b>
</o-notification>
<!--v-if-->
<!--v-if-->
</div>
</div>
</o-field>
<!--v-if-->
<!--v-if-->
<o-field data-v-a8bb2abf="" label="Instance Privacy Policy Source">
<div data-v-a8bb2abf="" class="">
<div data-v-a8bb2abf="" class="">
<fieldset data-v-a8bb2abf="">
<legend data-v-a8bb2abf="">Choose the source of the instance's Privacy Policy</legend>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instancePrivacyType" native-value="DEFAULT">Default Mobilizon privacy policy</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instancePrivacyType" native-value="URL">Custom URL</o-radio>
</o-field>
<o-field data-v-a8bb2abf="">
<o-radio data-v-a8bb2abf="" modelvalue="DEFAULT" name="instancePrivacyType" native-value="CUSTOM">Custom text</o-radio>
</o-field>
</fieldset>
</div>
<div data-v-a8bb2abf="" class="">
<div data-v-a8bb2abf="" class="notification"><b data-v-a8bb2abf="">Default</b>
<i18n-t-stub data-v-a8bb2abf="" tag="p" keypath="The {default_privacy_policy} will be used. They will be translated in the user's language." scope="parent" class="prose dark:prose-invert"></i18n-t-stub>
</div>
<!--v-if-->
<!--v-if-->
</div>
</div>
</o-field>
<!--v-if-->
<!--v-if-->
<o-button data-v-a8bb2abf="" native-type="submit" variant="primary">Save</o-button>
</form>
</section>
</div>"
`;

View File

@@ -0,0 +1,176 @@
import { describe, it, expect, vi } from "vitest";
import { DefaultApolloClient } from "@vue/apollo-composable";
import { shallowMount } from "@vue/test-utils";
import buildCurrentUserResolver from "@/apollo/user";
import flushPromises from "flush-promises";
import { cache } from "@/apollo/memory";
import {
createMockClient,
MockApolloClient,
RequestHandler,
} from "mock-apollo-client";
import SettingsView from "@/views/Admin/SettingsView.vue";
import { nullMock } from "../../common";
import { CONFIG } from "@/graphql/config";
import {
ADMIN_SETTINGS,
SAVE_ADMIN_SETTINGS,
LANGUAGES,
} from "@/graphql/admin";
let mockClient: MockApolloClient | null;
let requestHandlers: Record<string, RequestHandler>;
const languageMock = {
data: {
languages: [
{
__typename: "Language",
code: "es",
name: "Spanish",
},
{
__typename: "Language",
code: "fr-FR",
name: "Français",
},
{
__typename: "Language",
code: "en-EN",
name: "English",
},
],
},
};
const settingMock = {
data: {
adminSettings: {
__typename: "AdminSettings",
contact: "info@mobilizon.test",
defaultPicture: null,
instanceDescription: "Welcome to Mobilizon",
instanceFavicon: null,
instanceLanguages: ["fr"],
instanceLogo: null,
instanceLongDescription: "Mobilizon instance.",
instanceName: "Mobilizon.test",
instancePrivacyPolicy: '<p class="message-body">Privacy policy</p>',
instancePrivacyPolicyType: "DEFAULT",
instancePrivacyPolicyUrl: null,
instanceRules: null,
instanceSlogan: "Long life to Mobilizon",
instanceTerms: '<p class="message-body">Rulls and terms</p>',
instanceTermsType: "DEFAULT",
instanceTermsUrl: null,
primaryColor: null,
registrationsOpen: true,
registrationsModeration: false,
secondaryColor: null,
},
},
};
const generateWrapper = () => {
mockClient = createMockClient({
cache,
resolvers: buildCurrentUserResolver(cache),
});
requestHandlers = {
config: vi.fn().mockResolvedValue(nullMock),
settings: vi.fn().mockResolvedValue(settingMock),
save_settings: vi.fn().mockResolvedValue(nullMock),
languages: vi.fn().mockResolvedValue(languageMock),
};
mockClient.setRequestHandler(CONFIG, requestHandlers.config);
mockClient.setRequestHandler(ADMIN_SETTINGS, requestHandlers.settings);
mockClient.setRequestHandler(
SAVE_ADMIN_SETTINGS,
requestHandlers.save_settings
);
mockClient.setRequestHandler(LANGUAGES, requestHandlers.languages);
return shallowMount(SettingsView, {
props: {},
global: {
provide: {
[DefaultApolloClient]: mockClient,
},
},
});
};
describe("SettingsView", () => {
it("Show and save settings", async () => {
const wrapper = generateWrapper();
await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.exists()).toBe(true);
expect(requestHandlers.config).toHaveBeenCalled();
expect(requestHandlers.languages).toHaveBeenCalled();
expect(requestHandlers.settings).toHaveBeenCalled();
expect(requestHandlers.save_settings).toHaveBeenCalledTimes(0);
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.find("#instance-description").attributes("modelvalue")).toBe(
"Welcome to Mobilizon"
);
expect(wrapper.find("#instance-contact").attributes("modelvalue")).toBe(
"info@mobilizon.test"
);
const radiolist1 = wrapper
.findAll('o-radio[name="registrationsModeType"]')
.filter(
(radio) =>
radio.attributes("modelvalue") == radio.attributes("native-value")
);
expect(radiolist1.length).toBe(1);
expect(radiolist1[0].text()).toBe(
"Registration is allowed, anyone can register."
);
wrapper.vm.settingsToWrite.instanceDescription = "Best Mobilizon";
wrapper.vm.settingsToWrite.contact = "some@email.tld";
wrapper.vm.settingsToWrite.registrationsOpen = false;
await wrapper.vm.$nextTick();
expect(wrapper.find("#instance-description").attributes("modelvalue")).toBe(
"Best Mobilizon"
);
expect(wrapper.find("#instance-contact").attributes("modelvalue")).toBe(
"some@email.tld"
);
const radiolist2 = wrapper
.findAll('o-radio[name="registrationsModeType"]')
.filter(
(radio) =>
radio.attributes("modelvalue") == radio.attributes("native-value")
);
expect(radiolist2.length).toBe(1);
expect(radiolist2[0].text()).toBe("Registration is closed.");
wrapper.find("form").trigger("submit");
expect(requestHandlers.save_settings).toHaveBeenCalledTimes(1);
expect(requestHandlers.save_settings).toBeCalledWith({
...settingMock.data.adminSettings,
contact: "some@email.tld",
instanceDescription: "Best Mobilizon",
registrationsOpen: false,
defaultPicture: {},
instanceFavicon: {},
instanceLogo: {},
});
wrapper.vm.settingsToWrite.registrationsOpen = true;
wrapper.vm.settingsToWrite.registrationsModeration = true;
await wrapper.vm.$nextTick();
const radiolist3 = wrapper
.findAll('o-radio[name="registrationsModeType"]')
.filter(
(radio) =>
radio.attributes("modelvalue") == radio.attributes("native-value")
);
expect(radiolist3.length).toBe(1);
expect(radiolist3[0].text()).toBe(
"Registration is moderated, new user must be validated."
);
});
});