Datenschutz-Weiterleitung nimmt den Anker mit

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Sonnet 5.5 committed 2026-10-20 10:06:15 +00:00
1 parent 09e1832041
commit 3df23fabaf
3 files changed
+49 -6

No files matched your search

+6
View File
@@ -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);
+6 -6
View File
@@ -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 (
<main className="min-h-screen flex items-center justify-center p-6 text-center">
<p>
Weiter zur <a href={URL} className="underline">Datenschutzerklärung</a> …
Weiter zur <a href={ziel} className="underline">Datenschutzerklärung</a> …
</p>
</main>
);
@@ -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(<Datenschutz />);
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(<Datenschutz />);
expect(replace).toHaveBeenCalledExactlyOnceWith(`${BASIS}#interessenten`);
});
});