happy-dom icon indicating copy to clipboard operation
happy-dom copied to clipboard

Custom Element initialisation order is wrong (connectedCalback)

Open j-o-sh opened this issue 6 months ago • 3 comments

Describe the bug I initially commented this on a super old Ticket (#958) but have since dug deeper and think this deserves a newer issue. (I hope you agree 🙏)

The order when the connectedCallback will get called for customElements seems incoherent and is also dependent on the order of when document.body.innerHTML is changed vs. when customElements.define is called.

Things that depend on the parent DOM (like this.closest) seem to only work when the element is defined AFTER it has been set in the html, while other aspects like this.getAttribute seem to only work when the element is defined BEFORE the html is set.

To Reproduce Steps to reproduce the behavior:

test('Custom Elements should be connected before the connectedCallback is invoked', () => {
    document.body.innerHTML = '<div id="parent"><foo-bar></foo-bar></div>'
    customElements.define('foo-bar', class extends HTMLElement {
        foo = null
        connectedCallback() {
            this.foo = this.closest('#parent')
        }
    })

    // This fails but would passs if the order of `customElements.define` and
    // `document.body.innerHTML = ` would be reversed
    expect(document.querySelector('foo-bar').foo).not.toBeNull()
})

test('Custom Elements should be able to access their own attributes', () => {
    customElements.define('foo-bar', class extends HTMLElement {
        bar = null
        connectedCallback() {
            this.bar = this.getAttribute('something')
        }
    })
    document.body.innerHTML = '<div id="parent"><foo-bar something="baz"></foo-bar></div>'

    // This passes
    expect(document.querySelector('foo-bar')?.getAttribute('something')).toEqual('baz')

    // This fails but would passs if the order of `customElements.define` and
    // `document.body.innerHTML = ` would be reversed
    expect(document.querySelector('foo-bar').bar).toEqual('baz')
})

Expected behavior The order of defining a custom element and setting the html should not matter. Rather, the element should be known in the dom as an unknown HTMLElement when the html is written before the custom element is defined and known as the custom element after.

The connectedCallback of the custom element should only be invoked after connection to the DOM has completed and parent context as well as attributes, slots and inner context are available to the element.

Screenshots I wrote two unit tests to illustrate this.

Device: doesn't matter

Additional context

I could be willing to try and fix this bug if you don't find the time to do it but I would need someone to point me in the right direction. I pocked around a bit in the HappyDOM code but wasn't able to find any relevant section, unfortunatelly.

Hope you'll find the time to look into this. Thanks for an awesome testing library.

Cheers ✌️

j-o-sh avatar Aug 15 '24 09:08 j-o-sh