|
| 1 | +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +import{beforeEach,describe,expect,it}from'vitest'; |
| 5 | +import{RuleTester}from'eslint'; |
| 6 | +importtype{JSRuleDefinition}from'eslint'; |
| 7 | +importhtmlParserfrom'@html-eslint/parser'; |
| 8 | +import{elementsHtmlConfig}from'../configs/html.js'; |
| 9 | +importpreferAriaLabelInCompactContainersfrom'./prefer-aria-label-in-compact-containers.js'; |
| 10 | + |
| 11 | +functionisRecord(value: unknown): value is Record<PropertyKey,unknown>{ |
| 12 | +returntypeofvalue==='object'&&value!==null; |
| 13 | +} |
| 14 | + |
| 15 | +functionisJSRuleDefinition(rule: unknown): rule is JSRuleDefinition{ |
| 16 | +if(!isRecord(rule)||!isRecord(rule.meta)){ |
| 17 | +returnfalse; |
| 18 | +} |
| 19 | + |
| 20 | +const{ meta, create }=rule; |
| 21 | +return( |
| 22 | +typeofcreate==='function'&&meta.type==='problem'&&'docs'inmeta&&'schema'inmeta&&'messages'inmeta |
| 23 | +); |
| 24 | +} |
| 25 | + |
| 26 | +if(!isJSRuleDefinition(preferAriaLabelInCompactContainers)){ |
| 27 | +thrownewTypeError('Expected preferAriaLabelInCompactContainers to be an ESLint rule definition.'); |
| 28 | +} |
| 29 | + |
| 30 | +construle=preferAriaLabelInCompactContainers; |
| 31 | + |
| 32 | +functioncompactLabelError(control: string,container: string){ |
| 33 | +return{ |
| 34 | +messageId: 'prefer-aria-label'asconst, |
| 35 | +data: { control, container } |
| 36 | +}; |
| 37 | +} |
| 38 | + |
| 39 | +describe('preferAriaLabelInCompactContainers',()=>{ |
| 40 | +lettester: RuleTester; |
| 41 | + |
| 42 | +beforeEach(()=>{ |
| 43 | +tester=newRuleTester({ |
| 44 | +languageOptions: { |
| 45 | +parser: htmlParser, |
| 46 | +parserOptions: { |
| 47 | +frontmatter: true |
| 48 | +} |
| 49 | +} |
| 50 | +}); |
| 51 | +}); |
| 52 | + |
| 53 | +it('should define rule metadata',()=>{ |
| 54 | +expect(preferAriaLabelInCompactContainers.meta).toBeDefined(); |
| 55 | +expect(preferAriaLabelInCompactContainers.meta.type).toBe('problem'); |
| 56 | +expect(preferAriaLabelInCompactContainers.meta.docs).toBeDefined(); |
| 57 | +expect(preferAriaLabelInCompactContainers.meta.docs.description).toBe( |
| 58 | +'Prefer aria-label on form controls inside toolbars and page headers.' |
| 59 | +); |
| 60 | +expect(preferAriaLabelInCompactContainers.meta.docs.category).toBe('Best Practice'); |
| 61 | +expect(preferAriaLabelInCompactContainers.meta.docs.recommended).toBe(true); |
| 62 | +expect(preferAriaLabelInCompactContainers.meta.docs.url).toContain('/docs/lint/'); |
| 63 | +expect(preferAriaLabelInCompactContainers.meta.schema).toEqual([]); |
| 64 | +expect(preferAriaLabelInCompactContainers.meta.messages['prefer-aria-label']).toBe( |
| 65 | +'Remove <label> from <{{control}}> inside <{{container}}> and use aria-label instead to preserve the compact layout.' |
| 66 | +); |
| 67 | +}); |
| 68 | + |
| 69 | +it('should register the rule as a recommended error',()=>{ |
| 70 | +constplugin=elementsHtmlConfig.plugins?.['@nvidia-elements/lint']; |
| 71 | + |
| 72 | +expect(plugin?.rules?.['prefer-aria-label-in-compact-containers']).toBe(preferAriaLabelInCompactContainers); |
| 73 | +expect(elementsHtmlConfig.rules?.['@nvidia-elements/lint/prefer-aria-label-in-compact-containers']).toEqual([ |
| 74 | +'error' |
| 75 | +]); |
| 76 | +}); |
| 77 | + |
| 78 | +it('should allow labels that do not affect compact form controls',()=>{ |
| 79 | +tester.run('allowed labels',rule,{ |
| 80 | +valid: [ |
| 81 | +`<nve-input><label>Name</label><input /></nve-input>`, |
| 82 | +`<nve-toolbar><label nve-text="body sm">1 of 13</label></nve-toolbar>`, |
| 83 | +`<nve-page-header><nve-button><label>Action</label></nve-button></nve-page-header>`, |
| 84 | +`<nve-toolbar><div><label>Caption</label></div></nve-toolbar>` |
| 85 | +], |
| 86 | +invalid: [] |
| 87 | +}); |
| 88 | +}); |
| 89 | + |
| 90 | +it('should allow compact form controls without visual labels',()=>{ |
| 91 | +tester.run('aria labels',rule,{ |
| 92 | +valid: [ |
| 93 | +`<nve-toolbar><nve-input><input aria-label="Search" /></nve-input></nve-toolbar>`, |
| 94 | +`<nve-page-header><nve-select><select aria-label="Page"><option>One</option></select></nve-select></nve-page-header>`, |
| 95 | +`<nve-toolbar><nve-input><input /></nve-input></nve-toolbar>` |
| 96 | +], |
| 97 | +invalid: [] |
| 98 | +}); |
| 99 | +}); |
| 100 | + |
| 101 | +it('should report labels in compact form controls',()=>{ |
| 102 | +tester.run('compact control labels',rule,{ |
| 103 | +valid: [], |
| 104 | +invalid: [ |
| 105 | +{ |
| 106 | +code: `<nve-toolbar><nve-input><label>Search</label><input /></nve-input></nve-toolbar>`, |
| 107 | +errors: [compactLabelError('nve-input','nve-toolbar')] |
| 108 | +}, |
| 109 | +{ |
| 110 | +code: `<nve-page-header><nve-search slot="suffix"><label>Search</label><input type="search" /></nve-search></nve-page-header>`, |
| 111 | +errors: [compactLabelError('nve-search','nve-page-header')] |
| 112 | +}, |
| 113 | +{ |
| 114 | +code: `<nve-toolbar><div><nve-select><label>Page</label><select></select></nve-select></div></nve-toolbar>`, |
| 115 | +errors: [compactLabelError('nve-select','nve-toolbar')] |
| 116 | +}, |
| 117 | +{ |
| 118 | +code: `<nve-page-header><nve-star-rating><span><label>Rating</label></span><input type="range" /></nve-star-rating></nve-page-header>`, |
| 119 | +errors: [compactLabelError('nve-star-rating','nve-page-header')] |
| 120 | +}, |
| 121 | +{ |
| 122 | +code: `<nve-toolbar><nve-input><label>Name</label><input aria-label="Name" /></nve-input></nve-toolbar>`, |
| 123 | +errors: [compactLabelError('nve-input','nve-toolbar')] |
| 124 | +} |
| 125 | +] |
| 126 | +}); |
| 127 | +}); |
| 128 | + |
| 129 | +it('should report each label against its nearest form control',()=>{ |
| 130 | +tester.run('nested form controls',rule,{ |
| 131 | +valid: [], |
| 132 | +invalid: [ |
| 133 | +{ |
| 134 | +code: `<nve-toolbar> |
| 135 | + <nve-checkbox-group> |
| 136 | + <label>Options</label> |
| 137 | + <nve-checkbox><label>First</label><input type="checkbox" /></nve-checkbox> |
| 138 | + </nve-checkbox-group> |
| 139 | + </nve-toolbar>`, |
| 140 | +errors: [ |
| 141 | +compactLabelError('nve-checkbox-group','nve-toolbar'), |
| 142 | +compactLabelError('nve-checkbox','nve-toolbar') |
| 143 | +] |
| 144 | +} |
| 145 | +] |
| 146 | +}); |
| 147 | +}); |
| 148 | +}); |
0 commit comments