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