build: switch from yarn to npm to manage js dependencies and move js contents to root

yarn v1 is being deprecated and starts to have some issues

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2023-11-14 17:24:42 +01:00
parent 32055122c3
commit 2e72f6faf4
595 changed files with 12078 additions and 7843 deletions

View File

@@ -0,0 +1,158 @@
import { config, mount, VueWrapper } from "@vue/test-utils";
import ParticipationSection from "@/components/Participation/ParticipationSection.vue";
import { createRouter, createWebHistory, Router } from "vue-router";
import { routes } from "@/router";
import { CommentModeration, EventJoinOptions } from "@/types/enums";
import { beforeEach, describe, expect, it } from "vitest";
import Oruga from "@oruga-ui/oruga-next";
import FloatingVue from "floating-vue";
config.global.plugins.push(Oruga);
config.global.plugins.push(FloatingVue);
let router: Router;
beforeEach(async () => {
router = createRouter({
history: createWebHistory(),
routes: routes,
});
// await router.isReady();
});
const eventData = {
id: "1",
uuid: "e37910ea-fd5a-4756-7634-00971f3f4107",
options: {
commentModeration: CommentModeration.ALLOW_ALL,
},
beginsOn: new Date("2089-12-04T09:21:25Z"),
endsOn: new Date("2089-12-04T11:21:25Z"),
};
describe("ParticipationSection", () => {
let wrapper: VueWrapper;
const generateWrapper = (customProps: Record<string, unknown> = {}) => {
wrapper = mount(ParticipationSection, {
stubs: {
ParticipationButton: true,
},
props: {
participation: null,
event: eventData,
anonymousParticipation: null,
currentActor: { id: "5" },
identities: [],
anonymousParticipationConfig: {
allowed: true,
},
...customProps,
},
global: {
plugins: [router],
},
});
};
it("renders the participation section with minimal data", async () => {
generateWrapper();
await wrapper.vm.$nextTick();
expect(wrapper.exists()).toBe(true);
expect(wrapper.find(".event-participation").exists()).toBeTruthy();
// TODO: Move to participation button test
// const participationButton = wrapper.find(
// ".event-participation .participation-button a.button.is-large.is-primary"
// );
// expect(participationButton.attributes("href")).toBe(
// `/events/${eventData.uuid}/participate/with-account`
// );
// expect(participationButton.text()).toBe("Participate");
});
it("renders the participation section with existing confimed anonymous participation", async () => {
generateWrapper({ anonymousParticipation: true });
expect(wrapper.find(".event-participation > small").text()).toContain(
"You are participating in this event anonymously"
);
const cancelAnonymousParticipationButton = wrapper.find(
".event-participation > button.o-btn--text"
);
expect(cancelAnonymousParticipationButton.text()).toBe(
"Cancel anonymous participation"
);
wrapper.find(".event-participation small span").trigger("click");
expect(
wrapper
.findComponent({ ref: "anonymous-participation-modal" })
.isVisible()
).toBeTruthy();
cancelAnonymousParticipationButton.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.emitted("cancel-anonymous-participation")).toBeTruthy();
});
it("renders the participation section with existing confimed anonymous participation but event moderation", async () => {
generateWrapper({
anonymousParticipation: true,
event: { ...eventData, joinOptions: EventJoinOptions.RESTRICTED },
});
expect(wrapper.find(".event-participation > small").text()).toContain(
"You are participating in this event anonymously"
);
const cancelAnonymousParticipationButton = wrapper.find(
".event-participation > button.o-btn--text"
);
expect(cancelAnonymousParticipationButton.text()).toBe(
"Cancel anonymous participation"
);
wrapper.find(".event-participation small span").trigger("click");
await wrapper.vm.$nextTick();
const modal = wrapper.findComponent({
ref: "anonymous-participation-modal",
});
expect(modal.isVisible()).toBeTruthy();
expect(modal.find(".o-notification--primary").text()).toBe(
"As the event organizer has chosen to manually validate participation requests, your participation will be really confirmed only once you receive an email stating it's being accepted."
);
cancelAnonymousParticipationButton.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.emitted("cancel-anonymous-participation")).toBeTruthy();
});
it("renders the participation section with existing unconfirmed anonymous participation", async () => {
generateWrapper({ anonymousParticipation: false });
expect(wrapper.find(".event-participation > small").text()).toContain(
"You are participating in this event anonymously but didn't confirm participation"
);
});
it("renders the participation section but the event is already passed", async () => {
generateWrapper({
event: {
...eventData,
beginsOn: "2020-12-02T10:52:56Z",
endsOn: "2020-12-03T10:52:56Z",
},
});
expect(wrapper.find(".event-participation").exists()).toBeFalsy();
expect(wrapper.find("button.o-btn--primary").text()).toBe(
"Event already passed"
);
});
});

