fix tree hydration issue

This commit is contained in:
konnorrogers
2024-10-02 11:05:18 -04:00
parent 1fbd8e48b6
commit a8c7298dea
3 changed files with 51 additions and 49 deletions

View File

@@ -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>

View File

@@ -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)
})
});
}
}

View File

@@ -8,6 +8,8 @@ import { cleanupFixtures, ssrFixture as LitSSRFixture } from '@lit-labs/testing/
import type { LitElement, TemplateResult } from 'lit';
import type WebAwesomeElement from '../webawesome-element.js';
import { getDiffableHTML } from '@open-wc/semantic-dom-diff/get-diffable-html.js'
declare global {
interface Window {
clientComponents: string[];
@@ -20,16 +22,16 @@ declare global {
/**
* This will hopefully move to a library or be built into Lit. Right now this does nothing.
*/
// function handleHydrationError(e: Event) {
// const element = e.target as WebAwesomeElement;
// const str = `Expected <${element.localName}> to not have hydration error.`
function handleHydrationError(e: Event) {
const element = e.target as WebAwesomeElement;
const str = `Expected <${element.localName}> to not have hydration error.`
// expect(false).to.equal(true, str);
// }
expect(true).to.equal(false, str);
}
// This is a non-standard event I have added to the WebAwesomeElement base class.
// https://github.com/lit/lit/discussions/4703
// document.addEventListener('lit-hydration-error', handleHydrationError);
document.addEventListener('lit-hydration-error', handleHydrationError);
/**
* Loads up a fixture and loads all client components
@@ -52,15 +54,16 @@ export async function hydratedFixture<T extends HTMLElement = HTMLElement>(templ
hydrate: true
});
// @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;