Files
rpg-companion-sillytavern/src/systems/dashboard/defaultLayout.test.html
T
Lucas 'Paperboy' Rose-Winters 2edb41ebe6 feat(dashboard): implement dashboard data structure (Task 1.3)
Add dashboard configuration to extensionSettings and create default layout system:

State Management (state.js):
- Added extensionSettings.dashboard with version 2
- gridConfig: columns (12), rowHeight (80px), gap (12px), snapToGrid, showGrid
- tabs: Array of tab objects with widgets
- defaultTab: ID of tab to show on load
- Comprehensive inline documentation of structure

Default Layout Generator (defaultLayout.js):
- generateDefaultDashboard() - Creates 2-tab default layout
  - "Status" tab: userStats, infoBox, presentCharacters (3 widgets)
  - "Inventory" tab: inventory widget (1 widget)
- migrateV1ToV2Dashboard() - Migrates v1.x settings to v2.0
  - Respects user's visibility preferences (showUserStats, etc.)
  - Removes hidden widgets from migrated layout
  - Preserves user data during migration
- validateDashboardConfig() - Validates dashboard structure
- Utility functions: getWidgetCount(), findWidget()

Persistence Layer (persistence.js):
- Auto-migration on loadSettings() for existing users
- Validates dashboard config on load
- Regenerates default if config invalid or missing
- Seamless backward compatibility

Test Suite (defaultLayout.test.html):
- 4 test scenarios with visual verification
- Tests generation, validation, migration, utilities
- Live dashboard JSON preview
- Statistics panel (version, tabs, widgets, grid config)

Features:
- Automatic migration from v1.x hardcoded panel
- Preserves user preferences during migration
- Validates all dashboard configs on load
- Generates sensible defaults for new users

Acceptance Criteria Met:
✓ Dashboard config persists in extensionSettings
✓ Default layout generates on first load
✓ Existing users see migrated layout preserving their preferences
✓ All data structures validated

Epic 1, Task 1.3 Complete (1-2 day estimate, <10 min actual)
2025-10-23 09:26:10 +11:00

