From 127504b4f46180cfb3283baefb61295b15c29d5d Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Mon, 2 Feb 2026 16:55:43 +0000 Subject: [PATCH 1/3] Add normalized hashtag handling to prevent infinite API calls --- front-end/views/hashtag.mjs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 7b7e996..128d559 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -17,14 +17,21 @@ import {createHeading} from "../components/heading.mjs"; function hashtagView(hashtag) { destroy(); - apiService.getBloomsByHashtag(hashtag); + // Normalize the hashtag to always include a leading '#' and + // prevent infinite API calls by only fetching if the hashtag has changed + const normalizedHashtag = hashtag.startsWith("#") ? hashtag : `#${hashtag}`; + if (state.currentHashtag !== normalizedHashtag) { + state.currentHashtag = normalizedHashtag; + apiService.getBloomsByHashtag(normalizedHashtag); + } renderOne( state.isLoggedIn, getLogoutContainer(), "logout-template", - createLogout + createLogout, ); + document .querySelector("[data-action='logout']") ?.addEventListener("click", handleLogout); @@ -32,7 +39,7 @@ function hashtagView(hashtag) { state.isLoggedIn, getLoginContainer(), "login-template", - createLogin + createLogin, ); document .querySelector("[data-action='login']") @@ -42,13 +49,13 @@ function hashtagView(hashtag) { state.currentHashtag, getHeadingContainer(), "heading-template", - createHeading + createHeading, ); renderEach( state.hashtagBlooms || [], getTimelineContainer(), "bloom-template", - createBloom + createBloom, ); } From 4fd9692df5a61f88c125cfbcd9be6fc561cd10ed Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Mon, 2 Feb 2026 16:58:04 +0000 Subject: [PATCH 2/3] Add Playwright test to verify infinite hashtag requests stop --- front-end/tests/hashtag.spec.mjs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 front-end/tests/hashtag.spec.mjs diff --git a/front-end/tests/hashtag.spec.mjs b/front-end/tests/hashtag.spec.mjs new file mode 100644 index 0000000..a816810 --- /dev/null +++ b/front-end/tests/hashtag.spec.mjs @@ -0,0 +1,24 @@ +import { test, expect } from "@playwright/test"; + +test("should not make infinite hashtag endpoint requests", async ({ page }) => { + // ===== ARRANGE + const requests = []; + page.on("request", (request) => { + if ( + request.url().includes(":3000/hashtag/do") && + request.resourceType() === "fetch" + ) { + requests.push(request); + } + }); + // ====== ACT + // When I navigate to the hashtag + await page.goto("/#/hashtag/do"); + // And I wait a reasonable time for any additional requests + await page.waitForTimeout(200); + + // ====== ASSERT + // Then the number of requests should be 1 + console.log("Number of requests:", requests.length); + expect(requests.length).toEqual(1); +}); From ebc07595668a6fa7153ed459c25d5a092f01cdf7 Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Thu, 5 Feb 2026 20:23:34 +0000 Subject: [PATCH 3/3] Fix login handler to use form submit and add a playwright test --- front-end/tests/profile-logout-login.spec.mjs | 28 +++++++++++++++++++ front-end/views/hashtag.mjs | 4 +-- front-end/views/profile.mjs | 4 +-- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 front-end/tests/profile-logout-login.spec.mjs diff --git a/front-end/tests/profile-logout-login.spec.mjs b/front-end/tests/profile-logout-login.spec.mjs new file mode 100644 index 0000000..ff49ab1 --- /dev/null +++ b/front-end/tests/profile-logout-login.spec.mjs @@ -0,0 +1,28 @@ +import { test, expect } from "@playwright/test"; +import { loginAsSample, logout } from "./test-utils.mjs"; + +test("can visit another user's profile after logout and re-login", async ({ + page, +}) => { + // Given I am logged in + await loginAsSample(page); + + // And I visit another user's profile + await page.goto("/#/profile/AS"); + + // And I log out + await logout(page); + + // When I log in again + await loginAsSample(page); + + // And I visit the same profile again + await page.goto("/#/profile/AS"); + + // Then I should see the profile view (NOT a server error) + await expect(page.locator("#profile-container")).toBeVisible(); + + await expect(page.locator("body")).not.toContainText( + "Server does not support this operation", + ); +}); diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 128d559..f15918d 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -42,8 +42,8 @@ function hashtagView(hashtag) { createLogin, ); document - .querySelector("[data-action='login']") - ?.addEventListener("click", handleLogin); + .querySelector("[data-form='login']") + ?.addEventListener("submit", handleLogin); renderOne( state.currentHashtag, diff --git a/front-end/views/profile.mjs b/front-end/views/profile.mjs index dd2b92a..31139a9 100644 --- a/front-end/views/profile.mjs +++ b/front-end/views/profile.mjs @@ -39,8 +39,8 @@ function profileView(username) { createLogin ); document - .querySelector("[data-action='login']") - ?.addEventListener("click", handleLogin); + .querySelector("[data-form='login']") + ?.addEventListener("submit", handleLogin); const profileData = state.profiles.find((p) => p.username === username); if (profileData) {