Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,33 @@ export function Header({ cart }) {
<div className="left-section">
<Link to="/" className="header-link">
<img className="logo"
data-testid="header-logo"
src="images/logo-white.png" />
<img className="mobile-logo"
data-testid="header-mobile-logo"
src="images/mobile-logo-white.png" />
</Link>
</div>

<div className="middle-section">
<input className="search-bar" type="text" placeholder="Search" />
<input className="search-bar" type="text" placeholder="Search"
data-testid="header-search-bar" />

<button className="search-button">
<button className="search-button"
data-testid="header-search-button">
<img className="search-icon" src="images/icons/search-icon.png" />
</button>
</div>

<div className="right-section">
<Link className="orders-link header-link" to="/orders">
<Link className="orders-link header-link" to="/orders"
data-testid="header-orders-link">

<span className="orders-text">Orders</span>
</Link>

<Link className="cart-link header-link" to="/checkout">
<Link className="cart-link header-link" to="/checkout"
data-testid="header-cart-link">
<img className="cart-icon" src="images/icons/cart-icon.png" />
<div className="cart-quantity">{totalQuantity}</div>
<div className="cart-text">Cart</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { it, expect, describe, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import { Header } from './Header';

describe('Header component', () => {
let cart;

beforeEach(() => {
cart = [{
productId: 'e43638ce-6aa0-4b85-b27f-e1d07eb678c6',
quantity: 2,
deliveryOptionId: '1'
}, {
productId: '15b6fc6f-327a-4ec4-896f-486349e85a3d',
quantity: 3,
deliveryOptionId: '2'
}];
});

it('displays the header correctly', () => {
render(
<MemoryRouter>
<Header cart={cart} />
</MemoryRouter>
);

const logo = screen.getByTestId('header-logo');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we test the src attribute if the image is imported?

expect(logo).toHaveAttribute('src', 'images/logo-white.png');

const mobileLogo = screen.getByTestId('header-mobile-logo');
expect(mobileLogo).toHaveAttribute('src', 'images/mobile-logo-white.png');

expect(screen.getByTestId('header-search-bar')).toBeInTheDocument();
expect(screen.getByTestId('header-search-button')).toBeInTheDocument();

const ordersLink = screen.getByTestId('header-orders-link');
expect(ordersLink).toHaveTextContent('Orders');
expect(ordersLink).toHaveAttribute('href', '/orders');

const cartLink = screen.getByTestId('header-cart-link');
expect(cartLink).toHaveTextContent('Cart');
expect(cartLink).toHaveTextContent('5');
expect(cartLink).toHaveAttribute('href', '/checkout');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export function CheckoutPage({ cart, loadCart }) {
<>
<title>Checkout</title>

<div className="checkout-header">
<div className="checkout-header"
data-testid="checkout-header">
<div className="header-content">
<div className="checkout-header-left-section">
<a href="/">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { it, expect, describe, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import axios from 'axios';
import { CheckoutPage } from './CheckoutPage';

vi.mock('axios');

describe('CheckoutPage component', () => {
let loadCart;
let cart;
let deliveryOptions;
let paymentSummary;

beforeEach(() => {
loadCart = vi.fn();

cart = [{
productId: 'e43638ce-6aa0-4b85-b27f-e1d07eb678c6',
quantity: 2,
deliveryOptionId: '1',
product: {
id: "e43638ce-6aa0-4b85-b27f-e1d07eb678c6",
image: "images/products/athletic-cotton-socks-6-pairs.jpg",
name: "Black and Gray Athletic Cotton Socks - 6 Pairs",
rating: {
stars: 4.5,
count: 87
},
priceCents: 1090,
keywords: ["socks", "sports", "apparel"]
}
}, {
productId: '15b6fc6f-327a-4ec4-896f-486349e85a3d',
quantity: 1,
deliveryOptionId: '2',
product: {
id: "15b6fc6f-327a-4ec4-896f-486349e85a3d",
image: "images/products/intermediate-composite-basketball.jpg",
name: "Intermediate Size Basketball",
rating: {
stars: 4,
count: 127
},
priceCents: 2095,
keywords: ["sports", "basketballs"]
}
}];

deliveryOptions = [{
id: '1',
deliveryDays: 7,
priceCents: 0,
estimatedDeliveryTimeMs: 1747597994451,
}, {
id: '2',
deliveryDays: 3,
priceCents: 499,
estimatedDeliveryTimeMs: 1747252394451,
}, {
id: '3',
deliveryDays: 1,
priceCents: 999,
estimatedDeliveryTimeMs: 1747079594451,
}];

paymentSummary = {
totalItems: 3,
productCostCents: 4275,
shippingCostCents: 499,
totalCostBeforeTaxCents: 4774,
taxCents: 477,
totalCostCents: 5251
};

axios.get.mockImplementation(async (url) => {
if (url === '/api/delivery-options?expand=estimatedDeliveryTime') {
return { data: deliveryOptions };
}
if (url === '/api/payment-summary') {
return { data: paymentSummary };
}
});
});

it('displays the page correctly', async () => {
render(
<MemoryRouter>
<CheckoutPage cart={cart} loadCart={loadCart} />
</MemoryRouter>
);

// findByTestId() = waits until it finds a single element.
const paymentSummary = await screen.findByTestId('payment-summary-product-cost');

expect(axios.get).toHaveBeenNthCalledWith(
1,
'/api/delivery-options?expand=estimatedDeliveryTime'
);
expect(axios.get).toHaveBeenNthCalledWith(
2,
'/api/payment-summary'
);

// Check the order summary is overall correct (since OrderSummary
// is tested in more detail in OrderSummary.test.jsx)
expect(screen.getByText('Review your order')).toBeInTheDocument();
expect(
screen.getByText('Black and Gray Athletic Cotton Socks - 6 Pairs')
).toBeInTheDocument();
expect(
screen.getByText('Intermediate Size Basketball')
).toBeInTheDocument();

// Check the payment summary is overall correct (since PaymentSummary
// is tested in more detail in PaymentSummary.test.jsx)
expect(paymentSummary).toBeInTheDocument();
expect(screen.getByText('Payment Summary')).toBeInTheDocument();
expect(screen.getByTestId('payment-summary-product-cost'))
.toHaveTextContent('Items (3):');
expect(screen.getByTestId('payment-summary-shipping-cost'))
.toHaveTextContent('$4.99');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ export function DeliveryOptions({ cartItem, deliveryOptions, loadCart }) {

return (
<div key={deliveryOption.id} className="delivery-option"
onClick={updateDeliveryOption}>
onClick={updateDeliveryOption}
data-testid="delivery-option">
<input type="radio"
checked={deliveryOption.id === cartItem.deliveryOptionId}
onChange={() => {}}
className="delivery-option-input"
name={`delivery-option-${cartItem.productId}`} />
name={`delivery-option-${cartItem.productId}`}
data-testid="delivery-option-input"
/>
<div>
<div className="delivery-option-date">
{dayjs(deliveryOption.estimatedDeliveryTimeMs).format('dddd, MMMM D')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { it, expect, describe, vi, beforeEach } from 'vitest';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import axios from 'axios';
import { DeliveryOptions } from './DeliveryOptions';

vi.mock('axios');

describe('DeliveryOptions component', () => {
let cartItem;
let deliveryOptions;
let loadCart;
let user;

beforeEach(() => {
cartItem = {
productId: 'e43638ce-6aa0-4b85-b27f-e1d07eb678c6',
quantity: 2,
deliveryOptionId: '2',
};

deliveryOptions = [{
id: '1',
deliveryDays: 7,
priceCents: 0,
estimatedDeliveryTimeMs: 1747597994451,
}, {
id: '2',
deliveryDays: 3,
priceCents: 499,
estimatedDeliveryTimeMs: 1747252394451,
}, {
id: '3',
deliveryDays: 1,
priceCents: 999,
estimatedDeliveryTimeMs: 1747079594451,
}];

loadCart = vi.fn();
user = userEvent.setup();
});

it('renders delivery options correctly', () => {
render(
<DeliveryOptions
cartItem={cartItem}
deliveryOptions={deliveryOptions}
loadCart={loadCart}
/>
);

expect(screen.getByText('Choose a delivery option:')).toBeInTheDocument();

const deliveryOptionElems = screen.getAllByTestId('delivery-option');
expect(deliveryOptionElems.length).toBe(3);

expect(deliveryOptionElems[0]).toHaveTextContent('Sunday, May 18');
expect(deliveryOptionElems[0]).toHaveTextContent('FREE Shipping');
expect(
within(deliveryOptionElems[0]).getByTestId('delivery-option-input').checked
).toBe(false);

expect(deliveryOptionElems[1]).toHaveTextContent('Wednesday, May 14');
expect(deliveryOptionElems[1]).toHaveTextContent('$4.99 - Shipping');
expect(
within(deliveryOptionElems[1]).getByTestId('delivery-option-input').checked
).toBe(true);

expect(deliveryOptionElems[2]).toHaveTextContent('Monday, May 12');
expect(deliveryOptionElems[2]).toHaveTextContent('$9.99 - Shipping');
expect(
within(deliveryOptionElems[2]).getByTestId('delivery-option-input').checked
).toBe(false);
});

it('updates the delivery option', async () => {
render(
<DeliveryOptions
cartItem={cartItem}
deliveryOptions={deliveryOptions}
loadCart={loadCart}
/>
);

const deliveryOptionElems = screen.getAllByTestId('delivery-option');

await user.click(deliveryOptionElems[2]);
expect(axios.put).toHaveBeenCalledWith(
'/api/cart-items/e43638ce-6aa0-4b85-b27f-e1d07eb678c6',
{ deliveryOptionId: '3' }
);
expect(loadCart).toHaveBeenCalledTimes(1);

await user.click(deliveryOptionElems[0]);
expect(axios.put).toHaveBeenCalledWith(
'/api/cart-items/e43638ce-6aa0-4b85-b27f-e1d07eb678c6',
{ deliveryOptionId: '1' }
);
expect(loadCart).toHaveBeenCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,35 @@ export function OrderSummary({ cart, deliveryOptions, loadCart }) {
};

return (
<div key={cartItem.productId} className="cart-item-container">
<div key={cartItem.productId} className="cart-item-container"
data-testid="cart-item-container">
<div className="delivery-date">
Delivery date: {dayjs(selectedDeliveryOption.estimatedDeliveryTimeMs).format('dddd, MMMM D')}
</div>

<div className="cart-item-details-grid">
<img className="product-image"
src={cartItem.product.image} />
src={cartItem.product.image}
data-testid="cart-item-image" />

<div className="cart-item-details">
<div className="product-name">
<div className="product-name"
data-testid="cart-item-name">
{cartItem.product.name}
</div>
<div className="product-price">
<div className="product-price"
data-testid="cart-item-price">
{formatMoney(cartItem.product.priceCents)}
</div>
<div className="product-quantity">
<span>
<span data-testid="cart-item-quantity">
Quantity: <span className="quantity-label">{cartItem.quantity}</span>
</span>
<span className="update-quantity-link link-primary">
Update
</span>
<span className="delete-quantity-link link-primary"
data-testid="cart-item-delete-quantity-link"
onClick={deleteCartItem}>
Delete
</span>
Expand Down
Loading