ui/components/base/Nav.test.html

28 lines
5.5 KiB
HTML
Raw Normal View History

<!doctype html>
<html>
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>components.Nav tests</title><script src="../../ui.js" components="Nav"></script>
<script>
const navBrand = { icon: 'box', label: 'Apigo' }
const navItems = [{ type: 'label', label: 'Main' }, { type: 'button', name: 'home', label: 'Home', icon: 'house' }, { type: 'dropdown', name: 'admin', label: 'Admin', icon: 'shield', items: [{ type: 'label', label: 'Manage' }, { type: 'button', name: 'roles', label: 'Roles', icon: 'person' }, { type: 'switch', name: 'alerts', label: 'Alerts', bind: { alerts: false } }] }, { type: 'fill' }, { type: 'button', name: 'settings', label: 'Settings', icon: 'gear' }]
</script>
</head>
<body class="vh-100 overflow-hidden bg-body-tertiary"><div class="container-fluid h-100 py-3 d-flex flex-column">
<header class="d-flex align-items-center gap-2 mb-3 flex-shrink-0"><h1 class="h4 mb-0">components.Nav</h1><span id="status" class="badge text-bg-secondary">未测试</span><button id="runAll" class="btn btn-primary btn-sm ms-auto" type="button">测试全部</button><span id="coverage" class="small text-body-secondary"></span></header>
<main class="flex-fill overflow-auto"><Nav id="horizontalNav" $.state.brand="navBrand" $.state.list="navItems" $bind="Hash.nav"></Nav><div class="mt-3 border" style="height:220px"><Nav id="verticalNav" vertical $.state.brand="navBrand" $.state.list="navItems" $bind="Hash.nav"></Nav></div></main><pre id="result" class="h-25 overflow-auto border rounded bg-body p-2 mt-3 mb-0 flex-shrink-0 small"></pre>
</div><script>
const cases = ['vertical', 'brand', 'list', 'value', 'Hash.nav', 'openName', 'label', 'button', 'dropdown', 'fill', 'change-event', 'nav-event', 'item-shape']; const errors = []; const originalError = console.error; console.error = (...args) => { errors.push(args.map(String).join(' ')); originalError(...args) }; addEventListener('error', event => errors.push(String(event.message || event.error || event))); addEventListener('unhandledrejection', event => errors.push(String(event.reason || event))); const tick = () => new Promise(resolve => setTimeout(resolve, 60)); const assert = (r, feature, ok, message) => { r.assertions++; if (ok) r.passed++; else { r.failed++; r.failures.push({ feature, message }) } }
window.runTest = async () => { errors.length = 0; const r = { name: 'components.Nav', status: 'running', assertions: 0, passed: 0, failed: 0, failures: [], consoleErrors: [], coverage: { tested: [], total: cases, percent: 0 } }; await tick(); const horizontal = document.querySelector('#horizontalNav'); const vertical = document.querySelector('#verticalNav'); const navLink = (nav, label) => [...nav.querySelectorAll('.nav-link')].find(node => node.textContent.includes(label)); let change; const navEvents = []; horizontal.addEventListener('change', event => { change = event.detail }); horizontal.addEventListener('nav', event => navEvents.push(event.detail)); vertical.addEventListener('nav', event => navEvents.push(event.detail))
assert(r, 'vertical', vertical.vertical === true && vertical.querySelector('.flex-column'), 'vertical attribute enables vertical navigation')
assert(r, 'brand', horizontal.textContent.includes('Apigo') && horizontal.querySelector('.bi-box') && vertical.textContent.includes('Apigo'), 'brand renders the same icon and label in both layouts')
assert(r, 'list', horizontal.querySelectorAll('.nav-link, .navbar-text').length >= 3, 'list renders navigation items')
assert(r, 'label', horizontal.textContent.includes('Main'), 'label item renders non-clickable text')
assert(r, 'fill', horizontal.querySelector('.flex-fill'), 'fill item renders a horizontal spacer')
navLink(horizontal, 'Home').click(); await tick(); assert(r, 'button', horizontal.state.value === 'home', 'button item selects its name'); assert(r, 'value', change === 'home', 'value is selected item name and change detail'); assert(r, 'Hash.nav', Hash.nav === 'home' && location.hash.includes('nav=%22home%22') && vertical.state.value === 'home', '$bind writes Hash.nav and synchronizes the same data across layouts'); assert(r, 'change-event', change === 'home', 'change event exposes selected item name')
navLink(vertical, 'Admin').click(); await tick(); assert(r, 'dropdown', vertical.textContent.includes('Manage') && vertical.textContent.includes('Roles'), 'dropdown renders documented items field'); assert(r, 'openName', vertical.state.openName === 'admin', 'vertical dropdown stores its open name')
navLink(vertical, 'Roles').click(); await tick(); assert(r, 'item-shape', vertical.state.value === 'roles' && navLink(vertical, 'Roles').classList.contains('active'), 'item shape name and items select an active subitem')
assert(r, 'nav-event', navEvents.some(detail => detail.item?.name === 'home') && navEvents.some(detail => detail.item?.name === 'admin' && detail.open === true), 'nav event exposes item and dropdown open detail')
r.coverage.tested = [...cases]; r.coverage.percent = 100; r.consoleErrors = [...errors]; r.status = r.failed || r.consoleErrors.length ? 'failed' : 'passed'; document.querySelector('#status').className = 'badge text-bg-' + (r.status === 'passed' ? 'success' : 'danger'); document.querySelector('#status').textContent = r.status === 'passed' ? '通过' : '失败'; document.querySelector('#coverage').textContent = '覆盖 100%'; document.querySelector('#result').textContent = JSON.stringify(r, null, 2); return r }; document.querySelector('#runAll').onclick = window.runTest
</script></body></html>