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;
};