diff --git a/src/lib/datenschutzZiel.ts b/src/lib/datenschutzZiel.ts new file mode 100644 index 0000000..5ab5e78 --- /dev/null +++ b/src/lib/datenschutzZiel.ts @@ -0,0 +1,6 @@ +// Die Datenschutzerklärung liegt zentral im Kundenportal. Ein Anker aus der Adresse +// (zum Beispiel #interessenten aus der Kaltmail) wird nur übernommen, wenn er +// ausschließlich aus Buchstaben, Ziffern, Unterstrich und Bindestrich besteht. +const BASIS = "https://project.webklar.com/datenschutz.html"; + +export const weiterleitungsZiel = (hash: string): string => (/^#[A-Za-z0-9_-]{1,64}$/.test(hash) ? BASIS + hash : BASIS); diff --git a/src/pages/Datenschutz.tsx b/src/pages/Datenschutz.tsx index 96b699f..18cdc88 100644 --- a/src/pages/Datenschutz.tsx +++ b/src/pages/Datenschutz.tsx @@ -1,17 +1,17 @@ import { useEffect } from "react"; +import { weiterleitungsZiel } from "@/lib/datenschutzZiel"; // Die Datenschutzerklärung gilt für webklar.com und das Kundenportal gemeinsam -// und liegt zentral im Kundenportal. -const URL = "https://project.webklar.com/datenschutz.html"; - +// und liegt zentral im Kundenportal. Ein Anker (#interessenten) geht dorthin mit. const Datenschutz = () => { + const ziel = weiterleitungsZiel(window.location.hash); useEffect(() => { - window.location.replace(URL); - }, []); + window.location.replace(ziel); + }, [ziel]); return (

- Weiter zur Datenschutzerklärung … + Weiter zur Datenschutzerklärung …

); diff --git a/src/test/datenschutz-weiterleitung.test.tsx b/src/test/datenschutz-weiterleitung.test.tsx new file mode 100644 index 0000000..7945b14 --- /dev/null +++ b/src/test/datenschutz-weiterleitung.test.tsx @@ -0,0 +1,37 @@ +import { afterEach, describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import Datenschutz from "@/pages/Datenschutz"; +import { weiterleitungsZiel } from "@/lib/datenschutzZiel"; + +const BASIS = "https://project.webklar.com/datenschutz.html"; + +describe("Weiterleitung /datenschutz", () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + window.history.pushState({}, "", "/"); + }); + + it("nimmt einen gültigen Anker mit", () => { + expect(weiterleitungsZiel("#interessenten")).toBe(`${BASIS}#interessenten`); + expect(weiterleitungsZiel("#chat-anhaenge")).toBe(`${BASIS}#chat-anhaenge`); + }); + it("ohne Anker oder mit ungültigem Anker bleibt es bei der Seite", () => { + for (const hash of ["", "#", "#javascript:alert(1)", '#a"b', "#a b", `#${"a".repeat(65)}`]) { + expect(weiterleitungsZiel(hash)).toBe(BASIS); + } + }); + it("der Link im Text trägt denselben Anker", () => { + vi.spyOn(console, "error").mockImplementation(() => {}); // jsdom meldet die nicht umgesetzte Navigation + window.history.pushState({}, "", "/datenschutz#interessenten"); + render(); + expect(screen.getByRole("link", { name: "Datenschutzerklärung" })).toHaveAttribute("href", `${BASIS}#interessenten`); + }); + it("leitet mit dem Anker auf die Datenschutzerklärung im Portal weiter", () => { + window.history.pushState({}, "", "/datenschutz#interessenten"); + const replace = vi.fn(); + vi.stubGlobal("location", { hash: window.location.hash, replace }); + render(); + expect(replace).toHaveBeenCalledExactlyOnceWith(`${BASIS}#interessenten`); + }); +});