Skip to content

Commit b9745e3

Browse files
committed
feat: implement isNew utility function and update Posts component to display "New" badge conditionally
1 parent 1e4c03a commit b9745e3

4 files changed

Lines changed: 94 additions & 7 deletions

File tree

src/components/app/blog/Posts.test.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
11
import { render, screen } from "@testing-library/preact";
2-
import { test } from "vitest";
2+
import { test, vi, afterEach } from "vitest";
33

44
import Posts from "./Posts";
55

6+
afterEach(() => {
7+
vi.useRealTimers();
8+
});
9+
10+
test("shows the New badge when the featured post was published within the last 24 hours", () => {
11+
vi.useFakeTimers();
12+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
13+
render(
14+
<Posts
15+
posts={[
16+
{
17+
title: "Brand New Post",
18+
slug: "brand-new",
19+
date: "2026-03-27T10:00:00Z",
20+
},
21+
]}
22+
/>
23+
);
24+
expect(screen.getByText("New")).toBeInTheDocument();
25+
});
26+
27+
test("does not show the New badge when the featured post is older than 24 hours", () => {
28+
vi.useFakeTimers();
29+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
30+
render(
31+
<Posts
32+
posts={[
33+
{
34+
title: "Old Post",
35+
slug: "old-post",
36+
date: "2026-03-25",
37+
},
38+
]}
39+
/>
40+
);
41+
expect(screen.queryByText("New")).not.toBeInTheDocument();
42+
});
43+
644
test("should be able to render no posts without crashing", () => {
745
render(<Posts posts={[]} />);
846
expect(screen.getByText(/No posts available/)).toBeInTheDocument();

src/components/app/blog/Posts.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import classNames from "classnames";
22

33
import { type Frontmatter } from "~/types/frontmatter";
44

5-
import { toDisplayDate } from "./utils";
5+
import { toDisplayDate, isNew } from "./utils";
66

77
interface Props {
88
posts?: Frontmatter[];
@@ -36,9 +36,11 @@ const Posts = ({ posts }: Props) => {
3636
>
3737
<div className="mb-7 flex items-center justify-between gap-4 font-mono text-xs uppercase tracking-[0.18em] text-outline">
3838
<div className="inline-flex items-center gap-3">
39-
<span className="rounded bg-secondary/18 px-2 py-1 font-mono text-xs font-semibold uppercase tracking-[0.16em] text-secondary">
40-
New
41-
</span>
39+
{isNew(featuredPost.date) && (
40+
<span className="rounded bg-secondary/18 px-2 py-1 font-mono text-xs font-semibold uppercase tracking-[0.16em] text-secondary">
41+
New
42+
</span>
43+
)}
4244
<p className="font-mono text-sm text-on-surface-muted">
4345
{toDisplayDate(featuredPost.date)}
4446
</p>

src/components/app/blog/utils.test.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
1-
import { describe, expect, test } from "vitest";
1+
import { describe, expect, test, vi, afterEach } from "vitest";
22

3-
import { toDisplayDate } from "./utils";
3+
import { toDisplayDate, isNew } from "./utils";
4+
5+
describe("isNew", () => {
6+
afterEach(() => {
7+
vi.useRealTimers();
8+
});
9+
10+
test("returns false when no date is provided", () => {
11+
expect(isNew()).toBe(false);
12+
expect(isNew(undefined)).toBe(false);
13+
});
14+
15+
test("returns false for an invalid date string", () => {
16+
expect(isNew("not-a-date")).toBe(false);
17+
});
18+
19+
test("returns true when post date is exactly now", () => {
20+
vi.useFakeTimers();
21+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
22+
expect(isNew("2026-03-27")).toBe(true);
23+
});
24+
25+
test("returns true when post date is 23 hours ago", () => {
26+
vi.useFakeTimers();
27+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
28+
expect(isNew("2026-03-26T13:00:00Z")).toBe(true);
29+
});
30+
31+
test("returns false when post date is more than 24 hours ago", () => {
32+
vi.useFakeTimers();
33+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
34+
expect(isNew("2026-03-26T11:59:59Z")).toBe(false);
35+
});
36+
37+
test("returns false when post date is in the future", () => {
38+
vi.useFakeTimers();
39+
vi.setSystemTime(new Date("2026-03-27T12:00:00Z"));
40+
expect(isNew("2026-03-28")).toBe(false);
41+
});
42+
});
443

544
describe("toDisplayDate", () => {
645
test("returns UNKNOWN when no date is provided", () => {

src/components/app/blog/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
export const isNew = (rawDate?: string) => {
2+
if (!rawDate) return false;
3+
const postDate = new Date(rawDate);
4+
if (Number.isNaN(postDate.getTime())) return false;
5+
const diffMs = Date.now() - postDate.getTime();
6+
return diffMs >= 0 && diffMs <= 24 * 60 * 60 * 1000;
7+
};
8+
19
export const toDisplayDate = (rawDate?: string) => {
210
if (!rawDate) {
311
return "UNKNOWN";

0 commit comments

Comments
 (0)