test: fix front-end tests

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2023-11-15 12:29:39 +01:00
parent bfbc299f37
commit 105d3b5814
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
24 changed files with 185 additions and 349 deletions

View File

@ -76,7 +76,7 @@ lint-front:
- npm ci - npm ci
script: script:
- npm run lint || export EXITVALUE=1 - npm run lint || export EXITVALUE=1
- npm run prettier -c . || export EXITVALUE=1 - npx prettier -c . || export EXITVALUE=1
- exit $EXITVALUE - exit $EXITVALUE
build-frontend: build-frontend:

View File

@ -15,7 +15,7 @@ config :mobilizon, Mobilizon.Web.Endpoint,
check_origin: false, check_origin: false,
watchers: [ watchers: [
node: [ node: [
"node_modules/.bin/vite", "node_modules/.bin/vite"
] ]
] ]

13
package-lock.json generated
View File

@ -119,7 +119,8 @@
"vite": "^4.0.4", "vite": "^4.0.4",
"vite-plugin-pwa": "^0.16.4", "vite-plugin-pwa": "^0.16.4",
"vitest": "^0.34.1", "vitest": "^0.34.1",
"vue-i18n-extract": "^2.0.4" "vue-i18n-extract": "^2.0.4",
"vue-router-mock": "^1.0.0"
} }
}, },
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
@ -11332,6 +11333,16 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/vue-router-mock": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vue-router-mock/-/vue-router-mock-1.0.0.tgz",
"integrity": "sha512-j65lh+jhEuEM7sWAz/eenNsEV0VrRXFreNxtIGgLApZkM2h7orImvHSLJ3LhbUa2sJnTtjuy8otSrvoTsdGVYw==",
"dev": true,
"peerDependencies": {
"vue": "^3.2.23",
"vue-router": "^4.0.12"
}
},
"node_modules/vue-scrollto": { "node_modules/vue-scrollto": {
"version": "2.20.0", "version": "2.20.0",
"license": "MIT", "license": "MIT",

View File

@ -136,6 +136,7 @@
"vite": "^4.0.4", "vite": "^4.0.4",
"vite-plugin-pwa": "^0.16.4", "vite-plugin-pwa": "^0.16.4",
"vitest": "^0.34.1", "vitest": "^0.34.1",
"vue-i18n-extract": "^2.0.4" "vue-i18n-extract": "^2.0.4",
"vue-router-mock": "^1.0.0"
} }
} }

View File

