tests/testthat/test-shinylive_wasm.R

read_wasm_manifest_lines <- function(path) {
  x <- readLines(path, warn = FALSE)
  x <- trimws(sub("#.*$", "", x))
  x[nzchar(x)]
}

wasm_manifest_path <- function(file) {
  installed <- system.file("shiny", "wasm", file, package = "OpenSpecy")
  if (nzchar(installed)) return(installed)
  test_path("..", "..", "inst", "shiny", "wasm", file)
}

shiny_app_source_path <- function(file) {
  installed <- system.file("shiny", file, package = "OpenSpecy")
  if (nzchar(installed)) return(installed)

  workspace <- Sys.getenv("GITHUB_WORKSPACE", unset = "")
  candidates <- c(
    test_path("..", "..", "inst", "shiny", file),
    if (nzchar(workspace)) file.path(workspace, "inst", "shiny", file)
  )
  existing <- candidates[file.exists(candidates)]
  testthat::skip_if(
    length(existing) == 0L,
    "bundled Shiny application sources are unavailable"
  )
  existing[[1L]]
}

source_wasm_tool <- function(file, env) {
  path <- test_path("..", "..", "tools", "wasm", file)
  if (!file.exists(path)) {
    skip("Repository-only wasm deployment tools are not in the package tarball")
  }
  sys.source(path, envir = env)
}

shinylive_cache_worker_fixture <- function() {
  paste(c(
    'function dirname(path) { return path.slice(0, path.lastIndexOf("/")); }',
    "// src/shinylive-sw.ts",
    "var useCaching = false;",
    'var cacheName = "::shinyliveServiceworker";',
    'var version = "v10";',
    "function addCoiHeaders(response) { return response; }",
    'self.addEventListener("install", (event) => {',
    "  event.waitUntil(",
    "    Promise.all([self.skipWaiting(), caches.open(version + cacheName)])",
    "  );",
    "});",
    'self.addEventListener("activate", function(event) {',
    "  event.waitUntil(",
    "    (async () => {",
    "      await self.clients.claim();",
    "      const keys = await caches.keys();",
    "      return Promise.all(",
    "        keys.filter(function(key) {",
    "          return key.indexOf(version + cacheName) !== 0;",
    "        }).map(function(key) {",
    "          return caches.delete(key);",
    "        })",
    "      );",
    "    })()",
    "  );",
    "});",
    'self.addEventListener("fetch", function(event) {',
    "  const request = event.request;",
    "  const url = new URL(request.url);",
    "  if (self.location.origin !== url.origin) return;",
    "  const base_path = dirname(self.location.pathname);",
    "  const coiRequested = false;",
    '  if (request.method !== "GET") {',
    "    return;",
    "  }",
    "  if (useCaching) {",
    "    event.respondWith(",
    "      (async () => {",
    "        const cachedResponse = await caches.match(request);",
    "        if (cachedResponse) return cachedResponse;",
    "        const networkResponse = addCoiHeaders(await fetch(request));",
    "        const baseUrl = self.location.origin + dirname(self.location.pathname);",
    '        if (request.url.startsWith(baseUrl + "/shinylive/")) {',
    "          const cache = await caches.open(version + cacheName);",
    "          await cache.put(request, networkResponse.clone());",
    "        }",
    "        return networkResponse;",
    "      })()",
    "    );",
    "    return;",
    "  }",
    "  if (coiRequested) {",
    "    event.respondWith(Promise.resolve(fetch(request)));",
    "  }",
    "});",
    ""
  ), collapse = "\n")
}

write_shinylive_cache_fixture <- function(path, sha, worker = NULL,
                                           controlled_reload = TRUE) {
  dir.create(file.path(path, "shinylive"), recursive = TRUE,
             showWarnings = FALSE)
  if (is.null(worker)) worker <- shinylive_cache_worker_fixture()
  writeBin(charToRaw(worker), file.path(path, "shinylive-sw.js"))
  loader <- c(
    'if ("serviceWorker" in navigator) {',
    "navigator.serviceWorker.register(serviceWorkerPath, { type: \"module\" })",
    "  .then((registration) => registration.update());",
    "navigator.serviceWorker.ready.then(() => {",
    if (controlled_reload) "  if (!navigator.serviceWorker.controller)" else
      "  if (false)",
    if (controlled_reload) "    window.location.reload();" else
      "    console.log('uncontrolled');",
    "});",
    "}"
  )
  writeLines(loader, file.path(path, "shinylive", "load-shinylive-sw.js"))
  jsonlite::write_json(
    list(package = list(commit = sha)),
    file.path(path, "pinned-wasm-library.json"),
    auto_unbox = TRUE
  )
  invisible(path)
}

test_that("Shinylive runtime cache is SHA-versioned, scoped, and resilient", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("configure-shinylive-cache.R", env)
  sha <- strrep("a", 40L)
  other_sha <- strrep("c", 40L)
  fixture <- file.path(tempdir(), paste0("openspecy-cache-", Sys.getpid()))
  rotated <- paste0(fixture, "-rotated")
  on.exit(unlink(c(fixture, rotated), recursive = TRUE), add = TRUE)
  write_shinylive_cache_fixture(fixture, sha)

  expect_no_error(env$configure_shinylive_runtime_cache(fixture, sha))
  worker_path <- file.path(fixture, "shinylive-sw.js")
  worker <- rawToChar(readBin(
    worker_path, what = "raw", n = file.info(worker_path)$size
  ))
  expect_true(grepl("OPENSPECY_RUNTIME_CACHE_V1", worker, fixed = TRUE))
  expect_true(grepl(
    paste0('const openspecyPackageSha = "', sha, '";'),
    worker, fixed = TRUE
  ))
  expect_true(all(vapply(c(
    'relativePath === "app.json"',
    'relativePath === "pinned-wasm-library.json"',
    'relativePath.startsWith("shinylive/")',
    "await cache.match(request)",
    "addCoiHeaders(await fetch(request))",
    "networkResponse.ok && networkResponse.status === 200",
    "key.startsWith(openspecyCachePrefix)",
    "key !== openspecyCacheName",
    "CacheStorage read failures must not prevent a network load",
    "Quota and CacheStorage write failures are deliberately inert"
  ), grepl, logical(1), x = worker, fixed = TRUE)))
  expect_lt(
    regexpr("await cache.match(request)", worker, fixed = TRUE)[[1L]],
    regexpr("addCoiHeaders(await fetch(request))", worker,
            fixed = TRUE)[[1L]]
  )
  expect_false(grepl("key.indexOf(version + cacheName)", worker,
                     fixed = TRUE))
  expect_false(grepl("if (useCaching)", worker, fixed = TRUE))
  loader <- readLines(
    file.path(fixture, "shinylive", "load-shinylive-sw.js"), warn = FALSE
  )
  expect_true(any(grepl("OPENSPECY_RUNTIME_UPDATE_V1", loader,
                        fixed = TRUE)))
  expect_true(any(grepl(
    paste0('const openspecyWorkerSha = "', sha, '";'),
    loader, fixed = TRUE
  )))
  expect_true(any(grepl(
    '"controllerchange", openspecyReloadForWorker, { once: true }',
    loader, fixed = TRUE
  )))
  expect_true(any(grepl("openspecyReloadForWorker();", loader,
                        fixed = TRUE)))
  expect_error(
    env$configure_shinylive_runtime_cache(fixture, sha),
    "already runtime-cache configured"
  )

  write_shinylive_cache_fixture(rotated, other_sha)
  expect_no_error(env$configure_shinylive_runtime_cache(rotated, other_sha))
  rotated_worker <- rawToChar(readBin(
    file.path(rotated, "shinylive-sw.js"), what = "raw",
    n = file.info(file.path(rotated, "shinylive-sw.js"))$size
  ))
  expect_false(identical(worker, rotated_worker))
  expect_true(grepl(other_sha, rotated_worker, fixed = TRUE))
  expect_false(grepl(sha, rotated_worker, fixed = TRUE))

  node <- Sys.which("node")
  if (.Platform$OS.type == "windows" && !nzchar(node)) {
    node <- Sys.which("node.exe")
  }
  if (nzchar(node)) {
    harness <- test_path("..", "..", "tools", "wasm",
                         "test-shinylive-cache.js")
    syntax <- system2(node, c("--check", shQuote(worker_path)),
                      stdout = TRUE, stderr = TRUE)
    expect_null(attr(syntax, "status"), info = paste(syntax, collapse = "\n"))
    loader_syntax <- system2(
      node,
      c("--check", shQuote(file.path(
        fixture, "shinylive", "load-shinylive-sw.js"
      ))),
      stdout = TRUE, stderr = TRUE
    )
    expect_null(attr(loader_syntax, "status"),
                info = paste(loader_syntax, collapse = "\n"))
    behavior <- system2(
      node, c(shQuote(harness), shQuote(worker_path), sha),
      stdout = TRUE, stderr = TRUE
    )
    expect_null(attr(behavior, "status"),
                info = paste(behavior, collapse = "\n"))
    expect_true(any(grepl("runtime cache fixture passed", behavior,
                          fixed = TRUE)))
  }
})

