feat: test onboarding redirects
This commit is contained in:
parent
bfeca6e8e0
commit
3644a0d77d
|
|
@ -68,6 +68,10 @@ export const itDelete = (url: RequestInfo) => {
|
||||||
return itPost(url, {}, { method: "DELETE" });
|
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>>();
|
const itGetPromiseCache = new Map<string, Promise<any>>();
|
||||||
|
|
||||||
export function bustItGetCache(key?: string) {
|
export function bustItGetCache(key?: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,49 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WizardPage from "@/components/onboarding/WizardPage.vue";
|
import WizardPage from "@/components/onboarding/WizardPage.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
import type { Ref } from "vue";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
import AvatarImage from "@/components/ui/AvatarImage.vue";
|
||||||
import { useFileUpload } from "@/composables";
|
import { useFileUpload } from "@/composables";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { useFetch } from "@/fetchHelpers";
|
import { itPut, useFetch } from "@/fetchHelpers";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { profileNextRoute } from "@/services/onboarding";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
type Organisation = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
const user = useUserStore();
|
const user = useUserStore();
|
||||||
const route = useRoute();
|
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",
|
id: "0",
|
||||||
name: t("a.Auswählen"),
|
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 {
|
const {
|
||||||
|
|
@ -35,18 +57,15 @@ watch(avatarFileInfo, (info) => {
|
||||||
console.log("fileInfo changed", info);
|
console.log("fileInfo changed", info);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(selectedCompany, (company) => {
|
watch(selectedOrganisation, (organisation) => {
|
||||||
console.log("company changed", company);
|
console.log("organisation changed", organisation);
|
||||||
|
itPut("/api/core/me/", {
|
||||||
|
organisation: organisation.id,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextRoute = computed(() => {
|
const nextRoute = computed(() => {
|
||||||
if (route.params.courseType === "uk") {
|
return profileNextRoute(route.params.courseType);
|
||||||
return "setupComplete";
|
|
||||||
}
|
|
||||||
if (route.params.courseType === "vv") {
|
|
||||||
return "checkoutAddress";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -62,7 +81,11 @@ const nextRoute = computed(() => {
|
||||||
andere Personen einfacher finden.
|
andere Personen einfacher finden.
|
||||||
</p>
|
</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 class="mt-16 flex flex-col justify-between gap-12 lg:flex-row lg:gap-24">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -94,7 +117,7 @@ const nextRoute = computed(() => {
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
|
<router-link v-slot="{ navigate }" :to="{ name: nextRoute }" custom>
|
||||||
<button
|
<button
|
||||||
:disabled="!validCompany"
|
:disabled="!validOrganisation"
|
||||||
class="btn-blue flex items-center"
|
class="btn-blue flex items-center"
|
||||||
role="link"
|
role="link"
|
||||||
@click="navigate"
|
@click="navigate"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
|
import { profileNextRoute } from "@/services/onboarding";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
|
import type { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
|
||||||
|
import { START_LOCATION } from "vue-router";
|
||||||
|
|
||||||
export async function onboardingRedirect(
|
export async function onboardingRedirect(
|
||||||
to: RouteLocationNormalized,
|
to: RouteLocationNormalized,
|
||||||
from: RouteLocationNormalized,
|
from: RouteLocationNormalized,
|
||||||
next: NavigationGuardNext
|
next: NavigationGuardNext
|
||||||
) {
|
) {
|
||||||
const userStore = useUserStore();
|
const user = useUserStore();
|
||||||
|
|
||||||
// Guest
|
// Guest
|
||||||
if (!userStore.loggedIn) {
|
if (!user.loggedIn) {
|
||||||
if (to.name !== "accountCreate") {
|
if (to.name !== "accountCreate") {
|
||||||
return next({ name: "accountCreate", params: to.params });
|
return next({ name: "accountCreate", params: to.params });
|
||||||
}
|
}
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logged in
|
// Member
|
||||||
if (to.name === "accountCreate") {
|
if (to.name === "accountCreate") {
|
||||||
return next({ name: "accountConfirm", params: to.params });
|
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();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export function profileNextRoute(courseType: string | string[]) {
|
||||||
|
if (courseType === "uk") {
|
||||||
|
return "setupComplete";
|
||||||
|
}
|
||||||
|
if (courseType === "vv") {
|
||||||
|
return "checkoutAddress";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ export type UserState = {
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
|
organisation: string;
|
||||||
is_superuser: boolean;
|
is_superuser: boolean;
|
||||||
course_session_experts: string[];
|
course_session_experts: string[];
|
||||||
loggedIn: boolean;
|
loggedIn: boolean;
|
||||||
|
|
@ -57,6 +58,7 @@ const initialUserState: UserState = {
|
||||||
username: "",
|
username: "",
|
||||||
avatar_url: "",
|
avatar_url: "",
|
||||||
is_superuser: false,
|
is_superuser: false,
|
||||||
|
organisation: "",
|
||||||
course_session_experts: [],
|
course_session_experts: [],
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
language: defaultLanguage,
|
language: defaultLanguage,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue