feat: migrate Payload CMS to MDX and harden static infrastructure
This commit is contained in:
110
tests/contact-action.test.ts
Normal file
110
tests/contact-action.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { sendContactFormAction } from '../app/actions/contact';
|
||||
import * as mailer from '@/lib/mail/mailer';
|
||||
import { render } from '@mintel/mail';
|
||||
import React from 'react';
|
||||
|
||||
// Mock Next.js headers
|
||||
vi.mock('next/headers', () => ({
|
||||
headers: () =>
|
||||
Promise.resolve(
|
||||
new Map([
|
||||
['user-agent', 'test-agent'],
|
||||
['accept-language', 'de-DE,de;q=0.9'],
|
||||
['referer', 'http://localhost:3000/de/kontakt'],
|
||||
['x-forwarded-for', '127.0.0.1'],
|
||||
]),
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock services
|
||||
vi.mock('@/lib/services/create-services.server', () => ({
|
||||
getServerAppServices: () => ({
|
||||
logger: {
|
||||
child: () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
}),
|
||||
},
|
||||
analytics: {
|
||||
track: vi.fn(),
|
||||
setServerContext: vi.fn(),
|
||||
},
|
||||
errors: {
|
||||
captureException: vi.fn(),
|
||||
},
|
||||
notifications: {
|
||||
notify: vi.fn(),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock mailer
|
||||
vi.mock('@/lib/mail/mailer', () => ({
|
||||
sendEmail: vi.fn().mockResolvedValue({ success: true, messageId: 'test-id' }),
|
||||
}));
|
||||
|
||||
// Mock env
|
||||
vi.mock('@/lib/env', () => ({
|
||||
env: {
|
||||
MAIL_RECIPIENTS: 'internal@mintel.me',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('sendContactFormAction', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should successfully process a valid contact form submission', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', 'John Doe');
|
||||
formData.append('email', 'john@example.com');
|
||||
formData.append('message', 'Hello, this is a test message.');
|
||||
|
||||
const result = await sendContactFormAction(formData);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Verify internal notification was sent
|
||||
expect(mailer.sendEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyTo: 'john@example.com',
|
||||
subject: 'New Contact Form Submission',
|
||||
}),
|
||||
);
|
||||
|
||||
// Verify customer confirmation was sent
|
||||
expect(mailer.sendEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'john@example.com',
|
||||
subject: 'Thank you for your inquiry',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail if required fields are missing', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', 'John Doe');
|
||||
// Missing email and message
|
||||
|
||||
const result = await sendContactFormAction(formData);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Missing required fields');
|
||||
expect(mailer.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip email sending for test submissions', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', 'Tester');
|
||||
formData.append('email', 'testing@mintel.me');
|
||||
formData.append('message', 'Test');
|
||||
|
||||
const result = await sendContactFormAction(formData);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mailer.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
53
tests/contact-form.e2e.test.ts
Normal file
53
tests/contact-form.e2e.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import puppeteer, { Browser, Page } from 'puppeteer';
|
||||
|
||||
const BASE_URL = process.env.TEST_URL || 'http://localhost:3000';
|
||||
|
||||
describe('Contact Form E2E', () => {
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
let isServerUp = false;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Check if server is up
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/health`);
|
||||
if (res.ok) isServerUp = true;
|
||||
} catch (e) {
|
||||
isServerUp = false;
|
||||
}
|
||||
|
||||
if (isServerUp) {
|
||||
browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
});
|
||||
page = await browser.newPage();
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (browser) await browser.close();
|
||||
});
|
||||
|
||||
it('should submit the contact form and show success message', async ({ skip }) => {
|
||||
if (!isServerUp) skip();
|
||||
|
||||
await page.goto(`${BASE_URL}/de/kontakt`);
|
||||
|
||||
// Fill the form
|
||||
await page.waitForSelector('input[name="name"]');
|
||||
await page.type('input[name="name"]', 'E2E Tester');
|
||||
await page.type('input[name="email"]', 'testing@mintel.me');
|
||||
await page.type('textarea[name="message"]', 'This is an automated E2E test message.');
|
||||
|
||||
// Submit
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for success message (localized or generic)
|
||||
await page.waitForSelector('[role="alert"]', { timeout: 10000 });
|
||||
|
||||
const text = await page.evaluate(() => document.body.innerText);
|
||||
expect(text).toMatch(/Gesendet|Sent|Erfolgreich|Success/i);
|
||||
}, 30000);
|
||||
});
|
||||
23
tests/image-performance.test.ts
Normal file
23
tests/image-performance.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('Performance Standard: Image Sizes Prop', () => {
|
||||
it('should ensure the Header logo has a sizes prop to prevent Next.js warnings', () => {
|
||||
const headerPath = path.resolve(__dirname, '../components/Header.tsx');
|
||||
const headerContent = fs.readFileSync(headerPath, 'utf-8');
|
||||
|
||||
// Check if the logo image has a sizes prop
|
||||
// The logo uses src={logoSrc} and width={120} height={120}
|
||||
const hasSizesProp = /<Image[^>]*sizes=[^>]*>/g.test(headerContent);
|
||||
expect(hasSizesProp).toBe(true);
|
||||
});
|
||||
|
||||
it('should ensure the Footer logo has a sizes prop to prevent Next.js warnings', () => {
|
||||
const footerPath = path.resolve(__dirname, '../components/Footer.tsx');
|
||||
const footerContent = fs.readFileSync(footerPath, 'utf-8');
|
||||
|
||||
const hasSizesProp = /<Image[^>]*sizes=[^>]*>/g.test(footerContent);
|
||||
expect(hasSizesProp).toBe(true);
|
||||
});
|
||||
});
|
||||
46
tests/translation-links.test.ts
Normal file
46
tests/translation-links.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const DE_JSON = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, '../messages/de.json'), 'utf-8'),
|
||||
);
|
||||
const EN_JSON = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, '../messages/en.json'), 'utf-8'),
|
||||
);
|
||||
|
||||
describe('Translation Link Parity', () => {
|
||||
it('should ensure the German termsSlug points to an existing MDX page', () => {
|
||||
const slug = DE_JSON.Footer.termsSlug;
|
||||
expect(slug).toBeTruthy();
|
||||
|
||||
const pagePath = path.resolve(__dirname, `../content/pages/${slug}.mdx`);
|
||||
const exists = fs.existsSync(pagePath);
|
||||
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should ensure the English termsSlug points to an existing MDX page', () => {
|
||||
const slug = EN_JSON.Footer.termsSlug;
|
||||
expect(slug).toBeTruthy();
|
||||
|
||||
const pagePath = path.resolve(__dirname, `../content/pages/${slug}.mdx`);
|
||||
const exists = fs.existsSync(pagePath);
|
||||
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should ensure privacyPolicySlug points to an existing MDX page for DE', () => {
|
||||
const slug = DE_JSON.Footer.privacyPolicySlug;
|
||||
expect(slug).toBeTruthy();
|
||||
const pagePath = path.resolve(__dirname, `../content/pages/${slug}.mdx`);
|
||||
expect(fs.existsSync(pagePath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should ensure privacyPolicySlug points to an existing MDX page for EN', () => {
|
||||
const slug = EN_JSON.Footer.privacyPolicySlug;
|
||||
expect(slug).toBeTruthy();
|
||||
const pagePath = path.resolve(__dirname, `../content/pages/${slug}.mdx`);
|
||||
expect(fs.existsSync(pagePath)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user