feat(build): generate client handler bootstrap
req: client_local/001\nreq: client_local/004\nreq: client_local/006\nreq: client_local/007\nreq: client_local/008\nreq: client_local/009\nreq: client_local/010\nreq: client_local/014\nreq: security/002\nreq: security/006\nreq: v1_release/001\nreq: v1_release/008
This commit is contained in:
+75
-50
@@ -20,10 +20,9 @@ async fn client_handler_applies_effect_batch_without_network() -> WebDriverResul
|
||||
.parent()
|
||||
.expect("workspace root")
|
||||
.to_owned();
|
||||
build_browser_artifact(&workspace);
|
||||
let package = workspace.join("target/client-local-bindgen");
|
||||
let (package, bootstrap, rendered) = build_browser_artifact(&workspace);
|
||||
let runtime = workspace.join("hemx-js/runtime/hemx.js");
|
||||
let server = StaticServer::start(package, runtime);
|
||||
let server = StaticServer::start(package, runtime, bootstrap, rendered);
|
||||
|
||||
let mut webdriver = Command::new("geckodriver");
|
||||
webdriver.arg("--port").arg("4451");
|
||||
@@ -34,7 +33,17 @@ async fn client_handler_applies_effect_batch_without_network() -> WebDriverResul
|
||||
let driver = WebDriver::new(&format!("http://{WEBDRIVER_ADDR}"), caps).await?;
|
||||
let result = async {
|
||||
driver.goto(&server.url()).await?;
|
||||
wait_until(&driver, "return window.__clientReady === true").await?;
|
||||
wait_until(
|
||||
&driver,
|
||||
"return document.querySelector('[data-hemx-root]').hasAttribute('data-hemx-client-ready')",
|
||||
)
|
||||
.await?;
|
||||
driver
|
||||
.execute(
|
||||
"window.__clientErrors = []; document.querySelector('[data-hemx-root]').addEventListener('hemx:client-error', (event) => window.__clientErrors.push(event.detail)); return true",
|
||||
Vec::new(),
|
||||
)
|
||||
.await?;
|
||||
let network_before = resource_count(&driver).await?;
|
||||
|
||||
driver
|
||||
@@ -42,10 +51,9 @@ async fn client_handler_applies_effect_batch_without_network() -> WebDriverResul
|
||||
.await?
|
||||
.click()
|
||||
.await?;
|
||||
wait_for_text(
|
||||
wait_until(
|
||||
&driver,
|
||||
"[data-hemx-slot='counter_panel']",
|
||||
"updated by Rust/WASM (click, count=3)",
|
||||
"return document.querySelector('[data-sid]').textContent.includes('updated by Rust/WASM (click, count=3)')",
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -101,7 +109,7 @@ async fn client_handler_applies_effect_batch_without_network() -> WebDriverResul
|
||||
result.and(quit)
|
||||
}
|
||||
|
||||
fn build_browser_artifact(workspace: &Path) {
|
||||
fn build_browser_artifact(workspace: &Path) -> (PathBuf, PathBuf, String) {
|
||||
let status = Command::new("cargo")
|
||||
.current_dir(workspace)
|
||||
.args([
|
||||
@@ -129,6 +137,45 @@ fn build_browser_artifact(workspace: &Path) {
|
||||
.status()
|
||||
.expect("run wasm-bindgen");
|
||||
assert!(status.success(), "wasm-bindgen failed");
|
||||
|
||||
let rendered = Command::new("cargo")
|
||||
.current_dir(workspace)
|
||||
.args([
|
||||
"run",
|
||||
"-q",
|
||||
"-p",
|
||||
"hemx-client-local-example",
|
||||
"--features",
|
||||
"fixture",
|
||||
"--bin",
|
||||
"fixture",
|
||||
])
|
||||
.output()
|
||||
.expect("render generated client fixture");
|
||||
assert!(rendered.status.success(), "generated fixture render failed");
|
||||
let bootstrap =
|
||||
newest_generated_bootstrap(&workspace.join("target/wasm32-unknown-unknown/debug/build"));
|
||||
(
|
||||
output,
|
||||
bootstrap,
|
||||
String::from_utf8(rendered.stdout).expect("fixture is UTF-8"),
|
||||
)
|
||||
}
|
||||
|
||||
fn newest_generated_bootstrap(build_dir: &Path) -> PathBuf {
|
||||
fs::read_dir(build_dir)
|
||||
.expect("read wasm build directory")
|
||||
.filter_map(Result::ok)
|
||||
.filter(|entry| {
|
||||
entry
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.starts_with("hemx-client-local-example-")
|
||||
})
|
||||
.map(|entry| entry.path().join("out/hemx.client.js"))
|
||||
.filter(|path| path.is_file())
|
||||
.max_by_key(|path| path.metadata().and_then(|meta| meta.modified()).ok())
|
||||
.expect("generated hemx client bootstrap")
|
||||
}
|
||||
|
||||
struct StaticServer {
|
||||
@@ -137,7 +184,7 @@ struct StaticServer {
|
||||
}
|
||||
|
||||
impl StaticServer {
|
||||
fn start(package: PathBuf, runtime: PathBuf) -> Self {
|
||||
fn start(package: PathBuf, runtime: PathBuf, bootstrap: PathBuf, rendered: String) -> Self {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind browser fixture");
|
||||
listener.set_nonblocking(true).expect("nonblocking fixture");
|
||||
let address = listener.local_addr().expect("fixture address").to_string();
|
||||
@@ -146,7 +193,7 @@ impl StaticServer {
|
||||
thread::spawn(move || {
|
||||
while !thread_stop.load(Ordering::Relaxed) {
|
||||
match listener.accept() {
|
||||
Ok((stream, _)) => serve(stream, &package, &runtime),
|
||||
Ok((stream, _)) => serve(stream, &package, &runtime, &bootstrap, &rendered),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
thread::sleep(Duration::from_millis(10))
|
||||
}
|
||||
@@ -168,13 +215,16 @@ impl Drop for StaticServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn serve(mut stream: TcpStream, package: &Path, runtime: &Path) {
|
||||
fn serve(mut stream: TcpStream, package: &Path, runtime: &Path, bootstrap: &Path, rendered: &str) {
|
||||
let mut request = [0_u8; 2048];
|
||||
let length = stream.read(&mut request).unwrap_or(0);
|
||||
let first = String::from_utf8_lossy(&request[..length]);
|
||||
let path = first.split_whitespace().nth(1).unwrap_or("/");
|
||||
let (content_type, body) = match path {
|
||||
"/" => ("text/html; charset=utf-8", fixture_html().into_bytes()),
|
||||
"/" => (
|
||||
"text/html; charset=utf-8",
|
||||
fixture_html(rendered).into_bytes(),
|
||||
),
|
||||
"/hemx.js" => (
|
||||
"text/javascript; charset=utf-8",
|
||||
fs::read(runtime).expect("read runtime"),
|
||||
@@ -187,9 +237,17 @@ fn serve(mut stream: TcpStream, package: &Path, runtime: &Path) {
|
||||
"application/wasm",
|
||||
fs::read(package.join("client_local_bg.wasm")).expect("read wasm"),
|
||||
),
|
||||
"/hemx.client.js" => (
|
||||
"text/javascript; charset=utf-8",
|
||||
fs::read(bootstrap).expect("read generated client bootstrap"),
|
||||
),
|
||||
_ => ("text/plain", b"not found".to_vec()),
|
||||
};
|
||||
let status = if path == "/" || path == "/hemx.js" || path.starts_with("/client_local") {
|
||||
let status = if path == "/"
|
||||
|| path == "/hemx.js"
|
||||
|| path == "/hemx.client.js"
|
||||
|| path.starts_with("/client_local")
|
||||
{
|
||||
"200 OK"
|
||||
} else {
|
||||
"404 Not Found"
|
||||
@@ -198,28 +256,10 @@ fn serve(mut stream: TcpStream, package: &Path, runtime: &Path) {
|
||||
stream.write_all(&body).expect("write fixture body");
|
||||
}
|
||||
|
||||
fn fixture_html() -> String {
|
||||
r#"<!doctype html><html><body>
|
||||
<main data-hemx-root="client_local" data-hemx-st="count=3" data-hemx-client-state-version="1">
|
||||
<section data-hemx-slot="counter_panel">idle</section>
|
||||
<button type="button" data-hid="1" data-hemx-on="click" data-hemx-client="increment" data-hemx-client-fallback data-hemx-pending-class="is-pending">Increment locally</button>
|
||||
</main>
|
||||
<script src="/hemx.js"></script>
|
||||
<script type="module">
|
||||
import init, { __hemx_client_increment } from "/client_local.js";
|
||||
await init();
|
||||
const root = document.querySelector("[data-hemx-root]");
|
||||
const slot = root.querySelector("[data-hemx-slot]");
|
||||
const probe = __hemx_client_increment(1, "click", undefined, undefined, undefined, 1, "count=3");
|
||||
const batch = window.hemx.decodeBatch(probe);
|
||||
root.dataset.hemxBuild = String(batch.fingerprint);
|
||||
slot.dataset.sid = String(batch.ops[0].target.resource.id);
|
||||
window.hemx.registerClientHandler("increment", __hemx_client_increment);
|
||||
window.__clientErrors = [];
|
||||
root.addEventListener("hemx:client-error", (event) => window.__clientErrors.push(event.detail));
|
||||
window.__clientReady = true;
|
||||
</script></body></html>"#
|
||||
.to_owned()
|
||||
fn fixture_html(rendered: &str) -> String {
|
||||
format!(
|
||||
"<!doctype html><html><body>{rendered}<script src=\"/hemx.js\"></script><script type=\"module\" src=\"/hemx.client.js\"></script></body></html>"
|
||||
)
|
||||
}
|
||||
|
||||
async fn wait_until(driver: &WebDriver, script: &str) -> WebDriverResult<()> {
|
||||
@@ -241,21 +281,6 @@ async fn wait_until(driver: &WebDriver, script: &str) -> WebDriverResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_text(driver: &WebDriver, selector: &str, text: &str) -> WebDriverResult<()> {
|
||||
let deadline = Instant::now() + STARTUP_TIMEOUT;
|
||||
loop {
|
||||
if let Ok(element) = driver.find(By::Css(selector)).await {
|
||||
if element.text().await.unwrap_or_default().contains(text) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
panic!("timed out waiting for {text:?}");
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn resource_count(driver: &WebDriver) -> WebDriverResult<u64> {
|
||||
Ok(driver
|
||||
.execute(
|
||||
|
||||
Reference in New Issue
Block a user