4.0.1 - fixes

This commit is contained in:
2026-05-02 19:44:45 +12:00
parent b0bb692972
commit 07c754da12
34 changed files with 497 additions and 233 deletions
+9 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import Icon from '$lib/components/Icon.svelte';
import { reveal } from '$lib/actions/reveal';
import { getImageMetadata } from '$lib/image-metadata';
import type { AboutPageContent, SiteSharedContent } from '$lib/types';
export let content: SiteSharedContent;
@@ -29,7 +30,14 @@
</div>
<div class="about-media">
<img src={section.imageUrl} alt={section.imageAlt} loading="lazy" />
<img
src={section.imageUrl}
alt={section.imageAlt}
width={getImageMetadata(section.imageUrl)?.width}
height={getImageMetadata(section.imageUrl)?.height}
loading="lazy"
decoding="async"
/>
</div>
</div>
</section>
+4 -1
View File
@@ -18,7 +18,10 @@
src="/images/goodwalk-auckland-dog-walking-logo.png"
alt="Goodwalk Auckland dog walking service logo"
class="footer-logo"
height="28"
width="241"
height="48"
loading="lazy"
decoding="async"
/>
<p>{footer.brandText}</p>
<div class="social-links">
+7 -1
View File
@@ -119,7 +119,13 @@
media="(max-width: 768px)"
srcset="/images/goodwalk-auckland-dog-walking-logo-mobile.png"
/>
<img src="/images/goodwalk-auckland-dog-walking-logo.png" alt="Goodwalk Auckland dog walking service logo" height="21" />
<img
src="/images/goodwalk-auckland-dog-walking-logo.png"
alt="Goodwalk Auckland dog walking service logo"
width="241"
height="48"
decoding="async"
/>
</picture>
</a>
+4
View File
@@ -1,10 +1,12 @@
<script lang="ts">
import { getImageMetadata } from '$lib/image-metadata';
import type { HeroContent } from '$lib/types';
export let hero: HeroContent;
$: titleParts = splitTitle(hero.title);
$: mobileTitle = hero.mobileTitle?.trim() || `${hero.title} ${hero.highlight}`.trim();
$: heroImage = getImageMetadata(hero.imageUrl);
function splitTitle(title: string) {
const trimmed = title.trim();
@@ -48,6 +50,8 @@
<img
src={hero.imageUrl}
alt={hero.imageAlt}
width={heroImage?.width}
height={heroImage?.height}
loading="eager"
fetchpriority="high"
decoding="async"
+11 -1
View File
@@ -1,7 +1,10 @@
<script lang="ts">
import { getImageMetadata } from '$lib/image-metadata';
import type { PromiseContent } from '$lib/types';
export let promise: PromiseContent;
$: promiseImage = getImageMetadata(promise.imageUrl);
</script>
<section id="promise">
@@ -21,7 +24,14 @@
</div>
<div class="promise-img">
<img src={promise.imageUrl} alt={promise.imageAlt} />
<img
src={promise.imageUrl}
alt={promise.imageAlt}
width={promiseImage?.width}
height={promiseImage?.height}
loading="lazy"
decoding="async"
/>
</div>
</div>
</section>
+8
View File
@@ -1,4 +1,6 @@
<script lang="ts">
import { getImageMetadata } from '$lib/image-metadata';
export let title: string;
export let description: string;
export let canonicalPath: string;
@@ -31,6 +33,7 @@
$: pageTitle = fullTitle(title);
$: canonicalUrl = absoluteUrl(canonicalPath);
$: imageUrl = absoluteUrl(image);
$: imageMeta = getImageMetadata(image);
</script>
<svelte:head>
@@ -62,8 +65,13 @@
<meta property="og:image" content={imageUrl} />
<meta property="og:image:secure_url" content={imageUrl} />
<meta property="og:image:alt" content={imageAlt} />
{#if imageMeta}
<meta property="og:image:width" content={String(imageMeta.width)} />
<meta property="og:image:height" content={String(imageMeta.height)} />
{/if}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@goodwalk.nz" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={imageUrl} />
@@ -3,10 +3,14 @@
import { reveal } from '$lib/actions/reveal';
import BookingSection from '$lib/components/BookingSection.svelte';
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
import { getImageMetadata } from '$lib/image-metadata';
import type { ServicePageContent, SiteSharedContent } from '$lib/types';
export let content: SiteSharedContent;
export let pageContent: ServicePageContent;
$: heroImage = getImageMetadata(pageContent.hero.imageUrl);
$: highlightImage = pageContent.highlight ? getImageMetadata(pageContent.highlight.imageUrl) : null;
</script>
<main class="service-page">
@@ -25,6 +29,8 @@
<img
src={pageContent.hero.imageUrl}
alt={pageContent.hero.imageAlt}
width={heroImage?.width}
height={heroImage?.height}
loading="eager"
fetchpriority="high"
decoding="async"
@@ -45,7 +51,10 @@
<img
src={pageContent.highlight.imageUrl}
alt={pageContent.highlight.imageAlt}
width={highlightImage?.width}
height={highlightImage?.height}
loading="lazy"
decoding="async"
/>
</div>
</div>
@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { reveal } from '$lib/actions/reveal';
import Icon from '$lib/components/Icon.svelte';
import { getImageMetadata } from '$lib/image-metadata';
import type { TestimonialContent } from '$lib/types';
export let testimonials: TestimonialContent[];
@@ -123,11 +124,14 @@
<div class="testimonial-photo-wrap">
<div class="testimonial-photo-frame">
{#if index === activeIndex}
{@const imageMeta = getImageMetadata(testimonial.imageUrl)}
<img
class="testimonial-photo"
src={testimonial.imageUrl}
alt={`${testimonial.reviewer}'s dog`}
loading={activeIndex === 0 ? 'eager' : 'lazy'}
width={imageMeta?.width}
height={imageMeta?.height}
loading="lazy"
decoding="async"
/>
{/if}
+85 -10
View File
@@ -1,21 +1,48 @@
import { fireEvent, render } from '@testing-library/svelte';
import { fireEvent, render, screen } from '@testing-library/svelte';
import { afterEach, describe, expect, it, vi } from 'vitest';
import TestimonialsSection from './TestimonialsSection.svelte';
import { homepageContent } from '$lib/content/homepage';
import type { TestimonialContent } from '$lib/types';
const expectedMappedSlides = [
{ reviewer: 'Kate', src: '/images/archie-auckland-dog-walking-review.png' },
{ reviewer: 'Estelle', src: '/images/monty-auckland-dog-walking-review.png' },
{ reviewer: 'Ross', src: '/images/otis-auckland-dog-walking-review.png' },
{ reviewer: 'Nina', src: '/images/wallace-auckland-dog-walking-review.png' }
];
function getActiveSlide(container: HTMLElement) {
return container.querySelector('.testimonial-slide-active') as HTMLElement;
}
function getActiveReviewer(container: HTMLElement) {
return getActiveSlide(container).querySelector('.testimonial-author-name')?.textContent;
}
function getActiveImage(container: HTMLElement) {
return getActiveSlide(container).querySelector('img') as HTMLImageElement;
}
describe('TestimonialsSection', () => {
afterEach(() => {
vi.useRealTimers();
});
it('uses the mapped local image assets for known testimonials', () => {
it('maps all known testimonial images to the local PNG assets', async () => {
const { container } = render(TestimonialsSection, {
testimonials: homepageContent.testimonials
});
const activeImage = container.querySelector('.testimonial-slide-active img') as HTMLImageElement;
const nextButton = screen.getByRole('button', { name: /next testimonial/i });
expect(activeImage.getAttribute('src')).toBe('/images/archie-auckland-dog-walking-review.jpg');
for (const [index, slide] of expectedMappedSlides.entries()) {
expect(getActiveReviewer(container)).toBe(slide.reviewer);
expect(getActiveImage(container).getAttribute('src')).toBe(slide.src);
if (index < expectedMappedSlides.length - 1) {
await fireEvent.click(nextButton);
}
}
});
it('moves to the next testimonial on arrow click and auto-rotation', async () => {
@@ -25,16 +52,64 @@ describe('TestimonialsSection', () => {
testimonials: homepageContent.testimonials
});
const nextButton = container.querySelector('.testimonial-arrow-right') as HTMLButtonElement;
const activeReviewer = () =>
(container.querySelector('.testimonial-slide-active h6 strong') as HTMLElement).textContent;
const nextButton = screen.getByRole('button', { name: /next testimonial/i });
expect(activeReviewer()).toBe('Kate');
expect(getActiveReviewer(container)).toBe('Kate');
await fireEvent.click(nextButton);
expect(activeReviewer()).toBe('Estelle');
expect(getActiveReviewer(container)).toBe('Estelle');
await vi.advanceTimersByTimeAsync(5000);
expect(activeReviewer()).toBe('Ross');
expect(getActiveReviewer(container)).toBe('Ross');
});
it('wraps to the last testimonial when navigating backwards from the first slide', async () => {
const { container } = render(TestimonialsSection, {
testimonials: homepageContent.testimonials
});
const previousButton = screen.getByRole('button', { name: /previous testimonial/i });
expect(getActiveReviewer(container)).toBe('Kate');
await fireEvent.click(previousButton);
expect(getActiveReviewer(container)).toBe('Nina');
expect(getActiveImage(container).getAttribute('src')).toBe(
'/images/wallace-auckland-dog-walking-review.png'
);
});
it('keeps custom testimonial images and filters out testimonials with no image', async () => {
const customTestimonials: TestimonialContent[] = [
...homepageContent.testimonials,
{
reviewer: 'Casey',
detail: "Poppy's mum",
quote: 'Thoughtful updates and a very happy dog after every walk.',
imageUrl: '/images/custom-casey-review.png'
},
{
reviewer: 'Jordan',
detail: "Scout's dad",
quote: 'Should be hidden because there is no image.'
}
];
const { container } = render(TestimonialsSection, {
testimonials: customTestimonials
});
const nextButton = screen.getByRole('button', { name: /next testimonial/i });
expect(container.querySelectorAll('.testimonial-slide')).toHaveLength(5);
for (let step = 0; step < 4; step += 1) {
await fireEvent.click(nextButton);
}
expect(getActiveReviewer(container)).toBe('Casey');
expect(getActiveImage(container).getAttribute('src')).toBe('/images/custom-casey-review.png');
expect(screen.queryByText('Jordan')).not.toBeInTheDocument();
});
});