fix(techdemo): rebootstrap inserted islands
Keep opaque island widgets live when slhx swaps or later DOM insertion adds a new island, and tear down the listener when an island leaves the document. req: interop/001 req: examples/001
This commit is contained in:
@@ -80,12 +80,18 @@
|
|||||||
const state = { frame: 0, snapshot: parseSnapshot(island.getAttribute("data-island-snapshot")) };
|
const state = { frame: 0, snapshot: parseSnapshot(island.getAttribute("data-island-snapshot")) };
|
||||||
roots.set(island, state);
|
roots.set(island, state);
|
||||||
|
|
||||||
rootOf(island).addEventListener("slhx:island-orbit", (event) => {
|
const root = rootOf(island);
|
||||||
|
const update = (event) => {
|
||||||
state.snapshot = parseSnapshot(event.detail);
|
state.snapshot = parseSnapshot(event.detail);
|
||||||
island.setAttribute("data-island-snapshot", event.detail);
|
island.setAttribute("data-island-snapshot", event.detail);
|
||||||
});
|
};
|
||||||
|
root.addEventListener("slhx:island-orbit", update);
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
|
if (!document.contains(island)) {
|
||||||
|
root.removeEventListener("slhx:island-orbit", update);
|
||||||
|
return;
|
||||||
|
}
|
||||||
state.frame += 1;
|
state.frame += 1;
|
||||||
render(canvas, readout, state);
|
render(canvas, readout, state);
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
@@ -99,6 +105,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bootAddedIslands(records) {
|
||||||
|
for (const record of records) {
|
||||||
|
for (const node of record.addedNodes) {
|
||||||
|
if (node.nodeType === 1) {
|
||||||
|
scan();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", scan);
|
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", scan);
|
||||||
else scan();
|
else scan();
|
||||||
|
if (typeof MutationObserver !== "undefined") new MutationObserver(bootAddedIslands).observe(document.documentElement, { childList: true, subtree: true });
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
|
|||||||
assert_text(&driver, "No selectors. Generated resources address every target.").await?;
|
assert_text(&driver, "No selectors. Generated resources address every target.").await?;
|
||||||
assert_text(&driver, "Opaque island bridge").await?;
|
assert_text(&driver, "Opaque island bridge").await?;
|
||||||
wait_for_runtime(&driver).await?;
|
wait_for_runtime(&driver).await?;
|
||||||
|
insert_probe_island(&driver).await?;
|
||||||
|
wait_for_text(&driver, "#probe-island [data-island-readout]", "probe live").await?;
|
||||||
|
|
||||||
driver
|
driver
|
||||||
.find(By::Css(&handle_selector(ui::control_center::handles::simulate_push)))
|
.find(By::Css(&handle_selector(ui::control_center::handles::simulate_push)))
|
||||||
@@ -97,6 +99,7 @@ async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
|
|||||||
|
|
||||||
driver.find(By::Css("a[href='/architecture']")).await?.click().await?;
|
driver.find(By::Css("a[href='/architecture']")).await?.click().await?;
|
||||||
wait_for_text(&driver, &slot_selector(ui::control_center::slots::inspector), "Page swap").await?;
|
wait_for_text(&driver, &slot_selector(ui::control_center::slots::inspector), "Page swap").await?;
|
||||||
|
wait_for_text(&driver, "[data-island-readout]", "activity rows").await?;
|
||||||
assert!(driver.current_url().await?.as_str().ends_with("/architecture"));
|
assert!(driver.current_url().await?.as_str().ends_with("/architecture"));
|
||||||
|
|
||||||
driver.goto(&format!("http://{APP_ADDR}/")).await?;
|
driver.goto(&format!("http://{APP_ADDR}/")).await?;
|
||||||
@@ -133,6 +136,27 @@ fn card_button_selector(handle_id: impl std::fmt::Display, work_id: u64) -> Stri
|
|||||||
format!(r#"[data-hid="{handle_id}"][data-work-id="{work_id}"]"#)
|
format!(r#"[data-hid="{handle_id}"][data-work-id="{work_id}"]"#)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn insert_probe_island(driver: &WebDriver) -> WebDriverResult<()> {
|
||||||
|
driver
|
||||||
|
.execute(
|
||||||
|
r#"
|
||||||
|
document.querySelector('[data-slhx-root]').insertAdjacentHTML('beforeend', `
|
||||||
|
<article id="probe-island" data-slhx-island="orbit" data-island-snapshot="1|1|1|probe waiting">
|
||||||
|
<canvas width="32" height="16"></canvas>
|
||||||
|
<p data-island-readout="">waiting</p>
|
||||||
|
</article>
|
||||||
|
`);
|
||||||
|
setTimeout(() => {
|
||||||
|
document.querySelector('[data-slhx-root]').dispatchEvent(new CustomEvent('slhx:island-orbit', { bubbles: true, detail: '7|2|8|probe live' }));
|
||||||
|
}, 25);
|
||||||
|
return true;
|
||||||
|
"#,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn drag_card_to_lane(driver: &WebDriver, work_id: u64, lane: &str) -> WebDriverResult<()> {
|
async fn drag_card_to_lane(driver: &WebDriver, work_id: u64, lane: &str) -> WebDriverResult<()> {
|
||||||
driver
|
driver
|
||||||
.execute(
|
.execute(
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ fn product_is_e2e_working_over_http() {
|
|||||||
assert!(island.header("content-type").contains("javascript"));
|
assert!(island.header("content-type").contains("javascript"));
|
||||||
assert!(island.text().contains("slhx:island-orbit"));
|
assert!(island.text().contains("slhx:island-orbit"));
|
||||||
assert!(island.text().contains("data-slhx-island"));
|
assert!(island.text().contains("data-slhx-island"));
|
||||||
|
assert!(island.text().contains("MutationObserver"));
|
||||||
|
assert!(island.text().contains("removeEventListener"));
|
||||||
|
|
||||||
let architecture = request("GET", "/architecture", &[("X-SLHX-Partial", "1")], "");
|
let architecture = request("GET", "/architecture", &[("X-SLHX-Partial", "1")], "");
|
||||||
assert_eq!(architecture.status, 200);
|
assert_eq!(architecture.status, 200);
|
||||||
|
|||||||
Reference in New Issue
Block a user