View File

@@ -0,0 +1,273 @@
import { config, mount, VueWrapper } from "@vue/test-utils";
import ParticipationWithoutAccount from "@/components/Participation/ParticipationWithoutAccount.vue";
import { routes } from "@/router";
import {
CommentModeration,
EventJoinOptions,
ParticipantRole,
} from "@/types/enums";
import {
createMockClient,
MockApolloClient,
RequestHandler,
} from "mock-apollo-client";
import { ANONYMOUS_ACTOR_ID } from "@/graphql/config";
import { FETCH_EVENT_BASIC, JOIN_EVENT } from "@/graphql/event";
import { IEvent } from "@/types/event.model";
import { anonymousActorIdMock } from "../../mocks/config";
import {
fetchEventBasicMock,
joinEventMock,
joinEventResponseMock,
} from "../../mocks/event";
import { defaultResolvers } from "../../common";
import flushPromises from "flush-promises";
import { vi, describe, expect, it, beforeEach, afterEach } from "vitest";
import { DefaultApolloClient } from "@vue/apollo-composable";
import { Router, createRouter, createWebHistory } from "vue-router";
import Oruga from "@oruga-ui/oruga-next";
import { cache } from "@/apollo/memory";
config.global.plugins.push(Oruga);
let router: Router;
beforeEach(async () => {
router = createRouter({
history: createWebHistory(),
routes: routes,
});
// await router.isReady();
});
const eventData = {
id: "1",
uuid: "f37910ea-fd5a-4756-9679-00971f3f4106",
options: {
commentModeration: CommentModeration.ALLOW_ALL,
},
joinOptions: EventJoinOptions.FREE,
beginsOn: new Date("2089-12-04T09:21:25Z"),
endsOn: new Date("2089-12-04T11:21:25Z"),
participantStats: {
notApproved: 0,
notConfirmed: 0,
rejected: 0,
participant: 0,
creator: 1,
moderator: 0,
administrator: 0,
going: 1,
},
};
describe("ParticipationWithoutAccount", () => {
let wrapper: VueWrapper;
let mockClient: MockApolloClient | null;
let requestHandlers: Record<string, RequestHandler>;
afterEach(() => {
wrapper?.unmount();
cache.reset();
mockClient = null;
});
const generateWrapper = (
handlers: Record<string, unknown> = {},
customProps: Record<string, unknown> = {}
) => {
mockClient = createMockClient({
cache,
resolvers: defaultResolvers,
});
requestHandlers = {
anonymousActorIdQueryHandler: vi
.fn()
.mockResolvedValue(anonymousActorIdMock),
fetchEventQueryHandler: vi.fn().mockResolvedValue(fetchEventBasicMock),
joinEventMutationHandler: vi
.fn()
.mockResolvedValue(joinEventResponseMock),
...handlers,
};
mockClient.setRequestHandler(
ANONYMOUS_ACTOR_ID,
requestHandlers.anonymousActorIdQueryHandler
);
mockClient.setRequestHandler(
FETCH_EVENT_BASIC,
requestHandlers.fetchEventQueryHandler
);
mockClient.setRequestHandler(
JOIN_EVENT,
requestHandlers.joinEventMutationHandler
);
wrapper = mount(ParticipationWithoutAccount, {
props: {
uuid: eventData.uuid,
...customProps,
},
global: {
provide: {
[DefaultApolloClient]: mockClient,
},
plugins: [router],
},
});
};
it("renders the participation without account view with minimal data", async () => {
generateWrapper();
expect(wrapper.exists()).toBe(true);
expect(requestHandlers.anonymousActorIdQueryHandler).toHaveBeenCalled();
expect(requestHandlers.fetchEventQueryHandler).toHaveBeenCalledWith({
uuid: eventData.uuid,
});
await flushPromises();
expect(wrapper.find(".container").isVisible()).toBeTruthy();
expect(wrapper.find(".o-notification--info").text()).toBe(
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer."
);
wrapper.find('input[type="email"]').setValue("some@email.tld");
wrapper.find("textarea").setValue("a message long enough");
wrapper.find("form").trigger("submit");
await flushPromises();
expect(requestHandlers.joinEventMutationHandler).toHaveBeenCalledWith({
...joinEventMock,
});
const cachedData = mockClient?.cache.readQuery<{ event: IEvent }>({
query: FETCH_EVENT_BASIC,
variables: {
uuid: eventData.uuid,
},
});
if (cachedData) {
const { event } = cachedData;
expect(event.participantStats.going).toBe(
eventData.participantStats.going + 1
);
expect(event.participantStats.participant).toBe(
eventData.participantStats.participant + 1
);
}
await flushPromises();
expect(wrapper.find("form").exists()).toBeFalsy();
expect(wrapper.find("h1.title").text()).toBe(
"Request for participation confirmation sent"
);
// TextEncoder ~is~ was not in js-dom?
// expect(wrapper.find(".o-notification--error").text()).toBe(
// "Unable to save your participation in this browser."
// );
expect(wrapper.find("span.details").text()).toBe(
"Your participation will be validated once you click the confirmation link into the email."
);
expect(wrapper.html()).toMatchSnapshot();
});
it("renders the warning if the event participation is restricted", async () => {
generateWrapper({
fetchEventQueryHandler: vi.fn().mockResolvedValue({
data: {
event: {
...fetchEventBasicMock.data.event,
joinOptions: EventJoinOptions.RESTRICTED,
},
},
}),
joinEventMutationHandler: vi.fn().mockResolvedValue({
data: {
joinEvent: {
...joinEventResponseMock.data.joinEvent,
role: ParticipantRole.NOT_CONFIRMED,
},
},
}),
});
await flushPromises();
// expect(wrapper.vm.$data.event.joinOptions).toBe(
// EventJoinOptions.RESTRICTED
// );
expect(wrapper.findAll("section.container form > p")[1].text()).toContain(
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event."
);
expect(
wrapper.findAll("section.container form > p")[1].text()
).not.toContain(
"If you want, you may send a message to the event organizer here."
);
wrapper.find('input[type="email"]').setValue("some@email.tld");
wrapper.find("textarea").setValue("a message long enough");
wrapper.find("form").trigger("submit");
await flushPromises();
expect(requestHandlers.joinEventMutationHandler).toHaveBeenCalledWith({
...joinEventMock,
});
const cachedData = mockClient?.cache.readQuery<{ event: IEvent }>({
query: FETCH_EVENT_BASIC,
variables: {
uuid: eventData.uuid,
},
});
if (cachedData) {
const { event } = cachedData;
expect(event.participantStats.notConfirmed).toBe(
eventData.participantStats.notConfirmed + 1
);
}
await flushPromises();
expect(wrapper.find("form").exists()).toBeFalsy();
expect(wrapper.find("h1.title").text()).toBe(
"Request for participation confirmation sent"
);
expect(wrapper.find("span.details").text()).toBe(
"Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation."
);
expect(wrapper.html()).toMatchSnapshot();
});
it("handles being already a participant", async () => {
generateWrapper({
joinEventMutationHandler: vi
.fn()
.mockRejectedValue(
new Error("You are already a participant of this event")
),
});
await flushPromises();
wrapper.find('input[type="email"]').setValue("some@email.tld");
wrapper.find("textarea").setValue("a message long enough");
wrapper.find("form").trigger("submit");
await flushPromises();
expect(requestHandlers.joinEventMutationHandler).toHaveBeenCalledWith({
...joinEventMock,
});
await flushPromises();
expect(wrapper.find("form").exists()).toBeTruthy();
expect(wrapper.find(".o-notification--danger").text()).toContain(
"You are already a participant of this event"
);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,83 @@
// Vitest Snapshot v1
exports[`ParticipationWithoutAccount > handles being already a participant 1`] = `
"<section class=\\"container mx-auto\\">
<div class=\\"\\">
<form>
<p>This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.</p>
<transition-stub>
<article class=\\"o-notification o-notification--info\\">
<!--v-if-->
<!--v-if-->
<div class=\\"o-notification__wrapper\\">
<!--v-if-->
<div class=\\"o-notification__content\\">Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.</div>
</div>
</article>
</transition-stub>
<transition-stub>
<article class=\\"o-notification o-notification--danger\\">
<!--v-if-->
<!--v-if-->
<div class=\\"o-notification__wrapper\\">
<!--v-if-->
<div class=\\"o-notification__content\\">You are already a participant of this event</div>
</div>
</article>
</transition-stub>
<div class=\\"o-field o-field--filled\\"><label for=\\"anonymousParticipationEmail\\" class=\\"o-field__label\\">Email address</label>
<div class=\\"o-ctrl-input\\"><input id=\\"anonymousParticipationEmail\\" placeholder=\\"Your email\\" required=\\"\\" class=\\"o-input\\" type=\\"email\\" autocomplete=\\"off\\">
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
<!--v-if-->
</div>
<p>If you want, you may send a message to the event organizer here.</p>
<div class=\\"o-field o-field--filled\\"><label for=\\"anonymousParticipationMessage\\" class=\\"o-field__label\\">Message</label>
<div class=\\"o-ctrl-input\\"><textarea id=\\"anonymousParticipationMessage\\" minlength=\\"10\\" class=\\"o-input o-input__textarea\\"></textarea>
<!--v-if-->
<!--v-if-->
<!--v-if-->
</div>
<!--v-if-->
</div>
<div class=\\"o-field\\">
<!--v-if--><label class=\\"o-chk o-chk--checked\\"><input type=\\"checkbox\\" class=\\"o-chk__check o-chk__check--checked\\" true-value=\\"true\\" false-value=\\"false\\" value=\\"false\\"><span class=\\"o-chk__label\\"><b>Remember my participation in this browser</b><p>Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.</p></span></label>
<!--v-if-->
</div>
<div class=\\"flex gap-2 my-2\\"><button type=\\"submit\\" class=\\"o-btn o-btn--primary o-btn--disabled\\" disabled=\\"\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Send email</span>
<!--v-if--></span>
</button><button type=\\"button\\" class=\\"o-btn o-btn--text\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Back to previous page</span>
<!--v-if--></span>
</button></div>
</form>
</div>
</section>"
`;
exports[`ParticipationWithoutAccount > renders the participation without account view with minimal data 1`] = `
"<section class=\\"container mx-auto\\">
<div class=\\"\\">
<div>
<h1 class=\\"title\\">Request for participation confirmation sent</h1>
<p class=\\"prose dark:prose-invert\\"><span>Check your inbox (and your junk mail folder).</span><span class=\\"details\\">Your participation will be validated once you click the confirmation link into the email.</span></p>
<!--v-if-->
<p class=\\"prose dark:prose-invert\\">You may now close this window, or <a href=\\"/events/f37910ea-fd5a-4756-9679-00971f3f4106\\" class=\\"\\">return to the event's page</a>.</p>
</div>
</div>
</section>"
`;
exports[`ParticipationWithoutAccount > renders the warning if the event participation is restricted 1`] = `
"<section class=\\"container mx-auto\\">
<div class=\\"\\">
<div>
<h1 class=\\"title\\">Request for participation confirmation sent</h1>
<p class=\\"prose dark:prose-invert\\"><span>Check your inbox (and your junk mail folder).</span><span class=\\"details\\">Your participation will be validated once you click the confirmation link into the email, and after the organizer manually validates your participation.</span></p>
<!--v-if-->
<p class=\\"prose dark:prose-invert\\">You may now close this window, or <a href=\\"/events/f37910ea-fd5a-4756-9679-00971f3f4106\\" class=\\"\\">return to the event's page</a>.</p>
</div>
</div>
</section>"
`;