394 lines
12 KiB
HTML
394 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<title>Shadow DOM: Imperative Slot API slotchange event</title>
|
|
<meta name="author" title="Yu Han" href="mailto:yuzhehan@chromium.org">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<link rel="help" href="https://dom.spec.whatwg.org/#signaling-slot-change">
|
|
<script src="resources/shadow-dom.js"></script>
|
|
|
|
<div id="test_slotchange">
|
|
<div id="host">
|
|
<template id="shadow_root" data-mode="open" data-slot-assignment="manual">
|
|
<slot id="s1"><div id="fb">fallback</div></slot>
|
|
<slot id="s2"></slot>
|
|
<div>
|
|
<slot id="s2.5"></slot>
|
|
</div>
|
|
<slot id="s3"></slot>
|
|
</template>
|
|
<div id="c1"></div>
|
|
<div id="c2"></div>
|
|
</div>
|
|
<div id="c4"></div>
|
|
</div>
|
|
|
|
<script>
|
|
function getDataCollection() {
|
|
return {
|
|
s1EventCount: 0,
|
|
s2EventCount: 0,
|
|
s3EventCount: 0,
|
|
s1ResolveFn: null,
|
|
s2ResolveFn: null,
|
|
s3ResolveFn: null,
|
|
}
|
|
}
|
|
|
|
function setupShadowDOM(id, test, data) {
|
|
let tTree = createTestTree(id);
|
|
tTree.s1.addEventListener('slotchange', (event) => {
|
|
if (!event.isFakeEvent) {
|
|
test.step(function () {
|
|
assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
|
|
assert_equals(event.target, tTree.s1, 'slotchange event\'s target must be the slot element');
|
|
assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
|
|
});
|
|
data.s1EventCount++;
|
|
}
|
|
data.s1ResolveFn();
|
|
});
|
|
tTree.s2.addEventListener('slotchange', (event) => {
|
|
if (!event.isFakeEvent) {
|
|
test.step(function () {
|
|
assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
|
|
assert_equals(event.target, tTree.s2, 'slotchange event\'s target must be the slot element');
|
|
assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
|
|
});
|
|
data.s2EventCount++;
|
|
}
|
|
data.s2ResolveFn();
|
|
});
|
|
tTree.s3.addEventListener('slotchange', (event) => {
|
|
if (!event.isFakeEvent) {
|
|
test.step(function () {
|
|
assert_equals(event.type, 'slotchange', 'slotchange event\'s type must be "slotchange"');
|
|
// listen to bubbling events.
|
|
assert_equals(event.relatedTarget, undefined, 'slotchange must not set relatedTarget');
|
|
});
|
|
data.s3EventCount++;
|
|
}
|
|
data.s3ResolveFn();
|
|
});
|
|
return tTree;
|
|
}
|
|
|
|
function monitorSlots(data) {
|
|
const s1Promise = new Promise((resolve, reject) => {
|
|
data.s1ResolveFn = resolve;
|
|
});
|
|
const s2Promise = new Promise((resolve, reject) => {
|
|
data.s2ResolveFn = resolve;
|
|
});
|
|
const s3Promise = new Promise((resolve, reject) => {
|
|
data.s3ResolveFn = resolve;
|
|
});
|
|
return [s1Promise, s2Promise, s3Promise];
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
// Tests:
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise, s2Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
tTree.s2.assign(tTree.c2);
|
|
|
|
assert_equals(data.s1EventCount, 0, 'slotchange event must not be fired synchronously');
|
|
assert_equals(data.s2EventCount, 0);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
assert_equals(data.s2EventCount, 1);
|
|
}));
|
|
}, 'slotchange event must not fire synchronously.');
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise, s2Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign();;
|
|
tTree.s2.assign();
|
|
tTree.host.insertBefore(tTree.c4, tTree.c1);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 0);
|
|
assert_equals(data.s2EventCount, 0);
|
|
}));
|
|
|
|
// use fake event to trigger event handler.
|
|
let fakeEvent = new Event('slotchange');
|
|
fakeEvent.isFakeEvent = true;
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
tTree.s2.dispatchEvent(fakeEvent);
|
|
}, 'slotchange event should not fire when assignments do not change assignedNodes.');
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange,test, data);
|
|
let [s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1, tTree.c2);
|
|
|
|
s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise] = monitorSlots(data);
|
|
tTree.s1.assign(tTree.c1, tTree.c2);
|
|
tTree.s1.assign(tTree.c1, tTree.c2, tTree.c1, tTree.c2, tTree.c2);
|
|
|
|
s1Promise.then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
}));
|
|
|
|
let fakeEvent = new Event('slotchange');
|
|
fakeEvent.isFakeEvent = true;
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
}));
|
|
|
|
}, 'slotchange event should not fire when same node is assigned.');
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise, s2Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
tTree.s2.assign(tTree.c2);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
assert_equals(data.s2EventCount, 1);
|
|
}));
|
|
}, "Fire slotchange event when slot's assigned nodes changes.");
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise, s2Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
|
|
s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise, s2Promise] = monitorSlots(data);
|
|
tTree.s2.assign(tTree.c1);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 2);
|
|
assert_equals(data.s2EventCount, 1);
|
|
}));
|
|
}));
|
|
}, "Fire slotchange event on previous slot and new slot when node is reassigned.");
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
|
|
s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise] = monitorSlots(data);
|
|
tTree.s1.assign();
|
|
|
|
s1Promise.then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 2);
|
|
}));
|
|
}));
|
|
}, "Fire slotchange event on node assignment and when assigned node is removed.");
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1, tTree.c2);
|
|
|
|
s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise] = monitorSlots(data);
|
|
tTree.s1.assign(tTree.c2, tTree.c1);
|
|
|
|
s1Promise.then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 2);
|
|
}));
|
|
}));
|
|
}, "Fire slotchange event when order of assigned nodes changes.");
|
|
|
|
promise_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
|
|
return s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise] = monitorSlots(data);
|
|
tTree.c1.remove();
|
|
|
|
return s1Promise;
|
|
}))
|
|
.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 2);
|
|
}));
|
|
}, "Fire slotchange event when assigned node is removed.");
|
|
|
|
promise_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
[s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
|
|
return s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
|
|
[s1Promise] = monitorSlots(data);
|
|
tTree.s1.remove();
|
|
|
|
return s1Promise;
|
|
}))
|
|
.then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 2);
|
|
}));
|
|
}, "Fire slotchange event when removing a slot from Shadows Root that changes its assigned nodes.");
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.remove();
|
|
|
|
let fakeEvent = new Event('slotchange');
|
|
fakeEvent.isFakeEvent = true;
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
|
|
s1Promise.then(test.step_func(() => {
|
|
assert_equals(data.s2EventCount, 0);
|
|
|
|
[s1Promise, s2Promise] = monitorSlots(data);
|
|
tTree.shadow_root.insertBefore(tTree.s1, tTree.s2);
|
|
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
tTree.s2.dispatchEvent(fakeEvent);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 0);
|
|
assert_equals(data.s2EventCount, 0);
|
|
}));
|
|
}));
|
|
|
|
}, "No slotchange event when adding or removing an empty slot.");
|
|
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_slotchange, test, data);
|
|
let [s1Promise, s2Promise] = monitorSlots(data);
|
|
|
|
tTree.host.appendChild(document.createElement("div"));
|
|
|
|
let fakeEvent = new Event('slotchange');
|
|
fakeEvent.isFakeEvent = true;
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
tTree.s2.dispatchEvent(fakeEvent);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func(() => {
|
|
assert_equals(data.s1EventCount, 0);
|
|
assert_equals(data.s2EventCount, 0);
|
|
|
|
[s1Promise, s2Promise] = monitorSlots(data);
|
|
tTree.shadow_root.insertBefore(document.createElement("div"), tTree.s2);
|
|
|
|
tTree.s1.dispatchEvent(fakeEvent);
|
|
tTree.s2.dispatchEvent(fakeEvent);
|
|
|
|
Promise.all([s1Promise, s2Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 0);
|
|
assert_equals(data.s2EventCount, 0);
|
|
}));
|
|
}));
|
|
|
|
}, "No slotchange event when adding another slotable.");
|
|
|
|
</script>
|
|
|
|
<div id="test_nested_slotchange">
|
|
<div>
|
|
<template data-mode="open" data-slot-assignment="manual">
|
|
<div>
|
|
<template data-mode="open" data-slot-assignment="manual">
|
|
<slot id="s2"></slot>
|
|
<slot id="s3"></slot>
|
|
</template>
|
|
<slot id="s1"></slot>
|
|
</div>
|
|
</template>
|
|
<div id="c1"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
async_test((test) => {
|
|
const data = getDataCollection();
|
|
let tTree = setupShadowDOM(test_nested_slotchange, test, data);
|
|
let [s1Promise, s2Promise, s3Promise] = monitorSlots(data);
|
|
|
|
tTree.s3.assign(tTree.s1);
|
|
|
|
s3Promise.then(test.step_func(() => {
|
|
assert_equals(data.s3EventCount, 1);
|
|
[s1Promise, s2Promise, s3Promise] = monitorSlots(data);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
|
|
Promise.all([s1Promise, s3Promise]).then(test.step_func_done(() => {
|
|
assert_equals(data.s1EventCount, 1);
|
|
assert_equals(data.s3EventCount, 2);
|
|
}));
|
|
}));
|
|
}, "Fire slotchange event when assign node to nested slot, ensure event bubbles ups.");
|
|
|
|
promise_test(async t => {
|
|
async function mutationObserversRun() {
|
|
return new Promise(r => {
|
|
t.step_timeout(r, 0);
|
|
});
|
|
}
|
|
let tTree = createTestTree(test_slotchange);
|
|
|
|
tTree.s1.assign(tTree.c1);
|
|
tTree["s2.5"].assign(tTree.c2);
|
|
|
|
let slotChangedOrder = [];
|
|
|
|
// Clears out pending mutation observers
|
|
await mutationObserversRun();
|
|
|
|
tTree.s1.addEventListener("slotchange", function() {
|
|
slotChangedOrder.push("s1");
|
|
});
|
|
|
|
tTree.s3.addEventListener("slotchange", function() {
|
|
slotChangedOrder.push("s3");
|
|
});
|
|
|
|
tTree["s2.5"].addEventListener("slotchange", function() {
|
|
slotChangedOrder.push("s2.5");
|
|
});
|
|
|
|
tTree.s3.assign(tTree.c2, tTree.c1);
|
|
await mutationObserversRun();
|
|
assert_array_equals(slotChangedOrder, ["s1", "s2.5", "s3"]);
|
|
}, 'Signal a slot change should be done in tree order.');
|
|
</script>
|