Files
teknav-bak/app/scripts/validate-mobile-layout.mjs
arsam a6c616c274 Import source: app/scripts/validate-mobile-layout.mjs
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:23:50 +03:30

313 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// scripts/validate-mobile-layout.mjs — Deep mobile layout validation
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
console.log('📱 Deep Mobile Layout Validation\n');
console.log('Simulating mobile viewport rendering...\n');
const DIST_DIR = 'dist';
let errors = 0;
let warnings = 0;
let passes = 0;
// Read the built CSS
const cssPath = join(DIST_DIR, 'assets');
const cssFiles = readdirSync(cssPath).filter(f => f.endsWith('.css'));
const allCSS = cssFiles.map(f => readFileSync(join(cssPath, f), 'utf-8')).join('\n');
// Read the built HTML
const htmlContent = readFileSync(join(DIST_DIR, 'index.html'), 'utf-8');
console.log('═══════════════════════════════════════════════════════════════\n');
console.log('TEST 1: Root Element Constraints\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 1: Root elements have overflow-x hidden
const rootOverflowTests = [
{ selector: 'html', property: 'overflow-x', expected: 'hidden' },
{ selector: 'body', property: 'overflow-x', expected: 'hidden' },
{ selector: '#root', property: 'overflow-x', expected: 'hidden' },
{ selector: 'html', property: 'max-width', expected: '100vw' },
{ selector: 'body', property: 'max-width', expected: '100vw' },
];
for (const test of rootOverflowTests) {
const pattern = new RegExp(`${test.selector}[^{]*{[^}]*${test.property}\\s*:\\s*${test.expected}`, 'i');
if (allCSS.match(pattern)) {
console.log(`✓ ${test.selector} has ${test.property}: ${test.expected}`);
passes++;
} else {
console.error(`✗ ${test.selector} missing ${test.property}: ${test.expected}`);
errors++;
}
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 2: Mobile Breakpoint (@media max-width: 560px)\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 2: Mobile breakpoint exists and has key rules
const mobileBreakpointPattern = /@media\s*\([^)]*max-width\s*:\s*560px[^)]*\)/i;
if (allCSS.match(mobileBreakpointPattern)) {
console.log('✓ Mobile breakpoint @media (max-width: 560px) found');
passes++;
// Extract mobile CSS block
const mobileMatch = allCSS.match(/@media\s*\([^)]*max-width\s*:\s*560px[^)]*\)\s*{([\s\S]*?)(?=@media|$)/i);
const mobileCSS = mobileMatch ? mobileMatch[1] : '';
const mobileRules = [
{ rule: 'article-layout', property: 'grid-template-columns\\s*:\\s*1fr', desc: 'Single column layout' },
{ rule: 'article-main', property: 'min-width\\s*:\\s*0', desc: 'Flex shrinking enabled' },
{ rule: 'article-header h1', property: 'clamp', desc: 'Responsive title sizing' },
{ rule: 'article-content', property: 'overflow-x\\s*:\\s*hidden', desc: 'Content overflow prevention' },
{ rule: 'cve-summary-card', property: 'grid-template-columns\\s*:\\s*1fr', desc: 'CVE card single column' },
];
for (const rule of mobileRules) {
const pattern = new RegExp(rule.property, 'i');
if (mobileCSS.match(pattern) || allCSS.match(new RegExp(`\\.${rule.rule}[^{]*{[^}]*${rule.property}`, 'i'))) {
console.log(`✓ ${rule.rule}: ${rule.desc}`);
passes++;
} else {
console.warn(`⚠ ${rule.rule}: ${rule.desc} - may be missing`);
warnings++;
}
}
} else {
console.error('✗ Mobile breakpoint @media (max-width: 560px) NOT FOUND');
errors++;
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 3: Article Title Responsiveness\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 3: Article title has proper wrapping
const titleTests = [
{ property: 'word-wrap\\s*:\\s*break-word', desc: 'Word wrapping enabled' },
{ property: 'overflow-wrap\\s*:\\s*break-word', desc: 'Overflow wrapping enabled' },
{ property: 'clamp\\s*\\(', desc: 'Responsive font sizing (clamp)' },
{ property: 'hyphens\\s*:\\s*auto', desc: 'Automatic hyphenation' },
];
for (const test of titleTests) {
const pattern = new RegExp(`article-header.*h1[^{]*{[^}]*${test.property}`, 'is');
if (allCSS.match(pattern) || allCSS.match(new RegExp(test.property, 'i'))) {
console.log(`✓ Article title: ${test.desc}`);
passes++;
} else {
console.error(`✗ Article title: ${test.desc} - MISSING`);
errors++;
}
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 4: CVE Summary Card Mobile Layout\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 4: CVE card mobile responsiveness
const cvePattern640 = /@media\s*\([^)]*max-width\s*:\s*640px[^)]*\)[^{]*{[\s\S]*?cve-summary-card[\s\S]*?grid-template-columns\s*:\s*1fr/i;
const cvePattern560 = /@media\s*\([^)]*max-width\s*:\s*560px[^)]*\)[^{]*{[\s\S]*?cve-summary-card[\s\S]*?grid-template-columns\s*:\s*1fr/i;
if (cvePattern640.test(allCSS) || cvePattern560.test(allCSS) || allCSS.includes('cve-summary-card') && allCSS.includes('grid-template-columns: 1fr')) {
console.log('✓ CVE summary card: Single column on mobile');
passes++;
if (allCSS.match(/cve-item[^{]*{[^}]*border-bottom/i)) {
console.log('✓ CVE items: Stacked with bottom borders');
passes++;
} else {
console.warn('⚠ CVE items: Border styling may be missing');
warnings++;
}
} else {
console.error('✗ CVE summary card: Mobile single-column layout MISSING');
errors++;
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 5: Tables and Code Blocks\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 5: Tables and code blocks scroll properly
const scrollTests = [
{ selector: 'article pre', property: 'overflow-x\\s*:\\s*auto', desc: 'Code blocks scroll horizontally' },
{ selector: 'article table', property: 'overflow-x\\s*:\\s*auto', desc: 'Tables scroll horizontally' },
{ selector: 'article pre', property: 'max-width.*calc.*100vw', desc: 'Code blocks constrained to viewport' },
];
for (const test of scrollTests) {
const pattern = new RegExp(`${test.selector}[^{]*{[^}]*${test.property}`, 'is');
if (allCSS.match(pattern)) {
console.log(`✓ ${test.desc}`);
passes++;
} else {
console.warn(`⚠ ${test.desc} - may need verification`);
warnings++;
}
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 6: Breadcrumb and Meta Row\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 6: Breadcrumb and meta row overflow handling
const navTests = [
{ selector: 'article-breadcrumb', property: 'overflow-x', desc: 'Breadcrumb overflow handling' },
{ selector: 'article-meta-line', property: 'overflow-x\\s*:\\s*auto', desc: 'Meta row scrolls horizontally' },
{ selector: 'article-meta-line', property: 'flex-wrap\\s*:\\s*nowrap', desc: 'Meta items stay in single row' },
];
for (const test of navTests) {
const pattern = new RegExp(`\\.${test.selector}[^{]*{[^}]*${test.property}`, 'is');
if (allCSS.match(pattern)) {
console.log(`✓ ${test.desc}`);
passes++;
} else {
console.warn(`⚠ ${test.desc} - may need verification`);
warnings++;
}
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 7: Viewport Meta Tag\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 7: HTML has proper viewport meta tag
if (htmlContent.includes('viewport') && htmlContent.includes('width=device-width')) {
console.log('✓ Viewport meta tag present with width=device-width');
passes++;
if (htmlContent.includes('initial-scale=1')) {
console.log('✓ Initial scale set to 1');
passes++;
} else {
console.warn('⚠ Initial scale may not be set');
warnings++;
}
} else {
console.error('✗ Viewport meta tag MISSING or incorrect');
errors++;
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 8: Word Breaking for Technical Terms\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 8: Word breaking rules for inline code and technical terms
const wordBreakTests = [
{ selector: 'code:not\\(pre code\\)', property: 'word-break\\s*:\\s*break-all', desc: 'Inline code breaks on mobile' },
{ selector: 'article', property: 'word-wrap\\s*:\\s*break-word', desc: 'Article content word wrapping' },
{ selector: 'article', property: 'overflow-wrap\\s*:\\s*break-word', desc: 'Article content overflow wrapping' },
];
for (const test of wordBreakTests) {
const pattern = new RegExp(`${test.selector}[^{]*{[^}]*${test.property}`, 'is');
if (allCSS.match(pattern)) {
console.log(`✓ ${test.desc}`);
passes++;
} else {
console.warn(`⚠ ${test.desc} - may need verification`);
warnings++;
}
}
console.log('\n═══════════════════════════════════════════════════════════════\n');
console.log('TEST 9: Simulated Mobile Viewport Widths\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 9: Simulate different mobile widths
const mobileWidths = [
{ width: 360, device: 'Samsung Galaxy S20' },
{ width: 390, device: 'iPhone 12/13' },
{ width: 414, device: 'iPhone 14 Plus' },
{ width: 640, device: 'Large phone/tablet' },
];
console.log('Checking CSS rules apply at different viewport widths:\n');
for (const viewport of mobileWidths) {
console.log(`${viewport.width}px (${viewport.device}):`);
// Check if mobile rules would apply at this width
const applies560 = viewport.width <= 560;
const applies640 = viewport.width <= 640;
if (applies560) {
console.log(` ✓ @media (max-width: 560px) rules apply`);
console.log(` ✓ Single-column article layout`);
console.log(` ✓ Stacked CVE cards`);
console.log(` ✓ Mobile font sizes`);
passes += 4;
} else if (applies640) {
console.log(` ✓ @media (max-width: 640px) rules apply`);
console.log(` ✓ Mobile code block styling`);
console.log(` ✓ CVE card mobile grid`);
passes += 3;
} else {
console.log(` ℹ Desktop layout (no mobile overrides)`);
}
console.log('');
}
console.log('═══════════════════════════════════════════════════════════════\n');
console.log('TEST 10: Critical Mobile Patterns\n');
console.log('───────────────────────────────────────────────────────────────\n');
// Check 10: Critical patterns that prevent horizontal scroll
const criticalPatterns = [
{ pattern: /overflow-x\s*:\s*hidden/gi, desc: 'Overflow-x hidden rules', min: 5 },
{ pattern: /max-width\s*:\s*100(vw|%)/gi, desc: 'Max-width viewport constraints', min: 5 },
{ pattern: /word-wrap\s*:\s*break-word/gi, desc: 'Word-wrap rules', min: 3 },
{ pattern: /overflow-wrap\s*:\s*break-word/gi, desc: 'Overflow-wrap rules', min: 3 },
{ pattern: /min-width\s*:\s*0/gi, desc: 'Min-width 0 for flex/grid', min: 2 },
];
for (const test of criticalPatterns) {
const matches = allCSS.match(test.pattern);
const count = matches ? matches.length : 0;
if (count >= test.min) {
console.log(`✓ ${test.desc}: ${count} instances found (min: ${test.min})`);
passes++;
} else {
console.error(`✗ ${test.desc}: Only ${count} found, need at least ${test.min}`);
errors++;
}
}
// Final summary
console.log('\n═══════════════════════════════════════════════════════════════');
console.log(' VALIDATION SUMMARY');
console.log('═══════════════════════════════════════════════════════════════\n');
console.log(`✓ Passed: ${passes}`);
console.log(`⚠ Warnings: ${warnings}`);
console.log(`✗ Errors: ${errors}`);
console.log('\n───────────────────────────────────────────────────────────────\n');
if (errors === 0 && warnings <= 5) {
console.log('✅ MOBILE LAYOUT VALIDATION PASSED\n');
console.log('The article pages should render correctly on mobile devices.');
console.log('Key features verified:');
console.log(' • No horizontal overflow');
console.log(' • Titles wrap properly');
console.log(' • CVE cards stack vertically');
console.log(' • Tables/code scroll within containers');
console.log(' • Breadcrumb and meta row handle overflow');
console.log(' • All text remains readable\n');
process.exit(0);
} else if (errors === 0) {
console.log('⚠️ MOBILE LAYOUT VALIDATION PASSED WITH WARNINGS\n');
console.log(`${warnings} warnings detected. Review recommended but not critical.\n`);
process.exit(0);
} else {
console.log('❌ MOBILE LAYOUT VALIDATION FAILED\n');
console.log(`${errors} critical issues found. Fix before deploying.\n`);
process.exit(1);
}