Skip to content

Commit 2e904f9

Browse files
authored
feat #583: add affiliate marketing (#616)
1 parent 115ff93 commit 2e904f9

File tree

2 files changed

+78
-91
lines changed

2 files changed

+78
-91
lines changed
Lines changed: 49 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,91 @@
11
// Copyright (c) Gridiron Survivor.
22
// Licensed under the MIT License.
33

4-
import React from 'react';
5-
import { render, screen } from '@testing-library/react';
4+
import { render, screen, within } from '@testing-library/react';
65
import GiftDetailsView from './GiftDetailsView';
76
import { describe } from 'node:test';
8-
import { userEvent } from '@storybook/test';
97

108
const mockGift = {
119
id: '1',
1210
title: 'Blanket',
1311
price: '$29.99',
1412
description: 'fuzzy blanket',
1513
matchReasons: ['fits theme', 'highly rated'],
16-
matchScore: 0.98,
17-
imageUrl:
18-
'https://www.amazon.com/Cozzenity-Checkered-Blanket-Blankets-Lightweight/dp/B0F13991KJ/ref=sr_1_1_sspa?dib=eyJ2IjoiMSJ9.6wzR8Y2VhlZ6NsPFYW2cadhZFdeTIN7H6f_K3SufPMLJMToDPu0O_vCflzFycFVgQu9w8Xa2yC56dPFThjqnH5R3cM6i--ozxTqrjiTRLFj0OR_jh8wRe9Y0992_NPU9HrrjbKFeAWsFHKCcDxcGytcREvh7r2XZf-F-jcpqtBJFNhezM0lAhCO27_HYTgtb21GKie4DnzAWwESS488BC_7hi7FVKkDLJRJSmBIBro0WxlgebmNyTq6QqPcwQpM-o2Xa9hLm7DvYG0QSsRcR9QhK0aiRkQs6wLSW0YJHQBI.GiOlWv46xAPaV98XHUz3LzGTnXgnhW8lH6oq09Rz7Jk&dib_tag=se&hvadid=694526290027&hvdev=c&hvexpln=67&hvlocphy=9031568&hvnetw=g&hvocijid=18272751582845398260--&hvqmt=e&hvrand=18272751582845398260&hvtargid=kwd-460419430462&hydadcr=19233_13355042&keywords=amazon%2Bfuzzy%2Bblanket&mcid=5b4485e6f4883f9f84c099bcd937f120&qid=1747259407&sr=8-1-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&th=1',
14+
matchScore: 98,
15+
imageUrl: 'https://example.com/fuzzy-blanket.jpg',
1916
};
2017

21-
const mockBadGift = {
22-
id: '1',
23-
title: 'Blanket',
24-
price: '$29.99',
25-
description: 'fuzzy blanket',
26-
matchReasons: ['fits theme', 'highly rated'],
27-
matchScore: 0.98,
28-
imageUrl:'bad_url',
29-
}
30-
18+
const mockGiftWithBadUrl = {
19+
...mockGift,
20+
imageUrl: 'bad_url',
21+
};
3122

3223
const mockHandleFeedback = jest.fn();
3324

