feat(sprint-6): Phase 6 — Notifications (WebSocket) + PWA
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled

- WebSocket: Spring STOMP + SockJS, NotificationService, persistent notifications table
- NotificationController: GET/PUT endpoints for notification management
- Frontend: notification bell with unread badge, dropdown panel, real-time via STOMP
- PWA: manifest.json, service worker (manual sw.js), offline page, install prompt
- PWA icons (192+512), dark theme colors, standalone display
- Full i18n (de/en) for notifications and PWA
- Flyway V10 migration for notifications table
- spring-boot-starter-websocket dependency added
This commit is contained in:
Patrick Plate
2026-06-12 23:02:44 +02:00
parent 076fd6f9b3
commit 599514c0db
39 changed files with 6684 additions and 3217 deletions
@@ -59,9 +59,9 @@ test.describe("Group 1: Login Form Interactions", () => {
expect(page.url()).toContain("/login")
// The email input should fail native HTML5 validation
const isInvalid = await page.locator('input[id="email"]').evaluate(
(el: HTMLInputElement) => !el.checkValidity()
)
const isInvalid = await page
.locator('input[id="email"]')
.evaluate((el: HTMLInputElement) => !el.checkValidity())
expect(isInvalid).toBe(true)
})
@@ -192,7 +192,9 @@ test.describe("Group 3: Navigation & Layout", () => {
const url = page.url()
// If redirected to login — that's also acceptable behavior for protected routes
const is404OrLogin = url.includes("/login") || url.includes("not-found")
expect(is404OrLogin || (await page.locator("body").textContent()) !== "").toBe(true)
expect(
is404OrLogin || (await page.locator("body").textContent()) !== ""
).toBe(true)
})
test("3.3 - Responsive: mobile viewport (375px)", async ({ page }) => {
@@ -611,9 +613,7 @@ test.describe("Group 11: Accessibility Basics", () => {
await expect(emailInput).toHaveAttribute("autoComplete", "email")
})
test("11.5 - Form submit button is keyboard-accessible", async ({
page,
}) => {
test("11.5 - Form submit button is keyboard-accessible", async ({ page }) => {
await page.goto("/login", { waitUntil: "domcontentloaded" })
await page.waitForSelector('input[id="email"]', { timeout: 15000 })
@@ -683,7 +683,8 @@ test.describe("Group 12: Error States & Edge Cases", () => {
// Page should not crash — wait and verify it's still functional
await page.waitForTimeout(3000)
const isStillOnLogin = page.url().includes("/login") || page.url().includes("/dashboard")
const isStillOnLogin =
page.url().includes("/login") || page.url().includes("/dashboard")
expect(isStillOnLogin).toBe(true)
})