fix(blocks): add clickable dynamic navigation links and click-drag gesture prevention to ReferencesSlider
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Successful in 1m13s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Successful in 1m13s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
This commit is contained in:
88
components/blocks/ReferencesSlider.test.tsx
Normal file
88
components/blocks/ReferencesSlider.test.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
||||
|
||||
// Mock next/link to render standard <a> tag
|
||||
vi.mock('next/link', () => ({
|
||||
default: vi.fn(({ children, href, onClick, ...props }: any) => {
|
||||
return (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}));
|
||||
|
||||
// Mock next/image to render standard img tag
|
||||
vi.mock('next/image', () => ({
|
||||
default: (props: any) => {
|
||||
return <img {...props} />;
|
||||
}
|
||||
}));
|
||||
|
||||
describe('ReferencesSlider TDD', () => {
|
||||
const mockReferences: Reference[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Referenz Projekt Eins',
|
||||
slug: 'referenz-projekt-eins',
|
||||
category: 'Kabeltiefbau',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Referenz Projekt Zwei',
|
||||
slug: 'referenz-projekt-zwei',
|
||||
category: 'Bohrtechnik',
|
||||
}
|
||||
];
|
||||
|
||||
it('renders reference tiles correctly', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
expect(screen.getByText('Referenz Projekt Eins')).toBeTruthy();
|
||||
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('wraps each tile in a link to /referenzen/[slug]', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const links = screen.getAllByTestId('reference-tile-link');
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].getAttribute('href')).toBe('/referenzen/referenz-projekt-eins');
|
||||
expect(links[1].getAttribute('href')).toBe('/referenzen/referenz-projekt-zwei');
|
||||
});
|
||||
|
||||
it('allows navigation on simple click (no drag)', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate simple click sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
it('prevents navigation if mouse was dragged', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate drag sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
// Simulate mouse move of 20px
|
||||
fireEvent.mouseMove(firstLink, { pageX: 120, clientX: 120 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,9 +37,11 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const { data } = props;
|
||||
const references = props.references || [];
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const dragDistanceRef = React.useRef(0);
|
||||
const startXRef = React.useRef(0);
|
||||
const scrollLeftRef = React.useRef(0);
|
||||
const isDraggingRef = React.useRef(false);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
const [startX, setStartX] = React.useState(0);
|
||||
const [scrollLeft, setScrollLeft] = React.useState(0);
|
||||
|
||||
const badge = props.badge || data?.badge || 'Ausgewählte Projekte';
|
||||
const title = props.title || data?.title || 'Referenzen & Erfolge';
|
||||
@@ -60,24 +62,36 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
setIsDragging(true);
|
||||
setStartX(e.pageX - containerRef.current.offsetLeft);
|
||||
setScrollLeft(containerRef.current.scrollLeft);
|
||||
isDraggingRef.current = true;
|
||||
const startXVal = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
startXRef.current = startXVal;
|
||||
scrollLeftRef.current = containerRef.current.scrollLeft || 0;
|
||||
dragDistanceRef.current = 0;
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseMove = (e: React.MouseEvent) => {
|
||||
if (!isDragging || !containerRef.current) return;
|
||||
if (!isDraggingRef.current || !containerRef.current) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - containerRef.current.offsetLeft;
|
||||
const walk = (x - startX) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeft - walk;
|
||||
const x = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
const walk = (x - startXRef.current) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeftRef.current - walk;
|
||||
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
||||
};
|
||||
|
||||
const handleTileClick = (e: React.MouseEvent) => {
|
||||
if (dragDistanceRef.current > 10) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -117,7 +131,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: "easeOut" }}
|
||||
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
|
||||
>
|
||||
<div className="relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl">
|
||||
<Link
|
||||
href={`/referenzen/${ref.slug}`}
|
||||
onClick={handleTileClick}
|
||||
data-testid="reference-tile-link"
|
||||
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt={ref.title}
|
||||
@@ -135,7 +154,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
</span>
|
||||
<h4 className="font-heading text-xl md:text-2xl font-bold leading-tight break-words">{ref.title}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user