369 lines
12 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Default Layout Test</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1a1a2e;
color: #eee;
padding: 20px;
}
h1 {
margin-bottom: 20px;
color: #e94560;
}
.test-section {
background: #16213e;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
}
.test-section h2 {
color: #4ecca3;
margin-bottom: 10px;
font-size: 18px;
}
pre {
background: #0f3460;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
}
.result {
margin: 10px 0;
padding: 10px;
border-left: 3px solid #4ecca3;
background: #0f3460;
}
.result.pass {
border-color: #4ecca3;
}
.result.fail {
border-color: #e94560;
background: #2a0f1b;
}
button {
background: #e94560;
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: #d63651;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 10px;
}
.stat-box {
background: #0f3460;
padding: 10px;
border-radius: 5px;
}
.stat-label {
font-size: 11px;
opacity: 0.7;
text-transform: uppercase;
margin-bottom: 5px;
}
.stat-value {
font-size: 18px;
font-weight: bold;
color: #4ecca3;
}
</style>
</head>
<body>
<h1>🏗️ Default Layout Test Suite</h1>
<div class="test-section">
<h2>Test 1: Generate Default Dashboard</h2>
<div id="test1-results"></div>
<button onclick="test1()">Run Test 1</button>
</div>
<div class="test-section">
<h2>Test 2: Validate Dashboard Config</h2>
<div id="test2-results"></div>
<button onclick="test2()">Run Test 2</button>
</div>
<div class="test-section">
<h2>Test 3: Migrate v1.x Settings</h2>
<div id="test3-results"></div>
<button onclick="test3()">Run Test 3</button>
</div>
<div class="test-section">
<h2>Test 4: Find Widget Utility</h2>
<div id="test4-results"></div>
<button onclick="test4()">Run Test 4</button>
</div>
<div class="test-section">
<h2>Generated Dashboard JSON</h2>
<pre id="dashboard-json"></pre>
</div>
<div class="test-section">
<h2>Dashboard Statistics</h2>
<div id="stats" class="stats"></div>
</div>
<div style="margin-top: 20px;">
<button onclick="runAllTests()">🔄 Run All Tests</button>
</div>
<script type="module">
import {
generateDefaultDashboard,
migrateV1ToV2Dashboard,
validateDashboardConfig,
getWidgetCount,
findWidget
} from './defaultLayout.js';
let dashboard = null;
function pass(message) {
return `<div class="result pass">✓ ${message}</div>`;
}
function fail(message) {
return `<div class="result fail">✗ ${message}</div>`;
}
// Test 1: Generate default dashboard
window.test1 = function() {
const container = document.getElementById('test1-results');
container.innerHTML = '';
try {
dashboard = generateDefaultDashboard();
container.innerHTML += pass('Generated default dashboard');
if (dashboard.version === 2) {
container.innerHTML += pass(`Dashboard version: ${dashboard.version}`);
} else {
container.innerHTML += fail(`Wrong version: ${dashboard.version}`);
}
if (dashboard.tabs && dashboard.tabs.length === 2) {
container.innerHTML += pass(`Generated ${dashboard.tabs.length} tabs`);
} else {
container.innerHTML += fail(`Wrong tab count: ${dashboard.tabs?.length || 0}`);
}
const statusTab = dashboard.tabs.find(t => t.id === 'tab-status');
if (statusTab && statusTab.widgets.length === 3) {
container.innerHTML += pass(`Status tab has ${statusTab.widgets.length} widgets`);
} else {
container.innerHTML += fail(`Wrong widget count in status tab`);
}
const inventoryTab = dashboard.tabs.find(t => t.id === 'tab-inventory');
if (inventoryTab && inventoryTab.widgets.length === 1) {
container.innerHTML += pass(`Inventory tab has ${inventoryTab.widgets.length} widget`);
} else {
container.innerHTML += fail(`Wrong widget count in inventory tab`);
}
updateDashboardDisplay();
updateStats();
} catch (error) {
container.innerHTML += fail(`Error: ${error.message}`);
}
};
// Test 2: Validate dashboard config
window.test2 = function() {
const container = document.getElementById('test2-results');
container.innerHTML = '';
if (!dashboard) {
container.innerHTML += fail('No dashboard generated yet (run Test 1 first)');
return;
}
const valid = validateDashboardConfig(dashboard);
if (valid) {
container.innerHTML += pass('Dashboard config is valid');
} else {
container.innerHTML += fail('Dashboard config validation failed');
}
// Test invalid configs
const invalidConfigs = [
{ config: null, name: 'null config' },
{ config: {}, name: 'empty config' },
{ config: { version: 2 }, name: 'missing gridConfig' },
{ config: { version: 2, gridConfig: {}, tabs: 'not-array' }, name: 'tabs not array' }
];
for (const test of invalidConfigs) {
const result = validateDashboardConfig(test.config);
if (!result) {
container.innerHTML += pass(`Correctly rejected ${test.name}`);
} else {
container.innerHTML += fail(`Failed to reject ${test.name}`);
}
}
};
// Test 3: Migrate v1.x settings
window.test3 = function() {
const container = document.getElementById('test3-results');
container.innerHTML = '';
// Simulate v1.x settings with some sections hidden
const v1Settings = {
showUserStats: true,
showInfoBox: false, // Hidden
showCharacterThoughts: true,
showInventory: true
};
try {
const migrated = migrateV1ToV2Dashboard(v1Settings);
container.innerHTML += pass('Migrated v1.x settings');
const statusTab = migrated.tabs.find(t => t.id === 'tab-status');
const hasInfoBox = statusTab?.widgets.some(w => w.type === 'infoBox');
if (!hasInfoBox) {
container.innerHTML += pass('Correctly removed hidden infoBox widget');
} else {
container.innerHTML += fail('Failed to remove hidden infoBox widget');
}
container.innerHTML += `<div class="result">Migrated dashboard has ${migrated.tabs.length} tabs</div>`;
container.innerHTML += `<div class="result">Status tab has ${statusTab?.widgets.length || 0} widgets</div>`;
} catch (error) {
container.innerHTML += fail(`Error: ${error.message}`);
}
};
// Test 4: Find widget utility
window.test4 = function() {
const container = document.getElementById('test4-results');
container.innerHTML = '';
if (!dashboard) {
container.innerHTML += fail('No dashboard generated yet (run Test 1 first)');
return;
}
const found = findWidget(dashboard, 'widget-userstats');
if (found) {
container.innerHTML += pass(`Found widget: ${found.widget.id} at tab ${found.tabIndex}, widget ${found.widgetIndex}`);
container.innerHTML += `<div class="result">Widget type: ${found.widget.type}</div>`;
container.innerHTML += `<div class="result">Position: (${found.widget.x}, ${found.widget.y})</div>`;
container.innerHTML += `<div class="result">Size: ${found.widget.w}×${found.widget.h}</div>`;
} else {
container.innerHTML += fail('Failed to find widget-userstats');
}
const notFound = findWidget(dashboard, 'widget-nonexistent');
if (!notFound) {
container.innerHTML += pass('Correctly returned null for non-existent widget');
} else {
container.innerHTML += fail('Should return null for non-existent widget');
}
};
// Update dashboard JSON display
function updateDashboardDisplay() {
const jsonContainer = document.getElementById('dashboard-json');
if (dashboard) {
jsonContainer.textContent = JSON.stringify(dashboard, null, 2);
} else {
jsonContainer.textContent = '// No dashboard generated yet';
}
}
// Update stats
function updateStats() {
const statsContainer = document.getElementById('stats');
if (!dashboard) {
statsContainer.innerHTML = '<div class="stat-box">No dashboard generated yet</div>';
return;
}
const widgetCount = getWidgetCount(dashboard);
statsContainer.innerHTML = `
<div class="stat-box">
<div class="stat-label">Dashboard Version</div>
<div class="stat-value">${dashboard.version}</div>
</div>
<div class="stat-box">
<div class="stat-label">Total Tabs</div>
<div class="stat-value">${dashboard.tabs.length}</div>
</div>
<div class="stat-box">
<div class="stat-label">Total Widgets</div>
<div class="stat-value">${widgetCount}</div>
</div>
<div class="stat-box">
<div class="stat-label">Grid Columns</div>
<div class="stat-value">${dashboard.gridConfig.columns}</div>
</div>
<div class="stat-box">
<div class="stat-label">Row Height</div>
<div class="stat-value">${dashboard.gridConfig.rowHeight}px</div>
</div>
<div class="stat-box">
<div class="stat-label">Default Tab</div>
<div class="stat-value">${dashboard.defaultTab}</div>
</div>
`;
}
// Run all tests
window.runAllTests = function() {
test1();
setTimeout(() => test2(), 100);
setTimeout(() => test3(), 200);
setTimeout(() => test4(), 300);
};
// Auto-run on load
runAllTests();
</script>
</body>
</html>