Skip to content

Commit

Permalink
refactor: remove shortHumanReadableDate abstraction, and add unit tes…
Browse files Browse the repository at this point in the history
…ts to Common/BlogPostCard
  • Loading branch information
danielmarcano committed Oct 25, 2023
1 parent 46ec79e commit 040f26f
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 50 deletions.
123 changes: 123 additions & 0 deletions components/Common/BlogPostCard/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { render, screen } from '@testing-library/react';

import BlogPostCard from '@/components/Common/BlogPostCard';
import { LocaleProvider } from '@/providers/localeProvider';

function renderBlogPostCard({
title = 'Blog post title',
type = 'vulnerability',
description = 'Blog post description',
authors = [],
date = new Date(),
}) {
render(
<LocaleProvider>
<BlogPostCard
title={title}
type={type}
description={description}
authors={authors}
date={date}
/>
</LocaleProvider>
);

return { title, type, description, authors, date };
}

describe('BlogPostCard', () => {
describe('Rendering', () => {
it('Wraps the entire card within an article', () => {
renderBlogPostCard({});

expect(screen.getByRole('article')).toBeVisible();
});

it('Renders the title prop correctly', () => {
const { title } = renderBlogPostCard({});

// Title from Preview component
expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent(
title
);

// The second title should be hidden for screen-readers
// to prevent them from reading it twice
expect(screen.getAllByText(title)[1]).toHaveAttribute(
'aria-hidden',
'true'
);
});

it('Renders the description prop correctly', () => {
const { description } = renderBlogPostCard({});

expect(screen.getByText(description)).toBeVisible();
});

it.each([
{ label: 'Vulnerabilities', type: 'vulnerability' },
{ label: 'Announcements', type: 'announcement' },
{ label: 'Releases', type: 'release' },
])(
'Renders "%label" text when passing it the type "%type"',
({ label, type }) => {
renderBlogPostCard({ type });

expect(screen.getByText(label)).toBeVisible();
}
);

it('Renders all passed authors fullName(s), comma-separated', () => {
const authors = [
{ fullName: 'Jane Doe', src: '' },
{ fullName: 'John Doe', src: '' },
];

renderBlogPostCard({ authors });

const fullNames = authors.reduce((prev, curr, index) => {
if (index === 0) {
return curr.fullName;
}

return `${prev}, ${curr.fullName}`;
}, '');

expect(screen.getByText(fullNames)).toBeVisible();
});

it('Renders all passed authors fullName(s), comma-separated', () => {
const authors = [
{ fullName: 'Jane Doe', src: '' },
{ fullName: 'John Doe', src: '' },
];

renderBlogPostCard({ authors });

const fullNames = authors.reduce((prev, curr, index) => {
if (index === 0) {
return curr.fullName;
}

return `${prev}, ${curr.fullName}`;
}, '');

expect(screen.getByText(fullNames)).toBeVisible();
});

it('Renders date prop in short format', () => {
const date = new Date();

renderBlogPostCard({ date });

const dateTimeFormat = new Intl.DateTimeFormat(navigator.language, {
day: 'numeric',
month: 'short',
year: 'numeric',
});

expect(screen.getByText(dateTimeFormat.format(date))).toBeVisible();
});
});
});
2 changes: 1 addition & 1 deletion components/Common/BlogPostCard/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import BlogPostCard from './index';
import BlogPostCard from '@/components/Common/BlogPostCard';

type Story = StoryObj<typeof BlogPostCard>;
type Meta = MetaObj<typeof BlogPostCard>;
Expand Down
49 changes: 30 additions & 19 deletions components/Common/BlogPostCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import { FormattedMessage } from 'react-intl';

import AvatarGroup from '@/components/Common/AvatarGroup';
import Preview from '@/components/Common/Preview';
import { shortHumanReadableDate } from '@/util/shortHumanReadableDate';

import styles from './index.module.css';

const dateTimeFormat = new Intl.DateTimeFormat(navigator.language, {
day: 'numeric',
month: 'short',
year: 'numeric',
});

type Author = {
fullName: string;
src: ComponentProps<typeof AvatarGroup>['avatars'][number]['src'];
src: string;
};

export type BlogPostCardProps = {
type BlogPostCardProps = {
title: ComponentProps<typeof Preview>['title'];
type: Required<ComponentProps<typeof Preview>>['type'];
description: string;
Expand All @@ -28,19 +33,23 @@ const BlogPostCard: FC<BlogPostCardProps> = ({
authors,
date,
}) => {
const avatars = useMemo(() => {
const parsedAvatars = authors.map(({ fullName, src }) => ({
alt: fullName,
src,
toString() {
return fullName;
},
}));

return parsedAvatars;
}, [authors]);
const avatars = useMemo(
() =>
authors.map(({ fullName, src }) => ({
alt: fullName,
src,
toString: () => fullName,
})),
[authors]
);

const authorNames = avatars.join(', ');
const formattedDate = useMemo(
() => ({
ISOString: date.toISOString(),
short: dateTimeFormat.format(date),
}),
[date]
);

return (
<article className={styles.container}>
Expand All @@ -53,14 +62,16 @@ const BlogPostCard: FC<BlogPostCardProps> = ({
<p className={styles.subtitle}>
<FormattedMessage id={`components.common.card.${type}`} />
</p>
<h3 className={styles.title}>{title}</h3>
<p aria-hidden="true" className={styles.title}>
{title}
</p>
<p className={styles.description}>{description}</p>
<footer className={styles.footer}>
<AvatarGroup avatars={avatars} />
<div>
<p className={styles.author}>{authorNames}</p>
<time className={styles.date} dateTime={date.toISOString()}>
{shortHumanReadableDate(date)}
<p className={styles.author}>{avatars.join(', ')}</p>
<time className={styles.date} dateTime={formattedDate.ISOString}>
{formattedDate.short}
</time>
</div>
</footer>
Expand Down
21 changes: 0 additions & 21 deletions util/__tests__/shortHumanReadableDate.test.mjs

This file was deleted.

9 changes: 0 additions & 9 deletions util/shortHumanReadableDate.ts

This file was deleted.

0 comments on commit 040f26f

Please sign in to comment.