This commit is contained in:
Philippe Torrel
2026-11-05 14:46:04 +01:00
parent 00be33abcc
commit 24dacde5a9
56 changed files with 2911 additions and 2954 deletions
File diff suppressed because it is too large Load Diff
@@ -4,6 +4,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"test": "vitest",
"test:watch": "vitest --watch",
"test:ui": "vitest --ui",
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
@@ -17,6 +20,9 @@
"devDependencies": {
"@babel/core": "^8.1.0",
"@rolldown/plugin-babel": "^0.3.0",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.6.8",
"@types/babel__core": "^7.20.5",
"@types/node": "^26.6.6",
"@types/react": "^19.3.0",
@@ -24,6 +30,7 @@
"@vitejs/plugin-react": "^6.2.1",
"@vitest/ui": "^5.1.0",
"babel-plugin-react-compiler": "^1.0.0",
"jsdom": "^30.1.3",
"oxlint": "^1.91.0",
"typescript": "~7.0.2",
"vite": "^8.3.6",
@@ -5,24 +5,20 @@ import Footer from '../components/Footer';
const categories = [
{
imgSrc:
'https://images.unsplash.com/photo-1530011940847-04cba36df39f?q=80&w=1920',
imgAlt:
'Two female models sitting on a bed. Photo by Billie (@billiebodybrand) on Unsplash.',
imgSrc: 'https://images.unsplash.com/photo-1530011940847-04cba36df39f?q=80&w=1920',
imgAlt: 'Two female models sitting on a bed. Photo by Billie (@billiebodybrand) on Unsplash.',
href: '/women',
linkText: 'Women Clothing',
},
{
imgSrc:
'https://images.unsplash.com/photo-1595909315417-2edd382a56dc?q=80&w=1920',
imgSrc: 'https://images.unsplash.com/photo-1595909315417-2edd382a56dc?q=80&w=1920',
imgAlt:
'Jumping rope, smartphone, sports bra, a pair of sneakers, and a water bottle. Photo by erica steeves on Unsplash.',
href: '/accessories',
linkText: 'Accessories',
},
{
imgSrc:
'https://images.unsplash.com/photo-1467779009031-53938b78ca38?q=80&w=1920',
imgSrc: 'https://images.unsplash.com/photo-1467779009031-53938b78ca38?q=80&w=1920',
imgAlt: 'Group of men sitting in a van. Photo by Luke Porter on Unsplash.',
href: '/men',
linkText: 'Men Clothing',
@@ -0,0 +1,36 @@
import Hero from '../components/Hero';
import { render, screen } from '@testing-library/react';
import { it, expect, describe } from 'vitest';
// import '@testing-library/jest-dom/vitest';
// Test Suite
describe('Hero', () => {
it('should render h1 element', () => {
render(<Hero />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
});
it('should render the company name when passed', () => {
render(<Hero companyName="My Company" />);
screen.debug();
// Arrange
const heading = screen.getByRole('heading', { level: 1 });
// Act & Assert
expect(heading).toHaveTextContent('My Company');
// expect(heading).toBeInTheDocument();
});
it('should render the company name "Vogue Junction" as default', () => {
render(<Hero />);
// Arrange
const heading = screen.getByRole('heading', { level: 1 });
// Act & Assert
expect(heading).toHaveTextContent(/Vogue Junction/i);
});
});
@@ -0,0 +1,62 @@
import { describe, it, expect } from 'vitest';
// import { fireEvent } from '@testing-library/react';
import { screen, render } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { fakeProduct } from './__mocks__/product.mock';
// component
import ImageGallery from '../components/ImageGallery';
const product = fakeProduct;
const renderComponent = () => {
render(<ImageGallery product={product} />);
// screen.debug();
const images = screen.getAllByAltText(product.title);
return {
images,
mainImage: images[images.length - 1],
buttons: screen.getAllByRole('button'),
};
};
describe('ImageGallery', () => {
it('should render three buttons', () => {
// Arrange
const { buttons } = renderComponent();
// Act & Assert
expect(buttons).toHaveLength(3);
});
it('should render four images', () => {
// Arrange
const { images } = renderComponent();
// Act & Assert
expect(images).toHaveLength(4);
});
it('should render the first image as main image', () => {
// Arrange
const { mainImage } = renderComponent();
expect(mainImage).toBeInTheDocument();
expect(mainImage).toHaveAttribute(
'src',
product.images[0], // 'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/1.png',
);
});
it('should change the main image when the last image was clicked', async () => {
// Arrange
const { mainImage, buttons } = renderComponent();
const user = userEvent.setup();
// fireEvent.click(buttons[2]);
await user.click(buttons[2]); // best practice
expect(mainImage).toHaveAttribute(
'src',
product.images[2], // 'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/3.png',
);
});
});
@@ -0,0 +1,42 @@
import { describe, it, expect } from 'vitest';
import { fakeProducts } from './__mocks__/products.mock';
import ProductList from '../components/ProductList';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
const products = fakeProducts;
describe('ProductList', () => {
it('should render the correct number of products', () => {
render(
<MemoryRouter>
<ProductList data={products} />
</MemoryRouter>,
);
// screen.debug();
const headings = screen.getAllByRole('heading', { level: 3 });
expect(headings).toHaveLength(products.length);
});
it('should render the product list cards correctly with the title, image and price', () => {
render(
<MemoryRouter>
<ProductList data={products} />
</MemoryRouter>,
);
products.forEach((product) => {
const { title, thumbnail, price } = product;
const image = screen.getByAltText(title);
expect(screen.getByText(title)).toBeInTheDocument(); // title
expect(image).toBeInTheDocument(); // image alt text
expect(image).toHaveAttribute('src', thumbnail); // image src
expect(screen.getByText(`$${price}`)).toBeInTheDocument(); // price
});
});
});
@@ -0,0 +1,56 @@
export const fakeProduct = {
id: 174,
title: 'Prada Women Bag',
description:
"The Prada Women Bag is an iconic designer bag that exudes elegance and luxury. Crafted with precision and featuring the Prada logo, it's a statement piece for fashion enthusiasts.",
category: 'womens-bags',
price: 599.99,
discountPercentage: 18.3,
rating: 3.52,
stock: 43,
tags: ['fashion accessories', 'designer bags'],
brand: 'Prada',
sku: 'WXX4YTJM',
weight: 4,
dimensions: { width: 23.45, height: 16.1, depth: 5.78 },
warrantyInformation: '1 week warranty',
shippingInformation: 'Ships overnight',
availabilityStatus: 'In Stock',
reviews: [
{
rating: 5,
comment: 'Excellent quality!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Samantha Howard',
reviewerEmail: 'samantha.howard@x.dummyjson.com',
},
{
rating: 4,
comment: 'Would buy again!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Henry Hill',
reviewerEmail: 'henry.hill@x.dummyjson.com',
},
{
rating: 3,
comment: 'Not worth the price!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Lucas Ramirez',
reviewerEmail: 'lucas.ramirez@x.dummyjson.com',
},
],
returnPolicy: '7 days return policy',
minimumOrderQuantity: 1,
meta: {
createdAt: new Date('2024-05-23T08:56:21.627Z'),
updatedAt: new Date('2024-05-23T08:56:21.627Z'),
barcode: '4590351278063',
qrCode: 'https://assets.dummyjson.com/public/qr-code.png',
},
images: [
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/1.png',
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/2.png',
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/3.png',
],
thumbnail: 'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/thumbnail.png',
};
@@ -0,0 +1,168 @@
export const fakeProducts = [
{
id: 174,
title: 'Prada Women Bag',
description:
"The Prada Women Bag is an iconic designer bag that exudes elegance and luxury. Crafted with precision and featuring the Prada logo, it's a statement piece for fashion enthusiasts.",
category: 'womens-bags',
price: 599.99,
discountPercentage: 18.3,
rating: 3.52,
stock: 43,
tags: ['fashion accessories', 'designer bags'],
brand: 'Prada',
sku: 'WXX4YTJM',
weight: 4,
dimensions: { width: 23.45, height: 16.1, depth: 5.78 },
warrantyInformation: '1 week warranty',
shippingInformation: 'Ships overnight',
availabilityStatus: 'In Stock',
reviews: [
{
rating: 5,
comment: 'Excellent quality!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Samantha Howard',
reviewerEmail: 'samantha.howard@x.dummyjson.com',
},
{
rating: 4,
comment: 'Would buy again!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Henry Hill',
reviewerEmail: 'henry.hill@x.dummyjson.com',
},
{
rating: 3,
comment: 'Not worth the price!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Lucas Ramirez',
reviewerEmail: 'lucas.ramirez@x.dummyjson.com',
},
],
returnPolicy: '7 days return policy',
minimumOrderQuantity: 1,
meta: {
createdAt: new Date('2024-05-23T08:56:21.627Z'),
updatedAt: new Date('2024-05-23T08:56:21.627Z'),
barcode: '4590351278063',
qrCode: 'https://assets.dummyjson.com/public/qr-code.png',
},
images: [
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/1.png',
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/2.png',
'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/3.png',
],
thumbnail: 'https://cdn.dummyjson.com/products/images/womens-bags/Prada%20Women%20Bag/thumbnail.png',
},
{
id: 90,
title: 'Puma Future Rider Trainers',
description:
'The Puma Future Rider Trainers offer a blend of retro style and modern comfort. Perfect for casual wear, these trainers provide a fashionable and comfortable option for everyday use.',
category: 'mens-shoes',
price: 89.99,
discountPercentage: 3.64,
rating: 4.85,
stock: 10,
tags: ['footwear', 'casual shoes'],
brand: 'Puma',
sku: '64ORN32I',
weight: 8,
dimensions: { width: 14.58, height: 25.54, depth: 19.57 },
warrantyInformation: '2 year warranty',
shippingInformation: 'Ships in 1 month',
availabilityStatus: 'In Stock',
reviews: [
{
rating: 5,
comment: 'Very happy with my purchase!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Lucas Allen',
reviewerEmail: 'lucas.allen@x.dummyjson.com',
},
{
rating: 4,
comment: 'Awesome product!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Mason Pearson',
reviewerEmail: 'mason.pearson@x.dummyjson.com',
},
{
rating: 4,
comment: 'Very satisfied!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Hunter Gordon',
reviewerEmail: 'hunter.gordon@x.dummyjson.com',
},
],
returnPolicy: '90 days return policy',
minimumOrderQuantity: 9,
meta: {
createdAt: new Date('2024-05-23T08:56:21.627Z'),
updatedAt: new Date('2024-05-23T08:56:21.627Z'),
barcode: '3562849555769',
qrCode: 'https://assets.dummyjson.com/public/qr-code.png',
},
images: [
'https://cdn.dummyjson.com/products/images/mens-shoes/Puma%20Future%20Rider%20Trainers/1.png',
'https://cdn.dummyjson.com/products/images/mens-shoes/Puma%20Future%20Rider%20Trainers/2.png',
'https://cdn.dummyjson.com/products/images/mens-shoes/Puma%20Future%20Rider%20Trainers/3.png',
'https://cdn.dummyjson.com/products/images/mens-shoes/Puma%20Future%20Rider%20Trainers/4.png',
],
thumbnail: 'https://cdn.dummyjson.com/products/images/mens-shoes/Puma%20Future%20Rider%20Trainers/thumbnail.png',
},
{
id: 101,
title: 'Apple AirPods Max Silver',
description:
'The Apple AirPods Max in Silver are premium over-ear headphones with high-fidelity audio, adaptive EQ, and active noise cancellation. Experience immersive sound in style.',
category: 'mobile-accessories',
price: 549.99,
discountPercentage: 11.7,
rating: 3.11,
stock: 7,
tags: ['electronics', 'over-ear headphones'],
brand: 'Apple',
sku: 'HPK82VDE',
weight: 4,
dimensions: { width: 7.73, height: 18.36, depth: 17.87 },
warrantyInformation: '3 months warranty',
shippingInformation: 'Ships in 1 month',
availabilityStatus: 'In Stock',
reviews: [
{
rating: 1,
comment: 'Waste of money!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Harper Kelly',
reviewerEmail: 'harper.kelly@x.dummyjson.com',
},
{
rating: 3,
comment: 'Not as described!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Abigail Rivera',
reviewerEmail: 'abigail.rivera@x.dummyjson.com',
},
{
rating: 5,
comment: 'Excellent quality!',
date: new Date('2024-05-23T08:56:21.627Z'),
reviewerName: 'Nora Russell',
reviewerEmail: 'nora.russell@x.dummyjson.com',
},
],
returnPolicy: '90 days return policy',
minimumOrderQuantity: 2,
meta: {
createdAt: new Date('2024-05-23T08:56:21.627Z'),
updatedAt: new Date('2024-05-23T08:56:21.627Z'),
barcode: '9261269777547',
qrCode: 'https://assets.dummyjson.com/public/qr-code.png',
},
images: ['https://cdn.dummyjson.com/products/images/mobile-accessories/Apple%20AirPods%20Max%20Silver/1.png'],
thumbnail:
'https://cdn.dummyjson.com/products/images/mobile-accessories/Apple%20AirPods%20Max%20Silver/thumbnail.png',
},
];
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { add } from '../utils/helpers';
describe('add', () => {
it('should return 3 when 2 and 1 passed', () => {
expect(add(1, 2)).toBe(3);
});
it('should return 0.3 when 0.2 and 0.1 passed', () => {
expect(add(0.2, 0.1)).toBe(0.3);
});
});
@@ -0,0 +1,16 @@
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from '@testing-library/jest-dom/matchers';
// types für Testfunktionen
import '@testing-library/jest-dom/vitest';
// add jest-dom matchers to expect
expect.extend(matchers);
// Add cleanup function to run after each test
afterEach(() => {
cleanup();
});
// More setup code here, if needed
@@ -0,0 +1,19 @@
import { it, test, expect, describe } from 'vitest';
// Testsuite
describe('main', () => {
// Testcase
test('main should pass', () => {
// AAA
// Arrange
const initValue = true;
// Acting - Assertion
expect(initValue).toBe(true);
});
it('should return a number', () => {
expect(1).toBeTypeOf('number');
});
});
// console.log(fn()); // => true
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { multiply } from '../utils/helpers';
describe('multiply', () => {
it('should return 10 when 2 and 5 are passed', () => {
expect(multiply(2, 5)).toBe(10);
});
it('should return a number', () => {
expect(multiply(1, 2)).toBeTypeOf('number');
});
});
@@ -0,0 +1,12 @@
export const multiply = (a: number, b: number): number => {
return Number(a) * Number(b);
};
export const add = (a: number, b: number): number => {
return parseFloat((Number(a) + Number(b)).toFixed(10));
};
export default {
//multiply: multiply
multiply,
add,
};
@@ -1,11 +1,13 @@
import react, { reactCompilerPreset } from '@vitejs/plugin-react'
import babel from '@rolldown/plugin-babel'
import { defineConfig } from 'vite'
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';
import { defineConfig } from 'vitest/config'; // nicht mehr vite
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
babel({ presets: [reactCompilerPreset()] })
],
})
plugins: [react(), babel({ presets: [reactCompilerPreset()] })],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/tests/config/setupTests.ts',
},
});