Add GDPR compliant cookie banner and update footer/privacy policy to include GA and Clarity; added e2e and unit tests for cookie handling; updated snapshots
46
tests/cookie-options.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('visual regression', () => {
|
||||
test('cookie banner is visible', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
});
|
||||
|
||||
test('cookie banner is not visible when choices are accepted', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'accept');
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('cookie banner is not visible when choices are rejected', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('cookie banner is hidden when user selects accept', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
await page.locator('[data-consent="accept"]').click();
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(await page.evaluate(() => localStorage.getItem('mifi-ventures-cookie-consent'))).toBe('accept');
|
||||
});
|
||||
|
||||
test('cookie banner is hidden when user selects reject', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
await page.locator('[data-consent="reject"]').click();
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(await page.evaluate(() => localStorage.getItem('mifi-ventures-cookie-consent'))).toBe('reject');
|
||||
});
|
||||
});
|
||||
119
tests/unit/cookie-consent.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const SCRIPT_PATH = join(__dirname, '../../static/assets/js/cookie-consent.js');
|
||||
|
||||
function createBannerDOM() {
|
||||
const banner = document.createElement('div');
|
||||
banner.id = 'cookie-banner';
|
||||
|
||||
const acceptBtn = document.createElement('button');
|
||||
acceptBtn.type = 'button';
|
||||
acceptBtn.dataset.consent = 'accept';
|
||||
|
||||
const rejectBtn = document.createElement('button');
|
||||
rejectBtn.type = 'button';
|
||||
rejectBtn.dataset.consent = 'reject';
|
||||
|
||||
banner.appendChild(acceptBtn);
|
||||
banner.appendChild(rejectBtn);
|
||||
document.body.appendChild(banner);
|
||||
|
||||
return { banner, acceptBtn, rejectBtn };
|
||||
}
|
||||
|
||||
describe('cookie-consent.js', () => {
|
||||
const STORAGE_KEY = 'mifi-ventures-cookie-consent';
|
||||
let dom: ReturnType<typeof createBannerDOM>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Ensure a fresh DOM and storage for each test
|
||||
document.body.innerHTML = '';
|
||||
window.localStorage.clear();
|
||||
dom = createBannerDOM();
|
||||
|
||||
const code = readFileSync(SCRIPT_PATH, 'utf8');
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(code);
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dom.banner.remove();
|
||||
vi.restoreAllMocks();
|
||||
window.localStorage.clear();
|
||||
});
|
||||
|
||||
it('shows banner when no preference is stored', () => {
|
||||
expect(dom.banner.classList.contains('is-visible')).toBe(true);
|
||||
});
|
||||
|
||||
it('hides banner and loads analytics when preference is "accept"', () => {
|
||||
document.body.innerHTML = '';
|
||||
window.localStorage.setItem(STORAGE_KEY, 'accept');
|
||||
dom = createBannerDOM();
|
||||
|
||||
const appendChildSpy = vi.spyOn(document.head, 'appendChild');
|
||||
|
||||
const code = readFileSync(SCRIPT_PATH, 'utf8');
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(code);
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
expect(dom.banner.classList.contains('is-visible')).toBe(false);
|
||||
expect(appendChildSpy).toHaveBeenCalled();
|
||||
const urls = appendChildSpy.mock.calls
|
||||
.map((args) => (args[0] as HTMLScriptElement).src)
|
||||
.filter(Boolean);
|
||||
expect(urls.some((src) => src.includes('googletagmanager.com'))).toBe(true);
|
||||
});
|
||||
|
||||
it('hides banner when preference is "reject" and does not load analytics', () => {
|
||||
document.body.innerHTML = '';
|
||||
window.localStorage.setItem(STORAGE_KEY, 'reject');
|
||||
dom = createBannerDOM();
|
||||
|
||||
const appendChildSpy = vi.spyOn(document.head, 'appendChild');
|
||||
|
||||
const code = readFileSync(SCRIPT_PATH, 'utf8');
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(code);
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
|
||||
expect(dom.banner.classList.contains('is-visible')).toBe(false);
|
||||
const urls = appendChildSpy.mock.calls
|
||||
.map((args) => (args[0] as HTMLScriptElement).src)
|
||||
.filter(Boolean);
|
||||
expect(urls.some((src) => src.includes('googletagmanager.com'))).toBe(false);
|
||||
});
|
||||
|
||||
it('stores "accept" in localStorage, hides banner, and loads analytics on accept click', () => {
|
||||
const appendChildSpy = vi.spyOn(document.head, 'appendChild');
|
||||
|
||||
dom.acceptBtn.click();
|
||||
|
||||
expect(window.localStorage.getItem(STORAGE_KEY)).toBe('accept');
|
||||
expect(dom.banner.classList.contains('is-visible')).toBe(false);
|
||||
expect(appendChildSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('stores "reject" in localStorage and hides banner on reject click', () => {
|
||||
const appendChildSpy = vi.spyOn(document.head, 'appendChild');
|
||||
|
||||
dom.rejectBtn.click();
|
||||
|
||||
expect(window.localStorage.getItem(STORAGE_KEY)).toBe('reject');
|
||||
expect(dom.banner.classList.contains('is-visible')).toBe(false);
|
||||
const urls = appendChildSpy.mock.calls
|
||||
.map((args) => (args[0] as HTMLScriptElement).src)
|
||||
.filter(Boolean);
|
||||
expect(urls.some((src) => src.includes('googletagmanager.com'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,14 @@ test.describe('visual regression', () => {
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('home.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -15,6 +23,15 @@ test.describe('visual regression', () => {
|
||||
await expect(page).toHaveTitle(/SaaS Architecture Services | mifi Ventures/);
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/services');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('services.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -23,6 +40,16 @@ test.describe('visual regression', () => {
|
||||
await expect(page).toHaveTitle(/SaaS Product Architecture Consultant | mifi Ventures/);
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('#faq')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/services/hands-on-saas-architecture-consultant');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('services-hands-on-saas-architecture-consultant.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -31,6 +58,16 @@ test.describe('visual regression', () => {
|
||||
await expect(page).toHaveTitle(/MVP Architecture & Launch Consultant | mifi Ventures/);
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('#faq')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/services/mvp-architecture-and-launch');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('services-mvp-architecture-and-launch.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -39,6 +76,16 @@ test.describe('visual regression', () => {
|
||||
await expect(page).toHaveTitle(/Fractional CTO for Early-Stage SaaS | mifi Ventures/);
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('#faq')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/services/fractional-cto-for-early-stage-saas');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('services-fractional-cto-for-early-stage-saas.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -47,6 +94,16 @@ test.describe('visual regression', () => {
|
||||
await expect(page).toHaveTitle(/Startup Infrastructure Strategy | mifi Ventures/);
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('#faq')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/services/stage-aligned-infrastructure');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('services-stage-aligned-infrastructure.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -56,6 +113,14 @@ test.describe('visual regression', () => {
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/privacy-policy');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('privacy-policy.png', { fullPage: true });
|
||||
});
|
||||
|
||||
@@ -65,6 +130,14 @@ test.describe('visual regression', () => {
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(page.locator('#main')).toBeVisible();
|
||||
await expect(page.locator('.footer')).toBeVisible();
|
||||
await expect(page.locator('#cookie-banner')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('mifi-ventures-cookie-consent', 'reject');
|
||||
});
|
||||
|
||||
await page.goto('/terms-of-service');
|
||||
await expect(page.locator('#cookie-banner')).not.toBeVisible();
|
||||
await expect(page).toHaveScreenshot('terms-of-service.png', { fullPage: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 538 KiB After Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 548 KiB After Width: | Height: | Size: 571 KiB |
|
Before Width: | Height: | Size: 549 KiB After Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 544 KiB After Width: | Height: | Size: 715 KiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 375 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 789 KiB After Width: | Height: | Size: 805 KiB |
|
Before Width: | Height: | Size: 794 KiB After Width: | Height: | Size: 817 KiB |
|
Before Width: | Height: | Size: 911 KiB After Width: | Height: | Size: 927 KiB |
|
Before Width: | Height: | Size: 918 KiB After Width: | Height: | Size: 941 KiB |
|
Before Width: | Height: | Size: 687 KiB After Width: | Height: | Size: 703 KiB |
|
Before Width: | Height: | Size: 672 KiB After Width: | Height: | Size: 695 KiB |
|
Before Width: | Height: | Size: 870 KiB After Width: | Height: | Size: 886 KiB |
|
Before Width: | Height: | Size: 869 KiB After Width: | Height: | Size: 891 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 374 KiB |