-
Notifications
You must be signed in to change notification settings - Fork 400
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: scoped dynamic id support * chore: throw errors for css id selectors * test: integration tests * test: fix integration tests * refactor: account for any type * test: convert comments into unit tests * chore: add logError calls as suggested * chore: more feedback (skip tests)
- Loading branch information
Showing
33 changed files
with
660 additions
and
297 deletions.
There are no files selected for viewing
200 changes: 200 additions & 0 deletions
200
packages/lwc-engine/src/framework/__tests__/scoped-ids.spec.ts
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,200 @@ | ||
import { createElement, LightningElement } from '../main'; | ||
import { compileTemplate } from 'test-utils'; | ||
|
||
const childHtml = compileTemplate(`<template></template>`); | ||
class MyChild extends LightningElement { | ||
render() { | ||
return childHtml; | ||
} | ||
} | ||
|
||
describe('scoped-ids', () => { | ||
describe('expressions', () => { | ||
const html = compileTemplate(` | ||
<template> | ||
<!-- | ||
TODO: JSDOM ends up invoking elm.setAttribute('id', value) when setting | ||
property values, which we guard against for components by throwing. Add | ||
coverage for custom elements when the following issue is resolved: | ||
https://github.com/jsdom/jsdom/issues/2158 | ||
--> | ||
<x-child></x-child> | ||
<div id={identifier}></div> | ||
</template> | ||
`, { | ||
modules: { 'x-child': MyChild } | ||
}); | ||
|
||
describe.skip('custom elements', () => { | ||
it('should render a transformed id attribute when its value is set to a non-empty string', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return 'foo'; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const child = elm.shadowRoot.querySelector('x-child'); | ||
expect(child.getAttribute('id')).toEqual(expect.stringContaining('foo')); | ||
}); | ||
|
||
it('should render a transformed id attribute when its value is set to a boolean value', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return true; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const child = elm.shadowRoot.querySelector('x-child'); | ||
expect(child.getAttribute('id')).toEqual(expect.stringContaining('true')); | ||
}); | ||
|
||
it('should not render id attribute when its value is set to `null`', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return null; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const child = elm.shadowRoot.querySelector('x-child'); | ||
expect(child.getAttribute('id')).toEqual(null); | ||
}); | ||
|
||
it('should render expected id attribute value when its value is set to `undefined`', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return undefined; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
expect(() => { | ||
document.body.appendChild(elm); | ||
}).toLogError('Invalid id value "undefined". Expected a non-empty string.'); | ||
const child = elm.shadowRoot.querySelector('x-child'); | ||
expect(child.getAttribute('id')).toEqual('undefined'); | ||
}); | ||
|
||
it('should render the id attribute as a boolean attribute when its value is set to an empty string', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return ''; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
expect(() => { | ||
document.body.appendChild(elm); | ||
}).toLogError('Invalid id value "". Expected a non-empty string.'); | ||
const child = elm.shadowRoot.querySelector('x-child'); | ||
expect(child.getAttribute('id')).toEqual(''); | ||
}); | ||
}); | ||
|
||
describe('native elements', () => { | ||
it('should render a transformed id attribute when its value is set to a non-empty string', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return 'foo'; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const div = elm.shadowRoot.querySelector('div'); | ||
expect(div.getAttribute('id')).toEqual(expect.stringContaining('foo')); | ||
}); | ||
|
||
it('should render a transformed id attribute when its value is set to a boolean value', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return true; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const div = elm.shadowRoot.querySelector('div'); | ||
expect(div.getAttribute('id')).toEqual(expect.stringContaining('true')); | ||
}); | ||
|
||
it('should not render id attribute when its value is set to `null`', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return null; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
document.body.appendChild(elm); | ||
const div = elm.shadowRoot.querySelector('div'); | ||
expect(div.getAttribute('id')).toEqual(null); | ||
}); | ||
|
||
it('should not render id attribute when its value is set to `undefined`', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return undefined; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
expect(() => { | ||
document.body.appendChild(elm); | ||
}).toLogError('Invalid id value "undefined". Expected a non-empty string.'); | ||
const div = elm.shadowRoot.querySelector('div'); | ||
expect(div.getAttribute('id')).toEqual(null); | ||
}); | ||
|
||
it('should render the id attribute as a boolean attribute when its value is set to an empty string', () => { | ||
class MyComponent extends LightningElement { | ||
get identifier() { | ||
return ''; | ||
} | ||
render() { | ||
return html; | ||
} | ||
} | ||
|
||
const elm = createElement('x-foo', { is: MyComponent }); | ||
expect(() => { | ||
document.body.appendChild(elm); | ||
}).toLogError('Invalid id value "". Expected a non-empty string.'); | ||
const div = elm.shadowRoot.querySelector('div'); | ||
expect(div.getAttribute('id')).toEqual(''); | ||
}); | ||
}); | ||
}); | ||
}); |
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
71 changes: 71 additions & 0 deletions
71
.../src/components/scoped-ids/test-dynamic-scoped-ids/integration/dynamic-scoped-ids.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,71 @@ | ||
const assert = require('assert'); | ||
const URL = 'http://localhost:4567/dynamic-scoped-ids'; | ||
|
||
describe('Scoped ids (dynamic)', () => { | ||
before(() => { | ||
browser.url(URL); | ||
}); | ||
|
||
describe('should be transformed', function () { | ||
it('aria-activedescendant', () => { | ||
const { id, idref } = browser.execute(() => { | ||
var integration = document.querySelector('integration-dynamic-scoped-ids'); | ||
var idElm = integration.shadowRoot.querySelector('.activedescendant-id'); | ||
var idrefElm = integration.shadowRoot.querySelector('.activedescendant-idref'); | ||
return { | ||
id: idElm.id, | ||
idref: idrefElm.ariaActiveDescendant, | ||
}; | ||
}).value; | ||
assert(id.length > 0, 'id attr should be non-empty string'); | ||
assert.notStrictEqual(id, 'activedescendant', 'id attr should be transformed'); | ||
assert.notStrictEqual(idref, 'activedescendant', 'idref attr should be transformed'); | ||
assert(id === idref, 'id attr and idref attr should be the same value'); | ||
}); | ||
it('aria-details', () => { | ||
const { id, idref } = browser.execute(() => { | ||
var integration = document.querySelector('integration-dynamic-scoped-ids'); | ||
var idElm = integration.shadowRoot.querySelector('.details-id'); | ||
var idrefElm = integration.shadowRoot.querySelector('.details-idref'); | ||
return { | ||
id: idElm.id, | ||
idref: idrefElm.ariaDetails, | ||
}; | ||
}).value; | ||
assert(id.length > 0, 'id attr should be non-empty string'); | ||
assert.notStrictEqual(id, 'details', 'id attr should be transformed'); | ||
assert.notStrictEqual(idref, 'details', 'idref attr should be transformed'); | ||
assert(id === idref, 'id attr and idref attr should be the same value'); | ||
}); | ||
it('aria-errormessage', () => { | ||
const { id, idref } = browser.execute(() => { | ||
var integration = document.querySelector('integration-dynamic-scoped-ids'); | ||
var idElm = integration.shadowRoot.querySelector('.errormessage-id'); | ||
var idrefElm = integration.shadowRoot.querySelector('.errormessage-idref'); | ||
return { | ||
id: idElm.id, | ||
idref: idrefElm.ariaErrorMessage, | ||
}; | ||
}).value; | ||
assert(id.length > 0, 'id attr should be non-empty string'); | ||
assert.notStrictEqual(id, 'errormessage', 'id attr should be transformed'); | ||
assert.notStrictEqual(idref, 'errormessage', 'idref attr should be transformed'); | ||
assert(id === idref, 'id attr and idref attr should be the same value'); | ||
}); | ||
it('aria-flowto', () => { | ||
const { id, idref } = browser.execute(() => { | ||
var integration = document.querySelector('integration-dynamic-scoped-ids'); | ||
var idElm = integration.shadowRoot.querySelector('.flowto-id'); | ||
var idrefElm = integration.shadowRoot.querySelector('.flowto-idref'); | ||
return { | ||
id: idElm.id, | ||
idref: idrefElm.ariaFlowTo, | ||
}; | ||
}).value; | ||
assert(id.length > 0, 'id attr should be non-empty string'); | ||
assert.notStrictEqual(id, 'flowto', 'id attr should be transformed'); | ||
assert.notStrictEqual(idref, 'flowto', 'idref attr should be transformed'); | ||
assert(id === idref, 'id attr and idref attr should be the same value'); | ||
}); | ||
}); | ||
}); |
38 changes: 38 additions & 0 deletions
38
...scoped-ids/test-dynamic-scoped-ids/integration/dynamic-scoped-ids/dynamic-scoped-ids.html
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,38 @@ | ||
<template> | ||
<div> | ||
<div id={activedescendant} class="activedescendant-id">activedescendant-id</div> | ||
<div aria-activedescendant={activedescendant} class="activedescendant-idref">activedescendant-idref</div> | ||
</div> | ||
<div> | ||
<div id={controls} class="controls-id">controls-id</div> | ||
<div aria-controls={controls} class="controls-idref">controls-idref</div> | ||
</div> | ||
<div> | ||
<div id={describedby} class="describedby-id">describedby-id</div> | ||
<div aria-describedby={describedby} class="describedby-idref">describedby-idref</div> | ||
</div> | ||
<div> | ||
<div id={details} class="details-id">details-id</div> | ||
<div aria-details={details} class="details-idref">details-idref</div> | ||
</div> | ||
<div> | ||
<div aria-errormessage={errormessage} class="errormessage-idref">errormessage-idref</div> | ||
<div id={errormessage} class="errormessage-id">errormessage-id</div> | ||
</div> | ||
<div> | ||
<div id={flowto} class="flowto-id">flowto-id</div> | ||
<div aria-flowto={flowto} class="flowto-idref">flowto-idref</div> | ||
</div> | ||
<div> | ||
<div id={labelledby} class="labelledby-id">labelledby-id</div> | ||
<div aria-labelledby={labelledby} class="labelledby-idref">labelledby-idref</div> | ||
</div> | ||
<div> | ||
<div id={owns} class="owns-id">owns-id</div> | ||
<div aria-owns={owns} class="owns-idref">owns-idref</div> | ||
</div> | ||
<div> | ||
<label for={forfor} class="for-idref">for-idref</label> | ||
<div id={forfor} class="for-id">for-id</div> | ||
</div> | ||
</template> |
Oops, something went wrong.