test_that("Shinylive cache configurator rejects drift and mismatched pins", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("configure-shinylive-cache.R", env)
  sha <- strrep("d", 40L)
  base <- file.path(tempdir(), paste0("openspecy-cache-drift-", Sys.getpid()))
  on.exit(unlink(base, recursive = TRUE), add = TRUE)

  pin_mismatch <- file.path(base, "pin")
  write_shinylive_cache_fixture(pin_mismatch, strrep("e", 40L))
  expect_error(
    env$configure_shinylive_runtime_cache(pin_mismatch, sha),
    "manifest commit does not match"
  )

  loader_drift <- file.path(base, "loader")
  write_shinylive_cache_fixture(loader_drift, sha,
                                controlled_reload = FALSE)
  expect_error(
    env$configure_shinylive_runtime_cache(loader_drift, sha),
    "first-load service-worker control marker"
  )

  worker_drift <- sub(
    'var version = "v10";', 'var version = "v11";',
    shinylive_cache_worker_fixture(), fixed = TRUE
  )
  worker_path <- file.path(base, "worker")
  write_shinylive_cache_fixture(worker_path, sha, worker = worker_drift)
  expect_error(
    env$configure_shinylive_runtime_cache(worker_path, sha),
    "cache configuration header changed"
  )
})

test_that("Shinylive wasm package roots include app runtime packages", {
  roots <- read_wasm_manifest_lines(wasm_manifest_path("app-package-roots.txt"))
  hosted_packages <- .openspecy_app_packages(hosted = TRUE)

  expect_true("local::." %in% roots)
  expect_true(all(hosted_packages %in% roots))
  expect_true(all(c(
    "shiny", "plotly", "data.table", "jsonlite", "OpenSpecy"
  ) %in% c(roots, "OpenSpecy")))
  expect_true(all(c("curl", "scales") %in% hosted_packages))
  expect_true("shinyFiles" %in% .openspecy_app_packages())
  expect_false("shinyFiles" %in% hosted_packages)
  expect_false("shinyFiles" %in% roots)

  app_sources <- unlist(lapply(
    c("global.R", "ui.R", "server.R"),
    function(file) readLines(shiny_app_source_path(file), warn = FALSE)
  ), use.names = FALSE)
  expect_false(any(grepl("shinyFiles::", app_sources, fixed = TRUE)))
})

test_that("Fill Peaks has no compiled baseline runtime dependency", {
  description_path <- test_path("..", "..", "DESCRIPTION")
  source_path <- test_path("..", "..", "R", "subtr_baseline.R")
  if (!file.exists(description_path) || !file.exists(source_path)) {
    skip("Repository source files are not in the package tarball")
  }

  description <- read.dcf(description_path)[1, ]
  hard_fields <- intersect(c("Depends", "Imports", "LinkingTo"),
                           names(description))
  hard_dependencies <- paste(description[hard_fields], collapse = ",") |>
    strsplit(",", fixed = TRUE) |>
    unlist(use.names = FALSE) |>
    trimws() |>
    sub("\\s*\\(.*$", "", x = _)
  roots <- read_wasm_manifest_lines(wasm_manifest_path(
    "app-package-roots.txt"
  ))
  source <- readLines(source_path, warn = FALSE)

  expect_false("baseline" %in% hard_dependencies)
  expect_false("baseline" %in% roots)
  expect_false(any(grepl("baseline::", source, fixed = TRUE)))
})

test_that("Shinylive wasm library allow-list is intentionally small", {
  library_types <- read_wasm_manifest_lines(wasm_manifest_path("library-types.txt"))

  expect_setequal(
    library_types,
    c("medoid_derivative", "medoid_nobaseline",
      "model_derivative", "model_nobaseline")
  )
  expect_false(any(c("derivative", "nobaseline", "raw") %in% library_types))
})

test_that("staged-library smoke selects the Raman medoid partition", {
  smoke_path <- test_path("..", "..", "tools", "wasm",
                          "smoke-staged-libraries.R")
  if (!file.exists(smoke_path)) {
    skip("Repository-only wasm deployment tools are not in the package tarball")
  }
  source <- paste(readLines(smoke_path, warn = FALSE), collapse = "\n")

  expect_match(source, '"raman" %in% names\\(lib\\)', fixed = FALSE)
  expect_match(source, 'lib\\[\\["raman"\\]\\]', fixed = FALSE)
  expect_match(source, "OpenSpecy::is_OpenSpecy", fixed = TRUE)
})

test_that("pinned wasm artifacts must match their exact package commit", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("check-wasm-artifact.R", env)

  tmp <- file.path(tempdir(), "OpenSpecy-testthat-wasm-artifact")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  metadata <- file.path(tmp, "metadata")
  image <- file.path(tmp, "image")
  dir.create(metadata, recursive = TRUE, showWarnings = FALSE)
  dir.create(image, recursive = TRUE, showWarnings = FALSE)
  writeBin(charToRaw("library image"), file.path(image, "library.data.gz"))
  writeBin(charToRaw("library metadata"),
           file.path(image, "library.js.metadata"))

  desc <- read.dcf(test_path("..", "..", "DESCRIPTION"))[1, ]
  sha <- strrep("a", 40)
  artifact <- list(
    package = list(name = unname(desc[["Package"]]),
                   version = unname(desc[["Version"]]), commit = sha),
    wasm_build = list(artifact = paste0("openspecy-wasm-", sha))
  )
  resolved <- list(
    package = artifact$package,
    packages = list(list(Package = unname(desc[["Package"]]),
                         Version = unname(desc[["Version"]]))),
    image = lapply(c("library.data.gz", "library.js.metadata"), function(name) {
      path <- file.path(image, name)
      list(name = name, size = unname(file.info(path)$size),
           md5 = unname(tools::md5sum(path)))
    })
  )
  original_images <- resolved$image
  artifact_path <- file.path(metadata, "wasm-app-manifest.json")
  resolved_path <- file.path(metadata, "resolved-wasm-packages.json")
  jsonlite::write_json(artifact, artifact_path, auto_unbox = TRUE)
  jsonlite::write_json(resolved, resolved_path, auto_unbox = TRUE)

  expect_no_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION")
  ))
  artifact$package$commit <- strrep("b", 40)
  jsonlite::write_json(artifact, artifact_path, auto_unbox = TRUE)
  expect_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION")
  ), "artifact package commit mismatch")
  artifact$package$commit <- sha
  jsonlite::write_json(artifact, artifact_path, auto_unbox = TRUE)
  writeBin(charToRaw("library Image"), file.path(image, "library.data.gz"))
  expect_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION")
  ), "MD5 mismatch")

  writeBin(charToRaw("library image"), file.path(image, "library.data.gz"))
  resolved$image <- original_images[1L]
  jsonlite::write_json(resolved, resolved_path, auto_unbox = TRUE)
  expect_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION")
  ), "exactly library.data.gz")

  resolved$image <- original_images
  resolved$image[[1L]]$size <- resolved$image[[1L]]$size + 1
  jsonlite::write_json(resolved, resolved_path, auto_unbox = TRUE)
  expect_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION")
  ), "size mismatch")

  resolved$image <- original_images
  jsonlite::write_json(resolved, resolved_path, auto_unbox = TRUE)
  verified_path <- file.path(tmp, "verified-wasm-packages.json")
  jsonlite::write_json(resolved, verified_path, auto_unbox = TRUE)
  expect_no_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION"), verified_path
  ))
  verified <- resolved
  verified$packages[[1L]]$Version <- "0.0.0"
  jsonlite::write_json(verified, verified_path, auto_unbox = TRUE)
  expect_error(env$check_wasm_artifact(
    tmp, sha, test_path("..", "..", "DESCRIPTION"), verified_path
  ), "does not match")
})

