29 lines
5.9 KiB
HTML
29 lines
5.9 KiB
HTML
<!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' }, { type: 'button', name: 'logout', label: 'Logout', icon: 'door-open', noselect: true }]
|
|
</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', 'noselect-nav', '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')
|
|
const selectedBeforeLogout = horizontal.state.value; navLink(horizontal, 'Logout').click(); await tick(); assert(r, 'noselect-nav', horizontal.state.value === selectedBeforeLogout && navEvents.some(detail => detail.name === 'logout' && detail.selected === false), 'noselect action keeps selection and still dispatches nav')
|
|
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>
|