Skip to content

Commit 7d75258

Browse files
refactor: remove code duplication (#669)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 84c9cdf commit 7d75258

13 files changed

+469
-256
lines changed

src/2023/Cfp/CfpSection2023.test.tsx

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from "react";
2+
import { render, screen, waitFor } from "@testing-library/react";
3+
import "@testing-library/jest-dom";
4+
import CfpSection2023 from "./CfpSection2023";
5+
import { useWindowSize } from "react-use";
6+
import conferenceData from "../../data/2023.json";
7+
import { data } from "./CfpData";
8+
9+
// Mock useWindowSize to control the window size in tests
10+
jest.mock("react-use", () => ({
11+
...jest.requireActual("react-use"),
12+
useWindowSize: jest.fn(),
13+
}));
14+
15+
describe("CfpSection2023", () => {
16+
beforeEach(() => {
17+
// Reset the mock before each test
18+
(useWindowSize as jest.Mock).mockReset();
19+
(useWindowSize as jest.Mock).mockReturnValue({ width: 1024 }); // Default width
20+
});
21+
22+
it("should render without crashing", () => {
23+
render(<CfpSection2023 />);
24+
});
25+
26+
it("should render the title and subtitle", () => {
27+
render(<CfpSection2023 />);
28+
expect(
29+
screen.getByText("CFP Committee", { exact: false }),
30+
).toBeInTheDocument();
31+
expect(
32+
screen.getByText(
33+
"We're excited to announce the members of the Call for Papers committee for the next DevBcn conference! These experienced professionals will be reviewing and selecting the best talks and workshops for the upcoming event.",
34+
),
35+
).toBeInTheDocument();
36+
});
37+
38+
it("should render the tracks and members", () => {
39+
render(<CfpSection2023 />);
40+
data.forEach((track) => {
41+
expect(screen.getAllByText(track.name, { exact: false })).not.toBeNull();
42+
track.members
43+
.filter((member) => member.photo !== "")
44+
.forEach((member) => {
45+
expect(
46+
screen.getAllByText(member.name, { exact: false }),
47+
).not.toBeNull();
48+
});
49+
});
50+
});
51+
52+
it("should render member photos", () => {
53+
render(<CfpSection2023 />);
54+
data.forEach((track) => {
55+
track.members
56+
.filter((member) => member.photo !== "")
57+
.forEach((member) => {
58+
const image = screen.getAllByAltText(member.name);
59+
expect(image).not.toBeNull();
60+
expect(image.at(0)).toHaveAttribute("src", member.photo);
61+
});
62+
});
63+
});
64+
65+
it("should render twitter links", () => {
66+
render(<CfpSection2023 />);
67+
data.forEach((track) => {
68+
track.members
69+
.filter((member) => member.twitter !== "")
70+
.forEach((member) => {
71+
const twitterLinks = screen.getAllByRole("link");
72+
const twitterLink = twitterLinks.find(
73+
(link) => link.getAttribute("href") === member.twitter,
74+
);
75+
expect(twitterLink).toBeInTheDocument();
76+
expect(twitterLink).toHaveAttribute("href", member.twitter);
77+
});
78+
});
79+
});
80+
81+
it("should render linkedIn links", () => {
82+
render(<CfpSection2023 />);
83+
data.forEach((track) => {
84+
track.members
85+
.filter((member) => member.linkedIn !== "")
86+
.forEach((member) => {
87+
const linkedInLinks = screen.getAllByRole("link");
88+
const linkedInLink = linkedInLinks.find(
89+
(link) => link.getAttribute("href") === member.linkedIn,
90+
);
91+
expect(linkedInLink).toBeInTheDocument();
92+
expect(linkedInLink).toHaveAttribute("href", member.linkedIn);
93+
});
94+
});
95+
});
96+
97+
it("should update the document title", async () => {
98+
render(<CfpSection2023 />);
99+
await waitFor(() => {
100+
expect(document.title).toBe(
101+
`CFP Committee - DevBcn - ${conferenceData.edition}`,
102+
);
103+
});
104+
});
105+
106+
it("should not render the icons when the width is smaller than the breakpoint", () => {
107+
(useWindowSize as jest.Mock).mockReturnValue({ width: 767 });
108+
render(<CfpSection2023 />);
109+
const lessIcon = screen.queryByAltText("more than - icon");
110+
const moreIcon = screen.queryByAltText("Less than - icon");
111+
expect(lessIcon).not.toBeInTheDocument();
112+
expect(moreIcon).not.toBeInTheDocument();
113+
});
114+
});

src/2023/Cfp/CfpSection2023.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ const MemberName = styled.h5`
3535
text-align: left;
3636
`;
3737

38-
const CfpTrackComponent: FC<React.PropsWithChildren<CfpTrackProps>> = ({ track }) => (
38+
const CfpTrackComponent: FC<React.PropsWithChildren<CfpTrackProps>> = ({
39+
track,
40+
}) => (
3941
<>
4042
<section>
4143
<TrackName>{track.name}</TrackName>
@@ -90,8 +92,14 @@ const CfpSection2023: FC<React.PropsWithChildren<unknown>> = () => {
9092
/>
9193
{width > MOBILE_BREAKPOINT && (
9294
<>
93-
<StyledLessIcon src={MoreThanBlueWhiteIcon} />
94-
<StyledMoreIcon src={LessThanBlueWhiteIcon} />
95+
<StyledLessIcon
96+
title="Less than - icon"
97+
src={MoreThanBlueWhiteIcon}
98+
/>
99+
<StyledMoreIcon
100+
title="more than - icon"
101+
src={LessThanBlueWhiteIcon}
102+
/>
95103
</>
96104
)}
97105
</StyledSpeakersSection>

src/services/buildSlashes.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const buildSlashes = (module: number) => {
2+
const slashesElement = document.getElementById("Slashes");
3+
4+
const slashesWidth = slashesElement?.offsetWidth ?? 0;
5+
let slashes = "";
6+
for (let index = 0; index < slashesWidth; index++) {
7+
if (index % module === 0) slashes += "/ ";
8+
}
9+
return slashes;
10+
};

src/views/Home/components/Sponsors/BasicSponsor.tsx

+14-22
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,31 @@ import {
1111
StyledSponsorTitleSlashesContainer,
1212
} from "./Sponsors.style";
1313
import SponsorBadge from "./SponsorBadge";
14-
import {Color} from "../../../../styles/colors";
15-
import {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
16-
import {buildSlashes} from "./Sponsors";
17-
import {useWindowSize} from "react-use";
18-
import React, {FC, useCallback, useEffect, useState} from "react";
19-
import {Sponsor} from "./SponsorsData";
14+
import { Color } from "../../../../styles/colors";
15+
import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
16+
import React, { FC } from "react";
17+
import { Sponsor } from "./SponsorsData";
18+
import { useSponsorsHook } from "./useSponsorsHook";
2019

2120
interface Props {
2221
sponsors: Array<Sponsor> | null;
2322
}
2423

25-
export const BasicSponsor: FC<React.PropsWithChildren<Props>> = ({sponsors}) => {
26-
const { width } = useWindowSize();
27-
const [slashes, setSlashes] = useState("");
28-
const [isHovered, setIsHovered] = useState<boolean>(false);
29-
30-
useEffect(() => {
31-
const newSlashes = buildSlashes(2);
32-
33-
setSlashes(newSlashes);
34-
}, [width]);
35-
36-
const handleHoverSponsorBasic = useCallback(() => setIsHovered(true), []);
37-
const handleUnHoverSponsorBasic = useCallback(() => setIsHovered(false), []);
38-
24+
export const BasicSponsor: FC<React.PropsWithChildren<Props>> = ({
25+
sponsors,
26+
}) => {
27+
const { width, slashes, isHovered, handleHover, handleUnHover } =
28+
useSponsorsHook({
29+
numberOfSlashGroups: 2,
30+
});
3931
return (
4032
<>
4133
{sponsors !== null && sponsors.length > 0 && (
4234
<StyledSponsorItemContainer
4335
id="basic-sponsors"
4436
className="SponsorItem basic"
45-
onMouseEnter={handleHoverSponsorBasic}
46-
onMouseLeave={handleUnHoverSponsorBasic}
37+
onMouseEnter={handleHover}
38+
onMouseLeave={handleUnHover}
4739
>
4840
<SponsorBadge
4941
color={Color.DARK_BLUE}

src/views/Home/components/Sponsors/Communities.tsx

+12-18
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,23 @@ import {
1111
StyledSponsorTitleSlashesContainer,
1212
} from "./Sponsors.style";
1313
import SponsorBadge from "./SponsorBadge";
14-
import {Color} from "../../../../styles/colors";
15-
import {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
16-
import {buildSlashes} from "./Sponsors";
17-
import {useWindowSize} from "react-use";
18-
import React, {FC, useCallback, useEffect, useState} from "react";
19-
import {Sponsor} from "./SponsorsData";
14+
import { Color } from "../../../../styles/colors";
15+
import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
16+
import React, { FC } from "react";
17+
import { Sponsor } from "./SponsorsData";
18+
import { useSponsorsHook } from "./useSponsorsHook";
2019

2120
interface Props {
2221
sponsors: Array<Sponsor> | null;
2322
}
2423

25-
export const Communities: FC<React.PropsWithChildren<Props>> = ({sponsors}) => {
26-
const { width } = useWindowSize();
27-
const [slashes, setSlashes] = useState("");
28-
const [isHovered, setIsHovered] = useState<boolean>(false);
29-
useEffect(() => {
30-
const newSlashes = buildSlashes(2);
31-
32-
setSlashes(newSlashes);
33-
}, [width]);
34-
35-
const handleHover = useCallback(() => setIsHovered(true), []);
36-
const handleUnHover = useCallback(() => setIsHovered(false), []);
24+
export const Communities: FC<React.PropsWithChildren<Props>> = ({
25+
sponsors,
26+
}) => {
27+
const { width, slashes, isHovered, handleHover, handleUnHover } =
28+
useSponsorsHook({
29+
numberOfSlashGroups: 2,
30+
});
3731
return (
3832
<>
3933
{sponsors !== null && sponsors.length > 0 && (

src/views/Home/components/Sponsors/MediaPartners.tsx

+8-15
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import {
1313
import SponsorBadge from "./SponsorBadge";
1414
import { Color } from "../../../../styles/colors";
1515
import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
16-
import { buildSlashes } from "./Sponsors";
17-
import { useWindowSize } from "react-use";
18-
import React, { FC, useCallback, useEffect, useState } from "react";
16+
import React, { FC } from "react";
1917
import { Sponsor } from "./SponsorsData";
18+
import { useSponsorsHook } from "./useSponsorsHook";
2019

2120
interface Props {
2221
sponsors: Array<Sponsor> | null;
@@ -25,25 +24,19 @@ interface Props {
2524
export const MediaPartners: FC<React.PropsWithChildren<Props>> = ({
2625
sponsors,
2726
}) => {
28-
const { width } = useWindowSize();
29-
const [slashes, setSlashes] = useState("");
30-
const [isHovered, setIsHovered] = useState<boolean>(false);
31-
useEffect(() => {
32-
const newSlashes = buildSlashes(2);
27+
const { width, slashes, isHovered, handleHover, handleUnHover } =
28+
useSponsorsHook({
29+
numberOfSlashGroups: 2,
30+
});
3331

34-
setSlashes(newSlashes);
35-
}, [width]);
36-
37-
const handleHoverMediaPartner = useCallback(() => setIsHovered(true), []);
38-
const handleUnHoverMediaPartner = useCallback(() => setIsHovered(false), []);
3932
return (
4033
<>
4134
{sponsors !== null && sponsors.length > 0 && (
4235
<StyledSponsorItemContainer
4336
className="SponsorItem virtual"
4437
id="media-partners"
45-
onMouseEnter={handleHoverMediaPartner}
46-
onMouseLeave={handleUnHoverMediaPartner}
38+
onMouseEnter={handleHover}
39+
onMouseLeave={handleUnHover}
4740
>
4841
<SponsorBadge
4942
color={Color.DARK_BLUE}

src/views/Home/components/Sponsors/PremiumSponsors.tsx

+14-22
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,32 @@ import {
1111
StyledSponsorTitleSlashesContainer,
1212
} from "./Sponsors.style";
1313
import SponsorBadge from "./SponsorBadge";
14-
import {Color} from "../../../../styles/colors";
15-
import {BIG_BREAKPOINT} from "../../../../constants/BreakPoints";
16-
import {useWindowSize} from "react-use";
17-
import React, {FC, useCallback, useEffect, useState} from "react";
18-
import {buildSlashes} from "./Sponsors";
19-
import {Sponsor} from "./SponsorsData";
14+
import { Color } from "../../../../styles/colors";
15+
import { BIG_BREAKPOINT } from "../../../../constants/BreakPoints";
16+
import React, { FC } from "react";
17+
import { Sponsor } from "./SponsorsData";
18+
import { useSponsorsHook } from "./useSponsorsHook";
2019

2120
interface Props {
2221
sponsors: Array<Sponsor> | null;
2322
}
2423

25-
export const PremiumSponsors: FC<React.PropsWithChildren<Props>> = ({sponsors}) => {
26-
const { width } = useWindowSize();
27-
const [slashes, setSlashes] = useState("");
28-
const [isHovered, setIsHovered] = useState<boolean>(false);
29-
useEffect(() => {
30-
const newSlashes = buildSlashes(2);
24+
export const PremiumSponsors: FC<React.PropsWithChildren<Props>> = ({
25+
sponsors,
26+
}) => {
27+
const { width, slashes, isHovered, handleHover, handleUnHover } =
28+
useSponsorsHook({
29+
numberOfSlashGroups: 2,
30+
});
3131

32-
setSlashes(newSlashes);
33-
}, [width]);
34-
35-
const handleHoverSponsorPremium = useCallback(() => setIsHovered(true), []);
36-
const handleUnHoverSponsorPremium = useCallback(
37-
() => setIsHovered(false),
38-
[]
39-
);
4032
return (
4133
<>
4234
{sponsors !== null && sponsors.length > 0 && (
4335
<StyledSponsorItemContainer
4436
id="premium-sponsors"
4537
className="SponsorItem premium"
46-
onMouseEnter={handleHoverSponsorPremium}
47-
onMouseLeave={handleUnHoverSponsorPremium}
38+
onMouseEnter={handleHover}
39+
onMouseLeave={handleUnHover}
4840
>
4941
<SponsorBadge
5042
color={Color.DARK_BLUE}

0 commit comments

Comments
 (0)