init project

This commit is contained in:
root
2026-06-25 19:06:59 -04:00
parent c5dfee6efb
commit 5d017f533a
349 changed files with 31330 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
import { expect, test } from '@playwright/test';
for (const locale of ['en', 'fr', 'ar'] as const) {
test(`${locale} component fixture renders without horizontal overflow`, async ({ page }) => {
await page.goto(`/${locale}/component-lab`);
await expect(page.locator('h1')).toBeVisible();
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth,
);
expect(overflow).toBe(false);
await expect(page.locator('html')).toHaveAttribute('dir', locale === 'ar' ? 'rtl' : 'ltr');
});
}
test('component controls support keyboard interaction and dialog focus return', async ({
page,
}) => {
await page.goto('/en/component-lab');
const firstTab = page.getByRole('tab', { name: 'Reservations' });
await firstTab.focus();
await page.keyboard.press('ArrowRight');
await expect(page.getByRole('tab', { name: 'Fleet availability' })).toHaveAttribute(
'aria-selected',
'true',
);
const trigger = page.getByRole('button', { name: 'Open dialog' });
await trigger.click();
await expect(page.getByRole('dialog')).toBeVisible();
await page.keyboard.press('Escape');
await expect(page.getByRole('dialog')).toBeHidden();
await expect(trigger).toBeFocused();
});
test('component fixture reflows at narrow mobile and 200 percent zoom equivalent', async ({
page,
}) => {
await page.setViewportSize({ width: 320, height: 800 });
await page.goto('/fr/component-lab');
await page.evaluate(() => (document.body.style.zoom = '2'));
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
);
expect(overflow).toBe(false);
});
+51
View File
@@ -0,0 +1,51 @@
import { expect, test } from '@playwright/test';
for (const scenario of [
{ locale: 'en', dir: 'ltr' },
{ locale: 'fr', dir: 'ltr' },
{ locale: 'ar', dir: 'rtl' },
] as const) {
test(`${scenario.locale} homepage assembles the approved section sequence`, async ({ page }) => {
await page.goto(`/${scenario.locale}`);
await expect(page.locator('html')).toHaveAttribute('dir', scenario.dir);
await expect(page.locator('main h1')).toHaveCount(1);
await expect(page.locator('#product')).toBeVisible();
await expect(page.locator('#workflow')).toBeVisible();
await expect(page.locator('#modules')).toBeVisible();
await expect(page.locator('#pricing')).toBeVisible();
await expect(page.locator('#faq')).toBeVisible();
await expect(page.locator('main > section')).toHaveCount(12);
});
test(`${scenario.locale} unresolved actions are non-navigable`, async ({ page }) => {
await page.goto(`/${scenario.locale}`);
const disabledActions = page.locator('main [aria-disabled="true"]');
await expect(disabledActions).toHaveCount(5);
await expect(disabledActions.first()).not.toHaveAttribute('href');
});
}
test('Arabic preview preserves left-to-right vehicle identifiers', async ({ page }) => {
await page.goto('/ar');
const identifiers = page.locator('[role="table"] bdi');
await expect(identifiers).toHaveCount(2);
await expect(identifiers.first()).toHaveAttribute('dir', 'ltr');
});
test('homepage navigation targets resolve to real sections', async ({ page }) => {
await page.goto('/en');
for (const hash of ['product', 'workflow', 'modules', 'pricing', 'faq']) {
await expect(page.locator(`#${hash}`)).toHaveCount(1);
await expect(page.locator(`header a[href="/en#${hash}"]`)).toHaveCount(2);
}
});
test('FAQ uses native disclosure behavior', async ({ page }) => {
await page.goto('/en');
const firstItem = page.locator('#faq details').first();
await expect(firstItem).toHaveAttribute('open', '');
await firstItem.locator('summary').click();
await expect(firstItem).not.toHaveAttribute('open', '');
});
+28
View File
@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test';
test.use({ viewport: { width: 390, height: 844 } });
test('mobile navigation opens modally, closes with Escape, and returns focus', async ({ page }) => {
await page.goto('/en');
const trigger = page.getByRole('button', { name: 'Open navigation menu' });
await trigger.focus();
await trigger.click();
const dialog = page.getByRole('dialog', { name: 'Primary navigation' });
await expect(dialog).toBeVisible();
await expect(page.locator('body')).toHaveAttribute('data-navigation-open', 'true');
await expect(page.getByRole('button', { name: 'Close navigation menu' })).toBeFocused();
await page.keyboard.press('Escape');
await expect(dialog).not.toBeVisible();
await expect(trigger).toBeFocused();
});
test('skip navigation moves focus to the main landmark', async ({ page }) => {
await page.goto('/en');
await page.keyboard.press('Tab');
const skip = page.getByRole('link', { name: 'Skip to main content' });
await expect(skip).toBeFocused();
await skip.press('Enter');
await expect(page.locator('#main-content')).toBeFocused();
});
+12
View File
@@ -0,0 +1,12 @@
import { expect, test } from '@playwright/test';
test('server shell remains readable with JavaScript disabled', async ({ browser }) => {
const context = await browser.newContext({ javaScriptEnabled: false, colorScheme: 'dark' });
const page = await context.newPage();
await page.goto('/ar');
await expect(page.locator('html')).toHaveAttribute('lang', 'ar');
await expect(page.locator('html')).toHaveAttribute('dir', 'rtl');
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('footer')).toBeVisible();
await context.close();
});
+24
View File
@@ -0,0 +1,24 @@
import { expect, test } from '@playwright/test';
const widths = [320, 390, 768, 1024, 1440];
for (const locale of ['en', 'fr', 'ar'] as const) {
for (const width of widths) {
test(`${locale} has no horizontal overflow at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.goto(`/${locale}`);
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth - document.documentElement.clientWidth,
);
expect(overflow).toBeLessThanOrEqual(1);
});
}
}
test('shell remains operable at a 320 CSS pixel reflow equivalent to 200 percent zoom', async ({
page,
}) => {
await page.setViewportSize({ width: 640, height: 800 });
await page.goto('/fr');
await expect(page.getByRole('button', { name: 'Ouvrir le menu de navigation' })).toBeVisible();
});
+58
View File
@@ -0,0 +1,58 @@
import { expect, test } from '@playwright/test';
for (const scenario of [
{ locale: 'en', dir: 'ltr', title: /RentalDriveGo/ },
{ locale: 'fr', dir: 'ltr', title: /RentalDriveGo/ },
{ locale: 'ar', dir: 'rtl', title: /RentalDriveGo/ },
] as const) {
test(`${scenario.locale} shell is server rendered`, async ({ page, request }) => {
const raw = await request.get(`/${scenario.locale}`);
const html = await raw.text();
expect(raw.status()).toBe(200);
expect(html).toMatch(
new RegExp(`<html[^>]*lang=\"${scenario.locale}\"[^>]*dir=\"${scenario.dir}\"`),
);
const response = await page.goto(`/${scenario.locale}`);
expect(response?.status()).toBe(200);
await expect(page.locator('html')).toHaveAttribute('lang', scenario.locale);
await expect(page.locator('html')).toHaveAttribute('dir', scenario.dir);
await expect(page).toHaveTitle(scenario.title);
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('header')).toBeVisible();
await expect(page.locator('footer')).toBeVisible();
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
'href',
new RegExp(`/${scenario.locale}$`),
);
for (const language of ['en', 'fr', 'ar', 'x-default']) {
await expect(page.locator(`link[rel="alternate"][hreflang="${language}"]`)).toHaveCount(1);
}
await expect(page.locator('meta[property="og:locale"]')).toHaveAttribute(
'content',
scenario.locale,
);
});
}
test('root locale detection honors Accept-Language', async ({ browser }) => {
const context = await browser.newContext({
extraHTTPHeaders: { 'Accept-Language': 'ar;q=1,en;q=0.8' },
});
const page = await context.newPage();
await page.goto('/');
await expect(page).toHaveURL(/\/ar$/);
await context.close();
});
test('locale switching keeps the semantic route and approved campaign state', async ({ page }) => {
await page.goto('/en/privacy?utm_source=qa&email=drop-me#ignored');
await page.getByLabel('Language').first().selectOption('fr');
await expect(page).toHaveURL('/fr/confidentialite?utm_source=qa');
});
test('pending route shells are localized and noindex', async ({ page }) => {
await page.goto('/fr/confidentialite');
await expect(page.locator('h1')).toContainText('Confidentialité');
await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', /noindex/);
});
+20
View File
@@ -0,0 +1,20 @@
import { expect, test } from '@playwright/test';
for (const locale of ['fr', 'ar'] as const) {
test(`${locale} shell tolerates long content expansion`, async ({ page }) => {
await page.setViewportSize({ width: 390, height: 900 });
await page.goto(`/${locale}`);
await page
.locator('main article h3')
.first()
.evaluate((element) => {
element.textContent = [element.textContent, element.textContent, element.textContent]
.filter(Boolean)
.join(' ');
});
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth - document.documentElement.clientWidth,
);
expect(overflow).toBeLessThanOrEqual(1);
});
}
+58
View File
@@ -0,0 +1,58 @@
import { expect, test } from '@playwright/test';
test('explicit dark theme is present on the server-rendered root', async ({ browser, baseURL }) => {
const origin = new URL(baseURL ?? 'http://127.0.0.1:3000');
const context = await browser.newContext();
await context.addCookies([
{
name: 'hpc-theme',
value: 'dark',
domain: origin.hostname,
path: '/',
secure: false,
sameSite: 'Lax',
},
]);
const page = await context.newPage();
const response = await page.goto('/en');
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'dark');
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
const csp = response?.headers()['content-security-policy'] ?? '';
const nonce = await page.locator('#theme-bootstrap').getAttribute('nonce');
expect(nonce).toBeTruthy();
expect(csp).toContain(`'nonce-${nonce}'`);
await context.close();
});
test('system preference resolves before interaction', async ({ browser }) => {
const context = await browser.newContext({ colorScheme: 'dark' });
const page = await context.newPage();
await page.goto('/en');
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'system');
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
await context.close();
});
test('system follows operating-system changes while explicit preferences ignore them', async ({
page,
}) => {
await page.goto('/en');
const selector = page.getByLabel('Theme').first();
await selector.selectOption('system');
await page.emulateMedia({ colorScheme: 'dark' });
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
await page.emulateMedia({ colorScheme: 'light' });
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
await selector.selectOption('dark');
await page.emulateMedia({ colorScheme: 'light' });
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'dark');
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
expect(await page.evaluate(() => window.localStorage.getItem('hpc.theme.preference'))).toBe(
'dark',
);
expect(await page.context().cookies()).toEqual(
expect.arrayContaining([expect.objectContaining({ name: 'hpc-theme', value: 'dark' })]),
);
});