<!DOCTYPE html>
<html>
<head>
<title>Custom Elements: must enqueue an element on the appropriate element queue after checking callback is null and the attribute name</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="To enqueue a custom element callback reaction, the callback must be checked of being null and whether the attribute name is observed or not">
<link rel="help" content="https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-callback-reaction">
<link rel="help" content="https://github.com/w3c/webcomponents/issues/760">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<script>

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            const child = this.firstChild;
            child.remove();
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        connectedCallback() { logs.push('connected'); }
        disconnectedCallback() { logs.push('disconnected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);

    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'connected', 'disconnected', 'end']);
}, 'Disconnecting an element with disconnectedCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the removal completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            const child = this.firstChild;
            child.remove();
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        connectedCallback() { logs.push('connected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);

    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'end', 'connected']);
}, 'Disconnecting an element without disconnectedCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the removal completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        disconnectedCallback()
        {
            logs.push('begin');
            contentDocument.body.appendChild(this.firstChild);
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        connectedCallback() { logs.push('connected'); }
        disconnectedCallback() { logs.push('disconnected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    parent.remove();
    assert_array_equals(logs, ['connected', 'begin', 'disconnected', 'connected', 'end']);
}, 'Connecting a element with connectedCallback while it has a disconnectedCallback in its custom element reaction queue must result in disconnectedCallback getting invoked before the insertion completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        disconnectedCallback()
        {
            logs.push('begin');
            contentDocument.body.appendChild(this.firstChild);
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        disconnectedCallback() { logs.push('disconnected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    parent.remove();
    assert_array_equals(logs, ['begin', 'end', 'disconnected']);
}, 'Connecting an element without connectedCallback while it has a disconnectedCallback in its custom element reaction queue must not result in disconnectedCallback getting invoked before the insertion completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            document.adoptNode(this.firstChild);
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        adoptedCallback() { logs.push('adopted'); }
        connectedCallback() { logs.push('connected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'connected', 'adopted', 'end']);
}, 'Adopting an element with adoptingCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the adoption completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            document.adoptNode(this.firstChild);
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        connectedCallback() { logs.push('connected'); }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'end', 'connected']);
}, 'Adopting an element without adoptingCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the adoption completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            this.firstChild.setAttribute('title', 'foo');
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        attributeChangedCallback() { logs.push('attributeChanged'); }
        connectedCallback() { logs.push('connected'); }
        static get observedAttributes() { return ['title']; }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'connected', 'attributeChanged', 'end']);
}, 'Setting an observed attribute on an element with attributeChangedCallback while it has a connectedCallback in its custom element reaction queue must result in connectedCallback getting invoked before the attribute change completes');

test_with_window((contentWindow, contentDocument) => {
    class ParentElement extends contentWindow.HTMLElement {
        connectedCallback()
        {
            logs.push('begin');
            this.firstChild.setAttribute('lang', 'en');
            logs.push('end');
        }
    }
    contentWindow.customElements.define('parent-element', ParentElement);

    const logs = [];
    class ChildElement extends contentWindow.HTMLElement {
        attributeChangedCallback() { logs.push('attributeChanged'); }
        connectedCallback() { logs.push('connected'); }
        static get observedAttributes() { return ['title']; }
    }
    contentWindow.customElements.define('child-element', ChildElement);

    const parent = new ParentElement;
    const child = new ChildElement;
    parent.appendChild(child);
    contentDocument.body.appendChild(parent);
    assert_array_equals(logs, ['begin', 'end', 'connected']);
}, 'Setting an observed attribute on an element with attributeChangedCallback while it has a connectedCallback in its custom element reaction queue must not result in connectedCallback getting invoked before the attribute change completes');

</script>
</body>
</html>
