From 031f111d7eac7aaad9047d9deefa849dda07d166 Mon Sep 17 00:00:00 2001 From: Michael Adlung Date: Wed, 6 Aug 2025 14:36:37 +0200 Subject: [PATCH 01/12] fix: resolve merge conflicts --- src/api/message.js | 14 ++++++++-- src/pages/welcome/Welcome.jsx | 31 +++++++++++----------- src/routes/routes.js | 12 ++++----- src/service/message.js | 26 ------------------- src/service/postHandlers.js | 49 +++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 51 deletions(-) delete mode 100644 src/service/message.js create mode 100644 src/service/postHandlers.js diff --git a/src/api/message.js b/src/api/message.js index 953f24f8..a68194f2 100644 --- a/src/api/message.js +++ b/src/api/message.js @@ -9,12 +9,22 @@ * This file contains client-side API functions that call our express.js backend routes */ -export const getMessage = async () => { +export const getPost = async (postId) => { try { - const response = await fetch('/api/message'); + const response = await fetch(`/api/post/${postId}`); const data = await response.json(); return data.message; } catch (error) { throw new Error('Failed to load message: ', error); } }; + +export const getComments = async (postId) => { + try { + const response = await fetch(`/api/comments?postId=${postId}`); + const data = await response.json(); + return data.message; + } catch (error) { + throw new Error('Failed to load comments: ', error); + } +}; diff --git a/src/pages/welcome/Welcome.jsx b/src/pages/welcome/Welcome.jsx index 389012c5..ea041e77 100644 --- a/src/pages/welcome/Welcome.jsx +++ b/src/pages/welcome/Welcome.jsx @@ -15,30 +15,19 @@ import { } from '@carbon/react'; import { useEffect, useState } from 'react'; -import { getMessage } from '../../api/message.js'; +import { getComments, getPost } from '../../api/message.js'; import { Footer } from '../../components/footer/Footer'; import { WelcomeHeader } from './WelcomeHeader.jsx'; import { PageLayout } from '../../layouts/page-layout.jsx'; +import { use } from 'react'; // The styles are imported into index.scss by default. // Do the same unless you have a good reason not to. // import './welcome.scss'; const Welcome = () => { - const [message, setMessage] = useState(''); - - useEffect(() => { - const loadMessage = async () => { - try { - const msg = await getMessage(); - setMessage(msg); - } catch { - setMessage('Failed to load message'); - } - }; - - loadMessage(); - }, []); + const post = use(getPost(1)); + const comments = use(getComments(1)); return ( { keeping components clean and separating network logic.

- Message: {message || 'Loading...'} +
+ {post.title || 'Loading...'} +
+
+ Comments + {comments.map((comment) => ( + + {comment.title} + + ))} +
diff --git a/src/routes/routes.js b/src/routes/routes.js index 49043e51..b9bafba8 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -5,11 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import { getMessage } from '../service/message.js'; - -export const routeHandlers = { - getMessage, -}; +// eslint-disable-next-line import/namespace,import/default, import/no-named-as-default, import/no-named-as-default-member +import postHandlers from '../service/postHandlers.js'; /** * Registers all API routes on the given Express app instance. @@ -19,6 +16,7 @@ export const routeHandlers = { * @param app - Express app instance OR msw router in case of unit testing * @param handlers - Route handlers (can be mocked for testing) */ -export const getRoutes = (app, handlers = routeHandlers) => { - app.get('/api/message', handlers.getMessage); +export const getRoutes = (app, handlers = postHandlers) => { + app.get('/api/post/:id', handlers.getPost); + app.get('/api/comments', handlers.getComments); }; diff --git a/src/service/message.js b/src/service/message.js deleted file mode 100644 index f611e0cb..00000000 --- a/src/service/message.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright IBM Corp. 2025 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * This file contains the functions that do async network requests - */ - -export const getMessage = async (req, res) => { - try { - const response = await fetch( - // TODO: replace with actual endpoint URL - 'https://jsonplaceholder.typicode.com/posts/1', - ); - // The sample endpoint returns a blogpost - const blogpost = await response.json(); - - // Return the blogpost's title - res.json({ message: blogpost.title }); - } catch { - res.status(500).json({ message: 'Failed to fetch message' }); - } -}; diff --git a/src/service/postHandlers.js b/src/service/postHandlers.js new file mode 100644 index 00000000..464bf29c --- /dev/null +++ b/src/service/postHandlers.js @@ -0,0 +1,49 @@ +/** + * Copyright IBM Corp. 2025 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * This file contains the functions that do async network requests + */ + +export const getPost = async ({ params: { id } }, res) => { + console.log(id); + + try { + const response = await fetch( + // TODO: replace with actual endpoint URL + `https://jsonplaceholder.typicode.com/posts/${id}`, + ); + // The sample endpoint returns a blogpost + const blogpost = await response.json(); + + // Return the blogpost's title + res.json({ blogpost }); + } catch { + res.status(500).json({ message: 'Failed to fetch message' }); + } +}; + +export const getComments = async ({ query: { postId } }, res) => { + try { + const response = await fetch( + // TODO: replace with actual endpoint URL + `https://jsonplaceholder.typicode.com/comments?postId=${postId}`, + ); + // The sample endpoint returns a blogpost + const comments = await response.json(); + + // Return the blogpost's title + res.json({ comments }); + } catch { + res.status(500).json({ message: 'Failed to fetch message' }); + } +}; + +export default { + getComments, + getPost, +}; From c2258ff6b7220fff94257b1f5b0e8c9c670a75d7 Mon Sep 17 00:00:00 2001 From: Michael Adlung Date: Wed, 6 Aug 2025 16:09:08 +0200 Subject: [PATCH 02/12] Added comments rendering --- src/api/message.js | 14 +++---- src/pages/welcome/Welcome.jsx | 23 +++-------- src/pages/welcome/post/PostComponent.jsx | 50 ++++++++++++++++++++++++ src/service/postHandlers.js | 4 +- 4 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 src/pages/welcome/post/PostComponent.jsx diff --git a/src/api/message.js b/src/api/message.js index a68194f2..57c9edc9 100644 --- a/src/api/message.js +++ b/src/api/message.js @@ -11,19 +11,19 @@ export const getPost = async (postId) => { try { - const response = await fetch(`/api/post/${postId}`); - const data = await response.json(); - return data.message; + const response = await fetch(`http://localhost:5173/api/post/${postId}`); + return await response.json(); } catch (error) { - throw new Error('Failed to load message: ', error); + throw new Error('Failed to load post: ', error); } }; export const getComments = async (postId) => { try { - const response = await fetch(`/api/comments?postId=${postId}`); - const data = await response.json(); - return data.message; + const response = await fetch( + `http://localhost:5173/api/comments?postId=${postId}`, + ); + return await response.json(); } catch (error) { throw new Error('Failed to load comments: ', error); } diff --git a/src/pages/welcome/Welcome.jsx b/src/pages/welcome/Welcome.jsx index ea041e77..5dc17013 100644 --- a/src/pages/welcome/Welcome.jsx +++ b/src/pages/welcome/Welcome.jsx @@ -15,20 +15,17 @@ import { } from '@carbon/react'; import { useEffect, useState } from 'react'; -import { getComments, getPost } from '../../api/message.js'; import { Footer } from '../../components/footer/Footer'; import { WelcomeHeader } from './WelcomeHeader.jsx'; import { PageLayout } from '../../layouts/page-layout.jsx'; -import { use } from 'react'; +import PostComponent from './post/PostComponent.jsx'; +import { Suspense } from 'react'; // The styles are imported into index.scss by default. // Do the same unless you have a good reason not to. // import './welcome.scss'; const Welcome = () => { - const post = use(getPost(1)); - const comments = use(getComments(1)); - return ( { endpoint. This showcases how to perform data fetching while keeping components clean and separating network logic.

- -
- {post.title || 'Loading...'} -
-
- Comments - {comments.map((comment) => ( - - {comment.title} - - ))} -
-
+ + + diff --git a/src/pages/welcome/post/PostComponent.jsx b/src/pages/welcome/post/PostComponent.jsx new file mode 100644 index 00000000..f2efc044 --- /dev/null +++ b/src/pages/welcome/post/PostComponent.jsx @@ -0,0 +1,50 @@ +import { getComments, getPost } from '../../../api/message.js'; +import { Heading, Section, Tile } from '@carbon/react'; +import { useEffect, useState } from 'react'; + +const PostComponent = () => { + const [post, setPost] = useState(); + const [comments, setComments] = useState([]); + + useEffect(() => { + const loadPost = async () => { + try { + const post = await getPost(1); + setPost(post); + } catch { + setPost('Failed to load message'); + } + }; + + const loadComments = async () => { + try { + const comments = await getComments(1); + setComments(comments); + } catch { + setPost('Failed to load comments'); + } + }; + + loadPost(); + loadComments(); + }, []); + + return ( + +
+ {post?.title ?? 'Loading...'} +
+
+ Comments + {comments?.map((comment) => ( + + {`From ${comment.email}`} +
{comment.body}
+
+ ))} +
+
+ ); +}; + +export default PostComponent; diff --git a/src/service/postHandlers.js b/src/service/postHandlers.js index 464bf29c..12e8e94f 100644 --- a/src/service/postHandlers.js +++ b/src/service/postHandlers.js @@ -21,7 +21,7 @@ export const getPost = async ({ params: { id } }, res) => { const blogpost = await response.json(); // Return the blogpost's title - res.json({ blogpost }); + res.json(blogpost); } catch { res.status(500).json({ message: 'Failed to fetch message' }); } @@ -37,7 +37,7 @@ export const getComments = async ({ query: { postId } }, res) => { const comments = await response.json(); // Return the blogpost's title - res.json({ comments }); + res.json(comments); } catch { res.status(500).json({ message: 'Failed to fetch message' }); } From 60dfcd192c0d279b6292e8c253e797e12654ef47 Mon Sep 17 00:00:00 2001 From: Michael Adlung Date: Wed, 6 Aug 2025 16:41:16 +0200 Subject: [PATCH 03/12] Refactored network logic to move it out of the hook --- src/pages/welcome/post/PostComponent.jsx | 77 ++++++++++++++---------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/src/pages/welcome/post/PostComponent.jsx b/src/pages/welcome/post/PostComponent.jsx index f2efc044..20ef048e 100644 --- a/src/pages/welcome/post/PostComponent.jsx +++ b/src/pages/welcome/post/PostComponent.jsx @@ -1,49 +1,62 @@ import { getComments, getPost } from '../../../api/message.js'; -import { Heading, Section, Tile } from '@carbon/react'; +import { Heading, Layer, Section, Tile } from '@carbon/react'; import { useEffect, useState } from 'react'; const PostComponent = () => { const [post, setPost] = useState(); const [comments, setComments] = useState([]); - useEffect(() => { - const loadPost = async () => { - try { - const post = await getPost(1); - setPost(post); - } catch { - setPost('Failed to load message'); - } - }; + const loadPost = async () => { + try { + const post = await getPost(1); + setPost(post); + } catch { + setPost('Failed to load message'); + } + }; - const loadComments = async () => { - try { - const comments = await getComments(1); - setComments(comments); - } catch { - setPost('Failed to load comments'); - } - }; + const loadComments = async () => { + try { + const comments = await getComments(1); + setComments(comments); + } catch { + setPost('Failed to load comments'); + } + }; + useEffect(() => { loadPost(); loadComments(); }, []); return ( - -
- {post?.title ?? 'Loading...'} -
-
- Comments - {comments?.map((comment) => ( - - {`From ${comment.email}`} -
{comment.body}
-
- ))} -
-
+
+ Posts + +
+
+ {post?.title ?? 'Loading...'} +
{post?.body}
+
+
+ +
+ Comments +
+ + + {comments?.map((comment) => ( + + {`From ${comment.email}`} +
{comment.body}
+
+ ))} +
+
+
+
+
+
); }; From 3ef4622069a96c8711333549096ef97124b6d559 Mon Sep 17 00:00:00 2001 From: Mike <49228392+Mikadv@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:36:50 +0200 Subject: [PATCH 04/12] Potential fix for code scanning alert no. 6: Server-side request forgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/service/postHandlers.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/service/postHandlers.js b/src/service/postHandlers.js index 12e8e94f..e0d2cd46 100644 --- a/src/service/postHandlers.js +++ b/src/service/postHandlers.js @@ -12,6 +12,11 @@ export const getPost = async ({ params: { id } }, res) => { console.log(id); + // Validate that id is a positive integer + if (!/^\d+$/.test(id)) { + return res.status(400).json({ message: 'Invalid post id' }); + } + try { const response = await fetch( // TODO: replace with actual endpoint URL From 6a570932144ec30101dbb326d36f496467db29af Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Fri, 3 Oct 2025 12:53:23 +0200 Subject: [PATCH 05/12] refactor: update error messages in postHandlers for clarity and consistency also removed unnecessary console.log Resolves: no-ticket Signed-off-by: [Balint Lendvai] --- src/service/postHandlers.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/service/postHandlers.js b/src/service/postHandlers.js index e0d2cd46..c9fd02c8 100644 --- a/src/service/postHandlers.js +++ b/src/service/postHandlers.js @@ -10,8 +10,6 @@ */ export const getPost = async ({ params: { id } }, res) => { - console.log(id); - // Validate that id is a positive integer if (!/^\d+$/.test(id)) { return res.status(400).json({ message: 'Invalid post id' }); @@ -28,7 +26,7 @@ export const getPost = async ({ params: { id } }, res) => { // Return the blogpost's title res.json(blogpost); } catch { - res.status(500).json({ message: 'Failed to fetch message' }); + res.status(500).json({ message: 'Failed to fetch post' }); } }; @@ -44,7 +42,7 @@ export const getComments = async ({ query: { postId } }, res) => { // Return the blogpost's title res.json(comments); } catch { - res.status(500).json({ message: 'Failed to fetch message' }); + res.status(500).json({ message: 'Failed to fetch comments' }); } }; From 56aedf27741c1585991b0f08ea88b9c69b6ebac3 Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Fri, 3 Oct 2025 12:55:25 +0200 Subject: [PATCH 06/12] chore(api): add TODO comments for handling production and development environments in message API functions Resolves: no-ticket Signed-off-by: [Balint Lendvai] --- src/api/message.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/message.js b/src/api/message.js index 57c9edc9..94ab904f 100644 --- a/src/api/message.js +++ b/src/api/message.js @@ -11,6 +11,7 @@ export const getPost = async (postId) => { try { + // TODO: handle production and development environments const response = await fetch(`http://localhost:5173/api/post/${postId}`); return await response.json(); } catch (error) { @@ -20,6 +21,7 @@ export const getPost = async (postId) => { export const getComments = async (postId) => { try { + // TODO: handle production and development environments const response = await fetch( `http://localhost:5173/api/comments?postId=${postId}`, ); From 1e1885c5de0a1f0a8202461f46042c50109b3dd8 Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Fri, 3 Oct 2025 12:57:01 +0200 Subject: [PATCH 07/12] feat(welcome): enhance welcome page with structured data fetching explanation - Updated layout to improve readability and added a detailed explanation of the data fetching process. - fixed posts responsive behaviour in mobile size - Removed unnecessary styles from the dynamic message section paragraphs. Resolves: no-ticket Signed-off-by: [Balint Lendvai] --- src/pages/welcome/Welcome.jsx | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/pages/welcome/Welcome.jsx b/src/pages/welcome/Welcome.jsx index 5dc17013..53c8aa02 100644 --- a/src/pages/welcome/Welcome.jsx +++ b/src/pages/welcome/Welcome.jsx @@ -11,7 +11,10 @@ import { Grid, Heading, Tile, + UnorderedList, + ListItem, Section, + Stack, } from '@carbon/react'; import { useEffect, useState } from 'react'; @@ -135,16 +138,40 @@ const Welcome = () => { -

- Below is a dynamically fetched message from an external API - endpoint. This showcases how to perform data fetching while - keeping components clean and separating network logic. -

+ +

+ Below is a dynamically fetched message from an external API + endpoint. This showcases how to perform data fetching while + keeping components clean and separating network logic. Here is + how it works: +

+ + + UI Layer - PostComponent.jsx manages React + state and renders the data using Carbon Design components + + + API Layer - Client-side functions in{' '} + api/message.js handle HTTP requests to our + Express backend + + + Service Layer - Server-side handlers in{' '} + service/postHandlers.js proxy requests to + external APIs (JSONPlaceholder) + + +

+ This pattern keeps your components focused on presentation + while centralizing data fetching logic for reusability and + testability. +

+
From 9344d9f31505c85b7605533b6fa18ccb2df74cc4 Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Fri, 3 Oct 2025 12:58:31 +0200 Subject: [PATCH 08/12] fix(post): update error handling and improve layout in PostComponent - Handled review comments - Changed error handling to set comments state correctly on failure. - Enhanced layout by introducing Stack components for better spacing and readability. - Updated paragraph elements for post body and comments for consistency. Resolves: no-ticket Signed-off-by: [Balint Lendvai] --- src/pages/welcome/post/PostComponent.jsx | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/pages/welcome/post/PostComponent.jsx b/src/pages/welcome/post/PostComponent.jsx index 20ef048e..b00a338f 100644 --- a/src/pages/welcome/post/PostComponent.jsx +++ b/src/pages/welcome/post/PostComponent.jsx @@ -1,5 +1,5 @@ import { getComments, getPost } from '../../../api/message.js'; -import { Heading, Layer, Section, Tile } from '@carbon/react'; +import { Heading, Section, Tile, Stack, Layer } from '@carbon/react'; import { useEffect, useState } from 'react'; const PostComponent = () => { @@ -20,7 +20,7 @@ const PostComponent = () => { const comments = await getComments(1); setComments(comments); } catch { - setPost('Failed to load comments'); + setComments('Failed to load comments'); } }; @@ -33,28 +33,32 @@ const PostComponent = () => {
Posts -
-
- {post?.title ?? 'Loading...'} -
{post?.body}
+ +
+
+ {post?.title ?? 'Loading...'} +

{post?.body}

+
-
-
- Comments -
- - - {comments?.map((comment) => ( - - {`From ${comment.email}`} -
{comment.body}
-
- ))} -
-
+
+ + Comments +
+ + {comments?.map((comment) => ( + + + {`From ${comment.email}`} +

{comment.body}

+
+
+ ))} +
+
+
-
+
); From 77d44e8064bf1b55531fd99f66347cc1833526b3 Mon Sep 17 00:00:00 2001 From: Lee Chase Date: Fri, 3 Oct 2025 13:28:28 +0100 Subject: [PATCH 09/12] fix: remove layer level and linter issues --- package-lock.json | 44 ++++++++---------------- src/pages/welcome/post/PostComponent.jsx | 2 +- src/routes/routes.js | 2 +- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93ec08eb..898675e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1006,8 +1006,7 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-dart": { "version": "2.3.1", @@ -1147,16 +1146,14 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.12.tgz", "integrity": "sha512-JFffQ1dDVEyJq6tCDWv0r/RqkdSnV43P2F/3jJ9rwLgdsOIXwQbXrz6QDlvQLVvNSnORH9KjDtenFTGDyzfCaA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -1354,8 +1351,7 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -1503,7 +1499,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1550,7 +1545,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -4511,7 +4505,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/chai": { "version": "5.2.3", @@ -4567,7 +4562,6 @@ "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4578,7 +4572,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4788,7 +4781,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6630,8 +6622,7 @@ "version": "0.0.1467305", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1467305.tgz", "integrity": "sha512-LxwMLqBoPPGpMdRL4NkLFRNy3QLp6Uqa7GNp1v6JaBheop2QrB9Q7q0A/q/CYYP9sBfZdHOyszVx4gc9zyk7ow==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -6664,7 +6655,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dot-prop": { "version": "5.3.0", @@ -7117,7 +7109,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7178,7 +7169,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -9497,7 +9487,6 @@ "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.23", "@asamuzakjp/dom-selector": "^6.7.4", @@ -10202,6 +10191,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11259,7 +11249,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11343,7 +11332,6 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11375,7 +11363,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11405,6 +11392,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11420,6 +11408,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -11432,7 +11421,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -11615,7 +11605,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11625,7 +11614,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12126,7 +12114,6 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz", "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==", "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -13216,7 +13203,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", @@ -14250,7 +14236,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14326,7 +14311,6 @@ "integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.10", "@vitest/mocker": "4.0.10", diff --git a/src/pages/welcome/post/PostComponent.jsx b/src/pages/welcome/post/PostComponent.jsx index b00a338f..fee7b13e 100644 --- a/src/pages/welcome/post/PostComponent.jsx +++ b/src/pages/welcome/post/PostComponent.jsx @@ -47,7 +47,7 @@ const PostComponent = () => {
{comments?.map((comment) => ( - + {`From ${comment.email}`}

{comment.body}

diff --git a/src/routes/routes.js b/src/routes/routes.js index b9bafba8..2cfb7bf4 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -// eslint-disable-next-line import/namespace,import/default, import/no-named-as-default, import/no-named-as-default-member +// eslint-disable-next-line import/default, import/no-named-as-default, import/no-named-as-default-member import postHandlers from '../service/postHandlers.js'; /** From 6ce2949030b4211357da10853fcd918c327a6fb4 Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Thu, 13 Nov 2025 20:12:07 +0100 Subject: [PATCH 10/12] refactor(api): simplify API endpoint URLs in message.js - Removed hardcoded localhost URLs for fetching posts and comments. - removed unused eslint-disabled directive import/namespace Resolves: no-ticket --- src/api/message.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/api/message.js b/src/api/message.js index 94ab904f..21cec60d 100644 --- a/src/api/message.js +++ b/src/api/message.js @@ -11,8 +11,7 @@ export const getPost = async (postId) => { try { - // TODO: handle production and development environments - const response = await fetch(`http://localhost:5173/api/post/${postId}`); + const response = await fetch(`/api/post/${postId}`); return await response.json(); } catch (error) { throw new Error('Failed to load post: ', error); @@ -21,10 +20,7 @@ export const getPost = async (postId) => { export const getComments = async (postId) => { try { - // TODO: handle production and development environments - const response = await fetch( - `http://localhost:5173/api/comments?postId=${postId}`, - ); + const response = await fetch(`/api/comments?postId=${postId}`); return await response.json(); } catch (error) { throw new Error('Failed to load comments: ', error); From 047193309808437eaa07085446d39bb82da5747f Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Thu, 13 Nov 2025 20:26:04 +0100 Subject: [PATCH 11/12] chore(package): remove 'peer' property from multiple dependencies in package-lock.json - Cleaned up package-lock.json by removing unnecessary 'peer' properties from various dependencies. - This was done automatically by npm after i run a fresh npm install Resolves: no-ticket Signed-off-by: [Balint Lendvai] --- package-lock.json | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 898675e0..dd625429 100644 --- a/package-lock.json +++ b/package-lock.json @@ -172,7 +172,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -551,7 +550,6 @@ "integrity": "sha512-J8hh5li0Q0RRS6IGg+MVPQVfjp1ePxzQsyMyjQOHLux8i8HwiWJmKLQ+4lKMzRXorLG2RtEoPVl00EaKlQEgKQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@carbon/layout": "^11.43.0", "@ibm/telemetry-js": "^1.5.0" @@ -649,7 +647,6 @@ "integrity": "sha512-aOUNqVv/5TGhNTn1HV+620ZlqhE7+Chs0TJoxwe/CCsLOdziCX9st3c5inyINPZDyvDK46mas1RmqvZwNYe/mA==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@ibm/telemetry-js": "^1.5.0" } @@ -660,7 +657,6 @@ "integrity": "sha512-bjFzY8Wy5Umj+g41ZGj3L3b/z2gDBDKfzfc9M3ZAHaj73PZ7Z/Z5jT0IFlihEv8wUwZfbhbtnb4jeNhckfLINA==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@ibm/telemetry-js": "^1.5.0" } @@ -671,7 +667,6 @@ "integrity": "sha512-BISe++dQ3wz0GRHvqM8l+gtQbqgDrDMpzcQ246qCp+X8BA0+XnH8nL/rLhAL8SpRpLOBqfk/JPiKPlWeYzLb6Q==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.3", "@carbon/feature-flags": "0.32.0", @@ -757,7 +752,6 @@ "integrity": "sha512-Kq4gan/qQCErgt46OpA2EU91g1CEsH+3duTBUZmoLwGqlPd3AwzYvoIxisUCrMSYut5J9CuxNxEBnCD8Iv9ghg==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@carbon/colors": "^11.42.0", "@carbon/layout": "^11.43.0", @@ -772,7 +766,6 @@ "integrity": "sha512-5SwbuMj7VO7QAxB273MQVMoVLEvwcMVflhmriWA4I+YWMDwtoprPHiXseO6ueoRGJp+TOqnItvkPMPOdKz5sjQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@carbon/grid": "^11.45.0", "@carbon/layout": "^11.43.0", @@ -1613,7 +1606,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -4410,7 +4402,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4505,8 +4496,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/chai": { "version": "5.2.3", @@ -5347,7 +5337,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -6655,8 +6644,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dot-prop": { "version": "5.3.0", @@ -10191,7 +10179,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11392,7 +11379,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -11408,7 +11394,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -11421,8 +11406,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", @@ -14972,7 +14956,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 41f6fea4fc89b8560983616d318022e52da74aaa Mon Sep 17 00:00:00 2001 From: Balint Lendvai Date: Thu, 13 Nov 2025 22:14:31 +0100 Subject: [PATCH 12/12] fix: failing tests due to missing mock --- src/pages/welcome/post/PostComponent.jsx | 19 +++++++------ src/service/postHandlers.js | 6 ++-- src/test/router.js | 16 +++++++++-- src/test/server.js | 36 +++++++++++++++++++++++- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/pages/welcome/post/PostComponent.jsx b/src/pages/welcome/post/PostComponent.jsx index fee7b13e..23689695 100644 --- a/src/pages/welcome/post/PostComponent.jsx +++ b/src/pages/welcome/post/PostComponent.jsx @@ -20,7 +20,7 @@ const PostComponent = () => { const comments = await getComments(1); setComments(comments); } catch { - setComments('Failed to load comments'); + setComments([]); } }; @@ -46,14 +46,15 @@ const PostComponent = () => { Comments
- {comments?.map((comment) => ( - - - {`From ${comment.email}`} -

{comment.body}

-
-
- ))} + {Array.isArray(comments) && + comments.map((comment) => ( + + + {`From ${comment.email}`} +

{comment.body}

+
+
+ ))}
diff --git a/src/service/postHandlers.js b/src/service/postHandlers.js index c9fd02c8..6f550859 100644 --- a/src/service/postHandlers.js +++ b/src/service/postHandlers.js @@ -40,9 +40,9 @@ export const getComments = async ({ query: { postId } }, res) => { const comments = await response.json(); // Return the blogpost's title - res.json(comments); - } catch { - res.status(500).json({ message: 'Failed to fetch comments' }); + return res.json(comments); + } catch (error) { + return res.status(500).json({ message: 'Failed to fetch comments' }); } }; diff --git a/src/test/router.js b/src/test/router.js index 6b9f9a76..cc0f0199 100644 --- a/src/test/router.js +++ b/src/test/router.js @@ -9,10 +9,14 @@ import { http } from 'msw'; export const getRouter = (mocks, networking) => { const apiRoute = (verb, path, handler) => { - const mock = http[verb](path, async () => { + const mock = http[verb](path, async ({ request, params }) => { + // Parse query parameters from URL + const url = new URL(request.url); + const query = Object.fromEntries(url.searchParams.entries()); + const req = { - params: {}, - query: {}, + params, + query, }; const res = { @@ -20,6 +24,12 @@ export const getRouter = (mocks, networking) => { networking.removeRequest(path); return Response.json(data); }, + status: (code) => ({ + json: (data) => { + networking.removeRequest(path); + return Response.json(data, { status: code }); + }, + }), }; networking.addRequests(path); diff --git a/src/test/server.js b/src/test/server.js index 6405567a..3b3df556 100644 --- a/src/test/server.js +++ b/src/test/server.js @@ -6,6 +6,7 @@ */ import { setupServer } from 'msw/node'; +import { http } from 'msw'; import { getNetworking } from './networking'; import { getRouter } from './router'; import { getRoutes } from '../routes/routes'; @@ -14,9 +15,42 @@ const _setupServer = (...args) => { const mocks = []; const networking = getNetworking(); + // Set up internal API routes getRoutes(getRouter(mocks, networking)); - const server = setupServer(...mocks, ...args); + // Mock external API calls to jsonplaceholder + const externalMocks = [ + http.get('https://jsonplaceholder.typicode.com/posts/:id', ({ params }) => { + return Response.json({ + id: params.id, + title: 'Test Post Title', + body: 'Test post body content', + userId: 1, + }); + }), + http.get('https://jsonplaceholder.typicode.com/comments', ({ request }) => { + const url = new URL(request.url); + const postId = url.searchParams.get('postId'); + return Response.json([ + { + id: 1, + postId: parseInt(postId), + name: 'Test Comment 1', + email: 'test1@example.com', + body: 'Test comment 1 body', + }, + { + id: 2, + postId: parseInt(postId), + name: 'Test Comment 2', + email: 'test2@example.com', + body: 'Test comment 2 body', + }, + ]); + }), + ]; + + const server = setupServer(...mocks, ...externalMocks, ...args); server.networking = networking; return server; };