From 127504b4f46180cfb3283baefb61295b15c29d5d Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Mon, 2 Feb 2026 16:55:43 +0000 Subject: [PATCH 1/4] 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/4] 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 64ea09c6776b6fc07d052c38f5ea7ca06c3af6e4 Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Thu, 5 Feb 2026 21:34:52 +0000 Subject: [PATCH 3/4] Check that API response is for the current hashtag --- front-end/views/hashtag.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/front-end/views/hashtag.mjs b/front-end/views/hashtag.mjs index 128d559..c63005e 100644 --- a/front-end/views/hashtag.mjs +++ b/front-end/views/hashtag.mjs @@ -22,8 +22,16 @@ function hashtagView(hashtag) { const normalizedHashtag = hashtag.startsWith("#") ? hashtag : `#${hashtag}`; if (state.currentHashtag !== normalizedHashtag) { + // Update state only if the hashtag changed state.currentHashtag = normalizedHashtag; - apiService.getBloomsByHashtag(normalizedHashtag); + + // Fetch blooms for the new hashtag + apiService.getBloomsByHashtag(normalizedHashtag).then(()=>{ + // Check that the hashtag is still current to avoid old data + if (state.currentHashtag === normalizedHashtag){ + // Nothing to do here for now + } + }); } renderOne( state.isLoggedIn, From 80207bf2cb1c62bc5096b8493ea345dc3a2c325d Mon Sep 17 00:00:00 2001 From: sheida-shab Date: Thu, 5 Feb 2026 22:07:17 +0000 Subject: [PATCH 4/4] Replace fixed timeout with waitForResponse to fix hashtag test --- front-end/tests/hashtag.spec.mjs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/front-end/tests/hashtag.spec.mjs b/front-end/tests/hashtag.spec.mjs index a816810..2418580 100644 --- a/front-end/tests/hashtag.spec.mjs +++ b/front-end/tests/hashtag.spec.mjs @@ -3,9 +3,11 @@ import { test, expect } from "@playwright/test"; test("should not make infinite hashtag endpoint requests", async ({ page }) => { // ===== ARRANGE const requests = []; + const hashtag = "SwizBiz"; + page.on("request", (request) => { if ( - request.url().includes(":3000/hashtag/do") && + request.url().includes(`:3000/hashtag/${hashtag}`) && request.resourceType() === "fetch" ) { requests.push(request); @@ -13,9 +15,14 @@ test("should not make infinite hashtag endpoint requests", async ({ page }) => { }); // ====== 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); + await page.goto(`/#/hashtag/${hashtag}`); + + // Wait for the hashtag API to respond before continuing + await page.waitForResponse( + (response) => + response.url().includes(`/hashtag/${hashtag}`) && + response.status() === 200, + ); // ====== ASSERT // Then the number of requests should be 1