File size: 6,101 Bytes
b068b76 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import fc from 'fast-check';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const HTML_FILES = ['dashboard.html', 'admin.html', 'hf_console.html'];
const REQUIRED_CSS = ['/static/css/unified-ui.css', '/static/css/components.css'];
const REQUIRED_JS_BASE = '/static/js/ui-feedback.js';
const PAGE_CONTROLLERS = {
'dashboard.html': '/static/js/dashboard-app.js',
'admin.html': '/static/js/admin-app.js',
'hf_console.html': '/static/js/hf-console.js'
};
function readHTMLFile(filename) {
const filePath = path.join(__dirname, '..', filename);
return fs.readFileSync(filePath, 'utf-8');
}
function extractLinks(html, tag, attr) {
const regex = new RegExp(`<${tag}[^>]*${attr}=["']([^"']+)["']`, 'g');
const matches = [];
let match;
while ((match = regex.exec(html)) !== null) {
matches.push(match[1]);
}
return matches;
}
console.log('Running Property-Based Tests for HTML Structure...\n');
HTML_FILES.forEach(filename => {
console.log(`\nTesting ${filename}:`);
const html = readHTMLFile(filename);
console.log(' Property 12.1: Should load only unified-ui.css and components.css');
const cssLinks = extractLinks(html, 'link', 'href')
.filter(href => href.includes('.css') && !href.includes('fonts.googleapis.com'));
if (cssLinks.length !== 2) {
throw new Error(`Expected 2 CSS files, found ${cssLinks.length}: ${cssLinks.join(', ')}`);
}
if (!cssLinks.includes(REQUIRED_CSS[0])) {
throw new Error(`Missing required CSS: ${REQUIRED_CSS[0]}`);
}
if (!cssLinks.includes(REQUIRED_CSS[1])) {
throw new Error(`Missing required CSS: ${REQUIRED_CSS[1]}`);
}
console.log(' β Loads only unified-ui.css and components.css');
console.log(' Property 12.2: Should load only ui-feedback.js and page-specific controller');
const jsScripts = extractLinks(html, 'script', 'src');
if (jsScripts.length !== 2) {
throw new Error(`Expected 2 JS files, found ${jsScripts.length}: ${jsScripts.join(', ')}`);
}
if (!jsScripts.includes(REQUIRED_JS_BASE)) {
throw new Error(`Missing required JS: ${REQUIRED_JS_BASE}`);
}
if (!jsScripts.includes(PAGE_CONTROLLERS[filename])) {
throw new Error(`Missing page controller: ${PAGE_CONTROLLERS[filename]}`);
}
console.log(' β Loads only ui-feedback.js and page-specific controller');
console.log(' Property 12.3: Should use relative URLs for all static assets');
const allAssets = [...cssLinks, ...jsScripts];
allAssets.forEach(asset => {
if (!asset.startsWith('/static/')) {
throw new Error(`Asset does not use /static/ prefix: ${asset}`);
}
});
console.log(' β All static assets use relative URLs with /static/ prefix');
console.log(' Property 12.4: Should have consistent navigation structure');
if (!html.includes('<nav class="nav-links">')) {
throw new Error('Missing nav-links navigation');
}
if (!html.includes('href="/dashboard"')) {
throw new Error('Missing /dashboard link');
}
if (!html.includes('href="/admin"')) {
throw new Error('Missing /admin link');
}
if (!html.includes('href="/hf_console"')) {
throw new Error('Missing /hf_console link');
}
if (!html.includes('href="/docs"')) {
throw new Error('Missing /docs link');
}
console.log(' β Has consistent navigation structure');
console.log(' Property 12.5: Should have correct active link');
const expectedActive = {
'dashboard.html': '/dashboard',
'admin.html': '/admin',
'hf_console.html': '/hf_console'
};
const activeLink = expectedActive[filename];
const activePattern = new RegExp(`<a[^>]*class=["'][^"']*active[^"']*["'][^>]*href=["']${activeLink}["']`);
if (!activePattern.test(html)) {
throw new Error(`Active link not found for ${activeLink}`);
}
console.log(' β Has correct active link');
console.log(' Property 12.6: Should have appropriate body class');
const expectedClass = {
'dashboard.html': 'page-dashboard',
'admin.html': 'page-admin',
'hf_console.html': 'page-hf'
};
if (!html.includes(`class="page ${expectedClass[filename]}"`)) {
throw new Error(`Missing body class: ${expectedClass[filename]}`);
}
console.log(' β Has appropriate body class');
console.log(' Property 12.7: Should not load legacy CSS files');
const legacyCSS = [
'glassmorphism.css',
'modern-dashboard.css',
'light-minimal-theme.css',
'pro-dashboard.css',
'styles.css',
'dashboard.css'
];
legacyCSS.forEach(legacy => {
if (html.includes(legacy)) {
throw new Error(`Found legacy CSS file: ${legacy}`);
}
});
console.log(' β Does not load legacy CSS files');
console.log(' Property 12.8: Should not load legacy JS files');
const legacyJS = [
'dashboard.js',
'adminDashboard.js',
'api-client.js',
'ws-client.js',
'wsClient.js',
'websocket-client.js'
];
legacyJS.forEach(legacy => {
if (legacy !== PAGE_CONTROLLERS[filename].split('/').pop() && html.includes(legacy)) {
throw new Error(`Found legacy JS file: ${legacy}`);
}
});
console.log(' β Does not load legacy JS files');
});
console.log('\nProperty 12.9: All pages should have identical navigation structure');
const navStructures = HTML_FILES.map(filename => {
const html = readHTMLFile(filename);
const navMatch = html.match(/<nav class="nav-links">([\s\S]*?)<\/nav>/);
return navMatch ? navMatch[1].replace(/class="active"\s*/g, '').replace(/\s+/g, ' ').trim() : '';
});
const firstNav = navStructures[0];
navStructures.forEach((nav, index) => {
if (nav !== firstNav) {
throw new Error(`Navigation structure differs in ${HTML_FILES[index]}`);
}
});
console.log('β All pages have identical navigation structure');
console.log('\nβ All property-based tests for HTML structure passed!');
|