test_that("action-built wasm library image is bundled with an exact pin", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("bundle-wasm-library.R", env)

  tmp <- file.path(tempdir(), "OpenSpecy-testthat-wasm-bundle")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  repo_dir <- file.path(tmp, "repo")
  image_dir <- file.path(tmp, "image")
  site_dir <- file.path(tmp, "site")
  contrib <- file.path(repo_dir, "bin", "emscripten", "contrib", "4.6")
  dir.create(contrib, recursive = TRUE, showWarnings = FALSE)
  dir.create(image_dir, recursive = TRUE, showWarnings = FALSE)

  roots <- read_wasm_manifest_lines(wasm_manifest_path("app-package-roots.txt"))
  desc <- read.dcf(test_path("..", "..", "DESCRIPTION"))[1, ]
  roots[roots == "local::."] <- desc[["Package"]]
  versions <- rep("1.0.0", length(roots))
  versions[roots == desc[["Package"]]] <- desc[["Version"]]
  imports <- rep(NA_character_, length(roots))
  imports[roots == "dplyr"] <- "webr"
  write.dcf(data.frame(Package = roots, Version = versions,
                       Imports = imports),
            file.path(contrib, "PACKAGES"))

  writeBin(as.raw(seq_len(32)), file.path(image_dir, "library.data.gz"))
  jsonlite::write_json(
    list(files = data.frame(filename = paste0("/", roots, "/DESCRIPTION")),
         gzip = TRUE),
    file.path(image_dir, "library.js.metadata"),
    auto_unbox = TRUE
  )

  pin <- paste(rep("a", 40), collapse = "")
  env$bundle_wasm_library(
    image_dir, repo_dir, site_dir, pin,
    description_file = test_path("..", "..", "DESCRIPTION"),
    package_roots_file = wasm_manifest_path("app-package-roots.txt")
  )

  metadata <- readRDS(file.path(site_dir, "shinylive", "webr", "packages",
                                "metadata.rds"))
  expect_length(metadata, 1)
  expect_identical(metadata[[1]]$version, unname(desc[["Version"]]))
  expect_match(metadata[[1]]$ref, pin, fixed = TRUE)
  expect_identical(metadata[[1]]$type, "library")
  expect_true(all(file.exists(file.path(
    site_dir, "shinylive", "webr", "packages", metadata[[1]]$name,
    vapply(metadata[[1]]$assets, `[[`, character(1), "filename")
  ))))

  manifest <- jsonlite::fromJSON(file.path(site_dir,
                                            "pinned-wasm-library.json"))
  expect_identical(manifest$package$version, unname(desc[["Version"]]))
  expect_identical(manifest$package$commit, pin)
})

test_that("wasm package resolver includes the transitive hard closure", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("resolve-wasm-package-roots.R", env)

  tmp <- file.path(tempdir(), "OpenSpecy-testthat-wasm-resolver")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  dir.create(tmp, recursive = TRUE, showWarnings = FALSE)
  roots_file <- file.path(tmp, "roots.txt")
  description_file <- file.path(tmp, "DESCRIPTION")
  writeLines(c("local::.", "Alpha"), roots_file)
  write.dcf(
    data.frame(Package = "LocalPackage", Version = "1.0.0",
               Imports = "Alpha, Matrix, methods"),
    description_file
  )
  available <- rbind(
    Alpha = c(Package = "Alpha", Version = "1.0.0", Depends = NA,
              Imports = "Beta", LinkingTo = NA),
    Beta = c(Package = "Beta", Version = "1.0.0", Depends = NA,
             Imports = "Gamma", LinkingTo = NA),
    Gamma = c(Package = "Gamma", Version = "1.0.0", Depends = NA,
              Imports = NA, LinkingTo = NA),
    Matrix = c(Package = "Matrix", Version = "1.7.4", Depends = "R",
               Imports = "methods", LinkingTo = NA)
  )

  resolved <- env$resolve_wasm_package_roots(
    roots_file,
    description_file,
    available = available,
    platform_packages = "methods"
  )
  expect_identical(resolved,
                   c("local::.", "Alpha", "Beta", "Gamma", "Matrix"))
})

test_that("wasm library bundling rejects an incomplete hard closure", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("bundle-wasm-library.R", env)

  tmp <- file.path(tempdir(), "OpenSpecy-testthat-wasm-incomplete")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  repo_dir <- file.path(tmp, "repo")
  image_dir <- file.path(tmp, "image")
  contrib <- file.path(repo_dir, "bin", "emscripten", "contrib", "4.6")
  dir.create(contrib, recursive = TRUE, showWarnings = FALSE)
  dir.create(image_dir, recursive = TRUE, showWarnings = FALSE)

  roots <- read_wasm_manifest_lines(wasm_manifest_path("app-package-roots.txt"))
  desc <- read.dcf(test_path("..", "..", "DESCRIPTION"))[1, ]
  roots[roots == "local::."] <- desc[["Package"]]
  packages <- data.frame(Package = roots, Version = "1.0.0",
                         Imports = NA_character_)
  packages$Version[packages$Package == desc[["Package"]]] <- desc[["Version"]]
  packages$Imports[packages$Package == "dplyr"] <- "Matrix"
  write.dcf(packages, file.path(contrib, "PACKAGES"))
  writeBin(as.raw(seq_len(8)), file.path(image_dir, "library.data.gz"))
  jsonlite::write_json(
    list(files = data.frame(filename = paste0("/", roots, "/DESCRIPTION"))),
    file.path(image_dir, "library.js.metadata"),
    auto_unbox = TRUE
  )

  expect_error(
    env$bundle_wasm_library(
      image_dir, repo_dir, file.path(tmp, "site"), strrep("b", 40),
      description_file = test_path("..", "..", "DESCRIPTION"),
      package_roots_file = wasm_manifest_path("app-package-roots.txt")
    ),
    "Matrix"
  )
})

test_that("assembled Pages site separates landing, pkgdown, and app routes", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("check-pages-site.R", env)

  tmp <- file.path(tempdir(), "OpenSpecy-testthat-pages-site")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  dir.create(file.path(tmp, "app"), recursive = TRUE, showWarnings = FALSE)
  dir.create(file.path(tmp, "pkgdown"), showWarnings = FALSE)
  dir.create(file.path(tmp, "assets"), showWarnings = FALSE)
  root <- c(
    '<title>OpenSpecy | Raman and FTIR</title>',
    '<meta name="description" content="OpenSpecy spectral analysis">',
    '<meta property="og:title" content="OpenSpecy">',
    '<link rel="canonical" href="https://example.test/OpenSpecy/">',
    '<script type="application/ld+json">{}</script>',
    '<main><a href="pkgdown/">Docs</a>',
    '<div data-openspecy-embed>',
    '<iframe id="openspecy-app-frame" src="app/"></iframe>',
    '</div></main>'
  )
  writeLines(root, file.path(tmp, "index.html"))
  writeLines("runExportedApp({});", file.path(tmp, "app", "index.html"))
  writeLines("<!-- Generated by pkgdown -->",
             file.path(tmp, "pkgdown", "index.html"))
  writeLines("body {}", file.path(tmp, "assets", "site.css"))
  writeLines("void 0;", file.path(tmp, "assets", "site.js"))
  writeLines(c("User-agent: *", "Sitemap: https://example.test/sitemap.xml"),
             file.path(tmp, "robots.txt"))
  writeLines(c(
    "<urlset>",
    "<loc>https://example.test/app/</loc>",
    "<loc>https://example.test/pkgdown/</loc>",
    "</urlset>"
  ), file.path(tmp, "sitemap.xml"))

  expect_no_error(env$check_pages_site(tmp, max_bytes = 1024^2))
  writeLines(sub(
    '<iframe id="openspecy-app-frame"',
    '<div class="sourceCode"><iframe id="openspecy-app-frame"',
    root, fixed = TRUE
  ), file.path(tmp, "index.html"))
  expect_error(env$check_pages_site(tmp, max_bytes = 1024^2),
               "rendered as a source-code block")
  writeLines(root, file.path(tmp, "index.html"))

  writeLines('<body data-openspecy-app-placeholder></body>',
             file.path(tmp, "app", "index.html"))
  expect_error(env$check_pages_site(tmp, max_bytes = 1024^2),
               "rejects the shell-only")
  expect_no_error(env$check_pages_site(
    tmp, max_bytes = 1024^2, shell_only = TRUE
  ))
  writeLines("runExportedApp({});", file.path(tmp, "app", "index.html"))
  expect_error(env$check_pages_site(
    tmp, max_bytes = 1024^2, shell_only = TRUE
  ), "requires the explicit")

  dir.create(file.path(tmp, "wasm"))
  expect_error(env$check_pages_site(tmp, max_bytes = 1024^2),
               "must not contain a wasm repository")
})

