feat: test onboarding redirects

This commit is contained in:
Reto Aebersold 2023-11-14 10:25:09 +01:00 committed by Christian Cueni
parent bfeca6e8e0
commit 3644a0d77d
6 changed files with 171 additions and 19 deletions

View File

@ -68,6 +68,10 @@ export const itDelete = (url: RequestInfo) => {
return itPost(url, {}, { method: "DELETE" });
};
export const itPut = (url: RequestInfo, data: unknown) => {
return itPost(url, data, { method: "PUT" });
};
const itGetPromiseCache = new Map<string, Promise<any>>();
export function bustItGetCache(key?: string) {

View File

@ -1,27 +1,49 @@
<script setup lang="ts">
import WizardPage from "@/components/onboarding/WizardPage.vue";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import type { Ref } from "vue";
import { computed, ref, watch } from "vue";
import { useUserStore } from "@/stores/user";
import AvatarImage from "@/components/ui/AvatarImage.vue";
import { useFileUpload } from "@/composables";
import { useRoute } from "vue-router";
import { useFetch } from "@/fetchHelpers";
import { itPut, useFetch } from "@/fetchHelpers";
import { useTranslation } from "i18next-vue";
import { profileNextRoute } from "@/services/onboarding";
const { t } = useTranslation();
type Organisation = {
id: string;
name: string;
};
const user = useUserStore();
const route = useRoute();
const { data: companies } = useFetch(() => "/api/core/organisations/");
const fetchResult = useFetch("/api/core/organisations/");
const organisations: Ref<Organisation[] | null> = fetchResult.data;
const selectedCompany = ref({
const selectedOrganisation = ref({
id: "0",
name: t("a.Auswählen"),
});
const validCompany = computed(() => {
return selectedCompany.value.id !== "0";
watch(
organisations,
(newOrganisations) => {
if (newOrganisations) {
const userOrganisation = newOrganisations.find((c) => c.id === user.organisation);
if (userOrganisation) {
selectedOrganisation.value = userOrganisation;
}
}
},
{ immediate: true }
);
const validOrganisation = computed(() => {
return selectedOrganisation.value.id !== "0";
});
const {
@ -35,18 +57,15 @@ watch(avatarFileInfo, (info) => {
console.log("fileInfo changed", info);
});
watch(selectedCompany, (company) => {
console.log("company changed", company);
watch(selectedOrganisation, (organisation) => {
console.log("organisation changed", organisation);
itPut("/api/core/me/", {
organisation: organisation.id,
});
});
const nextRoute = computed(() => {
if (route.params.courseType === "uk") {
return "setupComplete";
}
if (route.params.courseType === "vv") {
return "checkoutAddress";
}
return "";
return profileNextRoute(route.params.courseType);
});
</script>
@ -62,7 +81,11 @@ const nextRoute = computed(() => {
andere Personen einfacher finden.
</p>
<ItDropdownSelect v-if="companies" v-model="selectedCompany" :items="companies" />
<ItDropdownSelect
v-if="organisations"
v-model="selectedOrganisation"
:items="organisations"
/>
<div class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
<div>
@ -94,7 +117,7 @@ const nextRoute = computed(() => {
<template #footer>
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
<button
:disabled="!validCompany"
:disabled="!validOrganisation"
class="btn-blue flex items-center"
role="link"
@click="navigate"

View File

@ -0,0 +1,103 @@
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, vi } from "vitest";
import { START_LOCATION } from "vue-router";
import { useUserStore } from "../../stores/user";
import { onboardingRedirect } from "../onboarding";
describe("Onboarding", () => {
afterEach(() => {
vi.restoreAllMocks();
});
beforeEach(() => {
setActivePinia(createPinia());
});
it("redirect guest", () => {
const user = useUserStore();
user.loggedIn = false;
const mock = vi.fn();
onboardingRedirect(routeLocation("accountConfirm", "uk"), START_LOCATION, mock);
expect(mock).toHaveBeenCalledWith({
name: "accountCreate",
params: { courseType: "uk" },
});
});
it("redirect logged-in user from accountCreate to accountConfirm", () => {
const user = useUserStore();
user.loggedIn = true;
const mockNext = vi.fn();
onboardingRedirect(routeLocation("accountCreate", "uk"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith({
name: "accountConfirm",
params: { courseType: "uk" },
});
});
it("UK: redirect to profile next route for logged-in user with organisation", () => {
const user = useUserStore();
user.loggedIn = true;
user.organisation = "1";
const mockNext = vi.fn();
onboardingRedirect(routeLocation("accountConfirm", "uk"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith({
name: "setupComplete",
params: { courseType: "uk" },
});
});
it("VV: redirect to profile next route for logged-in user with organisation", () => {
const user = useUserStore();
user.loggedIn = true;
user.organisation = "1";
const mockNext = vi.fn();
onboardingRedirect(routeLocation("accountConfirm", "vv"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith({
name: "checkoutAddress",
params: { courseType: "vv" },
});
});
it("no redirect for logged-in user without organisation to accountConfirm", () => {
const user = useUserStore();
user.loggedIn = true;
user.organisation = "";
const mockNext = vi.fn();
onboardingRedirect(routeLocation("accountConfirm", "uk"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith(); // No arguments passed means no redirection
});
it("no redirect for logged-in user to a non-relevant route", () => {
const user = useUserStore();
user.loggedIn = true;
const mockNext = vi.fn();
onboardingRedirect(routeLocation("someOtherRoute", "uk"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith(); // No arguments passed means no redirection
});
it("no redirect for guest on accountCreate page", () => {
const user = useUserStore();
user.loggedIn = false;
const mockNext = vi.fn();
onboardingRedirect(routeLocation("accountCreate", "uk"), START_LOCATION, mockNext);
expect(mockNext).toHaveBeenCalledWith(); // No arguments passed means no redirection
});
});
function routeLocation(name: string, courseType: string) {
return {
fullPath: "",
hash: "",
matched: [],
meta: {},
name: name,
params: {
courseType,
},
path: "",
query: {},
redirectedFrom: undefined,
};
}

View File

@ -1,25 +1,36 @@
import { profileNextRoute } from "@/services/onboarding";
import { useUserStore } from "@/stores/user";
import type { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
import { START_LOCATION } from "vue-router";
export async function onboardingRedirect(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) {
const userStore = useUserStore();
const user = useUserStore();
// Guest
if (!userStore.loggedIn) {
if (!user.loggedIn) {
if (to.name !== "accountCreate") {
return next({ name: "accountCreate", params: to.params });
}
return next();
}
// Logged in
// Member
if (to.name === "accountCreate") {
return next({ name: "accountConfirm", params: to.params });
}
// Maybe we can skip the account setup steps
if (
from === START_LOCATION &&
user.organisation &&
(to.name === "accountConfirm" || to.name === "accountProfile")
) {
return next({ name: profileNextRoute(to.params.courseType), params: to.params });
}
return next();
}

View File

@ -0,0 +1,9 @@
export function profileNextRoute(courseType: string | string[]) {
if (courseType === "uk") {
return "setupComplete";
}
if (courseType === "vv") {
return "checkoutAddress";
}
return "";
}

View File

@ -25,6 +25,7 @@ export type UserState = {
email: string;
username: string;
avatar_url: string;
organisation: string;
is_superuser: boolean;
course_session_experts: string[];
loggedIn: boolean;
@ -57,6 +58,7 @@ const initialUserState: UserState = {
username: "",
avatar_url: "",
is_superuser: false,
organisation: "",
course_session_experts: [],
loggedIn: false,
language: defaultLanguage,