fix(ui): prevent wrapped landing headings from overlapping
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { translations } from '../src/i18n/translations';
|
||||
|
||||
for (const language of ['en', 'nb'] as const) {
|
||||
test(`[P9 PRES-E01 E08] ${language} wrapped landing headings keep separate text lines`, async ({ page, context }, info) => {
|
||||
test.setTimeout(90000);
|
||||
await context.route('**/*', route => {
|
||||
const url = new URL(route.request().url());
|
||||
return ['localhost', '127.0.0.1'].includes(url.hostname) || ['data:', 'blob:', 'about:'].includes(url.protocol) ? route.continue() : route.abort();
|
||||
});
|
||||
await context.addInitScript(language => localStorage.setItem('uiLanguage', language), language);
|
||||
const observations = [];
|
||||
for (const theme of ['light', 'dark']) for (const width of [320, 390, 768, 1440]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto('/');
|
||||
await page.evaluate(theme => localStorage.setItem('jobtracker.themeMode', theme), theme);
|
||||
await page.reload();
|
||||
await expect(page.getByRole('heading', { name: translations[language].landingPreviewTitle, exact: true })).toBeVisible();
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
const headings = await page.locator('h2').evaluateAll(nodes => nodes.map(node => {
|
||||
const range = document.createRange(); range.selectNodeContents(node);
|
||||
const lines = [...range.getClientRects()].filter(rect => rect.width > 0).map(rect => ({ top: rect.top, bottom: rect.bottom }));
|
||||
return { text: node.textContent, lineHeight: getComputedStyle(node).lineHeight, lines };
|
||||
}));
|
||||
expect(headings.length).toBeGreaterThanOrEqual(4);
|
||||
if (width === 320) expect(headings.some(heading => heading.lines.length > 1)).toBe(true);
|
||||
for (const heading of headings) for (let index = 1; index < heading.lines.length; index++) {
|
||||
expect.soft(heading.lines[index].top, `${width}/${theme}: ${heading.text}`).toBeGreaterThanOrEqual(heading.lines[index - 1].bottom - 1);
|
||||
}
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width + 1);
|
||||
observations.push({ width, theme, language, headings });
|
||||
if (width === 320) await info.attach(`landing-${language}-${theme}-${width}`, { body: await page.screenshot({ fullPage: true, animations: 'disabled' }), contentType: 'image/png' });
|
||||
}
|
||||
await info.attach('landing-line-geometry', { body: JSON.stringify(observations, null, 2), contentType: 'application/json' });
|
||||
});
|
||||
}
|
||||
@@ -140,7 +140,7 @@ export default function LandingPage() {
|
||||
<Container maxWidth="lg" sx={{ py: { xs: 6, md: 9 } }}>
|
||||
<Box sx={{ textAlign: "center", mb: 5 }}>
|
||||
<Typography sx={{ color: "primary.main", fontWeight: 700, letterSpacing: 1, fontSize: 13 }}>{t("landingPreviewEyebrow")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 26, md: 34 }, mt: 1 }}>{t("landingPreviewTitle")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 26, md: 34 }, lineHeight: 1.4, mt: 1 }}>{t("landingPreviewTitle")}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ borderRadius: 3, overflow: "hidden", border: "1px solid", borderColor: "divider", boxShadow: 10, mb: 3, bgcolor: "background.paper" }}>
|
||||
<Box component="img" src="/mockups/dashboard.svg" alt={t("landingDashboardAlt")} sx={{ width: "100%", display: "block" }} />
|
||||
@@ -160,7 +160,7 @@ export default function LandingPage() {
|
||||
<Container id="features" maxWidth="lg" sx={{ py: { xs: 7, md: 10 } }}>
|
||||
<Box sx={{ textAlign: "center", mb: 6 }}>
|
||||
<Typography sx={{ color: "primary.main", fontWeight: 700, letterSpacing: 1, fontSize: 13 }}>{t("landingFeaturesEyebrow")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, mt: 1 }}>{t("landingFeaturesTitle")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, lineHeight: 1.4, mt: 1 }}>{t("landingFeaturesTitle")}</Typography>
|
||||
<Typography sx={{ color: "text.secondary", fontSize: 18, mt: 1.5 }}>
|
||||
{t("landingFeaturesBody")}
|
||||
</Typography>
|
||||
@@ -181,7 +181,7 @@ export default function LandingPage() {
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: "center", mb: 6 }}>
|
||||
<Typography sx={{ color: "#a5b4fc", fontWeight: 700, letterSpacing: 1, fontSize: 13 }}>{t("landingHowEyebrow")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, mt: 1 }}>{t("landingHowTitle")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, lineHeight: 1.4, mt: 1 }}>{t("landingHowTitle")}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr", md: "1fr 1fr 1fr" }, gap: 3 }}>
|
||||
{STEPS.map((s) => (
|
||||
@@ -199,7 +199,7 @@ export default function LandingPage() {
|
||||
<Container id="plans" maxWidth="lg" sx={{ py: { xs: 7, md: 10 } }}>
|
||||
<Box sx={{ textAlign: "center", mb: 6 }}>
|
||||
<Typography sx={{ color: "primary.main", fontWeight: 700, letterSpacing: 1, fontSize: 13 }}>{t("landingPlansEyebrow")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, mt: 1 }}>{t("landingPlansTitle")}</Typography>
|
||||
<Typography component="h2" sx={{ fontWeight: 800, fontSize: { xs: 28, md: 38 }, lineHeight: 1.4, mt: 1 }}>{t("landingPlansTitle")}</Typography>
|
||||
<Typography sx={{ color: "text.secondary", fontSize: 18, mt: 1.5 }}>
|
||||
{t("landingPlansBody")}
|
||||
</Typography>
|
||||
|
||||
Reference in New Issue
Block a user