mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 20:19:13 +00:00
Compare commits
19 Commits
konnorroge
...
konnorroge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37de495703 | ||
|
|
54de7b02a6 | ||
|
|
a8c7298dea | ||
|
|
1fbd8e48b6 | ||
|
|
cadc1b9267 | ||
|
|
75fbdb0155 | ||
|
|
43506918e1 | ||
|
|
312225e8c3 | ||
|
|
33f04fe934 | ||
|
|
dfa679cf5d | ||
|
|
8a78049e11 | ||
|
|
9d400906e3 | ||
|
|
b56761373b | ||
|
|
4390b57d8e | ||
|
|
634a796841 | ||
|
|
f9b3f1e01d | ||
|
|
6d2acab81f | ||
|
|
45f5bac5ac | ||
|
|
545ad3dced |
@@ -280,7 +280,9 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
</wa-select>
|
||||
|
||||
<script type="module">
|
||||
await customElements.whenDefined("wa-select")
|
||||
const select = document.querySelector('.custom-tag');
|
||||
await select.updateComplete
|
||||
|
||||
select.getTag = (option, index) => {
|
||||
// Use the same icon used in wa-option
|
||||
@@ -299,4 +301,115 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
|
||||
|
||||
:::warning
|
||||
Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities.
|
||||
:::
|
||||
:::
|
||||
|
||||
### Lazy loading options
|
||||
|
||||
Lazy loading options is very hard to get right. `<wa-select>` largely follows how a native `<select>` works.
|
||||
|
||||
Here are the following conditions:
|
||||
|
||||
- If a `<wa-select>` is created without any options, but is given a `value` attribute, its `value` will be `""`, and then when options are added, if any of the options have a value equal to the `<wa-select>` value, the value of the `<wa-select>` will equal that of the option.
|
||||
|
||||
EX: `<wa-select value="foo">` will have a value of `""` until `<wa-option value="foo">Foo</wa-option>` connects, at which point its value will become `"foo"` when submitting.
|
||||
|
||||
- If a `<wa-select multiple>` with an initial value has multiple values, but only some of the options are present, it will only respect the options that are present, and if a selected option is loaded in later, *AND* the value of the select has not changed via user interaction or direct property assignment, it will add the selected option to the form value and to the `.value` of the select.
|
||||
|
||||
This can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<wa-select>` and `<wa-select multiple>` when given initial value attributes. Feel free to play around with it in a codepen.
|
||||
|
||||
```html {.example}
|
||||
<form id="lazy-options-example">
|
||||
<div>
|
||||
<wa-select name="select-1" value="foo" label="Single select (with existing options)">
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<wa-select name="select-2" value="foo" label="Single select (with no existing options)">
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<wa-select name="select-3" value="foo bar baz" multiple label="Multiple Select (with existing options)">
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<wa-select name="select-4" value="foo" multiple label="Multiple Select (with no existing options)">
|
||||
</wa-select>
|
||||
<br>
|
||||
<wa-button type="button">Add "foo" option</wa-button>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
|
||||
<div style="display: flex; gap: 16px;">
|
||||
<wa-button type="reset">Reset</wa-button>
|
||||
<wa-button type="submit" variant="brand">Show FormData</wa-button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<pre hidden><code id="lazy-options-example-form-data"></code></pre>
|
||||
|
||||
<br>
|
||||
</form>
|
||||
|
||||
<script type="module">
|
||||
function addFooOption(e) {
|
||||
const addFooButton = e.target.closest("wa-button[type='button']")
|
||||
if (!addFooButton) {
|
||||
return
|
||||
}
|
||||
const select = addFooButton.parentElement.querySelector("wa-select")
|
||||
|
||||
if (select.querySelector("wa-option[value='foo']")) {
|
||||
// Foo already exists. no-op.
|
||||
return
|
||||
}
|
||||
|
||||
const option = document.createElement("wa-option")
|
||||
option.setAttribute("value", "foo")
|
||||
option.innerText = "Foo"
|
||||
select.append(option)
|
||||
}
|
||||
|
||||
function handleLazySubmit (event) {
|
||||
event.preventDefault()
|
||||
|
||||
const formData = new FormData(event.target)
|
||||
const codeElement = document.querySelector("#lazy-options-example-form-data")
|
||||
|
||||
const obj = {}
|
||||
for (const key of formData.keys()) {
|
||||
const val = formData.getAll(key).length > 1 ? formData.getAll(key) : formData.get(key)
|
||||
obj[key] = val
|
||||
}
|
||||
|
||||
codeElement.textContent = JSON.stringify(obj, null, 2)
|
||||
|
||||
const preElement = codeElement.parentElement
|
||||
preElement.removeAttribute("hidden")
|
||||
}
|
||||
|
||||
const container = document.querySelector("#lazy-options-example")
|
||||
container.addEventListener("click", addFooOption)
|
||||
container.addEventListener("submit", handleLazySubmit)
|
||||
</script>
|
||||
```
|
||||
@@ -4,6 +4,20 @@ description: Trees allow you to display a hierarchical list of selectable tree i
|
||||
layout: component
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-tree selection="multiple">
|
||||
<wa-tree-item>
|
||||
Parent Node
|
||||
<wa-tree-item selected>Child Node 1</wa-tree-item>
|
||||
<wa-tree-item>
|
||||
Child Node 2
|
||||
<wa-tree-item>Child Node 2 - 1</wa-tree-item>
|
||||
<wa-tree-item>Child Node 2 - 2</wa-tree-item>
|
||||
</wa-tree-item>
|
||||
</wa-tree-item>
|
||||
</wa-tree>
|
||||
```
|
||||
|
||||
```html {.example}
|
||||
<wa-tree>
|
||||
<wa-tree-item>
|
||||
@@ -241,4 +255,4 @@ Decorative icons can be used before labels to provide hints for each node.
|
||||
</wa-tree-item>
|
||||
</wa-tree-item>
|
||||
</wa-tree>
|
||||
```
|
||||
```
|
||||
@@ -614,6 +614,129 @@ describe('<wa-select>', () => {
|
||||
|
||||
expect(tag.hasAttribute('pill')).to.be.true;
|
||||
});
|
||||
|
||||
describe('With lazily loaded options', () => {
|
||||
describe('With no existing options', () => {
|
||||
it('Should wait to select the option when the option exists for single select', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html`<form><wa-select name="select" value="option-1"></wa-select></form>`
|
||||
);
|
||||
const el = form.querySelector<WaSelect>('wa-select')!;
|
||||
|
||||
expect(el.value).to.equal('');
|
||||
expect(new FormData(form).get('select')).equal('');
|
||||
|
||||
const option = document.createElement('wa-option');
|
||||
option.value = 'option-1';
|
||||
option.innerText = 'Option 1';
|
||||
el.append(option);
|
||||
|
||||
await aTimeout(10);
|
||||
await el.updateComplete;
|
||||
expect(el.value).to.equal('option-1');
|
||||
expect(new FormData(form).get('select')).equal('option-1');
|
||||
});
|
||||
|
||||
it('Should wait to select the option when the option exists for multiple select', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html`<form><wa-select name="select" value="option-1" multiple></wa-select></form>`
|
||||
);
|
||||
|
||||
const el = form.querySelector<WaSelect>('wa-select')!;
|
||||
expect(Array.isArray(el.value)).to.equal(true);
|
||||
expect(el.value!.length).to.equal(0);
|
||||
|
||||
const option = document.createElement('wa-option');
|
||||
option.value = 'option-1';
|
||||
option.innerText = 'Option 1';
|
||||
el.append(option);
|
||||
|
||||
await aTimeout(10);
|
||||
await el.updateComplete;
|
||||
expect(el.value!.length).to.equal(1);
|
||||
expect(el.value).to.have.members(['option-1']);
|
||||
expect(new FormData(form).getAll('select')).have.members(['option-1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With existing options', () => {
|
||||
it('Should not select the option if options already exist for single select', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html` <form>
|
||||
<wa-select name="select" value="foo">
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
</form>`
|
||||
);
|
||||
|
||||
const el = form.querySelector<WaSelect>('wa-select')!;
|
||||
expect(el.value).to.equal('');
|
||||
expect(new FormData(form).get('select')).to.equal('');
|
||||
|
||||
const option = document.createElement('wa-option');
|
||||
option.value = 'foo';
|
||||
option.innerText = 'Foo';
|
||||
el.append(option);
|
||||
|
||||
await aTimeout(10);
|
||||
await el.updateComplete;
|
||||
expect(el.value).to.equal('foo');
|
||||
expect(new FormData(form).get('select')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('Should not select the option if options already exists for multiple select', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html` <form>
|
||||
<wa-select name="select" value="foo" multiple>
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
</form>`
|
||||
);
|
||||
|
||||
const el = form.querySelector<WaSelect>('wa-select')!;
|
||||
expect(el.value).to.be.an('array');
|
||||
expect(el.value!.length).to.equal(0);
|
||||
|
||||
const option = document.createElement('wa-option');
|
||||
option.value = 'foo';
|
||||
option.innerText = 'Foo';
|
||||
el.append(option);
|
||||
|
||||
await aTimeout(10);
|
||||
await el.updateComplete;
|
||||
expect(el.value).to.have.members(['foo']);
|
||||
expect(new FormData(form).getAll('select')).to.have.members(['foo']);
|
||||
});
|
||||
|
||||
it('Should only select the existing options if options already exists for multiple select', async () => {
|
||||
const form = await fixture<HTMLFormElement>(
|
||||
html` <form>
|
||||
<wa-select name="select" value="foo bar baz" multiple>
|
||||
<wa-option value="bar">Bar</wa-option>
|
||||
<wa-option value="baz">Baz</wa-option>
|
||||
</wa-select>
|
||||
</form>`
|
||||
);
|
||||
|
||||
const el = form.querySelector<WaSelect>('wa-select')!;
|
||||
expect(el.value).to.have.members(['bar', 'baz']);
|
||||
expect(el.value!.length).to.equal(2);
|
||||
expect(new FormData(form).getAll('select')).to.have.members(['bar', 'baz']);
|
||||
|
||||
const option = document.createElement('wa-option');
|
||||
option.value = 'foo';
|
||||
option.innerText = 'Foo';
|
||||
el.append(option);
|
||||
|
||||
await aTimeout(10);
|
||||
await el.updateComplete;
|
||||
expect(el.value).to.have.members(['foo', 'bar', 'baz']);
|
||||
expect(new FormData(form).getAll('select')).to.have.members(['foo', 'bar', 'baz']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -162,30 +162,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
return val;
|
||||
}
|
||||
|
||||
private _value: string | string[] | null = this.defaultValue;
|
||||
|
||||
/**
|
||||
* The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the
|
||||
* value attribute will be a space-delimited list of values based on the options selected, and the value property will
|
||||
* be an array. **For this reason, values must not contain spaces.**
|
||||
*/
|
||||
get value() {
|
||||
if (this.valueHasChanged) {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
return this._value ?? this.defaultValue;
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
set value(val: string | string[] | null) {
|
||||
if (this._value === val) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.valueHasChanged = true;
|
||||
this._value = val;
|
||||
}
|
||||
@property({ attribute: false }) value: string | string[] | null = null;
|
||||
|
||||
/** The select's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -286,11 +263,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
if (!this.hasInteracted) {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
});
|
||||
this.handleDefaultSlotChange();
|
||||
|
||||
// Because this is a form control, it shouldn't be opened initially
|
||||
this.open = false;
|
||||
}
|
||||
@@ -541,6 +515,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
const oldValue = this.value;
|
||||
|
||||
if (option && !option.disabled) {
|
||||
this.valueHasChanged = true;
|
||||
if (this.multiple) {
|
||||
this.toggleOptionSelection(option);
|
||||
} else {
|
||||
@@ -566,20 +541,20 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
if (!customElements.get('wa-option')) {
|
||||
customElements.whenDefined('wa-option').then(() => this.handleDefaultSlotChange());
|
||||
}
|
||||
|
||||
const allOptions = this.getAllOptions();
|
||||
const value = Array.isArray(this.value) ? this.value : [this.value];
|
||||
const val = this.valueHasChanged ? this.value : this.defaultValue;
|
||||
const value = Array.isArray(val) ? val : [val];
|
||||
const values: string[] = [];
|
||||
|
||||
// Check for duplicate values in menu items
|
||||
if (customElements.get('wa-option')) {
|
||||
allOptions.forEach(option => values.push(option.value));
|
||||
allOptions.forEach(option => values.push(option.value));
|
||||
|
||||
// Select only the options that match the new value
|
||||
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
|
||||
} else {
|
||||
// Rerun this handler when `<wa-option>` is registered
|
||||
customElements.whenDefined('wa-option').then(() => this.handleDefaultSlotChange());
|
||||
}
|
||||
// Select only the options that match the new value
|
||||
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
|
||||
}
|
||||
|
||||
private handleTagRemove(event: WaRemoveEvent, option: WaOption) {
|
||||
@@ -657,8 +632,10 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
// This method must be called whenever the selection changes. It will update the selected options cache, the current
|
||||
// value, and the display value
|
||||
private selectionChanged() {
|
||||
const options = this.getAllOptions();
|
||||
|
||||
// Update selected options cache
|
||||
this.selectedOptions = this.getAllOptions().filter(el => el.selected);
|
||||
this.selectedOptions = options.filter(el => el.selected);
|
||||
|
||||
// Update the value and display label
|
||||
if (this.multiple) {
|
||||
@@ -671,8 +648,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
this.displayLabel = this.localize.term('numOptionsSelected', this.selectedOptions.length);
|
||||
}
|
||||
} else {
|
||||
this.value = this.selectedOptions[0]?.value ?? '';
|
||||
this.displayLabel = this.selectedOptions[0]?.getTextLabel?.() ?? '';
|
||||
const selectedOption = this.selectedOptions[0];
|
||||
this.value = selectedOption?.value ?? '';
|
||||
this.displayLabel = selectedOption?.getTextLabel?.() ?? '';
|
||||
}
|
||||
|
||||
// Update validity
|
||||
@@ -896,7 +874,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
@blur=${this.handleBlur}
|
||||
/>
|
||||
|
||||
${this.multiple ? html`<div part="tags" class="select__tags">${this.tags}</div>` : ''}
|
||||
<!-- Tags need to wait for first hydration before populating otherwise it will create a hydration mismatch. -->
|
||||
${this.multiple && this.hasUpdated ? html`<div part="tags" class="select__tags">${this.tags}</div>` : ''}
|
||||
|
||||
<input
|
||||
class="select__value-input"
|
||||
|
||||
@@ -680,18 +680,17 @@ describe('<wa-tree>', () => {
|
||||
</wa-tree-item>
|
||||
</wa-tree>
|
||||
`);
|
||||
|
||||
const treeItems = Array.from<WaTreeItem>(tree.querySelectorAll('wa-tree-item'));
|
||||
|
||||
// Act
|
||||
await tree.updateComplete;
|
||||
await Promise.allSettled(treeItems.map(treeItem => treeItem.updateComplete));
|
||||
|
||||
// Assert
|
||||
// @TODO: Figure out why this fails in hydration
|
||||
if (fixture.type !== 'ssr-client-hydrated') {
|
||||
treeItems.forEach(treeItem => {
|
||||
expect(treeItem).to.have.attribute('selected');
|
||||
});
|
||||
}
|
||||
treeItems.forEach(treeItem => {
|
||||
expect(treeItem).to.have.attribute('selected');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -716,14 +715,12 @@ describe('<wa-tree>', () => {
|
||||
|
||||
// Act
|
||||
await tree.updateComplete;
|
||||
await Promise.allSettled(treeItems.map(treeItem => treeItem.updateComplete));
|
||||
|
||||
// Assert
|
||||
// @TODO: Figure out why this fails in hydration
|
||||
if (fixture.type !== 'ssr-client-hydrated') {
|
||||
treeItems.forEach(treeItem => {
|
||||
expect(treeItem).to.have.attribute('selected');
|
||||
});
|
||||
}
|
||||
treeItems.forEach(treeItem => {
|
||||
expect(treeItem).to.have.attribute('selected');
|
||||
});
|
||||
expect(treeItems[0].indeterminate).to.be.false;
|
||||
});
|
||||
});
|
||||
@@ -748,15 +745,11 @@ describe('<wa-tree>', () => {
|
||||
|
||||
// Act
|
||||
await tree.updateComplete;
|
||||
await Promise.allSettled(treeItems.map(treeItem => treeItem.updateComplete));
|
||||
|
||||
// Assert
|
||||
expect(treeItems[0]).not.to.have.attribute('selected');
|
||||
|
||||
// @TODO: figure out why this fails with SSR.
|
||||
if (fixture.type !== 'ssr-client-hydrated') {
|
||||
expect(treeItems[0].indeterminate).to.be.true;
|
||||
}
|
||||
|
||||
expect(treeItems[0].indeterminate).to.be.true;
|
||||
expect(treeItems[1]).to.have.attribute('selected');
|
||||
expect(treeItems[2]).not.to.have.attribute('selected');
|
||||
expect(treeItems[3]).not.to.have.attribute('selected');
|
||||
@@ -767,7 +760,7 @@ describe('<wa-tree>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/shoelace-style/shoelace/issues/1916
|
||||
// // https://github.com/shoelace-style/shoelace/issues/1916
|
||||
it("Should not render 'null' if it can't find a custom icon", async () => {
|
||||
const tree = await fixture<WaTree>(html`
|
||||
<wa-tree>
|
||||
|
||||
@@ -141,26 +141,28 @@ export default class WaTree extends WebAwesomeElement {
|
||||
|
||||
// Initializes new items by setting the `selectable` property and the expanded/collapsed icons if any
|
||||
private initTreeItem = (item: WaTreeItem) => {
|
||||
item.selectable = this.selection === 'multiple';
|
||||
item.updateComplete.then(() => {
|
||||
item.selectable = this.selection === 'multiple';
|
||||
|
||||
['expand', 'collapse']
|
||||
.filter(status => !!this.querySelector(`[slot="${status}-icon"]`))
|
||||
.forEach((status: 'expand' | 'collapse') => {
|
||||
const existingIcon = item.querySelector(`[slot="${status}-icon"]`);
|
||||
const expandButtonIcon = this.getExpandButtonIcon(status);
|
||||
['expand', 'collapse']
|
||||
.filter(status => !!this.querySelector(`[slot="${status}-icon"]`))
|
||||
.forEach((status: 'expand' | 'collapse') => {
|
||||
const existingIcon = item.querySelector(`[slot="${status}-icon"]`);
|
||||
const expandButtonIcon = this.getExpandButtonIcon(status);
|
||||
|
||||
if (!expandButtonIcon) return;
|
||||
if (!expandButtonIcon) return;
|
||||
|
||||
if (existingIcon === null) {
|
||||
// No separator exists, add one
|
||||
item.append(expandButtonIcon);
|
||||
} else if (existingIcon.hasAttribute('data-default')) {
|
||||
// A default separator exists, replace it
|
||||
existingIcon.replaceWith(expandButtonIcon);
|
||||
} else {
|
||||
// The user provided a custom icon, leave it alone
|
||||
}
|
||||
});
|
||||
if (existingIcon === null) {
|
||||
// No separator exists, add one
|
||||
item.append(expandButtonIcon);
|
||||
} else if (existingIcon.hasAttribute('data-default')) {
|
||||
// A default separator exists, replace it
|
||||
existingIcon.replaceWith(expandButtonIcon);
|
||||
} else {
|
||||
// The user provided a custom icon, leave it alone
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
private handleTreeChanged = (mutations: MutationRecord[]) => {
|
||||
@@ -359,15 +361,19 @@ export default class WaTree extends WebAwesomeElement {
|
||||
this.setAttribute('aria-multiselectable', isSelectionMultiple ? 'true' : 'false');
|
||||
|
||||
for (const item of items) {
|
||||
item.selectable = isSelectionMultiple;
|
||||
item.updateComplete.then(() => {
|
||||
item.selectable = isSelectionMultiple;
|
||||
});
|
||||
}
|
||||
|
||||
if (isSelectionMultiple) {
|
||||
await this.updateComplete;
|
||||
|
||||
[...this.querySelectorAll(':scope > wa-tree-item')].forEach((treeItem: WaTreeItem) =>
|
||||
syncCheckboxes(treeItem, true)
|
||||
);
|
||||
[...this.querySelectorAll(':scope > wa-tree-item')].forEach((treeItem: WaTreeItem) => {
|
||||
treeItem.updateComplete.then(() => {
|
||||
syncCheckboxes(treeItem, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
* These fixtures will also auto-load all of our components.
|
||||
*/
|
||||
|
||||
import { aTimeout, fixture } from '@open-wc/testing';
|
||||
import { aTimeout, expect, fixture } from '@open-wc/testing';
|
||||
import { cleanupFixtures, ssrFixture as LitSSRFixture } from '@lit-labs/testing/fixtures.js';
|
||||
import type { LitElement, TemplateResult } from 'lit';
|
||||
import type WebAwesomeElement from '../webawesome-element.js';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -19,8 +20,11 @@ declare global {
|
||||
/**
|
||||
* This will hopefully move to a library or be built into Lit. Right now this does nothing.
|
||||
*/
|
||||
function handleHydrationError() {
|
||||
// console.error('LIT HYDRATION ERROR');
|
||||
function handleHydrationError(e: Event) {
|
||||
const element = e.target as WebAwesomeElement;
|
||||
const str = `Expected <${element.localName}> to not have hydration error.`;
|
||||
|
||||
expect(true).to.equal(false, str);
|
||||
}
|
||||
|
||||
// This is a non-standard event I have added to the WebAwesomeElement base class.
|
||||
@@ -32,7 +36,6 @@ document.addEventListener('lit-hydration-error', handleHydrationError);
|
||||
*/
|
||||
export async function clientFixture<T extends HTMLElement = HTMLElement>(template: TemplateResult | string) {
|
||||
// Load all component definitions "customElements.define()"
|
||||
// await Promise.allSettled(window.clientComponents.map(str => import(str)));
|
||||
return await fixture<T>(template);
|
||||
}
|
||||
|
||||
@@ -49,18 +52,16 @@ export async function hydratedFixture<T extends HTMLElement = HTMLElement>(templ
|
||||
hydrate: true
|
||||
});
|
||||
|
||||
// Load all component definitions "customElements.define()"
|
||||
// await Promise.allSettled(window.clientComponents.map(str => import(str)));
|
||||
// @ts-expect-error Assume its a lit element.
|
||||
await hydratedElement.updateComplete;
|
||||
|
||||
// This can be removed when this is fixed: https://github.com/lit/lit/issues/4709
|
||||
// This forces every element to "hydrate" and then wait for an update to complete (hydration)
|
||||
await Promise.allSettled(
|
||||
[...hydratedElement.querySelectorAll<LitElement>('*')].map(el => {
|
||||
[...hydratedElement.querySelectorAll<LitElement>('*')].map(async el => {
|
||||
el.removeAttribute('defer-hydration');
|
||||
return el.updateComplete;
|
||||
}),
|
||||
// @ts-expect-error Assume its a lit element.
|
||||
await hydratedElement.updateComplete
|
||||
})
|
||||
);
|
||||
|
||||
return hydratedElement;
|
||||
|
||||
Reference in New Issue
Block a user