test_that("Pages shell staging preserves generated sibling routes", {
  env <- new.env(parent = globalenv())
  source_wasm_tool("stage-pages-shell.R", env)

  repo_root <- normalizePath(test_path("..", ".."), winslash = "/")
  tmp <- file.path(tempdir(), "OpenSpecy-testthat-pages-shell")
  on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
  dir.create(file.path(tmp, "app"), recursive = TRUE, showWarnings = FALSE)
  dir.create(file.path(tmp, "pkgdown"), showWarnings = FALSE)
  writeLines("app sentinel", file.path(tmp, "app", "sentinel.txt"))
  writeLines("pkgdown sentinel", file.path(tmp, "pkgdown", "sentinel.txt"))

  expect_no_error(env$stage_pages_shell(tmp, repo_root = repo_root))
  expect_no_error(env$stage_pages_shell(tmp, repo_root = repo_root))
  expect_true(all(file.exists(file.path(tmp, c(
    "index.html", "robots.txt", "sitemap.xml", ".nojekyll",
    "assets/site.css", "assets/site.js", "assets/openspecy-logo.png",
    "assets/favicon.png", "assets/favicon.ico",
    "articles/sop.html", "reference/index.html", "authors.html"
  )))))
  expect_identical(readLines(file.path(tmp, "app", "sentinel.txt")),
                   "app sentinel")
  expect_identical(readLines(file.path(tmp, "pkgdown", "sentinel.txt")),
                   "pkgdown sentinel")
  redirect <- readLines(file.path(tmp, "articles", "sop.html"), warn = FALSE)
  expect_true(any(grepl("../pkgdown/articles/sop.html", redirect,
                         fixed = TRUE)))
  expect_false(any(grepl('url="/pkgdown/', redirect, fixed = TRUE)))
})

test_that("only one workflow publishes the combined native Pages site", {
  workflow_dir <- test_path("..", "..", ".github", "workflows")
  if (!dir.exists(workflow_dir)) {
    skip("Repository-only workflow files are not in the package tarball")
  }

  workflow_files <- list.files(workflow_dir, pattern = "\\.ya?ml$",
                               full.names = TRUE)
  workflow_text <- unlist(lapply(workflow_files, readLines, warn = FALSE))
  shinylive <- readLines(file.path(workflow_dir, "deploy-shinylive.yml"),
                         warn = FALSE)
  wasm <- readLines(file.path(workflow_dir, "deploy-cran-repo.yml"),
                    warn = FALSE)

  expect_false(any(grepl("github-pages-deploy-action", workflow_text,
                         fixed = TRUE)))
  expect_equal(sum(grepl("actions/deploy-pages@v4", workflow_text,
                         fixed = TRUE)), 1L)
  expect_true(any(grepl("actions/configure-pages@v5", shinylive,
                        fixed = TRUE)))
  expect_true(any(grepl("actions/upload-pages-artifact@v4", shinylive,
                        fixed = TRUE)))
  expect_true(any(grepl('dest_dir = "_site/pkgdown"', shinylive,
                         fixed = TRUE)))
  expect_true(any(grepl("tools/wasm/stage-pages-shell.R", shinylive,
                         fixed = TRUE)))
  expect_true(any(grepl("_site/app", shinylive, fixed = TRUE)))
  expect_true(any(grepl("_site/pkgdown", shinylive, fixed = TRUE)))
  expect_false(any(grepl("_site/openspecy", shinylive, fixed = TRUE)))
  expect_true(any(grepl('repository: ${{ github.repository }}', shinylive,
                        fixed = TRUE)))
  expect_false(any(grepl("wincowgerDEV/OpenSpecy-package", shinylive,
                         fixed = TRUE)))
  expect_true(any(grepl(
    "SHINYLIVE_SMOKE_URL=http://127.0.0.1:8080/ ",
    shinylive, fixed = TRUE
  )))
  expect_true(any(grepl('pkgdown_url="${public_url}pkgdown/"', shinylive,
                         fixed = TRUE)))
  expect_false(any(grepl("_site/wasm", c(shinylive, wasm), fixed = TRUE)))
  expect_true(any(grepl("path: _wasm/pinned", wasm, fixed = TRUE)))
  expect_equal(sum(grepl("pak-version: repo", shinylive, fixed = TRUE)), 1L)
  expect_equal(sum(grepl("pak-version: repo", wasm, fixed = TRUE)), 1L)
  expect_equal(sum(grepl("any::pkgdown", shinylive, fixed = TRUE)), 1L)
  expect_true(any(grepl("tools/wasm/build-wasm-repo.ps1", wasm,
                         fixed = TRUE)))
  expect_true(any(grepl("uses: actions/cache@v5", wasm, fixed = TRUE)))
  expect_true(any(grepl("path: _wasm/rwasm-dependency-cache", wasm,
                        fixed = TRUE)))
  expect_true(any(grepl("wasm-deps-v1-", wasm, fixed = TRUE)))
  expect_gte(sum(grepl("wasm-deps-v1-${{ runner.os }}-", wasm,
                       fixed = TRUE)), 2L)
  expect_true(any(grepl("test-dependency-cache.ps1", wasm,
                         fixed = TRUE)))
  expect_true(any(grepl("tools/wasm/test-workspace-path.ps1", wasm,
                         fixed = TRUE)))
  expect_false(any(grepl("r-wasm/actions/build-rwasm@v3", wasm,
                         fixed = TRUE)))
  expect_gte(sum(grepl("check-wasm-artifact.R", shinylive,
                       fixed = TRUE)), 2L)
  expect_true(any(grepl("check-wasm-repo.R", shinylive, fixed = TRUE)))

  driver_path <- test_path("..", "..", "tools", "wasm", "rwasm-build",
                           "Dockerfile")
  expect_true(file.exists(driver_path))
  driver <- readLines(driver_path, warn = FALSE)
  expect_true(any(grepl(
    "ghcr.io/r-wasm/webr@sha256:2bd309d7a4ea1daed82b6fdb8e325b0de715fcd8592c5b6f3b3b88366e70cb76",
    driver, fixed = TRUE
  )))
  expect_true(any(grepl(
    "rm -f /etc/apt/sources.list.d/rig.list", driver, fixed = TRUE
  )))
  expect_true(any(grepl("for attempt in 1 2 3", driver, fixed = TRUE)))
  expect_true(all(vapply(
    c("libhdf5-dev", "libjpeg-dev", "libpng-dev", "pandoc"),
    function(package) any(grepl(package, driver, fixed = TRUE)),
    logical(1)
  )))
})

