-
-
Notifications
You must be signed in to change notification settings - Fork 639
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add option for enforcing label's htmlFor matches control's id
This change adds an option to the `label-has-associated-control` rule, enforcing that the label's htmlFor attribute matches the associated control's id attribute. Previously, the only validation done on htmlFor was that it was on the label component and had text. There was no attempt to cross-check that value against any attribute on the associated control. Not, when the option is enabled, cases where they don't match will report. I also took the opportunity to update the error messages so that each assert type gets an error message with verbiage specific to the assertion. (not sure if this should be called out as a separate feature in the changelog?). Note: the current implementation only checks the first instance it finds of child component that matches each control component type. It assumes that there won't be any acceptable cases where a label would have multiple inputs nested beneath it. Let me know if that assumption doesn't hold. Closes:
- Loading branch information
1 parent
bfb0e9e
commit 4a4d2ff
Showing
6 changed files
with
381 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import expect from 'expect'; | ||
import getChildComponent from '../../../src/util/getChildComponent'; | ||
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock'; | ||
import JSXElementMock from '../../../__mocks__/JSXElementMock'; | ||
import JSXExpressionContainerMock from '../../../__mocks__/JSXExpressionContainerMock'; | ||
|
||
describe('getChildComponent', () => { | ||
describe('no FancyComponent', () => { | ||
it('should return undefined', () => { | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('span', [], [ | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('span', [], [ | ||
JSXElementMock('span', [], []), | ||
]), | ||
]), | ||
]), | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('img', [ | ||
JSXAttributeMock('src', 'some/path'), | ||
]), | ||
]), | ||
'FancyComponent', | ||
5, | ||
)).toBeUndefined(); | ||
}); | ||
}); | ||
describe('contains an indicated component', () => { | ||
it('should return input', () => { | ||
const MockInput = JSXElementMock('input'); | ||
|
||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockInput, | ||
]), | ||
'input', | ||
)).toEqual(MockInput); | ||
}); | ||
it('should return FancyComponent', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
|
||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
'FancyComponent', | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
it('FancyComponent is outside of default depth, should return undefined', () => { | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('FancyComponent'), | ||
]), | ||
]), | ||
'FancyComponent', | ||
)).toBeUndefined(); | ||
}); | ||
it('FancyComponent is inside of custom depth, should return component', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
]), | ||
'FancyComponent', | ||
2, | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
it('deep nesting, should return component', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('span', [], [ | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('span', [], [ | ||
JSXElementMock('span', [], [ | ||
JSXElementMock('span', [], [ | ||
MockFancyComponent, | ||
]), | ||
]), | ||
]), | ||
]), | ||
]), | ||
JSXElementMock('span', [], []), | ||
JSXElementMock('img', [ | ||
JSXAttributeMock('src', 'some/path'), | ||
]), | ||
]), | ||
'FancyComponent', | ||
6, | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
}); | ||
describe('Indeterminate situations', () => { | ||
describe('expression container children', () => { | ||
it('should return component', () => { | ||
const MysteryComponent = JSXExpressionContainerMock('mysteryBox'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MysteryComponent, | ||
]), | ||
'FancyComponent', | ||
)).toEqual(MysteryComponent); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('Glob name matching', () => { | ||
describe('component name contains question mark ? - match any single character', () => { | ||
it('should return component', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
'Fanc?Co??onent', | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
it('should return undefined', () => { | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('FancyComponent'), | ||
]), | ||
'FancyComponent?', | ||
)).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('component name contains asterisk * - match zero or more characters', () => { | ||
it('should return component (suffix wildcard)', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
'Fancy*', | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
it('should return component (prefix wildcard)', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
'*Component', | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
it('should return component (mixed wildcard)', () => { | ||
const MockFancyComponent = JSXElementMock('FancyComponent'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockFancyComponent, | ||
]), | ||
'Fancy*C*t', | ||
)).toEqual(MockFancyComponent); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('using a custom elementType function', () => { | ||
it('should return the component when the custom elementType returns the proper name', () => { | ||
const MockCustomInput = JSXElementMock('CustomInput'); | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
MockCustomInput, | ||
]), | ||
'input', | ||
2, | ||
() => 'input', | ||
)).toEqual(MockCustomInput); | ||
}); | ||
it('should return undefined when the custom elementType returns a wrong name', () => { | ||
expect(getChildComponent( | ||
JSXElementMock('div', [], [ | ||
JSXElementMock('CustomInput'), | ||
]), | ||
'input', | ||
2, | ||
() => 'button', | ||
)).toBeUndefined(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.