| 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!'); | |