test_that("hosted preflight is exact and the full pre-push gate is unskippable", {
  preflight_path <- test_path("..", "..", "tools", "wasm",
                              "test-shinylive-action.ps1")
  prepush_path <- test_path("..", "..", "tools", "wasm",
                             "test-shinylive-prepush.ps1")
  build_path <- test_path("..", "..", "tools", "wasm",
                           "build-wasm-repo.ps1")
  docker_path <- test_path("..", "..", "tools", "wasm",
                            "docker-preflight.ps1")
  docker_test_path <- test_path("..", "..", "tools", "wasm",
                                 "test-docker-engine.ps1")
  shell_path <- test_path("..", "..", "tools", "wasm",
                           "test-pages-shell.ps1")
  if (!all(file.exists(c(
    preflight_path, prepush_path, build_path, docker_path,
    docker_test_path, shell_path
  )))) {
    skip("Repository-only wasm preflight scripts are not in the package tarball")
  }
  preflight <- readLines(preflight_path, warn = FALSE)
  prepush <- readLines(prepush_path, warn = FALSE)
  build <- readLines(build_path, warn = FALSE)
  docker <- readLines(docker_path, warn = FALSE)
  docker_test <- readLines(docker_test_path, warn = FALSE)
  shell <- readLines(shell_path, warn = FALSE)

  expect_true(any(grepl("git rev-parse HEAD", preflight, fixed = TRUE)))
  expect_true(any(grepl("options(pkgdown.internet=FALSE)", preflight,
                         fixed = TRUE)))
  expect_true(any(grepl("check-wasm-artifact.R", preflight, fixed = TRUE)))
  expect_true(any(grepl("check-wasm-repo.R", preflight, fixed = TRUE)))
  expect_true(any(grepl("configure-shinylive-cache.R", preflight,
                        fixed = TRUE)))
  preflight_bundle <- grep('"tools/wasm/bundle-wasm-library.R"', preflight,
                           fixed = TRUE)
  preflight_cache <- grep('"tools/wasm/configure-shinylive-cache.R"',
                          preflight, fixed = TRUE)
  preflight_check <- grep('"tools/wasm/check-shinylive-export.R"', preflight,
                          fixed = TRUE)
  expect_length(preflight_bundle, 1L)
  expect_length(preflight_cache, 1L)
  expect_length(preflight_check, 1L)
  expect_lt(preflight_bundle, preflight_cache)
  expect_lt(preflight_cache, preflight_check)
  expect_true(any(grepl('$env:R_LIBS_USER = $tools', preflight,
                        fixed = TRUE)))
  expect_true(any(grepl(
    '$env:R_PKG_CACHE_DIR = Join-Path $tools "pkg-cache"',
    preflight, fixed = TRUE
  )))
  expect_true(any(grepl('"--no-lock"', preflight, fixed = TRUE)))
  expect_true(any(grepl("Remove-Item -LiteralPath $tools", preflight,
                        fixed = TRUE)))
  expect_true(any(grepl('Assert-Equal $playwrightVersion "1.61.1"',
                        preflight, fixed = TRUE)))
  expect_true(any(grepl("require('@playwright/test'); require('http-server')",
                        preflight, fixed = TRUE)))
  expect_true(any(grepl("ConnectAsync", preflight, fixed = TRUE)))
  expect_true(any(grepl("RedirectStandardError", preflight, fixed = TRUE)))
  expect_false(any(grepl("defaultLibrary", preflight, fixed = TRUE)))
  expect_true(any(grepl("git status --porcelain", prepush, fixed = TRUE)))
  expect_true(any(grepl("docker-preflight.ps1", prepush, fixed = TRUE)))
  expect_true(any(grepl("Assert-OpenSpecyDockerEngine", prepush,
                         fixed = TRUE)))
  expect_true(any(grepl("build-wasm-repo.ps1", prepush, fixed = TRUE)))
  expect_true(any(grepl("test-shinylive-action.ps1", prepush,
                        fixed = TRUE)))
  expect_true(any(grepl("-StageLibraries", prepush, fixed = TRUE)))
  expect_true(any(grepl("-Bootstrap", prepush, fixed = TRUE)))
  expect_true(any(grepl("FULL SHINYLIVE PRE-PUSH REHEARSAL PASSED", prepush,
                        fixed = TRUE)))
  expect_true(any(grepl("tools/wasm/rwasm-build", build, fixed = TRUE)))
  expect_true(any(grepl("docker-preflight.ps1", build, fixed = TRUE)))
  expect_true(any(grepl("Assert-OpenSpecyDockerEngine", build,
                         fixed = TRUE)))
  expect_true(any(grepl("MaxAttempts = 3", docker, fixed = TRUE)))
  expect_true(any(grepl("attempt -lt $MaxAttempts", docker,
                         fixed = TRUE)))
  expect_true(any(grepl("Start-Sleep -Seconds $RetryDelaySeconds", docker,
                         fixed = TRUE)))
  expect_true(any(grepl("check-wasm-artifact.R", build, fixed = TRUE)))
  expect_true(any(grepl("DependencyCacheDir", build, fixed = TRUE)))
  expect_true(any(grepl("DependencyCacheSeed", build, fixed = TRUE)))
  expect_true(any(grepl("dependency-cache.ps1", build, fixed = TRUE)))
  expect_true(any(grepl("Get-OpenSpecyCompatibleWasmCacheSeed", build,
                         fixed = TRUE)))
  expect_true(any(grepl("evict-wasm-cache-package.R", build, fixed = TRUE)))
  expect_true(any(grepl("Restoring wasm dependency cache", build,
                        fixed = TRUE)))
  expect_true(any(grepl("git rev-parse HEAD", build, fixed = TRUE)))
  expect_true(any(grepl("git status --porcelain", build, fixed = TRUE)))
  expect_true(any(grepl('"archive", "--format=zip"', build,
                         fixed = TRUE)))
  expect_true(any(grepl(':/github/workspace:ro', build, fixed = TRUE)))
  expect_true(any(grepl(':/github/output', build, fixed = TRUE)))
  expect_true(any(grepl('"/github/output/image"', build, fixed = TRUE)))
  expect_true(any(grepl('Remove-Item -LiteralPath $sourceSnapshot', build,
                         fixed = TRUE)))
  expect_false(any(grepl('$repoRoot + ":/github/workspace"', build,
                          fixed = TRUE)))
  expect_true(any(grepl("workspace-path.ps1", c(preflight, build),
                         fixed = TRUE)))
  path_consumers <- list(preflight, build, shell)
  expect_true(all(vapply(
    path_consumers,
    function(x) any(grepl("Get-OpenSpecyRepoRelativePath", x, fixed = TRUE)),
    logical(1)
  )))
  expect_false(any(grepl("MakeRelativeUri", unlist(path_consumers),
                         fixed = TRUE)))
  expect_true(any(grepl("{{.Server.Version}}", docker, fixed = TRUE)))
  expect_true(any(grepl("{{.OSType}}", docker, fixed = TRUE)))
  expect_true(any(grepl('osType -ne "linux"', docker, fixed = TRUE)))
  expect_true(any(grepl("wsl --update", docker, fixed = TRUE)))
  expect_true(any(grepl("wsl --shutdown", docker, fixed = TRUE)))
  expect_true(any(grepl(
    "No wasm build or cached/native fallback was used", docker, fixed = TRUE
  )))
  expect_true(any(grepl("environment-only check", docker_test,
                         fixed = TRUE)))
  expect_false(any(grepl("git status", docker_test, fixed = TRUE)))
  expect_true(any(grepl("--shell-only", shell, fixed = TRUE)))
  expect_true(any(grepl("action_equivalent = $false", shell, fixed = TRUE)))
  expect_true(any(grepl('wasm_build = "not_run"', shell, fixed = TRUE)))
  expect_true(any(grepl('hosted_webr_browser = "not_run"', shell,
                         fixed = TRUE)))
})

test_that("wasm workspace path resolver handles absolute paths portably", {
  script <- test_path("..", "..", "tools", "wasm",
                      "test-workspace-path.ps1")
  if (!file.exists(script)) {
    skip("Repository-only workspace path contract is not in the package tarball")
  }
  powershell <- Sys.which("powershell.exe")
  if (!nzchar(powershell)) powershell <- Sys.which("pwsh")
  skip_if(!nzchar(powershell), "PowerShell is unavailable")
  output <- system2(
    powershell,
    c("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", shQuote(script)),
    stdout = TRUE, stderr = TRUE
  )
  expect_null(attr(output, "status"), info = paste(output, collapse = "\n"))
  expect_true(any(grepl("emits portable repository-relative paths", output,
                         fixed = TRUE)))
})

test_that("pkgdown installs the checked-out package before rendering", {
  workflow_path <- test_path("..", "..", ".github", "workflows",
                             "pkgdown.yaml")
  if (!file.exists(workflow_path)) {
    skip("Repository-only workflow files are not in the package tarball")
  }

  workflow <- readLines(workflow_path, warn = FALSE)
  install_step <- grep("Install current package source", workflow,
                       fixed = TRUE)
  build_step <- grep("- name: Build site", workflow, fixed = TRUE)

  expect_length(install_step, 1L)
  expect_length(build_step, 1L)
  expect_lt(install_step, build_step)
  expect_true(any(grepl(
    'install.packages(".", repos = NULL, type = "source")',
    workflow, fixed = TRUE
  )))
  expect_true(any(grepl(
    'exists(".fill_peaks_smooth", namespace, inherits = FALSE)',
    workflow, fixed = TRUE
  )))
})