3425
describe('GiftDetailsView', () => {
35-
//testing the gift and handleFeedback props
36-
it('renders the component with correct props', () => {
26+
it('renders the component with gift details', () => {
3727
render(
3828
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
3929
);
40-
});
4130

42-
//testing the Amazon URL
43-
it('generates the correct Amazon URL', async () => {
44-
render(
45-
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
46-
);
47-
48-
const hrefLink = screen.getByRole('link', { name: /view/i });
49-
await userEvent.click(hrefLink);
31+
const giftImage = screen.getByRole('img');
32+
expect(giftImage).toHaveAttribute('src', mockGift.imageUrl);
5033

51-
expect(hrefLink).toHaveAttribute(
52-
'href',
53-
'https://www.amazon.com/s?k=Blanket',
54-
);
34+
const matchScore = screen.getByRole('score');
35+
expect(matchScore).toHaveTextContent(/98% Match/);
5536

56-
})
37+
const price = screen.getByLabelText(/price/i);
38+
expect(price).toHaveTextContent(mockGift.price);
5739

58-
// testing conditional rendering for ImageURL
59-
it('renders image if URL is present', () => {
60-
render(
61-
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
62-
);
40+
const title = screen.getByRole('heading', {
41+
level: 3,
42+
name: mockGift.title,
43+
});
44+
expect(title).toBeInTheDocument();
6345

64-
const image = screen.getByTestId('valid-image');
65-
expect(image).toBeInTheDocument();
66-
})
46+
const description = screen.getByText(mockGift.description);
47+
expect(description).toHaveTextContent(mockGift.description);
6748

68-
//testing that Gift Icon appears if not valid image URL
69-
//CAN NOT FIGURE THIS OUT
70-
it('renders Gift Icon if URl is not valid', ()=> {
71-
render(
72-
<GiftDetailsView gift={mockBadGift} handleFeedback={mockHandleFeedback} />,
73-
);
49+
const matchReasons = screen.getByRole('list');
50+
const matchItems = within(matchReasons)
51+
.getAllByRole('listitem')
52+
.map((li) => li.textContent);
53+
expect(matchItems).toEqual(expect.arrayContaining(mockGift.matchReasons));
54+
});
7455

75-
const giftIcon = screen.getByTestId('gift-icon');
76-
expect(giftIcon).toBeInTheDocument();
77-
})
56+
it('generates the correct Amazon URL', async () => {
57+
// Cloning the env variable
58+
const original_NEXT_PUBLIC_AMAZON_AFFILIATE_TAG =
59+
process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG;
60+
process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG = 'test-tag';
7861

79-
it('renders a valid MatchScore and price', () => {
8062
render(
8163
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
8264
);
8365

84-
const matchScore = screen.getByTestId('valid-matchScore');
85-
expect(matchScore).toBeInTheDocument();
86-
87-
const price = screen.getByTestId('valid-price');
88-
expect(price).toBeInTheDocument();
89-
})
90-
91-
it('renders a valid title and description', () => {
92-
render(
93-
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
66+
const hrefLink = screen.getByRole('link', { name: /view/i });
67+
expect(hrefLink).toHaveAttribute(
68+
'href',
69+
`https://www.amazon.com/s?k=${mockGift.title}&tag=${process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG}`,
9470
);
9571

96-
const title = screen.getByTestId('valid-title');
97-
expect(title).toBeInTheDocument();
98-
99-
const description = screen.getByTestId('valid-description');
100-
expect(description).toBeInTheDocument();
72+
// Restoring the env variable
73+
process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG =
74+
original_NEXT_PUBLIC_AMAZON_AFFILIATE_TAG;
10175
});
10276

103-
it('correctly renders a list for match reasons', () => {
77+
it('renders default Gift Icon on invalid image url', () => {
10478
render(
105-
<GiftDetailsView gift={mockGift} handleFeedback={mockHandleFeedback} />,
79+
<GiftDetailsView
80+
gift={mockGiftWithBadUrl}
81+
handleFeedback={mockHandleFeedback}
82+
/>,
10683
);
10784

108-
mockGift.matchReasons.forEach((reason) => {
109-
expect(screen.getByText(reason)).toBeInTheDocument();
110-
})
111-
})
85+
const giftIcon = screen.getByRole('img', {
86+
name: /gift placeholder image/i,
87+
});
11288

89+
expect(giftIcon).toBeInTheDocument();
90+
});
11391
});

components/GiftDetailsView/GiftDetailsView.tsx

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import {
66
CardHeader,
77
CardTitle,
88
} from '../Card/Card';
9-
import { SquareArrowOutUpRight, ThumbsDown, Gift } from 'lucide-react';
9+
import {
10+
SquareArrowOutUpRight,
11+
ThumbsDown,
12+
Gift as GiftIcon,
13+
} from 'lucide-react';
1014
import { IGiftSuggestion } from '@/app/types/giftSuggestion';
1115
import { useState, useCallback } from 'react';
1216

@@ -34,53 +38,58 @@ const GiftDetailsView = ({
3438

3539
const handleAmazonLink = ({ searchTerm }: { searchTerm: string }) => {
3640
const encodedSearch = encodeURIComponent(searchTerm).replace(/%20/g, '+');
37-
return `https://www.amazon.com/s?k=${encodedSearch}`;
41+
42+
return `https://www.amazon.com/s?k=${encodedSearch}&tag=${process.env.NEXT_PUBLIC_AMAZON_AFFILIATE_TAG}`;
3843
};
3944

45+
const showImage = gift.imageUrl && isValidUrl(gift.imageUrl) && !imageError;
46+
const matchScore = `${gift.matchScore}% Match`;
47+
4048
return (
4149
<>
4250
<div className="relative w-full h-40 bg-white rounded-t-md">
43-
{gift.imageUrl && isValidUrl(gift.imageUrl) && !imageError ? (
51+
{showImage ? (
4452
<img
45-
src={gift.imageUrl}
53+
src={gift.imageUrl || ''}
4654
alt={gift.title}
4755
className="w-full h-full object-contain p-2"
4856
onError={handleImageError}
49-
data-testid="valid-image"
5057
/>
5158
) : (
5259
<div className="w-full h-full flex items-center justify-center">
53-
<Gift className="w-16 h-16 text-gray-300" data-testid="gift-icon" />
60+
<GiftIcon
61+
role="img"
62+
className="w-16 h-16 text-gray-300"
63+
aria-label="gift placeholder image"
64+
/>
5465
</div>
5566
)}
5667

5768
<div className="absolute top-2 left-2 right-2 flex justify-between items-center">
5869
<div
59-
data-testid="valid-matchScore"
70+
role="score"
71+
aria-label="match score"
6072
className="text-xs px-3 py-1 flex items-center justify-center font-semibold bg-giftSuggestionTextBackground text-giftSuggestionTextGreen rounded-full shadow-sm"
6173
>
62-
{gift.matchScore}% Match
74+
{matchScore}
6375
</div>
64-
<div
65-
data-testid="valid-price"
76+
<data
77+
value={gift.price}
78+
aria-label="price"
6679
className="px-3 py-1 font-semibold text-giftSuggestionDarkGreen bg-white/90 rounded-full shadow-sm"
6780
>
6881
{gift.price}
69-
</div>
82+
</data>
7083
</div>
7184
</div>
7285

7386
<CardHeader className="p-0 mx-4">
74-
<CardTitle
75-
data-testid="valid-title"
76-
className="text-base font-bold text-giftSuggestionDarkGreen"
77-
>
78-
{gift.title}
87+
<CardTitle>
88+
<h3 className="text-base font-bold text-giftSuggestionDarkGreen">
89+
{gift.title}
90+
</h3>
7991
</CardTitle>
80-
<CardDescription
81-
data-testid="valid-description"
82-
className="text-sm text-giftSuggestionTextLightGreen"
83-
>
92+
<CardDescription className="text-sm text-giftSuggestionTextLightGreen">
8493
{gift.description}
8594
</CardDescription>
8695
</CardHeader>

0 commit comments

Comments
 (0)