poka.sh

Docs

Test a viewer

how-to

A viewer is a function from an *Ast to a string, so most of its testing is plain Rust unit tests that run natively, without wasm and without the host. This page lists the checks, from fastest to slowest.

1. Unit tests for render

Keep rendering in a function that takes the decoded *Ast and returns the HTML, and put tests beside it in main.rs:

#[cfg(test)]
mod tests {
    use super::*;

    fn ast(content: &str) -> TextAst {
        TextAst { filename: "notes.txt".into(), content: content.into(), ..Default::default() }
    }

    #[test]
    fn empty_content_says_so() {
        let html = render(&ast(""));
        assert!(html.contains("(empty)") && !html.contains("<table"), "{html}");
    }
}

Run one viewer's tests:

cargo test -p poka-text-viewer

Build the *Ast with struct update syntax (..Default::default()) so a field added to the proto later does not break the test. Put the HTML in the assertion message, as above, so a failure shows what was rendered.

Cases worth a test in every viewer:

  • The empty input. An *Ast with no rows, no nodes or no content renders a clear "empty" message, not a broken table.
  • The truncated input. When the proto says the content was capped, the page says how much is shown and how much exists.
  • A hostile string in every field (below).
  • One card. test_support::assert_one_card(&html, filename, file_size) checks that the preview has exactly one shared card with each host placeholder once, and that the viewer did not print the file's name or size itself.
  • Large input. The caps are memory_mb and max_time_ms in plugin.json. If the viewer builds output proportional to the input, test that a large *Ast stays inside them.

2. The hostile string test

poka_viewer_common::test_support exports HOSTILE, a string that breaks out of a double-quoted attribute, opens a tag and a script, and closes a comment, and assert_inert, which checks the string appears in the HTML only as escaped text.

use poka_viewer_common::test_support::{assert_inert, HOSTILE};

#[test]
fn every_string_from_the_file_is_escaped() {
    let a = TextAst {
        filename: HOSTILE.into(),
        source_format: HOSTILE.into(),
        content: format!("{HOSTILE}\nsecond"),
        ..Default::default()
    };
    assert_inert(&render(&a));
}

Fill every string field the viewer shows. assert_inert fails when the string is missing from the output, so a field the test forgot or the viewer does not render shows up as a failure rather than a silent pass.

3. Garbage on stdin

A viewer must not panic on bytes that are not its *Ast. The decode step returns an error and the viewer prints error_html; check that path by calling the decode on a few byte strings (empty, one byte, random) and confirming that you get the error HTML back.

4. The whole workspace

cargo test --workspace
cargo build --release --workspace --target wasm32-wasip1

CI runs both. The first runs every viewer's tests natively, the second builds every viewer to wasm. A viewer that compiles natively can still fail to build for wasm32-wasip1 (a dependency that uses threads or sockets, for example), so run the wasm build before you push.

One thing to know when adding a viewer: viewers/poka-viewer-common/src/theme.rs has a test, every_viewer_uses_the_shared_theme, that counts the *-viewer directories with assert_eq!(seen, ...) and checks that each uses THEME_CSS. A new viewer changes that count, so update it in the same PR.

5. The manifest and the repo checks

python3 -m pip install -r build/scripts/requirements.txt   # pinned jsonschema, once
python3 build/scripts/validate-viewer-plugin.py            # schema and source policy
python3 build/scripts/validate-viewer-ids.py --parsers ../poka-parsers/parsers
python3 -m unittest discover -s build/scripts/tests        # the validators' own tests
python3 build/scripts/viewer-inventory.py --check          # the coverage table is current
python3 build/scripts/test-manifests.py                    # cross-checks the manifests
python3 build/scripts/check-docs.py --check                # after touching docs/

validate-viewer-ids.py --parsers needs the poka-parsers sibling. It confirms that every viewer id a parser names exists here. CI cannot run that part, because poka-parsers is private, so it only runs locally.

These are the commands in README.md, "Testing", which also lists the licence and third-party notices checks. Say in your PR what you ran and what you did not.

6. See it in the app

A viewer is only reached through a parser, so the end-to-end check is a real file through a parser that names your viewer. Build and install both (python3 build/scripts/install-extensions.py here, and the parsers' installer in poka-parsers) and open a file in the host. The workspace-level steps are in the poka-parsers tutorial "Write a parser".