-
Notifications
You must be signed in to change notification settings - Fork 386
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(engine-core): fix non-native ARIA reflection [backport] (#3919)
- Loading branch information
1 parent
4590a9d
commit f8732a1
Showing
16 changed files
with
806 additions
and
55 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
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
183 changes: 183 additions & 0 deletions
183
packages/@lwc/integration-karma/test/component/aria-reflection/index.spec.js
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,183 @@ | ||
import { createElement } from 'lwc'; | ||
import { ariaPropertiesMapping, extractDataIds } from 'test-utils'; | ||
import NoPropDeclared from 'x/noPropDeclared'; | ||
import PropDeclared from 'x/propDeclared'; | ||
import ApiPropDeclared from 'x/apiPropDeclared'; | ||
import TrackPropDeclared from 'x/trackPropDeclared'; | ||
import NoPropDeclaredNoSuper from 'x/noPropDeclaredNoSuper'; | ||
import PropDeclaredNoSuper from 'x/propDeclaredNoSuper'; | ||
import ApiPropDeclaredNoSuper from 'x/apiPropDeclaredNoSuper'; | ||
import TrackPropDeclaredNoSuper from 'x/trackPropDeclaredNoSuper'; | ||
|
||
describe('aria reflection', () => { | ||
// Test with and without a custom superclass, since we may set the property accessor differently in each case | ||
const variants = [ | ||
{ | ||
name: 'has custom superclass', | ||
components: { | ||
NoPropDeclared: { | ||
tagName: 'x-no-prop-declared', | ||
Ctor: NoPropDeclared, | ||
}, | ||
PropDeclared: { | ||
tagName: 'x-prop-declared', | ||
Ctor: PropDeclared, | ||
}, | ||
ApiPropDeclared: { | ||
tagName: 'x-api-prop-declared', | ||
Ctor: ApiPropDeclared, | ||
}, | ||
TrackPropDeclared: { | ||
tagName: 'x-track-prop-declared', | ||
Ctor: TrackPropDeclared, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: 'no custom superclass', | ||
components: { | ||
NoPropDeclared: { | ||
tagName: 'x-no-prop-declared-no-super', | ||
Ctor: NoPropDeclaredNoSuper, | ||
}, | ||
PropDeclared: { | ||
tagName: 'x-prop-declared-no-super', | ||
Ctor: PropDeclaredNoSuper, | ||
}, | ||
ApiPropDeclared: { | ||
tagName: 'x-api-prop-declared-no-super', | ||
Ctor: ApiPropDeclaredNoSuper, | ||
}, | ||
TrackPropDeclared: { | ||
tagName: 'x-track-prop-declared-no-super', | ||
Ctor: TrackPropDeclaredNoSuper, | ||
}, | ||
}, | ||
}, | ||
]; | ||
|
||
const scenarios = [ | ||
{ | ||
name: 'No prop declared', | ||
componentKey: 'NoPropDeclared', | ||
expectAttrReflection: true, | ||
}, | ||
{ | ||
name: 'Prop declared', | ||
componentKey: 'PropDeclared', | ||
// declaring a prop in the component results in no attribute reflection | ||
expectAttrReflection: false, | ||
}, | ||
{ | ||
name: '@api prop declared', | ||
componentKey: 'ApiPropDeclared', | ||
// declaring a prop in the component results in no attribute reflection | ||
expectAttrReflection: false, | ||
}, | ||
{ | ||
name: '@track prop declared', | ||
componentKey: 'TrackPropDeclared', | ||
// declaring a prop in the component results in no attribute reflection | ||
expectAttrReflection: false, | ||
}, | ||
]; | ||
|
||
scenarios.forEach(({ name: scenarioName, componentKey, expectAttrReflection }) => { | ||
describe(scenarioName, () => { | ||
variants.forEach(({ name: variantName, components }) => { | ||
describe(variantName, () => { | ||
const { tagName, Ctor } = components[componentKey]; | ||
|
||
Object.entries(ariaPropertiesMapping).forEach(([propName, attrName]) => { | ||
function validateAria(elm, expected) { | ||
const dataIds = extractDataIds(elm); | ||
|
||
// rendering the prop works | ||
expect(dataIds[propName].textContent).toBe( | ||
expected === null ? '' : expected | ||
); | ||
|
||
// the property is correct | ||
expect(elm[propName]).toBe(expected); | ||
expect(elm.getPropInternal(propName)).toBe(expected); | ||
|
||
// the attr is reflected (if we expect that to work) | ||
expect(elm.getAttribute(attrName)).toBe( | ||
expectAttrReflection ? expected : null | ||
); | ||
expect(elm.getAttrInternal(attrName)).toBe( | ||
expectAttrReflection ? expected : null | ||
); | ||
} | ||
|
||
describe(propName, () => { | ||
it('no initial value', async () => { | ||
const elm = createElement(tagName, { is: Ctor }); | ||
|
||
document.body.appendChild(elm); | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, null); | ||
expect(elm.renderCount).toBe(1); | ||
}); | ||
|
||
it('set externally', async () => { | ||
const elm = createElement(tagName, { is: Ctor }); | ||
|
||
// set initial prop before rendering | ||
elm[propName] = 'foo'; | ||
|
||
document.body.appendChild(elm); | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, 'foo'); | ||
expect(elm.renderCount).toBe(1); | ||
|
||
// mutate prop | ||
elm[propName] = 'bar'; | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, 'bar'); | ||
expect(elm.renderCount).toBe(2); | ||
|
||
// remove | ||
elm[propName] = null; | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, null); | ||
expect(elm.renderCount).toBe(3); | ||
}); | ||
|
||
it('set internally', async () => { | ||
const elm = createElement(tagName, { is: Ctor }); | ||
|
||
// set initial prop before rendering | ||
elm.setPropInternal(propName, 'foo'); | ||
|
||
document.body.appendChild(elm); | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, 'foo'); | ||
expect(elm.renderCount).toBe(1); | ||
|
||
// mutate prop | ||
elm.setPropInternal(propName, 'bar'); | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, 'bar'); | ||
expect(elm.renderCount).toBe(2); | ||
|
||
// remove | ||
elm.setPropInternal(propName, null); | ||
|
||
await Promise.resolve(); | ||
validateAria(elm, null); | ||
expect(elm.renderCount).toBe(3); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.