feat(task-12): link Bohrtechnik to service pages and remove reference detailed links
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 36s
Build & Deploy / 🧪 QA (push) Successful in 2m7s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Successful in 3m31s
Build & Deploy / 🏗️ Build (push) Successful in 3m45s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 36s
Build & Deploy / 🧪 QA (push) Successful in 2m7s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Successful in 3m31s
Build & Deploy / 🏗️ Build (push) Successful in 3m45s
Build & Deploy / 🚀 Deploy (push) Successful in 33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
This commit is contained in:
@@ -3,7 +3,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { MapPin, Calendar, Briefcase, ArrowUpRight } from 'lucide-react';
|
||||
import { MapPin, Calendar, Briefcase } from 'lucide-react';
|
||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import Image from 'next/image';
|
||||
@@ -85,14 +85,9 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
<Container className="relative z-20 mt-16 md:mt-24">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
||||
{references.map((ref) => (
|
||||
<TrackedLink
|
||||
<div
|
||||
key={ref.slug}
|
||||
href={`/${locale}/referenzen/${ref.slug}`}
|
||||
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2"
|
||||
eventProperties={{
|
||||
location: 'referenzen_grid',
|
||||
target_slug: ref.slug,
|
||||
}}
|
||||
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 cursor-default"
|
||||
>
|
||||
<div className="flex flex-col h-full relative">
|
||||
{/* Image Section */}
|
||||
@@ -102,14 +97,14 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
src={ref.frontmatter.featuredImage}
|
||||
alt={ref.frontmatter.title}
|
||||
fill
|
||||
className="object-cover group-hover:scale-105 transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:grayscale-0"
|
||||
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%]"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
|
||||
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
|
||||
<div className="absolute inset-0 bg-black/5 transition-colors duration-500" />
|
||||
|
||||
{/* Location Badge */}
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
@@ -122,12 +117,12 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
|
||||
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 group-hover:text-primary transition-colors line-clamp-3 leading-[1.2]">
|
||||
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 transition-colors line-clamp-3 leading-[1.2]">
|
||||
{ref.frontmatter.title}
|
||||
</h3>
|
||||
|
||||
{/* Technical Meta Data */}
|
||||
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 pr-16 md:pr-20 relative z-10">
|
||||
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
|
||||
<div className="min-w-0">
|
||||
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('client')}</span>
|
||||
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
||||
@@ -144,13 +139,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Industrial Hover Indicator */}
|
||||
<div className="absolute right-6 bottom-6 md:right-8 md:bottom-8 w-10 h-10 bg-neutral-50 border border-neutral-200 rounded flex items-center justify-center transform group-hover:bg-primary group-hover:border-primary transition-colors duration-300">
|
||||
<ArrowUpRight className="w-5 h-5 text-neutral-dark group-hover:text-white transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</TrackedLink>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
|
||||
// Mock next/link to render standard <a> tag
|
||||
vi.mock('next/link', () => ({
|
||||
@@ -52,45 +51,14 @@ describe('ReferencesSlider TDD', () => {
|
||||
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('wraps each tile in a link to /referenzen/[slug]', () => {
|
||||
it('renders each tile as a static block and NOT wrapped in a link', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const links = screen.getAllByTestId('reference-tile-link');
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-eins');
|
||||
expect(links[1].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-zwei');
|
||||
});
|
||||
const links = screen.queryAllByTestId('reference-tile-link');
|
||||
expect(links).toHaveLength(0);
|
||||
|
||||
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);
|
||||
const tiles = screen.getAllByTestId('reference-tile');
|
||||
expect(tiles).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -92,11 +92,6 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
||||
};
|
||||
|
||||
const handleTileClick = (e: React.MouseEvent) => {
|
||||
if (dragDistanceRef.current > 10) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="referenzen" className="py-24 bg-neutral-dark text-white relative overflow-hidden">
|
||||
@@ -135,10 +130,8 @@ 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"
|
||||
>
|
||||
<Link
|
||||
href={`/${locale}/referenzen/${ref.slug}`}
|
||||
onClick={handleTileClick}
|
||||
data-testid="reference-tile-link"
|
||||
<div
|
||||
data-testid="reference-tile"
|
||||
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
@@ -158,7 +151,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>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -39,6 +39,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
||||
title: "E-TIB Bohrtechnik GmbH",
|
||||
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
|
||||
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
||||
url: "/de/bohrtechnik",
|
||||
backgroundImage: "/assets/photos/DSC08653.JPG"
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -39,6 +39,7 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
||||
title: "E-TIB Bohrtechnik GmbH",
|
||||
description: "Precise horizontal directional drilling in all soil classes for surface-friendly and trenchless pipe laying.",
|
||||
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
||||
url: "/en/drilling-technology",
|
||||
backgroundImage: "/assets/photos/DSC08653.JPG"
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -45,7 +45,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 66.38,
|
||||
y: 14.04,
|
||||
description: 'Netzanbindung Solarpark',
|
||||
href: '/referenzen/dettmannsdorf',
|
||||
},
|
||||
{
|
||||
id: 'proetzel',
|
||||
@@ -54,7 +53,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 78.84,
|
||||
y: 32.5,
|
||||
description: 'Kabeltrasse Solarpark',
|
||||
href: '/referenzen/proetzel',
|
||||
},
|
||||
{
|
||||
id: 'ostbevern',
|
||||
@@ -63,7 +61,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 26.64,
|
||||
y: 39.84,
|
||||
description: 'Netzanbindung Windpark',
|
||||
href: '/referenzen/ostbevern',
|
||||
},
|
||||
{
|
||||
id: 'petershagen',
|
||||
@@ -72,7 +69,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 36.26,
|
||||
y: 35.79,
|
||||
description: 'Netzanbindung Windpark',
|
||||
href: '/referenzen/petershagen',
|
||||
},
|
||||
{
|
||||
id: 'ketzin',
|
||||
@@ -81,7 +77,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 72,
|
||||
y: 34,
|
||||
description: 'Netzanbindung Windpark',
|
||||
href: '/referenzen/ketzin',
|
||||
},
|
||||
{
|
||||
id: 'jevenstedt',
|
||||
@@ -90,7 +85,6 @@ export const defaultLocations: Location[] = [
|
||||
x: 43,
|
||||
y: 9,
|
||||
description: 'Netzanbindung Windpark',
|
||||
href: '/referenzen/jevenstedt',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
38
tests/task-12-links.test.ts
Normal file
38
tests/task-12-links.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { defaultLocations } from '../lib/map-data';
|
||||
|
||||
describe('Task 12 TDD - Bohrtechnik and Reference Links', () => {
|
||||
const deHomePath = path.join(process.cwd(), 'content', 'de', 'home.mdx');
|
||||
const enHomePath = path.join(process.cwd(), 'content', 'en', 'home.mdx');
|
||||
|
||||
it('should verify E-TIB Bohrtechnik GmbH has the correct page link in DE and EN home.mdx', () => {
|
||||
const deContent = fs.readFileSync(deHomePath, 'utf8');
|
||||
const enContent = fs.readFileSync(enHomePath, 'utf8');
|
||||
|
||||
// Assert that E-TIB Bohrtechnik GmbH has url: "/de/bohrtechnik" in German content
|
||||
expect(deContent).toContain('title: "E-TIB Bohrtechnik GmbH"');
|
||||
// We expect it to have the url parameter added
|
||||
const bohrtechnikDeBlock = deContent.substring(deContent.indexOf('title: "E-TIB Bohrtechnik GmbH"'));
|
||||
const urlDeIndex = bohrtechnikDeBlock.indexOf('url:');
|
||||
const nextItemDeIndex = bohrtechnikDeBlock.indexOf('title:', 10);
|
||||
|
||||
// Ensure "url:" exists within the E-TIB Bohrtechnik block and points to "/de/bohrtechnik"
|
||||
expect(deContent).toMatch(/title:\s*"E-TIB Bohrtechnik GmbH"[\s\S]*?url:\s*["']\/de\/bohrtechnik["']/);
|
||||
|
||||
// Assert that E-TIB Bohrtechnik GmbH has url: "/en/drilling-technology" in English content
|
||||
expect(enContent).toContain('title: "E-TIB Bohrtechnik GmbH"');
|
||||
expect(enContent).toMatch(/title:\s*"E-TIB Bohrtechnik GmbH"[\s\S]*?url:\s*["']\/en\/drilling-technology["']/);
|
||||
});
|
||||
|
||||
it('should verify that all reference projects in map-data have no href links', () => {
|
||||
// Check that all defaultLocations of type 'project' have href undefined or removed
|
||||
const projects = defaultLocations.filter(loc => loc.type === 'project');
|
||||
expect(projects.length).toBeGreaterThan(0);
|
||||
|
||||
projects.forEach(project => {
|
||||
expect(project.href).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user