test_that("hosted deployment exports the exact current bundled app", {
  workflow_path <- test_path("..", "..", ".github", "workflows",
                             "deploy-shinylive.yml")
  smoke_path <- test_path("..", "..", "tools", "wasm",
                          "shinylive-smoke.spec.js")
  if (!file.exists(workflow_path) || !file.exists(smoke_path)) {
    skip("Repository-only hosted deployment files are not in the package tarball")
  }

  workflow <- readLines(workflow_path, warn = FALSE)
  smoke <- readLines(smoke_path, warn = FALSE)
  install_step <- grep("Install current package and app source", workflow,
                       fixed = TRUE)
  build_step <- grep("- name: Build pkgdown site", workflow, fixed = TRUE)
  bundle_step <- grep("- name: Bundle pinned wasm package library", workflow,
                      fixed = TRUE)
  cache_step <- grep("- name: Configure SHA-versioned Shinylive runtime cache",
                     workflow, fixed = TRUE)
  check_step <- grep("- name: Check Shinylive export", workflow, fixed = TRUE)

  expect_length(install_step, 1L)
  expect_length(build_step, 1L)
  expect_length(bundle_step, 1L)
  expect_length(cache_step, 1L)
  expect_length(check_step, 1L)
  expect_lt(install_step, build_step)
  expect_lt(bundle_step, cache_step)
  expect_lt(cache_step, check_step)
  expect_true(any(grepl(
    'install.packages(".", repos = NULL, type = "source")',
    workflow, fixed = TRUE
  )))
  expect_true(any(grepl("tools::md5sum", workflow, fixed = TRUE)))
  expect_true(any(grepl("options(pkgdown.internet = FALSE)", workflow,
                         fixed = TRUE)))
  expect_true(any(grepl('dest_dir = "_site/pkgdown"', workflow,
                         fixed = TRUE)))
  expect_true(any(grepl("stage-pages-shell.R", workflow, fixed = TRUE)))
  expect_true(any(grepl("check-pages-site.R _site", workflow,
                         fixed = TRUE)))
  expect_true(any(grepl('--app-dir "inst/shiny"', workflow, fixed = TRUE)))
  expect_true(any(grepl("for attempt in 1 2", workflow, fixed = TRUE)))
  expect_true(any(grepl("Upload hosted smoke diagnostics", workflow,
                        fixed = TRUE)))
  expect_true(any(grepl("pinned-wasm-library.json", workflow, fixed = TRUE)))
  expect_true(any(grepl("tools/wasm/configure-shinylive-cache.R", workflow,
                        fixed = TRUE)))
  expect_true(any(grepl('worker_url="${app_url}shinylive-sw.js"', workflow,
                        fixed = TRUE)))
  expect_true(any(grepl("OPENSPECY_RUNTIME_CACHE_V1", workflow,
                        fixed = TRUE)))
  expect_true(any(grepl(
    'grep -q "${PACKAGE_SHA}" <<< "$pin_body"', workflow, fixed = TRUE
  )))
  expect_true(any(grepl(
    "test.setTimeout(largeUploads.length ? 2400000 : 1800000)", smoke,
    fixed = TRUE
  )))
  expect_true(any(grepl("timeout: 600000", smoke, fixed = TRUE)))
  expect_true(any(grepl("toBeChecked()", smoke, fixed = TRUE)))
  expect_true(any(grepl('toHaveValue("Top Matches")', smoke,
                        fixed = TRUE)))
  expect_false(any(grepl("downloadSelectize", smoke, fixed = TRUE)))
  expect_true(any(grepl("verifyNativeDownload", smoke, fixed = TRUE)))
  expect_true(any(grepl("savedFirstBytesHex", smoke, fixed = TRUE)))
  expect_true(any(grepl("contentType", smoke, fixed = TRUE)))
  expect_true(any(grepl("disposition", smoke, fixed = TRUE)))
  expect_true(any(grepl("requestUrl", smoke, fixed = TRUE)))
  expect_true(any(grepl("clickResponse", smoke, fixed = TRUE)))
  expect_false(any(grepl('if (!clickResponse.length)', smoke,
                         fixed = TRUE)))
  expect_true(any(grepl("probeDownloadEndpoint", smoke, fixed = TRUE)))
  expect_true(any(grepl("native browser download", smoke, fixed = TRUE)))
  expect_true(any(grepl("shinylive-download-", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('fetch(element.href, { cache: "no-store" })', smoke,
                        fixed = TRUE)))
  expect_false(any(grepl('downloadFailure === "canceled"', smoke,
                         fixed = TRUE)))
  expect_false(any(grepl("handler checks remain authoritative", smoke,
                         ignore.case = TRUE)))
  expect_true(all(vapply(
    c(
      "Test Data", "Test Map", "User Metadata", "Processed Spectra",
      "Top Matches", "Thresholded Particles"
    ),
    function(label) any(grepl(paste0('label: "', label, '"'), smoke,
                              fixed = TRUE)),
    logical(1)
  )))
  expect_true(any(grepl('download.path()', smoke, fixed = TRUE)))
  expect_true(any(grepl('fs.readFileSync(downloadPath)', smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('locator("#heatmap_frame")', smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("tinyEnviFiles", smoke, fixed = TRUE)))
  expect_true(any(grepl("#openspecy_workerfs_files", smoke, fixed = TRUE)))
  expect_true(any(grepl('locator("#settings_csv")', smoke, fixed = TRUE)))
  expect_true(any(grepl(
    "input[type='file']:not(#openspecy_workerfs_files):not(#settings_csv)",
    smoke, fixed = TRUE
  )))
  expect_false(any(grepl(
    'locator("input[type=\'file\']").toHaveCount(1)', smoke, fixed = TRUE
  )))
  expect_true(any(grepl("mountedInput.setInputFiles(mapUploadPath)", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("OPENSPECY_SMOKE_LARGE_UPLOAD", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("OPENSPECY_SMOKE_LARGE_UPLOADS", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('candidate.type === "heatmap"', smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('label: "Test Map Top Matches"', smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('"CA small UF.dat"', smoke, fixed = TRUE)))
  expect_true(any(grepl("probeEndpoint: false", smoke, fixed = TRUE)))
  top_n_locator <- grep(
    'const mapTopNInput = appFrame.locator("#top_n_input")',
    smoke, fixed = TRUE
  )
  top_n_tab <- grep('name: "Identification", exact: true', smoke,
                    fixed = TRUE)
  top_n_fill <- grep('await mapTopNInput.fill("1")', smoke, fixed = TRUE)
  top_n_blur <- grep('await mapTopNInput.press("Tab")', smoke, fixed = TRUE)
  top_n_grouping <- grep(
    'appFrame.locator("#top_n_per_organization"), false', smoke, fixed = TRUE
  )
  detailed_metadata <- grep(
    'appFrame.locator("#simple_metadata"), false', smoke, fixed = TRUE
  )
  selected_col_id <- grep(
    'locator("#eventmetadata table")).toContainText("0_1"',
    smoke, fixed = TRUE
  )
  map_run <- grep("await runButton.click();", smoke, fixed = TRUE)
  top_n_records <- grep("csvRecordCount(mapTopMatches.content", smoke,
                        fixed = TRUE)
  expect_length(top_n_locator, 1L)
  expect_length(top_n_tab, 1L)
  expect_length(top_n_fill, 1L)
  expect_length(top_n_blur, 1L)
  expect_length(top_n_grouping, 1L)
  expect_length(detailed_metadata, 1L)
  expect_length(selected_col_id, 1L)
  expect_length(top_n_records, 1L)
  expect_lt(top_n_tab, top_n_locator)
  expect_lt(top_n_locator, top_n_fill)
  expect_lt(top_n_fill, top_n_blur)
  expect_lt(top_n_blur, top_n_grouping)
  expect_true(any(map_run > top_n_grouping & map_run < top_n_records))
  expect_lt(detailed_metadata, selected_col_id)
  expect_lt(top_n_blur, top_n_records)
  expect_true(any(grepl(".toBe(209)", smoke, fixed = TRUE)))
  expect_true(any(grepl("character === '\"'", smoke, fixed = TRUE)))
  expect_true(any(grepl("cannot allocate vector", smoke, fixed = TRUE)))
  expect_true(any(grepl("\\$table\\.DataTable is not a function", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("trace index .* out of bounds", smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("stableFor: 1500", smoke, fixed = TRUE)))
  expect_true(sum(grepl(
    'waitForStableDownloadGeneration(', smoke, fixed = TRUE
  )) >= 2L)
  expect_true(any(grepl(
    'filenamePattern: /^Thresholded-Particles-.*\\.zip$/i',
    smoke, fixed = TRUE
  )))
  expect_true(any(grepl('contentTypePattern: /^application\\/zip/i',
                        smoke, fixed = TRUE)))
  expect_true(any(grepl('expectedPrefix: Buffer.from("PK", "ascii")',
                        smoke, fixed = TRUE)))
  particle_outputs <- grep(
    'const selectedParticleOutputs = new Set(["details", "summary"])',
    smoke, fixed = TRUE
  )
  particle_output_names <- grep(
    'const particleOutputNames = ["details", "processed", "summary", "figures"]',
    smoke, fixed = TRUE
  )
  thresholded_label <- grep('label: "Thresholded Particles"', smoke,
                            fixed = TRUE)
  expect_length(particle_outputs, 1L)
  expect_length(particle_output_names, 1L)
  expect_length(thresholded_label, 1L)
  expect_lt(particle_outputs, thresholded_label)
  expect_lt(particle_output_names, thresholded_label)
  thresholded_call <- smoke[grep('label: "Thresholded Particles"', smoke,
                                 fixed = TRUE)[1L] + 0:12]
  expect_true(any(grepl("probeEndpoint: false", thresholded_call,
                        fixed = TRUE)))
  expect_true(any(grepl("eventTimeout: 300000", thresholded_call,
                        fixed = TRUE)))
  expect_true(any(grepl('toContain("particle_summary.csv")', smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('require("./zip-entries")', smoke, fixed = TRUE)))
  expect_false(any(grepl('require("child_process")', smoke,
                         fixed = TRUE)))
  expect_false(any(grepl('"tar"', smoke, fixed = TRUE)))

  zip_entries_path <- test_path("..", "..", "tools", "wasm",
                                "zip-entries.js")
  zip_entries <- readLines(zip_entries_path, warn = FALSE)
  expect_true(any(grepl("function parseZipEntryNames", zip_entries,
                        fixed = TRUE)))
  expect_true(any(grepl("ZIP central-directory self-test passed", zip_entries,
                        fixed = TRUE)))
  expect_true(any(grepl('new URL("pkgdown/", url)', smoke,
                         fixed = TRUE)))
  expect_true(any(grepl('a[href^="pkgdown/"]', smoke, fixed = TRUE)))

  local_smoke_path <- test_path("..", "..", "tools",
                                "shiny-local-smoke.spec.js")
  local_smoke <- readLines(local_smoke_path, warn = FALSE)
  expect_true(any(grepl("CA_tiny_map.zip", local_smoke, fixed = TRUE)))
  expect_true(any(grepl("Thresholded Particles", local_smoke,
                        fixed = TRUE)))
  expect_true(any(grepl("fetchDownload", local_smoke, fixed = TRUE)))
  expect_true(any(grepl("topMatches.elapsed", local_smoke,
                        fixed = TRUE)))
  expect_true(any(grepl('"CA small UF.dat"', local_smoke, fixed = TRUE)))
  expect_true(any(grepl("toHaveLength(209)", local_smoke,
                        fixed = TRUE)))

  quality_gate_path <- test_path(
    "..", "..", ".agents", "skills", "openspecy-run-quality-gates",
    "scripts", "quality-gates.ps1"
  )
  quality_gate <- readLines(quality_gate_path, warn = FALSE)
  expect_true(any(grepl("[switch]$HostedAppStatic", quality_gate,
                        fixed = TRUE)))
  expect_true(any(grepl("filter = 'shinylive_wasm'", quality_gate,
                        fixed = TRUE)))
  expect_true(any(grepl('Get-ChildItem "tools/wasm" -File -Filter *.js',
                        quality_gate, fixed = TRUE)))
  expect_true(any(grepl('node.Source "tools/wasm/zip-entries.js"', quality_gate,
                        fixed = TRUE)))
  expect_true(any(grepl("Language.Parser]::ParseFile", quality_gate,
                        fixed = TRUE)))
})

test_that("static landing and Shiny app provide the embed handshake", {
  app_path <- run_app(test_mode = TRUE)
  ui_source <- readLines(file.path(app_path, "ui.R"), warn = FALSE)
  bridge_path <- file.path(app_path, "www", "parent-frame.js")

  expect_true(file.exists(bridge_path))
  expect_true(any(grepl("parent-frame.js", ui_source, fixed = TRUE)))
  bridge <- readLines(bridge_path, warn = FALSE)
  expect_true(any(grepl("shiny:idle.openspecyParent", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("shiny:connected.openspecyParent", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("notifyIfConnectedAndIdle", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("readyProbeTimer", bridge, fixed = TRUE)))
  expect_true(any(grepl("probeReadyState", bridge, fixed = TRUE)))
  expect_equal(sum(grepl("probeReadyState();", bridge, fixed = TRUE)), 2L)
  expect_true(any(grepl("data-openspecy-parent-bridge", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("window.jQuery || window.$", bridge, fixed = TRUE)))
  expect_true(any(grepl("data-openspecy-parent-bindings", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("window.top.postMessage", bridge, fixed = TRUE)))
  expect_true(any(grepl("openspecy:ready", bridge, fixed = TRUE)))
  expect_true(any(grepl("shiny:busy.openspecyBusy", bridge, fixed = TRUE)))
  expect_true(any(grepl("openspecy-analysis-phase", bridge, fixed = TRUE)))
  expect_false(any(grepl("openspecy-analysis-complete", bridge,
                         fixed = TRUE)))
  expect_true(any(grepl("openspecy-upload-materialized", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("data-openspecy-materialized-files", bridge,
                        fixed = TRUE)))
  expect_true(any(grepl("Array.isArray(state.files)", bridge,
                        fixed = TRUE)))
  expect_false(any(grepl("shiny:value.openspecyBusy", bridge, fixed = TRUE)))
  expect_true(any(grepl("openspecy-busy-visible", bridge, fixed = TRUE)))
  expect_true(any(grepl("openspecy:workerfs", bridge, fixed = TRUE)))
  expect_true(any(grepl('Shiny.setInputValue(', bridge, fixed = TRUE)))
  expect_true(any(grepl('action: "upload"', bridge, fixed = TRUE)))
  expect_true(any(grepl("data-openspecy-upload-status", bridge,
                        fixed = TRUE)))
  expect_false(any(grepl("nativeWasmLimit", bridge, fixed = TRUE)))

  readme_path <- test_path("..", "..", "README.md")
  site_dir <- test_path("..", "..", "site")
  homepage_path <- file.path(site_dir, "index.html")
  script_path <- file.path(site_dir, "assets", "site.js")
  css_path <- file.path(site_dir, "assets", "site.css")
  robots_path <- file.path(site_dir, "robots.txt")
  sitemap_path <- file.path(site_dir, "sitemap.xml")
  pkgdown_config <- test_path("..", "..", "_pkgdown.yml")
  required <- c(
    readme_path, homepage_path, script_path, css_path, robots_path,
    sitemap_path, pkgdown_config
  )
  if (!all(file.exists(required))) {
    skip("Repository-only hosted-site sources are not in the package tarball")
  }

  readme <- readLines(readme_path, warn = FALSE)
  homepage <- readLines(homepage_path, warn = FALSE)
  homepage_text <- paste(homepage, collapse = "\n")
  script <- readLines(script_path, warn = FALSE)
  css <- readLines(css_path, warn = FALSE)
  robots <- readLines(robots_path, warn = FALSE)
  sitemap <- readLines(sitemap_path, warn = FALSE)
  pkgdown <- readLines(pkgdown_config, warn = FALSE)
  expect_false(any(grepl("data-openspecy-embed", readme, fixed = TRUE)))
  expect_false(any(grepl("openspecy-app-frame", readme, fixed = TRUE)))
  expect_false(file.exists(test_path("..", "..", "pkgdown", "index.md")))
  expect_false(file.exists(test_path("..", "..", "pkgdown", "extra.js")))
  expect_false(file.exists(test_path("..", "..", "pkgdown", "extra.css")))
  expect_true(any(grepl("data-openspecy-embed", homepage, fixed = TRUE)))
  expect_true(any(grepl('src="app/"', homepage, fixed = TRUE)))
  expect_true(any(grepl('href="pkgdown/"', homepage, fixed = TRUE)))
  expect_false(any(grepl('src="/app/"|href="/pkgdown/"', homepage)))
  expect_equal(sum(grepl("<h1[ >]", homepage)), 1L)
  expect_true(any(grepl(
    "OpenSpecy | Free Raman &amp; FTIR Spectral Analysis App",
    homepage, fixed = TRUE
  )))
  expect_true(any(grepl('<meta name="description"', homepage,
                         fixed = TRUE)))
  expect_true(any(grepl('<link rel="canonical"', homepage,
                         fixed = TRUE)))
  expect_true(any(grepl('property="og:title"', homepage, fixed = TRUE)))
  expect_true(any(grepl('name="twitter:card"', homepage, fixed = TRUE)))
  expect_true(any(grepl('type="application/ld+json"', homepage,
                         fixed = TRUE)))
  expect_true(all(vapply(
    c('id="start"', 'id="web-app"', 'id="learn"', 'id="science"',
      'id="partners"', 'id="contact"'),
    function(section) any(grepl(section, homepage, fixed = TRUE)),
    logical(1)
  )))
  expect_true(any(grepl("y2F4Fu6A4aA&amp;list=PLqdH8O1nalYa4a8JXQ6GbNsH3YQV_aY7g",
                        homepage, fixed = TRUE)))
  pew_acknowledgement <- paste0(
    "Support for this project was provided by the Pew-Gerstner Fellows ",
    "Program in Marine Conservation at The Pew Charitable Trusts. The views ",
    "expressed herein are those of the author(s) and do not necessarily ",
    "reflect the views of The Pew Charitable Trusts."
  )
  expect_true(grepl(pew_acknowledgement, homepage_text, fixed = TRUE))
  expect_true(grepl(
    paste0(
      "https://www.youtube-nocookie.com/embed/8zrlQeTCwkQ?autoplay=1",
      "&amp;mute=1&amp;playsinline=1&amp;rel=0"
    ), homepage_text, fixed = TRUE
  ))
  expect_true(any(grepl('class="hero-video-card"', homepage,
                         fixed = TRUE)))
  expect_false(grepl(
    '<iframe[[:space:]]+src="https://www.youtube-nocookie.com',
    homepage_text, perl = TRUE
  ))
  expect_true(any(grepl('data-video-title="OpenSpecy project video"',
                         homepage, fixed = TRUE)))
  expect_true(grepl(
    "\\.hero-video-card\\s*\\{[^}]*transform:\\s*none",
    paste(css, collapse = "\n"), perl = TRUE
  ))
  expect_false(any(grepl('class="spectrum-card"', homepage,
                          fixed = TRUE)))
  expect_true(any(grepl("Walking Softer", homepage, fixed = TRUE)))
  expect_true(all(vapply(
    c("10.1021/acs.analchem.5c00962", "10.1021/acs.analchem.1c00123",
      "mailto:wincowger@gmail.com", "youtube-nocookie.com"),
    function(marker) grepl(marker, homepage_text, fixed = TRUE),
    logical(1)
  )))
  expect_true(grepl(
    'href="mailto:wincowger@gmail.com?subject=OpenSpecy%20inquiry" target="_blank" rel="noopener noreferrer"',
    homepage_text, fixed = TRUE
  ))
  json_text <- sub(
    '(?s).*<script type="application/ld\\+json">[[:space:]]*(.*?)[[:space:]]*</script>.*',
    "\\1", homepage_text, perl = TRUE
  )
  structured <- jsonlite::fromJSON(json_text, simplifyVector = FALSE)
  expect_true(structured$`@type` %in% c(
    "WebApplication", "SoftwareApplication"
  ))
  expect_false(any(grepl("requestFullscreen", script, fixed = TRUE)))
  expect_true(any(grepl("openspecy-app-fullscreen-open", script,
                         fixed = TRUE)))
  expect_true(any(grepl("DOMContentLoaded", script, fixed = TRUE)))
  expect_true(any(grepl('querySelectorAll("[data-video-embed]")', script,
                         fixed = TRUE)))
  expect_true(any(grepl("initVideo", script, fixed = TRUE)))
  expect_true(any(grepl("container.replaceChildren(frame)", script,
                         fixed = TRUE)))
  expect_true(any(grepl("event.origin !== window.location.origin", script,
                         fixed = TRUE)))
  expect_false(any(grepl(
    'event.key !== "Escape" || !shell.classList.contains("is-fullscreen")',
    script, fixed = TRUE
  )))
  expect_true(any(grepl("app-shell.is-fullscreen", css,
                         fixed = TRUE)))
  expect_true(any(grepl("Sitemap: https://www.openanalysis.org/OpenSpecyV2/",
                         robots, fixed = TRUE)))
  expect_true(any(grepl("OpenSpecyV2/pkgdown/", sitemap,
                         fixed = TRUE)))
  expect_true(any(grepl(
    "url: https://www.openanalysis.org/OpenSpecyV2/pkgdown/",
    pkgdown, fixed = TRUE
  )))
})

test_that("bundled app has no floating wasm package installer", {
  app_path <- run_app(test_mode = TRUE)
  global_source <- readLines(file.path(app_path, "global.R"), warn = FALSE)
  server_source <- readLines(file.path(app_path, "server.R"), warn = FALSE)

  expect_false(any(grepl("webr::install", global_source, fixed = TRUE)))
  expect_false(any(grepl("install_wasm_packages", global_source,
                         fixed = TRUE)))
  expect_true(any(grepl("validate_wasm_package_version", global_source,
                        fixed = TRUE)))
  expect_false(any(grepl("curl::has_internet()", server_source,
                         fixed = TRUE)))
  expect_false(any(grepl("googletranslate|output\\$translate",
                         c(global_source, server_source))))

  prepare_path <- test_path("..", "..", "tools", "wasm",
                            "prepare-shinylive-app.R")
  if (file.exists(prepare_path)) {
    prepare_source <- readLines(prepare_path, warn = FALSE)
    expect_true(any(grepl("openspecy.shiny.wasm.artifact", prepare_source,
                          fixed = TRUE)))
    expect_false(any(grepl("openspecy.shiny.wasm.repo", prepare_source,
                           fixed = TRUE)))
    expect_true(any(grepl("patch_shinylive_workerfs", prepare_source,
                          fixed = TRUE)))
    workerfs_patch <- readLines(test_path(
      "..", "..", "tools", "wasm", "patch-shinylive-workerfs.R"
    ), warn = FALSE)
    workerfs_bridge <- readLines(test_path(
      "..", "..", "tools", "wasm", "shinylive-workerfs-bridge.js"
    ), warn = FALSE)
    expect_true(any(grepl("expected_sha256", workerfs_patch,
                          fixed = TRUE)))
    expect_true(any(grepl("rawToChar(readBin", workerfs_patch,
                          fixed = TRUE)))
    expect_true(any(grepl("OPENSPECY_WORKERFS_BRIDGE_V1", workerfs_bridge,
                          fixed = TRUE)))
    expect_true(any(grepl('fs.mount("WORKERFS"', workerfs_bridge,
                          fixed = TRUE)))
    expect_true(any(grepl("handle.webRProxy.webR.FS", workerfs_bridge,
                          fixed = TRUE)))
    expect_true(any(grepl("openspecyWorkerfsCleanup(fs)", workerfs_bridge,
                          fixed = TRUE)))
  }
})

test_that("bundled app rejects a mismatched wasm package version", {
  missing <- .openspecy_app_packages()[
    !vapply(.openspecy_app_packages(), requireNamespace, logical(1),
            quietly = TRUE)
  ]
  skip_if(length(missing), paste(
    "Missing Shiny app packages:",
    paste(missing, collapse = ", ")
  ))

  app_path <- run_app(test_mode = TRUE)
  env <- new.env(parent = globalenv())
  old_wd <- getwd()
  old_options <- options(c("openspecy.shiny.wasm",
                           "openspecy.shiny.wasm.package_version",
                           "openspecy.shiny.wasm.package_sha"))
  old_env <- Sys.getenv("OPENSPECY_SHINY_WASM", unset = NA)
  on.exit(setwd(old_wd), add = TRUE)
  on.exit(options(old_options), add = TRUE)
  on.exit({
    if (is.na(old_env)) Sys.unsetenv("OPENSPECY_SHINY_WASM") else
      Sys.setenv(OPENSPECY_SHINY_WASM = old_env)
  }, add = TRUE)

  options(
    openspecy.shiny.wasm = TRUE,
    openspecy.shiny.wasm.package_version = "0.0.0",
    openspecy.shiny.wasm.package_sha = "test-commit"
  )
  setwd(app_path)

  expect_error(
    sys.source(file.path(app_path, "global.R"), envir = env),
    "pinned build requires 0.0.0"
  )
})

test_that("bundled Shiny app exposes medoid/model only in wasm mode", {
  missing <- .openspecy_app_packages()[
    !vapply(.openspecy_app_packages(), requireNamespace, logical(1),
            quietly = TRUE)
  ]
  skip_if(length(missing), paste(
    "Missing Shiny app packages:",
    paste(missing, collapse = ", ")
  ))

  app_path <- run_app(test_mode = TRUE)
  env <- new.env(parent = globalenv())
  old_wd <- getwd()
  old_opt <- getOption("openspecy.shiny.wasm")
  old_libs <- getOption("openspecy.shiny.wasm.libraries")
  old_env <- Sys.getenv("OPENSPECY_SHINY_WASM", unset = NA)
  on.exit(setwd(old_wd), add = TRUE)
  on.exit({
    options(openspecy.shiny.wasm = old_opt)
    options(openspecy.shiny.wasm.libraries = old_libs)
    if (is.na(old_env)) {
      Sys.unsetenv("OPENSPECY_SHINY_WASM")
    } else {
      Sys.setenv(OPENSPECY_SHINY_WASM = old_env)
    }
  }, add = TRUE)

  options(openspecy.shiny.wasm = FALSE)
  Sys.unsetenv("OPENSPECY_SHINY_WASM")
  setwd(app_path)
  sys.source(file.path(app_path, "global.R"), envir = env)

  options(openspecy.shiny.wasm = FALSE)
  expect_true("full" %in% unname(env$app_library_type_choices()))

  options(openspecy.shiny.wasm = TRUE)
  expect_setequal(unname(env$app_library_type_choices()),
                  c("medoid", "model"))
  expect_error(env$app_validate_library_type("derivative"),
               "only includes medoid and model")
  expect_error(env$app_validate_library_type("medoid_derivative"), NA)
})

Try the OpenSpecy package in your browser

Any scripts or data that you put into this service are public.

OpenSpecy documentation built on Oct. 6, 2026, 1:07 a.m.