From a8c7298dea631945ae177721867969bbaf4a6037 Mon Sep 17 00:00:00 2001 From: konnorrogers Date: Wed, 2 Oct 2024 11:05:18 -0400 Subject: [PATCH] fix tree hydration issue --- src/components/tree/tree.test.ts | 31 ++++++++------------- src/components/tree/tree.ts | 48 ++++++++++++++++++-------------- src/internal/test/fixture.ts | 21 ++++++++------ 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/components/tree/tree.test.ts b/src/components/tree/tree.test.ts index 2b6b766e7..66c721844 100644 --- a/src/components/tree/tree.test.ts +++ b/src/components/tree/tree.test.ts @@ -680,18 +680,17 @@ describe('', () => { `); + const treeItems = Array.from(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('', () => { // 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('', () => { // 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('', () => { }); }); - // 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(html` diff --git a/src/components/tree/tree.ts b/src/components/tree/tree.ts index 7d2657cfb..5aee33ea2 100644 --- a/src/components/tree/tree.ts +++ b/src/components/tree/tree.ts @@ -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) + }) + }); } } diff --git a/src/internal/test/fixture.ts b/src/internal/test/fixture.ts index 3dc6117f6..d7ab22a19 100644 --- a/src/internal/test/fixture.ts +++ b/src/internal/test/fixture.ts @@ -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(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('*')].map(el => { + [...hydratedElement.querySelectorAll('*')].map(async el => { el.removeAttribute('defer-hydration'); return el.updateComplete; }), - // @ts-expect-error Assume its a lit element. - await hydratedElement.updateComplete ); return hydratedElement;