Skip to content

Instantly share code, notes, and snippets.

@davidmaxwaterman
Last active January 25, 2023 21:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidmaxwaterman/9cea7a06a4648738910be9e891ac48cb to your computer and use it in GitHub Desktop.
Save davidmaxwaterman/9cea7a06a4648738910be9e891ac48cb to your computer and use it in GitHub Desktop.
Notes on writing end-to-end tests with gun's panic server/client.

The docs on Gun's Panic are written from the point of view of someone testing Gun itself, rather than an app that uses Gun. As such it isn't very helpful in how to get started, and the tests aren't all that applicable. So, I figured I'd share my findings after building Panic tests for my app.

refs:

NB, in my case, I was using Gun in an extension, so my 'app' is quite well separated from the panic server. Most people will be testing Gun in a regular web page, so it might be simpler.

  1. in the project you want to test, install dev dependency gun-server: npm install --save-dev panic-server
  2. make a test directory, eg test/e2e
  3. copy the index.html and demo.js from https://github.com/gundb/panic-server#-basic-test-example to test/e2e, perhaps renaming demo.js appropriately, to run-tests.js
  4. to run the tests:
  5. start a gun relay peer: (cd node_modules/gun && npm run start)&
  6. start the panic server, eg: node test/e2e/run-testsjs &
  7. start a web server to serve the index.html file (may need to npm install --save-dev @web/dev-server: npx wds -r test/e2e/ &
  8. build and run your app
  9. after a test run, you need to clean up, which I do by:
  10. kill the gun relay peer: pkill -f examples/http.js
  11. kill the panic server: pkill -f test/e2e/run.js
  12. kill wds pkill -f bin/wds

As mentioned above, many people will be testing a web page/app. In that case, you will likely not need the test/e2e/index.html test, and instead include the script tags that are in the index.html into your app's index.html.

The demo.js/run-test.js file is written in old node, and I rewrote it into modern node, allowing me to use module imports and top-level async/await. The file needed renaming to '.mjs', but otherwise it worked wonders.

Note that it is a 'TODO' to adapt it to use mocha/chai in the style of a regular test suite more along the lines of the test included in the gun repo.

import server from "panic-server/src/server.js";
import clients from "panic-server/src/clients.js";

// Start the server on port 8080.
server().listen(8080);

// Create dynamic lists of
// browsers and servers.
const servers = clients.filter('Node.js');
const browsers = clients.excluding(servers);

// Wait for the browser to connect.
await browsers.atLeast(2);

await browsers.each((browser, id) => {
  // set up each browser with common code
  browser.run((context) => {
    const button = document.querySelector("button");
    button.addEventListener("click", () => {}); // dummy to be able to select this code easily in debugger

    // add something to look at
    let element = document.createElement('h1');
    element.innerHTML = 'SYNC TESTING PAGE';
    document.body.appendChild(element);
    element = document.createElement('h2');
    element.innerHTML = `I AM CLIENT: ${context.props.id}`;
    document.body.appendChild(element);
  }, {id});
});

const browser0 = browsers.pluck(1);
const browser1 = browsers.excluding(browser0).pluck(1);

// set up first browser
await browser0.run(async () => {
  console.log("browser0:creating user");
  // create user and wait for response
  {
    const data = await new Promise((resolve) => {
      // set up listener
      const listener = (event) => {
        const {source, data} = event;
        console.log("MAXMAXMAX:", source, data);
        const meantForMe = source === window && data?.to === "panic";
        if (meantForMe) {
          resolve(data);
          window.removeEventListener("message", listener);
        }
      };

      window.addEventListener("message", listener);

      // create user
      window.postMessage({
        to: "contentScript",
        method: "createUser",
        username: "max",
        password: "waterman",
      });
    });

    // check response - throw an error if it isn't what is expected
    const {message} = data;
    const {type} = message;
    if (type == "setCreateSuccess") {
      console.log("browser0: createUser success:", data);
    } else {
      throw `browser0: didn't get setCreateSuccess:${data}`;
    }
  }

});

// set up second browser
await browser1.run(async () => {
  // user should already have been created on browser[0]

  console.log("browser1:logging in");
  // log in the user
  {
    const data = await new Promise((resolve) => {
      // set up listener
      const listener = (event) => {
        const {source, data} = event;
        const meantForMe = source === window && data?.to === "panic";
        if (meantForMe) {
          resolve(data);
          window.removeEventListener("message", listener);
        }
      };

      window.addEventListener("message", listener);

      window.postMessage({
        to: "contentScript",
        method: "loginUser",
        username: "max",
        password: "waterman",
      });
    });

    // check response - throw an error if it isn't what is expected
    const {message} = data;
    const {type} = message;
    if (type === "setLoginSuccess") {
      console.log("browser1: loginUser success:", data);
    } else {
      throw `browser1: didn't get setLoginSuccess:${data}`;
    }
  }

});

// now user is created and both browsers are logged in

// set up listener in browser1 and create a bookmark in browser0,
// and confirm browser1's listener gets the correct event
{
  const browser1Promise = browser1.run(async () => {
    const data = await new Promise((resolve) => {
      // set up listener
      const listener = (event) => {
        const {source, data} = event;
        console.log("browser1: got event when testing create():", data);
        const meantForMe = source === window && data?.to === "panic";
        if (meantForMe) {
          resolve(data);
          window.removeEventListener("message", listener);
        }
      };

      window.addEventListener("message", listener);
    })

    return data;
  });

  await browser0.run(() => {
    console.log("browser0: creating bookmark");

    window.postMessage({
      to: "contentScript",
      method: "create",
      params: {
        parentId: "unfiled_____",
        title: "Examples",
        url: "http://www.example.com/",
      }
    });
  });

  console.log("MAXMAXMAX: bookmark created in browser0");

  const data = await browser1Promise;
  console.log("MAXMAXMAX: got event in browser1 when creating bookmark in browser0:", JSON.stringify(data, null, 2));
  if (data[0].method === "onCreated") {
    const bookmark = data[0].params[1];
    if (bookmark.parentId === "unfiled_____"
      && bookmark.title === "Examples"
      && bookmark.url === "http://www.example.com/"
    ) {
      console.log("MAXMAXMAX: bookmark created SUCCESS");
    } else {
      console.log("MAXMAXMAX: bookmark create FAILED");
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment