UNPKG

@birhaus/test-utils

Version:

BIRHAUS Testing & Validation Framework - Comprehensive testing utilities for cognitive load, accessibility, and BIRHAUS principle compliance

310 lines (307 loc) 12 kB
'use strict'; // src/cognitive/MillersLawValidator.ts var MillersLawValidator = class { constructor(options = {}) { this.options = { maxNavigationItems: 7, maxFormFields: 7, maxSelectOptions: 7, maxTabs: 4, maxActions: 4, strict: false, includeDisabled: false, checkHidden: false, navigationSelector: 'nav, [role="navigation"], [data-birhaus-component="navigation"]', formSelector: 'form, [data-birhaus-component="form"]', selectSelector: 'select, [data-birhaus-component="select"]', tabSelector: '[role="tablist"], [data-birhaus-component="tabs"]', actionSelector: 'button, [role="button"], a[href], [data-birhaus-component="button"]', ...options }; } /** * Validate all Miller's Law compliance issues in the current DOM */ validateAll(container = document.body) { const violations = []; violations.push(...this.validateNavigation(container)); violations.push(...this.validateForms(container)); violations.push(...this.validateSelects(container)); violations.push(...this.validateTabs(container)); violations.push(...this.validateActions(container)); return violations; } /** * Validate navigation items (BIRHAUS Principle #1) */ validateNavigation(container = document.body) { const violations = []; const navElements = container.querySelectorAll(this.options.navigationSelector); navElements.forEach((nav) => { const items = this.getNavigationItems(nav); if (items.length > this.options.maxNavigationItems) { const severity = this.getSeverity(items.length, this.options.maxNavigationItems); violations.push({ type: "navigation", element: nav, count: items.length, maxRecommended: this.options.maxNavigationItems, severity, message: `Navigation has ${items.length} items (Miller's Law recommends max ${this.options.maxNavigationItems})`, messageEs: `Navegaci\xF3n tiene ${items.length} elementos (Ley de Miller recomienda m\xE1ximo ${this.options.maxNavigationItems})`, birhausPrinciple: 1, suggestions: [ "Group related navigation items into dropdown menus", "Use progressive disclosure to hide secondary navigation", "Consider using breadcrumbs for deep navigation", "Implement search functionality for large menus" ] }); } }); return violations; } /** * Validate form field counts (BIRHAUS Principle #4) */ validateForms(container = document.body) { const violations = []; const formElements = container.querySelectorAll(this.options.formSelector); formElements.forEach((form) => { const fields = this.getFormFields(form); if (fields.length > this.options.maxFormFields) { const severity = this.getSeverity(fields.length, this.options.maxFormFields); violations.push({ type: "form", element: form, count: fields.length, maxRecommended: this.options.maxFormFields, severity, message: `Form has ${fields.length} fields (Miller's Law recommends max ${this.options.maxFormFields})`, messageEs: `Formulario tiene ${fields.length} campos (Ley de Miller recomienda m\xE1ximo ${this.options.maxFormFields})`, birhausPrinciple: 4, suggestions: [ "Split form into multiple steps using BirhausForm", "Use progressive disclosure to reveal fields conditionally", "Group related fields into collapsible sections", "Consider using a wizard pattern for complex forms" ] }); } }); return violations; } /** * Validate select option counts */ validateSelects(container = document.body) { const violations = []; const selectElements = container.querySelectorAll(this.options.selectSelector); selectElements.forEach((select) => { const options = this.getSelectOptions(select); if (options.length > this.options.maxSelectOptions) { const severity = this.getSeverity(options.length, this.options.maxSelectOptions); violations.push({ type: "select", element: select, count: options.length, maxRecommended: this.options.maxSelectOptions, severity, message: `Select has ${options.length} options (Miller's Law recommends max ${this.options.maxSelectOptions})`, messageEs: `Lista desplegable tiene ${options.length} opciones (Ley de Miller recomienda m\xE1ximo ${this.options.maxSelectOptions})`, birhausPrinciple: 1, suggestions: [ "Use BirhausCombobox with search functionality", "Group options into categories", "Implement progressive filtering", "Consider using autocomplete instead of select" ] }); } }); return violations; } /** * Validate tab counts (BIRHAUS 4-3-1 Rule) */ validateTabs(container = document.body) { const violations = []; const tabElements = container.querySelectorAll(this.options.tabSelector); tabElements.forEach((tabContainer) => { const tabs = this.getTabs(tabContainer); if (tabs.length > this.options.maxTabs) { const severity = this.getSeverity(tabs.length, this.options.maxTabs); violations.push({ type: "tabs", element: tabContainer, count: tabs.length, maxRecommended: this.options.maxTabs, severity, message: `Tabs have ${tabs.length} items (BIRHAUS 4-3-1 rule recommends max ${this.options.maxTabs})`, messageEs: `Pesta\xF1as tienen ${tabs.length} elementos (regla BIRHAUS 4-3-1 recomienda m\xE1ximo ${this.options.maxTabs})`, birhausPrinciple: 1, suggestions: [ "Use BirhausTabs with overflow dropdown", "Group related tabs into sections", "Consider using accordion instead of tabs", "Implement nested navigation structure" ] }); } }); return violations; } /** * Validate action button counts */ validateActions(container = document.body) { const violations = []; const containers = container.querySelectorAll('form, [role="dialog"], [data-birhaus-component="card"], .actions'); containers.forEach((actionContainer) => { const actions = this.getActions(actionContainer); if (actions.length > this.options.maxActions) { const severity = this.getSeverity(actions.length, this.options.maxActions); violations.push({ type: "actions", element: actionContainer, count: actions.length, maxRecommended: this.options.maxActions, severity, message: `Container has ${actions.length} actions (BIRHAUS 4-3-1 rule recommends max ${this.options.maxActions})`, messageEs: `Contenedor tiene ${actions.length} acciones (regla BIRHAUS 4-3-1 recomienda m\xE1ximo ${this.options.maxActions})`, birhausPrinciple: 3, suggestions: [ "Combine related actions into dropdown menus", "Use progressive disclosure for secondary actions", "Implement one clear primary action pattern", "Move less important actions to overflow menu" ] }); } }); return violations; } /** * Get navigation items from a navigation element */ getNavigationItems(nav) { const items = nav.querySelectorAll('a, button, [role="menuitem"], [role="link"]'); return this.filterElements(Array.from(items)); } /** * Get form fields from a form element */ getFormFields(form) { const fields = form.querySelectorAll('input, select, textarea, [role="textbox"], [role="combobox"], [role="listbox"]'); return this.filterElements(Array.from(fields)); } /** * Get options from a select element */ getSelectOptions(select) { if (select.tagName.toLowerCase() === "select") { const options2 = select.querySelectorAll("option"); return this.filterElements(Array.from(options2)); } const options = select.querySelectorAll('[role="option"], [data-value]'); return this.filterElements(Array.from(options)); } /** * Get tabs from a tab container */ getTabs(tabContainer) { const tabs = tabContainer.querySelectorAll('[role="tab"], [data-birhaus-component="tab"]'); return this.filterElements(Array.from(tabs)); } /** * Get action buttons from a container */ getActions(container) { const actions = container.querySelectorAll(this.options.actionSelector); return this.filterElements(Array.from(actions)); } /** * Filter elements based on options (hidden, disabled, etc.) */ filterElements(elements) { return elements.filter((element) => { if (!this.options.includeDisabled && element.hasAttribute("disabled")) { return false; } if (!this.options.checkHidden) { const style = getComputedStyle(element); if (style.display === "none" || style.visibility === "hidden") { return false; } } return true; }); } /** * Calculate severity based on how much the count exceeds the limit */ getSeverity(count, maxRecommended) { const excess = count - maxRecommended; const excessPercentage = excess / maxRecommended * 100; if (excessPercentage > 100) return "critical"; if (excessPercentage > 50) return "high"; if (excessPercentage > 25) return "medium"; return "low"; } }; function expectNavigationCompliance(container, maxItems = 7) { const validator = new MillersLawValidator({ maxNavigationItems: maxItems }); const violations = validator.validateNavigation(container); if (violations.length > 0) { const violation = violations[0]; throw new Error( `Miller's Law violation: ${violation.message} Suggestions: ${violation.suggestions.join(", ")}` ); } } function expectFormCompliance(container, maxFields = 7) { const validator = new MillersLawValidator({ maxFormFields: maxFields }); const violations = validator.validateForms(container); if (violations.length > 0) { const violation = violations[0]; throw new Error( `Miller's Law violation: ${violation.message} Suggestions: ${violation.suggestions.join(", ")}` ); } } function expectTabCompliance(container, maxTabs = 4) { const validator = new MillersLawValidator({ maxTabs }); const violations = validator.validateTabs(container); if (violations.length > 0) { const violation = violations[0]; throw new Error( `BIRHAUS 4-3-1 Rule violation: ${violation.message} Suggestions: ${violation.suggestions.join(", ")}` ); } } function expectMillersLawCompliance(container, options) { const validator = new MillersLawValidator(options); const violations = validator.validateAll(container); if (violations.length > 0) { const criticalViolations = violations.filter((v) => v.severity === "critical"); const highViolations = violations.filter((v) => v.severity === "high"); if (criticalViolations.length > 0 || highViolations.length > 0) { const errorMessages = [...criticalViolations, ...highViolations].map((v) => `${v.type}: ${v.message}`).join("\n"); throw new Error( `Miller's Law violations detected: ${errorMessages} Total violations: ${violations.length} (${criticalViolations.length} critical, ${highViolations.length} high)` ); } } } exports.MillersLawValidator = MillersLawValidator; exports.expectFormCompliance = expectFormCompliance; exports.expectMillersLawCompliance = expectMillersLawCompliance; exports.expectNavigationCompliance = expectNavigationCompliance; exports.expectTabCompliance = expectTabCompliance; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map