Skip to content

Commit 2e89acb

Browse files
Merge pull request #290 from Nosto/copilot/fix-289
feat: introduce simple fetch facade module
2 parents 8fb1aea + 9a6c6cb commit 2e89acb

File tree

6 files changed

+102
-12
lines changed

6 files changed

+102
-12
lines changed

src/components/NostoDynamicCard/NostoDynamicCard.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { assertRequired } from "@/utils"
2+
import { getText } from "@/utils/fetch"
23
import { customElement } from "../decorators"
34
import { NostoElement } from "../NostoElement"
45

@@ -84,11 +85,7 @@ async function getMarkup(element: NostoDynamicCard) {
8485
if (element.variantId) {
8586
params.set("variant", element.variantId)
8687
}
87-
const result = await fetch(`/products/${element.handle}?${params}`)
88-
if (!result.ok) {
89-
throw new Error("Failed to fetch product data")
90-
}
91-
let markup = await result.text()
88+
let markup = await getText(`/products/${element.handle}?${params}`)
9289
if (element.section) {
9390
const parser = new DOMParser()
9491
const doc = parser.parseFromString(markup, "text/html")

src/components/NostoSectionCampaign/NostoSectionCampaign.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { nostojs } from "@nosto/nosto-js"
2+
import { getText } from "@/utils/fetch"
23
import { customElement } from "../decorators"
34
import { NostoElement } from "../NostoElement"
45
import { addRequest } from "../NostoCampaign/orchestrator"
@@ -51,11 +52,7 @@ async function getSectionMarkup(element: NostoSectionCampaign, rec: JSONResult)
5152
const target = new URL("/search", window.location.href)
5253
target.searchParams.set("section_id", element.section)
5354
target.searchParams.set("q", handles)
54-
const result = await fetch(target)
55-
if (!result.ok) {
56-
throw new Error(`Failed to fetch section ${element.section}`)
57-
}
58-
const sectionHtml = await result.text()
55+
const sectionHtml = await getText(target.href)
5956
const parser = new DOMParser()
6057
const doc = parser.parseFromString(sectionHtml, "text/html")
6158
if (rec.title) {

src/utils/fetch.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Internal function to handle common fetch logic with error checking.
3+
* @param url - The URL to fetch
4+
* @returns Promise that resolves to the Response object
5+
* @throws Error if the fetch request fails
6+
*/
7+
async function fetchWithErrorHandling(url: string) {
8+
const response = await fetch(url)
9+
if (!response.ok) {
10+
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`)
11+
}
12+
return response
13+
}
14+
15+
/**
16+
* Fetches a URL and returns the response as text.
17+
* @param url - The URL to fetch
18+
* @returns Promise that resolves to the response text
19+
* @throws Error if the fetch request fails
20+
*/
21+
export async function getText(url: string) {
22+
const response = await fetchWithErrorHandling(url)
23+
return response.text()
24+
}
25+
26+
/**
27+
* Fetches a URL and returns the response as a JSON object.
28+
* @param url - The URL to fetch
29+
* @returns Promise that resolves to the parsed JSON response
30+
* @throws Error if the fetch request fails or JSON parsing fails
31+
*/
32+
export async function getJSON(url: string) {
33+
const response = await fetchWithErrorHandling(url)
34+
return response.json()
35+
}

test/components/NostoDynamicCard.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ describe("NostoDynamicCard", () => {
152152

153153
const card = (<nosto-dynamic-card handle="handle-error" template="default" />) as NostoDynamicCard
154154

155-
await expect(card.connectedCallback()).rejects.toThrow("Failed to fetch product data")
155+
await expect(card.connectedCallback()).rejects.toThrow("Failed to fetch /products/handle-error")
156156
})
157157

158158
it("throws error when markup is invalid", async () => {

test/components/NostoSectionCampaign.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe("NostoSectionCampaign", () => {
4545

4646
const el = (<nosto-section-campaign placement="placement1" section="missing-section" />) as NostoSectionCampaign
4747

48-
await expect(el.connectedCallback()).rejects.toThrow("Failed to fetch section missing-section")
48+
await expect(el.connectedCallback()).rejects.toThrow("Failed to fetch http://localhost:3000/search")
4949
expect(el.hasAttribute("loading")).toBe(false)
5050
})
5151

test/utils/fetch.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, it, expect, vi, afterEach } from "vitest"
2+
import { getText, getJSON } from "@/utils/fetch"
3+
import { addHandlers } from "../msw.setup"
4+
import { http, HttpResponse } from "msw"
5+
6+
describe("fetch facade", () => {
7+
afterEach(() => {
8+
vi.clearAllMocks()
9+
})
10+
11+
describe("getText", () => {
12+
it("should fetch URL and return text content", async () => {
13+
addHandlers(
14+
http.get("https://example.com/test", () => {
15+
return HttpResponse.text("Hello, World!")
16+
})
17+
)
18+
19+
const result = await getText("https://example.com/test")
20+
expect(result).toBe("Hello, World!")
21+
})
22+
23+
it("should throw error when fetch response is not ok", async () => {
24+
addHandlers(
25+
http.get("https://example.com/missing", () => {
26+
return HttpResponse.text("Not Found", { status: 404 })
27+
})
28+
)
29+
30+
await expect(getText("https://example.com/missing")).rejects.toThrow(
31+
"Failed to fetch https://example.com/missing: 404 Not Found"
32+
)
33+
})
34+
})
35+
36+
describe("getJSON", () => {
37+
it("should fetch URL and return JSON object", async () => {
38+
const mockData = { message: "Hello", count: 42 }
39+
addHandlers(
40+
http.get("https://api.example.com/data", () => {
41+
return HttpResponse.json(mockData)
42+
})
43+
)
44+
45+
const result = await getJSON("https://api.example.com/data")
46+
expect(result).toEqual(mockData)
47+
})
48+
49+
it("should throw error when fetch response is not ok", async () => {
50+
addHandlers(
51+
http.get("https://api.example.com/error", () => {
52+
return HttpResponse.json({ error: "Server Error" }, { status: 500 })
53+
})
54+
)
55+
56+
await expect(getJSON("https://api.example.com/error")).rejects.toThrow(
57+
"Failed to fetch https://api.example.com/error: 500 Internal Server Error"
58+
)
59+
})
60+
})
61+
})

0 commit comments

Comments
 (0)