@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
JavaScript
;
// 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