@ -61,7 +61,11 @@
<transition-group <transition-group
key="list" key="list"
name="comment-list" name="comment-list"
v-if="filteredOrderedComments.length && currentActor" v-if="
filteredOrderedComments &&
filteredOrderedComments.length &&
currentActor
"
class="comment-list" class="comment-list"
tag="ul" tag="ul"
> >
@ -110,29 +114,30 @@ import { AbsintheGraphQLError } from "@/types/errors.model";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Notifier } from "@/plugins/notifier"; import { Notifier } from "@/plugins/notifier";
const props = defineProps<{
event: IEvent;
newComment?: IComment;
}>();
const event = computed(() => props.event);
const newCommentProps = computed(() => props.newComment);
const { currentActor } = useCurrentActorClient(); const { currentActor } = useCurrentActorClient();
const { result: commentsResult, loading: commentsLoading } = useQuery<{ const { result: commentsResult, loading: commentsLoading } = useQuery<{
event: Pick<IEvent, "id" | "uuid" | "comments">; event: Pick<IEvent, "id" | "uuid" | "comments">;
}>( }>(
COMMENTS_THREADS_WITH_REPLIES, COMMENTS_THREADS_WITH_REPLIES,
() => ({ eventUUID: props.event?.uuid }), () => ({ eventUUID: event.value?.uuid }),
() => ({ enabled: props.event?.uuid !== undefined }) () => ({ enabled: event.value?.uuid !== undefined })
); );
const comments = computed(() => commentsResult.value?.event.comments ?? []); const comments = computed(() => commentsResult.value?.event.comments ?? []);
const props = defineProps<{
event: IEvent;
newComment?: IComment;
}>();
const Editor = defineAsyncComponent( const Editor = defineAsyncComponent(
() => import("@/components/TextEditor.vue") () => import("@/components/TextEditor.vue")
); );
const newCommentProps = computed(() => props.newComment);
const newCommentValue = ref<IComment>(new CommentModel(newCommentProps.value)); const newCommentValue = ref<IComment>(new CommentModel(newCommentProps.value));
const emptyCommentError = ref(false); const emptyCommentError = ref(false);
@ -170,18 +175,18 @@ const {
) => { ) => {
if (data == null) return; if (data == null) return;
// comments are attached to the event, so we can pass it to replies later // comments are attached to the event, so we can pass it to replies later
const newCommentLocal = { ...data.createComment, event: props.event }; const newCommentLocal = { ...data.createComment, event: event.value };
// we load all existing threads // we load all existing threads
const commentThreadsData = store.readQuery<{ event: IEvent }>({ const commentThreadsData = store.readQuery<{ event: IEvent }>({
query: COMMENTS_THREADS_WITH_REPLIES, query: COMMENTS_THREADS_WITH_REPLIES,
variables: { variables: {
eventUUID: props.event?.uuid, eventUUID: event.value?.uuid,
}, },
}); });
if (!commentThreadsData) return; if (!commentThreadsData) return;
const { event } = commentThreadsData; const { event: cachedEvent } = commentThreadsData;
const oldComments = [...event.comments]; const oldComments = [...cachedEvent.comments];
// if it's no a root comment, we first need to find // if it's no a root comment, we first need to find
// existing replies and add the new reply to it // existing replies and add the new reply to it
@ -206,12 +211,12 @@ const {
query: COMMENTS_THREADS_WITH_REPLIES, query: COMMENTS_THREADS_WITH_REPLIES,
data: { data: {
event: { event: {
...event, ...cachedEvent,
comments: oldComments, comments: oldComments,
}, },
}, },
variables: { variables: {
eventUUID: props.event?.uuid, eventUUID: event.value?.uuid,
}, },
}); });
}, },
@ -239,10 +244,10 @@ const createCommentForEvent = (comment: IComment) => {
if (emptyCommentError.value) return; if (emptyCommentError.value) return;
if (!comment.actor) return; if (!comment.actor) return;
if (!props.event?.id) return; if (!event.value?.id) return;
createCommentForEventMutation({ createCommentForEventMutation({
eventId: props.event?.id, eventId: event.value?.id,
text: comment.text, text: comment.text,
inReplyToCommentId: comment.inReplyToComment?.id, inReplyToCommentId: comment.inReplyToComment?.id,
isAnnouncement: comment.isAnnouncement, isAnnouncement: comment.isAnnouncement,
@ -266,12 +271,12 @@ const { mutate: deleteComment, onError: deleteCommentMutationError } =
const commentsData = store.readQuery<{ event: IEvent }>({ const commentsData = store.readQuery<{ event: IEvent }>({
query: COMMENTS_THREADS_WITH_REPLIES, query: COMMENTS_THREADS_WITH_REPLIES,
variables: { variables: {
eventUUID: props.event?.uuid, eventUUID: event.value?.uuid,
}, },
}); });
if (!commentsData) return; if (!commentsData) return;
const { event } = commentsData; const { event: cachedEvent } = commentsData;
let updatedComments: IComment[] = [...event.comments]; let updatedComments: IComment[] = [...cachedEvent.comments];
if (variables?.originCommentId) { if (variables?.originCommentId) {
// we have deleted a reply to a thread // we have deleted a reply to a thread
@ -309,11 +314,11 @@ const { mutate: deleteComment, onError: deleteCommentMutationError } =
store.writeQuery({ store.writeQuery({
query: COMMENTS_THREADS_WITH_REPLIES, query: COMMENTS_THREADS_WITH_REPLIES,
variables: { variables: {
eventUUID: props.event?.uuid, eventUUID: event.value?.uuid,
}, },
data: { data: {
event: { event: {
...event, ...cachedEvent,
comments: updatedComments, comments: updatedComments,
}, },
}, },
@ -359,14 +364,14 @@ const filteredOrderedComments = computed((): IComment[] => {
const isEventOrganiser = computed((): boolean => { const isEventOrganiser = computed((): boolean => {
const organizerId = const organizerId =
props.event?.organizerActor?.id || props.event?.attributedTo?.id; event.value?.organizerActor?.id || event.value?.attributedTo?.id;
return organizerId !== undefined && currentActor.value?.id === organizerId; return organizerId !== undefined && currentActor.value?.id === organizerId;
}); });
const areCommentsClosed = computed((): boolean => { const areCommentsClosed = computed((): boolean => {
return ( return (
currentActor.value?.id !== undefined && currentActor.value?.id !== undefined &&
props.event?.options.commentModeration !== CommentModeration.CLOSED event.value?.options.commentModeration !== CommentModeration.CLOSED
); );
}); });

View File

@ -250,7 +250,7 @@ const props = withDefaults(
event: IEvent; event: IEvent;
currentActor: IPerson; currentActor: IPerson;
rootComment?: boolean; rootComment?: boolean;
readOnly: boolean; readOnly?: boolean;
}>(), }>(),
{ rootComment: true, readOnly: false } { rootComment: true, readOnly: false }
); );

View File

@ -36,7 +36,7 @@
</div> </div>
<div class="p-2"> <div class="p-2">
<div class=""> <div class="reported_by">
<span v-if="report.reporter?.type === ActorType.APPLICATION"> <span v-if="report.reporter?.type === ActorType.APPLICATION">
{{ {{
t("Reported by someone on {domain}", { t("Reported by someone on {domain}", {

View File

@ -189,7 +189,7 @@ routes.push({
redirect: { name: RouteName.PAGE_NOT_FOUND }, redirect: { name: RouteName.PAGE_NOT_FOUND },
}); });
export const router = createRouter({ const router = createRouter({
scrollBehavior, scrollBehavior,
history: createWebHistory("/"), history: createWebHistory("/"),
routes, routes,
@ -216,3 +216,5 @@ router.onError((error, to) => {
window.location.href = to.fullPath; window.location.href = to.fullPath;
} }
}); });
export { router };

View File

@ -142,6 +142,7 @@ import RouteName from "@/router/name";
import { LoginError, LoginErrorCode } from "@/types/enums"; import { LoginError, LoginErrorCode } from "@/types/enums";
import { useCurrentUserClient } from "@/composition/apollo/user"; import { useCurrentUserClient } from "@/composition/apollo/user";
import { useHead } from "@vueuse/head"; import { useHead } from "@vueuse/head";
import { enumTransformer, useRouteQuery } from "vue-use-route-query";
const { t } = useI18n({ useScope: "global" }); const { t } = useI18n({ useScope: "global" });
const router = useRouter(); const router = useRouter();
@ -174,9 +175,8 @@ const credentials = reactive({
typeof route.query.password === "string" ? route.query.password : "", typeof route.query.password === "string" ? route.query.password : "",
}); });
const redirect = ref<string | undefined>(""); const redirect = useRouteQuery("redirect", "");
const errorCode = useRouteQuery("code", null, enumTransformer(LoginErrorCode));
const errorCode = ref<LoginErrorCode | null>(null);
const { const {
onDone: onLoginMutationDone, onDone: onLoginMutationDone,
@ -195,6 +195,7 @@ onLoginMutationDone(async (result) => {
await setupClientUserAndActors(data.login); await setupClientUserAndActors(data.login);
if (redirect.value) { if (redirect.value) {
console.debug("We have a redirect", redirect.value);
router.push(redirect.value); router.push(redirect.value);
return; return;
} }
@ -293,10 +294,6 @@ const currentProvider = computed(() => {
}); });
onMounted(() => { onMounted(() => {
const query = route?.query;
errorCode.value = query?.code as LoginErrorCode;
redirect.value = query?.redirect as string | undefined;
// Already-logged-in and accessing /login // Already-logged-in and accessing /login
if (currentUser.value?.isLoggedIn) { if (currentUser.value?.isLoggedIn) {
console.debug( console.debug(

View File

@ -1,14 +1,7 @@
import "./specs/mocks/matchMedia"; import "./specs/mocks/matchMedia";
import { config } from "@vue/test-utils"; import { config } from "@vue/test-utils";
import { createHead } from "@vueuse/head"; import { createHead } from "@vueuse/head";
import { createI18n } from "vue-i18n"; import { i18n } from "@/utils/i18n";
import en_US from "@/i18n/en_US.json";
const i18n = createI18n({
legacy: false,
messages: { en_US },
locale: "en_US",
});
const head = createHead(); const head = createHead();

View File

@ -119,7 +119,7 @@ describe("CommentTree", () => {
await flushPromises(); await flushPromises();
expect(wrapper.find("p.text-center").exists()).toBe(false); expect(wrapper.find("p.text-center").exists()).toBe(false);
expect(wrapper.findAllComponents("comment-stub").length).toBe(2); expect(wrapper.findAllComponents("event-comment-stub").length).toBe(2);
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
}); });
@ -130,6 +130,9 @@ describe("CommentTree", () => {
newComment: { newComment: {
text: newCommentForEventMock.text, text: newCommentForEventMock.text,
isAnnouncement: false, isAnnouncement: false,
insertedAt: "2021-12-03T13:02:00Z",
updatedAt: "2021-12-03T13:02:00Z",
publishedAt: "2021-12-03T13:02:00Z",
}, },
} }
); );
@ -137,7 +140,7 @@ describe("CommentTree", () => {
await flushPromises(); await flushPromises();
expect(wrapper.find("form").isVisible()).toBe(true); expect(wrapper.find("form").isVisible()).toBe(true);
expect(wrapper.findAllComponents("comment-stub").length).toBe(2); expect(wrapper.findAllComponents("event-comment-stub").length).toBe(2);
wrapper.getComponent({ ref: "commenteditor" }); wrapper.getComponent({ ref: "commenteditor" });
wrapper.find("form").trigger("submit"); wrapper.find("form").trigger("submit");

View File

@ -1,31 +1,31 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`CommentTree > renders a comment tree with comments 1`] = ` exports[`CommentTree > renders a comment tree with comments 1`] = `
"<div data-v-5d0380ab=\\"\\"> "<div data-v-5d0380ab=\\"\\">
<form class=\\"\\" data-v-5d0380ab=\\"\\"> <form data-v-5d0380ab=\\"\\" class=\\"mt-2\\">
<!--v-if--> <!--v-if-->
<article class=\\"flex flex-wrap items-start gap-2\\" data-v-5d0380ab=\\"\\"> <article data-v-5d0380ab=\\"\\" class=\\"flex flex-wrap items-start gap-2\\">
<figure class=\\"\\" data-v-5d0380ab=\\"\\"> <figure data-v-5d0380ab=\\"\\" class=\\"\\">
<identity-picker-wrapper-stub modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\" data-v-5d0380ab=\\"\\"></identity-picker-wrapper-stub> <identity-picker-wrapper-stub data-v-5d0380ab=\\"\\" modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\"></identity-picker-wrapper-stub>
</figure> </figure>
<div class=\\"flex-1\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"flex-1\\">
<div class=\\"flex flex-col gap-2\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"flex flex-col gap-2\\">
<div class=\\"editor-wrapper\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"editor-wrapper\\">
<editor-stub currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" data-v-5d0380ab=\\"\\"></editor-stub> <editor-stub data-v-5d0380ab=\\"\\" currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" placeholder=\\"Write a new comment\\"></editor-stub>
<!--v-if--> <!--v-if-->
</div> </div>
<!--v-if--> <!--v-if-->
</div> </div>
</div> </div>
<div class=\\"\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"\\">
<o-button-stub variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\" data-v-5d0380ab=\\"\\"></o-button-stub> <o-button-stub data-v-5d0380ab=\\"\\" variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" loading=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\"></o-button-stub>
</div> </div>
</article> </article>
</form> </form>
<transition-group-stub data-v-5d0380ab=\\"\\"> <transition-group-stub data-v-5d0380ab=\\"\\" tag=\\"div\\" name=\\"comment-empty-list\\" appear=\\"false\\" persisted=\\"false\\" css=\\"true\\" class=\\"mt-2\\">
<transition-group-stub data-v-5d0380ab=\\"\\"> <transition-group-stub data-v-5d0380ab=\\"\\" name=\\"comment-list\\" tag=\\"ul\\" appear=\\"false\\" persisted=\\"false\\" css=\\"true\\" class=\\"comment-list\\">
<comment-stub comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" class=\\"root-comment\\" data-v-5d0380ab=\\"\\"></comment-stub> <event-comment-stub data-v-5d0380ab=\\"\\" comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" readonly=\\"false\\" class=\\"root-comment my-2\\"></event-comment-stub>
<comment-stub comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" class=\\"root-comment\\" data-v-5d0380ab=\\"\\"></comment-stub> <event-comment-stub data-v-5d0380ab=\\"\\" comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" readonly=\\"false\\" class=\\"root-comment my-2\\"></event-comment-stub>
</transition-group-stub> </transition-group-stub>
</transition-group-stub> </transition-group-stub>
</div>" </div>"
@ -34,34 +34,34 @@ exports[`CommentTree > renders a comment tree with comments 1`] = `
exports[`CommentTree > renders a loading comment tree 1`] = ` exports[`CommentTree > renders a loading comment tree 1`] = `
"<div data-v-5d0380ab=\\"\\"> "<div data-v-5d0380ab=\\"\\">
<!--v-if--> <!--v-if-->
<p class=\\"text-center\\" data-v-5d0380ab=\\"\\">Loading comments…</p> <p data-v-5d0380ab=\\"\\" class=\\"text-center\\">Loading comments…</p>
</div>" </div>"
`; `;
exports[`CommentTree > renders an empty comment tree 1`] = ` exports[`CommentTree > renders an empty comment tree 1`] = `
"<div data-v-5d0380ab=\\"\\"> "<div data-v-5d0380ab=\\"\\">
<form class=\\"\\" data-v-5d0380ab=\\"\\"> <form data-v-5d0380ab=\\"\\" class=\\"mt-2\\">
<!--v-if--> <!--v-if-->
<article class=\\"flex flex-wrap items-start gap-2\\" data-v-5d0380ab=\\"\\"> <article data-v-5d0380ab=\\"\\" class=\\"flex flex-wrap items-start gap-2\\">
<figure class=\\"\\" data-v-5d0380ab=\\"\\"> <figure data-v-5d0380ab=\\"\\" class=\\"\\">
<identity-picker-wrapper-stub modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\" data-v-5d0380ab=\\"\\"></identity-picker-wrapper-stub> <identity-picker-wrapper-stub data-v-5d0380ab=\\"\\" modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\"></identity-picker-wrapper-stub>
</figure> </figure>
<div class=\\"flex-1\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"flex-1\\">
<div class=\\"flex flex-col gap-2\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"flex flex-col gap-2\\">
<div class=\\"editor-wrapper\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"editor-wrapper\\">
<editor-stub currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" data-v-5d0380ab=\\"\\"></editor-stub> <editor-stub data-v-5d0380ab=\\"\\" currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" placeholder=\\"Write a new comment\\"></editor-stub>
<!--v-if--> <!--v-if-->
</div> </div>
<!--v-if--> <!--v-if-->
</div> </div>
</div> </div>
<div class=\\"\\" data-v-5d0380ab=\\"\\"> <div data-v-5d0380ab=\\"\\" class=\\"\\">
<o-button-stub variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\" data-v-5d0380ab=\\"\\"></o-button-stub> <o-button-stub data-v-5d0380ab=\\"\\" variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" loading=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\"></o-button-stub>
</div> </div>
</article> </article>
</form> </form>
<transition-group-stub data-v-5d0380ab=\\"\\"> <transition-group-stub data-v-5d0380ab=\\"\\" tag=\\"div\\" name=\\"comment-empty-list\\" appear=\\"false\\" persisted=\\"false\\" css=\\"true\\" class=\\"mt-2\\">
<empty-content-stub icon=\\"comment\\" descriptionclasses=\\"\\" inline=\\"true\\" center=\\"false\\" data-v-5d0380ab=\\"\\"></empty-content-stub> <empty-content-stub data-v-5d0380ab=\\"\\" icon=\\"comment\\" descriptionclasses=\\"\\" inline=\\"true\\" center=\\"false\\"></empty-content-stub>
</transition-group-stub> </transition-group-stub>
</div>" </div>"
`; `;

View File

@ -1,4 +1,4 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`GroupSection > renders group section with basic informations 1`] = ` exports[`GroupSection > renders group section with basic informations 1`] = `
"<section class=\\"flex flex-col mb-3 border-2 border-mbz-purple\\"> "<section class=\\"flex flex-col mb-3 border-2 border-mbz-purple\\">

View File

@ -1,11 +1,11 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ParticipationWithoutAccount > handles being already a participant 1`] = ` exports[`ParticipationWithoutAccount > handles being already a participant 1`] = `
"<section class=\\"container mx-auto\\"> "<section class=\\"container mx-auto\\">
<div class=\\"\\"> <div class=\\"\\">
<form> <form>
<p>This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.</p> <p>This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.</p>
<transition-stub> <transition-stub name=\\"fade\\" appear=\\"false\\" persisted=\\"true\\" css=\\"true\\">
<article class=\\"o-notification o-notification--info\\"> <article class=\\"o-notification o-notification--info\\">
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->
@ -15,7 +15,7 @@ exports[`ParticipationWithoutAccount > handles being already a participant 1`] =
</div> </div>
</article> </article>
</transition-stub> </transition-stub>
<transition-stub> <transition-stub name=\\"fade\\" appear=\\"false\\" persisted=\\"true\\" css=\\"true\\">
<article class=\\"o-notification o-notification--danger\\"> <article class=\\"o-notification o-notification--danger\\">
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->

View File

@ -1,11 +1,12 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`PostListItem > renders post list item with basic informations 1`] = ` exports[`PostListItem > renders post list item with basic informations 1`] = `
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\"> "<a data-v-6ca7cc69=\\"\\" href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\">
<!--v-if-->
<div data-v-6ca7cc69=\\"\\" class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\">
<h3 data-v-6ca7cc69=\\"\\" class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\">My Blog Post</h3>
<p data-v-6ca7cc69=\\"\\" class=\\"flex gap-2\\"><span data-v-6ca7cc69=\\"\\" aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span data-v-6ca7cc69=\\"\\" dir=\\"auto\\" class=\\"\\">Dec 2, 2020</span></p>
<!--v-if--> <!--v-if-->
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\">
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3>
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p>
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->
</div> </div>
@ -13,24 +14,26 @@ exports[`PostListItem > renders post list item with basic informations 1`] = `
`; `;
exports[`PostListItem > renders post list item with publisher name 1`] = ` exports[`PostListItem > renders post list item with publisher name 1`] = `
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\"> "<a data-v-6ca7cc69=\\"\\" href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\">
<!--v-if--> <!--v-if-->
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\"> <div data-v-6ca7cc69=\\"\\" class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\">
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3> <h3 data-v-6ca7cc69=\\"\\" class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\">My Blog Post</h3>
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p> <p data-v-6ca7cc69=\\"\\" class=\\"flex gap-2\\"><span data-v-6ca7cc69=\\"\\" aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span data-v-6ca7cc69=\\"\\" dir=\\"auto\\" class=\\"\\">Dec 2, 2020</span></p>
<!--v-if-->
<p data-v-6ca7cc69=\\"\\" class=\\"flex gap-1\\"><span data-v-6ca7cc69=\\"\\" aria-hidden=\\"true\\" class=\\"material-design-icon account-edit-icon\\" role=\\"img\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z\\"><!--v-if--></path></svg></span>Published by <b data-v-6ca7cc69=\\"\\" class=\\"\\">An author</b></p>
<!--v-if--> <!--v-if-->
<p class=\\"flex gap-1\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon account-edit-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z\\"><!--v-if--></path></svg></span>Published by <b class=\\"\\" data-v-6ca7cc69=\\"\\">An author</b></p>
</div> </div>
</a>" </a>"
`; `;
exports[`PostListItem > renders post list item with tags 1`] = ` exports[`PostListItem > renders post list item with tags 1`] = `
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\"> "<a data-v-6ca7cc69=\\"\\" href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\">
<!--v-if-->
<div data-v-6ca7cc69=\\"\\" class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\">
<h3 data-v-6ca7cc69=\\"\\" class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\">My Blog Post</h3>
<p data-v-6ca7cc69=\\"\\" class=\\"flex gap-2\\"><span data-v-6ca7cc69=\\"\\" aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span data-v-6ca7cc69=\\"\\" dir=\\"auto\\" class=\\"\\">Dec 2, 2020</span></p>
<div data-v-6ca7cc69=\\"\\" class=\\"flex flex-wrap gap-y-0 gap-x-2\\"><span data-v-6ca7cc69=\\"\\" aria-hidden=\\"true\\" class=\\"material-design-icon tag-icon\\" role=\\"img\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z\\"><!--v-if--></path></svg></span><span data-v-6955ca87=\\"\\" data-v-6ca7cc69=\\"\\" class=\\"rounded-md truncate text-sm text-violet-title px-2 py-1 bg-purple-3 dark:text-violet-3\\">A tag</span></div>
<!--v-if--> <!--v-if-->
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\">
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3>
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p>
<div class=\\"flex flex-wrap gap-y-0 gap-x-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon tag-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z\\"><!--v-if--></path></svg></span><span class=\\"rounded-md truncate text-sm text-violet-title px-2 py-1 bg-purple-3 dark:text-violet-3\\" data-v-6955ca87=\\"\\" data-v-6ca7cc69=\\"\\">A tag</span></div>
<!--v-if--> <!--v-if-->
</div> </div>
</a>" </a>"

View File

@ -36,11 +36,7 @@ describe("ReportCard", () => {
reportData.reported.name reportData.reported.name
); );
expect(wrapper.find(".flex.gap-1 div p:nth-child(2)").text()).toBe( expect(wrapper.find(".reported_by span:first-child").text()).toBe(
`@${reportData.reported.preferredUsername}`
);
expect(wrapper.find(".reported_by div:first-child").text()).toBe(
`Reported by John Snow` `Reported by John Snow`
); );
}); });
@ -50,7 +46,7 @@ describe("ReportCard", () => {
reporter: { domain: "somewhere.else", type: ActorType.APPLICATION }, reporter: { domain: "somewhere.else", type: ActorType.APPLICATION },
}); });
expect(wrapper.find(".reported_by div:first-child").text()).toBe( expect(wrapper.find(".reported_by span:first-child").text()).toBe(
"Reported by someone on somewhere.else" "Reported by someone on somewhere.else"
); );
}); });

View File

@ -1,16 +1,16 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ReportModal > renders report modal with basic informations and submits it 1`] = ` exports[`ReportModal > renders report modal with basic informations and submits it 1`] = `
"<div class=\\"p-2\\" data-v-e0cceef3=\\"\\"> "<div data-v-e0cceef3=\\"\\" class=\\"p-2\\">
<!--v-if--> <!--v-if-->
<section data-v-e0cceef3=\\"\\"> <section data-v-e0cceef3=\\"\\">
<div class=\\"flex gap-1 flex-row mb-3\\" data-v-e0cceef3=\\"\\"><span class=\\"o-icon o-icon--warning hidden md:block flex-1\\" data-v-e0cceef3=\\"\\"><i class=\\"mdi mdi-alert 48\\"></i></span> <div data-v-e0cceef3=\\"\\" class=\\"flex gap-1 flex-row mb-3 bg-mbz-yellow p-3 rounded items-center\\"><span data-v-e0cceef3=\\"\\" class=\\"o-icon o-icon--warning hidden md:block flex-1\\"><i class=\\"mdi mdi-alert 48\\"></i></span>
<p data-v-e0cceef3=\\"\\">The report will be sent to the moderators of your instance. You can explain why you report this content below.</p> <p data-v-e0cceef3=\\"\\">The report will be sent to the moderators of your instance. You can explain why you report this content below.</p>
</div> </div>
<div class=\\"\\" data-v-e0cceef3=\\"\\"> <div data-v-e0cceef3=\\"\\">
<!--v-if--> <!--v-if-->
<div class=\\"o-field o-field--filled\\" data-v-e0cceef3=\\"\\"><label for=\\"additional-comments\\" class=\\"o-field__label\\">Additional comments</label> <div data-v-e0cceef3=\\"\\" class=\\"o-field o-field--filled\\"><label for=\\"additional-comments\\" class=\\"o-field__label\\">Additional comments</label>
<div class=\\"o-ctrl-input\\" data-v-e0cceef3=\\"\\"><textarea id=\\"additional-comments\\" class=\\"o-input o-input__textarea\\"></textarea> <div data-v-e0cceef3=\\"\\" class=\\"o-ctrl-input\\"><textarea id=\\"additional-comments\\" autofocus=\\"\\" class=\\"o-input o-input__textarea\\"></textarea>
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->
@ -20,9 +20,9 @@ exports[`ReportModal > renders report modal with basic informations and submits
<!--v-if--> <!--v-if-->
</div> </div>
</section> </section>
<footer class=\\"flex gap-2 py-3\\" data-v-e0cceef3=\\"\\"><button type=\\"button\\" class=\\"o-btn o-btn--outlined\\" data-v-e0cceef3=\\"\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Cancel</span> <footer data-v-e0cceef3=\\"\\" class=\\"flex gap-2 py-3\\"><button data-v-e0cceef3=\\"\\" type=\\"button\\" class=\\"o-btn o-btn--outlined\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Cancel</span>
<!--v-if--></span> <!--v-if--></span>
</button><button type=\\"button\\" class=\\"o-btn o-btn--primary\\" data-v-e0cceef3=\\"\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Send the report</span> </button><button data-v-e0cceef3=\\"\\" type=\\"button\\" class=\\"o-btn o-btn--primary\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Send the report</span>
<!--v-if--></span> <!--v-if--></span>
</button></footer> </button></footer>
</div>" </div>"

View File

@ -1,9 +1,3 @@
const useRouterMock = vi.fn(() => ({
push: function () {
// do nothing
},
}));
import { config, mount } from "@vue/test-utils"; import { config, mount } from "@vue/test-utils";
import PasswordReset from "@/views/User/PasswordReset.vue"; import PasswordReset from "@/views/User/PasswordReset.vue";
import { createMockClient, RequestHandler } from "mock-apollo-client"; import { createMockClient, RequestHandler } from "mock-apollo-client";
@ -11,15 +5,17 @@ import { RESET_PASSWORD } from "@/graphql/auth";
import { resetPasswordResponseMock } from "../../mocks/auth"; import { resetPasswordResponseMock } from "../../mocks/auth";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import flushPromises from "flush-promises"; import flushPromises from "flush-promises";
import { describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import { DefaultApolloClient } from "@vue/apollo-composable"; import { DefaultApolloClient } from "@vue/apollo-composable";
import Oruga from "@oruga-ui/oruga-next"; import Oruga from "@oruga-ui/oruga-next";
import {
VueRouterMock,
createRouterMock,
injectRouterMock,
} from "vue-router-mock";
config.global.plugins.push(Oruga); config.global.plugins.push(Oruga);
config.plugins.VueWrapper.install(VueRouterMock);
vi.mock("vue-router/dist/vue-router.mjs", () => ({
useRouter: useRouterMock,
}));
let requestHandlers: Record<string, RequestHandler>; let requestHandlers: Record<string, RequestHandler>;
@ -58,8 +54,21 @@ const generateWrapper = (
}; };
describe("Reset page", () => { describe("Reset 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("renders correctly", () => { it("renders correctly", () => {
const wrapper = generateWrapper(); const wrapper = generateWrapper();
expect(wrapper.router).toBe(router);
expect(wrapper.findAll('input[type="password"').length).toBe(2); expect(wrapper.findAll('input[type="password"').length).toBe(2);
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
}); });
@ -93,10 +102,6 @@ describe("Reset page", () => {
}); });
it("redirects to homepage if token is valid", async () => { it("redirects to homepage if token is valid", async () => {
const push = vi.fn(); // needs to write this code before render()
useRouterMock.mockImplementationOnce(() => ({
push,
}));
const wrapper = generateWrapper(); const wrapper = generateWrapper();
wrapper wrapper
@ -110,6 +115,6 @@ describe("Reset page", () => {
token: "some-token", token: "some-token",
}); });
await flushPromises(); await flushPromises();
expect(push).toHaveBeenCalledWith({ name: RouteName.HOME }); expect(wrapper.router.push).toHaveBeenCalledWith({ name: RouteName.HOME });
}); });
}); });

View File

@ -1,4 +1,4 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Reset page > renders correctly 1`] = ` exports[`Reset page > renders correctly 1`] = `
"<section class=\\"container mx-auto\\"> "<section class=\\"container mx-auto\\">
@ -25,7 +25,7 @@ exports[`Reset page > renders correctly 1`] = `
exports[`Reset page > shows error if token is invalid 1`] = ` exports[`Reset page > shows error if token is invalid 1`] = `
"<section class=\\"container mx-auto\\"> "<section class=\\"container mx-auto\\">
<h1 class=\\"\\">Password reset</h1> <h1 class=\\"\\">Password reset</h1>
<transition-stub title=\\"Error\\"> <transition-stub name=\\"fade\\" appear=\\"false\\" persisted=\\"true\\" css=\\"true\\" title=\\"Error\\">
<article class=\\"o-notification o-notification--danger\\"> <article class=\\"o-notification o-notification--danger\\">
<!--v-if--> <!--v-if-->
<!--v-if--> <!--v-if-->

View File

@ -1,15 +1,3 @@
const useRouterMock = vi.fn(() => ({
push: function () {
// do nothing
},
replace: function () {
// do nothing
},
}));
const useRouteMock = vi.fn(function () {
// do nothing
});
import { config, mount, VueWrapper } from "@vue/test-utils"; import { config, mount, VueWrapper } from "@vue/test-utils";
import Login from "@/views/User/LoginView.vue"; import Login from "@/views/User/LoginView.vue";
import { import {
@ -26,23 +14,37 @@ import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model"; import { ICurrentUser } from "@/types/current-user.model";
import flushPromises from "flush-promises"; import flushPromises from "flush-promises";
import RouteName from "@/router/name"; import RouteName from "@/router/name";
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DefaultApolloClient } from "@vue/apollo-composable"; import { DefaultApolloClient } from "@vue/apollo-composable";
import Oruga from "@oruga-ui/oruga-next"; import Oruga from "@oruga-ui/oruga-next";
import { cache } from "@/apollo/memory"; import { cache } from "@/apollo/memory";
import {
vi.mock("vue-router/dist/vue-router.mjs", () => ({ VueRouterMock,
useRouter: useRouterMock, createRouterMock,
useRoute: useRouteMock, injectRouterMock,
})); getRouter,
} from "vue-router-mock";
config.global.plugins.push(Oruga); config.global.plugins.push(Oruga);
config.plugins.VueWrapper.install(VueRouterMock);
describe("Render login form", () => { describe("Render login form", () => {
let wrapper: VueWrapper; let wrapper: VueWrapper;
let mockClient: MockApolloClient | null; let mockClient: MockApolloClient | null;
let requestHandlers: Record<string, RequestHandler>; let requestHandlers: Record<string, RequestHandler>;
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);
});
const generateWrapper = ( const generateWrapper = (
handlers: Record<string, unknown> = {}, handlers: Record<string, unknown> = {},
customProps: Record<string, unknown> = {}, customProps: Record<string, unknown> = {},
@ -90,6 +92,7 @@ describe("Render login form", () => {
generateWrapper(); generateWrapper();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
expect(requestHandlers.configQueryHandler).toHaveBeenCalled(); expect(requestHandlers.configQueryHandler).toHaveBeenCalled();
@ -103,15 +106,10 @@ describe("Render login form", () => {
}); });
it("renders and submits the login form", async () => { it("renders and submits the login form", async () => {
const replace = vi.fn(); // needs to write this code before render()
const push = vi.fn();
useRouterMock.mockImplementationOnce(() => ({
replace,
push,
}));
generateWrapper(); generateWrapper();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
expect(requestHandlers.configQueryHandler).toHaveBeenCalled(); expect(requestHandlers.configQueryHandler).toHaveBeenCalled();
@ -133,16 +131,12 @@ describe("Render login form", () => {
expect(currentUser?.email).toBe("some@email.tld"); expect(currentUser?.email).toBe("some@email.tld");
expect(currentUser?.id).toBe("1"); expect(currentUser?.id).toBe("1");
await flushPromises(); await flushPromises();
expect(replace).toHaveBeenCalledWith({ name: RouteName.HOME }); expect(wrapper.router.replace).toHaveBeenCalledWith({
name: RouteName.HOME,
});
}); });
it("handles a login error", async () => { it("handles a login error", async () => {
const replace = vi.fn(); // needs to write this code before render()
const push = vi.fn();
useRouterMock.mockImplementationOnce(() => ({
push,
replace,
}));
generateWrapper({ generateWrapper({
loginMutationHandler: vi.fn().mockResolvedValue({ loginMutationHandler: vi.fn().mockResolvedValue({
errors: [ errors: [
@ -156,6 +150,7 @@ describe("Render login form", () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await flushPromises();
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
expect(requestHandlers.configQueryHandler).toHaveBeenCalled(); expect(requestHandlers.configQueryHandler).toHaveBeenCalled();
@ -170,28 +165,21 @@ describe("Render login form", () => {
expect(wrapper.find(".o-notification--danger").text()).toContain( expect(wrapper.find(".o-notification--danger").text()).toContain(
"Impossible to authenticate, either your email or password are invalid." "Impossible to authenticate, either your email or password are invalid."
); );
expect(push).not.toHaveBeenCalled(); expect(wrapper.router.push).not.toHaveBeenCalled();
}); });
it("handles redirection after login", async () => { it("handles redirection after login", async () => {
const replace = vi.fn(); // needs to write this code before render()
const push = vi.fn();
useRouterMock.mockImplementationOnce(() => ({
replace,
push,
}));
useRouteMock.mockImplementationOnce(() => ({
query: { redirect: "/about/instance" },
}));
generateWrapper(); generateWrapper();
getRouter().setQuery({ redirect: "/about/instance" });
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await flushPromises();
wrapper.find('form input[type="email"]').setValue("some@email.tld"); wrapper.find('form input[type="email"]').setValue("some@email.tld");
wrapper.find('form input[type="password"]').setValue("somepassword"); wrapper.find('form input[type="password"]').setValue("somepassword");
wrapper.find("form").trigger("submit"); wrapper.find("form").trigger("submit");
await flushPromises(); await flushPromises();
expect(push).toHaveBeenCalledWith("/about/instance"); expect(wrapper.router.push).toHaveBeenCalledWith("/about/instance");
}); });
}); });

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`App component > renders a Vue component 1`] = ` exports[`App component > renders a Vue component 1`] = `
"<nav class=\\"bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900\\" id=\\"navbar\\"> "<nav class=\\"bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900\\" id=\\"navbar\\">
<div class=\\"container mx-auto flex flex-wrap items-center mx-auto gap-4\\"> <div class=\\"container mx-auto flex flex-wrap items-center mx-auto gap-2 sm:gap-4\\">
<router-link to=\\"[object Object]\\" class=\\"flex items-center\\"> <router-link to=\\"[object Object]\\" class=\\"flex items-center flex-1\\">
<mobilizon-logo-stub invert=\\"false\\" class=\\"w-40\\"></mobilizon-logo-stub> <mobilizon-logo-stub invert=\\"false\\" class=\\"w-40\\"></mobilizon-logo-stub>
</router-link> </router-link>
<!--v-if--><button type=\\"button\\" class=\\"inline-flex items-center p-2 ml-1 text-sm text-zinc-500 rounded-lg md:hidden hover:bg-zinc-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:focus:ring-gray-600\\" aria-controls=\\"mobile-menu-2\\" aria-expanded=\\"false\\"><span class=\\"sr-only\\">Open main menu</span><svg class=\\"w-6 h-6\\" aria-hidden=\\"true\\" fill=\\"currentColor\\" viewBox=\\"0 0 20 20\\" xmlns=\\"http://www.w3.org/2000/svg\\"> <!--v-if--><button type=\\"button\\" class=\\"inline-flex items-center p-2 ml-1 text-sm text-zinc-500 rounded-lg md:hidden hover:bg-zinc-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:focus:ring-gray-600\\" aria-controls=\\"mobile-menu-2\\" aria-expanded=\\"false\\"><span class=\\"sr-only\\">Open main menu</span><svg class=\\"w-6 h-6\\" aria-hidden=\\"true\\" fill=\\"currentColor\\" viewBox=\\"0 0 20 20\\" xmlns=\\"http://www.w3.org/2000/svg\\">
@ -16,181 +16,9 @@ exports[`App component > renders a Vue component 1`] = `
<li> <li>
<router-link to=\\"[object Object]\\" class=\\"block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700\\">Login</router-link> <router-link to=\\"[object Object]\\" class=\\"block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700\\">Login</router-link>
</li> </li>
<li> <!--v-if-->
<router-link to=\\"[object Object]\\" class=\\"block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700\\">Register</router-link>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>"
<!-- <o-navbar
id=\\"navbar\\"
type=\\"is-secondary\\"
wrapper-class=\\"container mx-auto\\"
v-model:active=\\"mobileNavbarActive\\"
>
<template #brand>
<o-navbar-item
tag=\\"router-link\\"
:to=\\"{ name: RouteName.HOME }\\"
:aria-label=\\"$t('Home')\\"
>
<logo />
</o-navbar-item>
</template>
<template #start>
<o-navbar-item tag=\\"router-link\\" :to=\\"{ name: RouteName.SEARCH }\\">{{
$t(\\"Explore\\")
}}</o-navbar-item>
<o-navbar-item
v-if=\\"currentActor.id && currentUser?.isLoggedIn\\"
tag=\\"router-link\\"
:to=\\"{ name: RouteName.MY_EVENTS }\\"
>{{ $t(\\"My events\\") }}</o-navbar-item
>
<o-navbar-item
tag=\\"router-link\\"
:to=\\"{ name: RouteName.MY_GROUPS }\\"
v-if=\\"
config &&
config.features.groups &&
currentActor.id &&
currentUser?.isLoggedIn
\\"
>{{ $t(\\"My groups\\") }}</o-navbar-item
>
<o-navbar-item
tag=\\"span\\"
v-if=\\"
config &&
config.features.eventCreation &&
currentActor.id &&
currentUser?.isLoggedIn
\\"
>
<o-button
v-if=\\"!hideCreateEventsButton\\"
tag=\\"router-link\\"
:to=\\"{ name: RouteName.CREATE_EVENT }\\"
variant=\\"primary\\"
>{{ $t(\\"Create\\") }}</o-button
>
</o-navbar-item>
</template>
<template #end>
<o-navbar-item tag=\\"div\\">
<search-field @navbar-search=\\"mobileNavbarActive = false\\" />
</o-navbar-item>
<o-navbar-dropdown
v-if=\\"currentActor.id && currentUser?.isLoggedIn\\"
right
collapsible
ref=\\"user-dropdown\\"
tabindex=\\"0\\"
tag=\\"span\\"
@keyup.enter=\\"toggleMenu\\"
>
<template #label v-if=\\"currentActor\\">
<div class=\\"identity-wrapper\\">
<div>
<figure class=\\"image is-32x32\\" v-if=\\"currentActor.avatar\\">
<img
class=\\"is-rounded\\"
alt=\\"avatarUrl\\"
:src=\\"currentActor.avatar.url\\"
/>
</figure>
<o-icon v-else icon=\\"account-circle\\" />
</div>
<div class=\\"media-content is-hidden-desktop\\">
<span>{{ displayName(currentActor) }}</span>
<span class=\\"has-text-grey-dark\\" v-if=\\"currentActor.name\\"
>@{{ currentActor.preferredUsername }}</span
>
</div>
</div>
</template>
No identities dropdown if no identities
<span v-if=\\"identities.length <= 1\\"></span>
<o-navbar-item
tag=\\"span\\"
v-for=\\"identity in identities\\"
v-else
:active=\\"identity.id === currentActor.id\\"
:key=\\"identity.id\\"
tabindex=\\"0\\"
@click=\\"setIdentity({
preferredUsername: identity.preferredUsername,
})\\"
@keyup.enter=\\"setIdentity({
preferredUsername: identity.preferredUsername,
})\\"
>
<span>
<div class=\\"media-left\\">
<figure class=\\"image is-32x32\\" v-if=\\"identity.avatar\\">
<img
class=\\"is-rounded\\"
loading=\\"lazy\\"
:src=\\"identity.avatar.url\\"
alt
/>
</figure>
<o-icon v-else size=\\"is-medium\\" icon=\\"account-circle\\" />
</div>
<div class=\\"media-content\\">
<span>{{ displayName(identity) }}</span>
<span class=\\"has-text-grey-dark\\" v-if=\\"identity.name\\"
>@{{ identity.preferredUsername }}</span
>
</div>
</span>
<hr class=\\"navbar-divider\\" role=\\"presentation\\" />
</o-navbar-item>
<o-navbar-item
tag=\\"router-link\\"
:to=\\"{ name: RouteName.UPDATE_IDENTITY }\\"
>{{ $t(\\"My account\\") }}</o-navbar-item
>
<o-navbar-item
v-if=\\"currentUser.role === ICurrentUserRole.ADMINISTRATOR\\"
tag=\\"router-link\\"
:to=\\"{ name: RouteName.ADMIN_DASHBOARD }\\"
>{{ $t(\\"Administration\\") }}</o-navbar-item
>
<o-navbar-item
tag=\\"span\\"
tabindex=\\"0\\"
@click=\\"logout\\"
@keyup.enter=\\"logout\\"
>
<span>{{ $t(\\"Log out\\") }}</span>
</o-navbar-item>
</o-navbar-dropdown>
<o-navbar-item v-else tag=\\"div\\">
<div class=\\"buttons\\">
<router-link
class=\\"button is-primary\\"
v-if=\\"config && config.registrationsOpen\\"
:to=\\"{ name: RouteName.REGISTER }\\"
>
<strong>{{ $t(\\"Sign up\\") }}</strong>
</router-link>
<router-link
class=\\"button is-light\\"
:to=\\"{ name: RouteName.LOGIN }\\"
>{{ $t(\\"Log in\\") }}</router-link
>
</div>
</o-navbar-item>
</template>
</o-navbar> -->"
`; `;

View File

@ -134,6 +134,7 @@ export const loginMock = {
__typename: "Config", __typename: "Config",
auth: { auth: {
__typename: "Auth", __typename: "Auth",
databaseLogin: true,
oauthProviders: [], oauthProviders: [],
}, },
registrationsOpen: true, registrationsOpen: true,

View File

@ -106,6 +106,7 @@ export const eventCommentThreadsMock = {
}, },
deletedAt: null, deletedAt: null,
insertedAt: "2020-12-03T09:02:00Z", insertedAt: "2020-12-03T09:02:00Z",
publishedAt: "2020-12-03T09:02:00Z",
isAnnouncement: false, isAnnouncement: false,
language: "en", language: "en",
}, },
@ -137,6 +138,7 @@ export const eventCommentThreadsMock = {
}, },
deletedAt: null, deletedAt: null,
insertedAt: "2020-12-03T11:02:00Z", insertedAt: "2020-12-03T11:02:00Z",
publishedAt: "2020-12-03T11:02:00Z",
isAnnouncement: false, isAnnouncement: false,
language: "en", language: "en",
}, },
@ -164,7 +166,6 @@ export const newCommentForEventResponse: DataMock = {
local: true, local: true,
visibility: "PUBLIC", visibility: "PUBLIC",
totalReplies: 0, totalReplies: 0,
updatedAt: "2020-12-03T13:02:00Z",
originComment: null, originComment: null,
inReplyToComment: null, inReplyToComment: null,
replies: [], replies: [],
@ -183,6 +184,8 @@ export const newCommentForEventResponse: DataMock = {
}, },
deletedAt: null, deletedAt: null,
insertedAt: "2020-12-03T13:02:00Z", insertedAt: "2020-12-03T13:02:00Z",
updatedAt: "2020-12-03T13:02:00Z",
publishedAt: "2020-12-03T13:02:00Z",
isAnnouncement: false, isAnnouncement: false,
language: "en", language: "en",
}, },

View File

@ -114,7 +114,7 @@ export default defineConfig(({ command }) => {
coverage: { coverage: {
reporter: ["text", "json", "html"], reporter: ["text", "json", "html"],
}, },
setupFiles: path.resolve(__dirname, "./tests/unit/setup.ts"), setupFiles: [path.resolve(__dirname, "./tests/unit/setup.ts")],
include: [path.resolve(__dirname, "./tests/unit/specs/**/*.spec.ts")], include: [path.resolve(__dirname, "./tests/unit/specs/**/*.spec.ts")],
}, },
}; };