Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Improve testing suite
Sebastian Martinez committed 4 years ago
commit 286cb1cd21b458523aa165e1da02b187e9ea7cb6
parent 6f2f8c454fa72fc2b9f65531e9e95ad6c0752470
45 files changed +2559 -491
deleted .github/workflows/check-components.yml
@@ -1,14 +0,0 @@
-
name: check-components 
-
on: [push, pull_request]
-

-
jobs:
-
  check:
-
    runs-on: ubuntu-latest
-
    steps:
-
      - uses: actions/checkout@v2
-
      - uses: actions/setup-node@v1
-
        with:
-
          node-version: '14'
-
      - run: npm ci
-
      - run: ./scripts/test
-
        shell: bash
added .github/workflows/check-integration-test.yml
@@ -0,0 +1,18 @@
+
name: check-integration-test
+
on: [push, pull_request]
+
jobs:
+
  cypress-run:
+
    runs-on: ubuntu-latest
+
    steps:
+
      - name: Checkout
+
        uses: actions/checkout@v2
+
      # Install NPM dependencies, cache them correctly
+
      # and run all Cypress tests
+
      - name: Cypress run
+
        uses: cypress-io/github-action@v2
+
        with:
+
          browser: chrome
+
          start: npm start
+
          wait-on: 'http://localhost:3000'
+
          command: npm run test:e2e
+

added .github/workflows/check-unit-test.yml
@@ -0,0 +1,14 @@
+
name: check-unit-test
+
on: [push, pull_request]
+

+
jobs:
+
  check:
+
    runs-on: ubuntu-latest
+
    steps:
+
      - uses: actions/checkout@v2
+
      - uses: actions/setup-node@v1
+
        with:
+
          node-version: '14'
+
      - run: npm ci
+
      - run: ./scripts/unit-test
+
        shell: bash
modified .gitignore
@@ -5,3 +5,6 @@ NOTES
# Coverage
.nyc_output
coverage
+

+
# Integration Tests
+
cypress/screenshots
added cypress/fixtures/projectBranches.json
@@ -0,0 +1,6 @@
+
{
+
  "heads": {
+
    "master": "cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
    "main": "56e4e029c294b08546386e1fb706b772c7433c49"
+
  }
+
}
added cypress/fixtures/projectCommit.json
@@ -0,0 +1,115 @@
+
{
+
  "header": {
+
    "sha1": "cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
    "author": {
+
      "name": "dabit3",
+
      "email": "dabit3@gmail.com"
+
    },
+
    "summary": "initial commit",
+
    "description": "this is the first commit of many",
+
    "committer": {
+
      "name": "dabit3",
+
      "email": "dabit3@gmail.com"
+
    },
+
    "committerTime": 1646236685
+
  },
+
  "stats": {
+
    "additions": 0,
+
    "deletions": 0
+
  },
+
  "diff": {
+
    "created": [
+
      "README.md"
+
    ],
+
    "deleted": [
+
      "LICENSE"
+
    ],
+
    "moved": [],
+
    "copied": [],
+
    "modified": [
+
      {
+
        "path": "foo.bar",
+
        "diff": {
+
          "type": "plain",
+
          "hunks": [
+
            {
+
              "header": "@@ -13,7 +13,11 @@ If executed, this proposal will:\n",
+
              "lines": [
+
                {
+
                  "type": "context",
+
                  "line": "\n",
+
                  "lineNumOld": 13,
+
                  "lineNumNew": 13
+
                },
+
                {
+
                  "type": "context",
+
                  "line": "After execution, the Timelock holds all Uniswap LP tokens for the RAD/USDC pair.\n",
+
                  "lineNumOld": 14,
+
                  "lineNumNew": 14
+
                },
+
                {
+
                  "type": "context",
+
                  "line": "\n",
+
                  "lineNumOld": 15,
+
                  "lineNumNew": 15
+
                },
+
                {
+
                  "type": "deletion",
+
                  "line": "To prevent front-running, the RAD/USDC balances are set through the Uniswap router *proxy* contract, deployed at `0xB76FC4EbE4fC0CC34AF440Ad79565A68Bfcb095e`. Only the Radicle Foundation can set these balances, via the `setLiquidity` function. This contract function must be called as close as possible to the execution of this proposal, to provide liquidity at the correct market price.\n",
+
                  "lineNum": 16
+
                },
+
                {
+
                  "type": "addition",
+
                  "line": "To prevent front-running, the RAD/USDC balances are set through the Uniswap\n",
+
                  "lineNum": 16
+
                },
+
                {
+
                  "type": "addition",
+
                  "line": "router *proxy* contract, deployed at `0xB76FC4EbE4fC0CC34AF440Ad79565A68Bfcb095e`.\n",
+
                  "lineNum": 17
+
                },
+
                {
+
                  "type": "addition",
+
                  "line": "Only the Radicle Foundation can set these balances, via the `setLiquidity`\n",
+
                  "lineNum": 18
+
                },
+
                {
+
                  "type": "addition",
+
                  "line": "function. This contract function must be called as close as possible to the\n",
+
                  "lineNum": 19
+
                },
+
                {
+
                  "type": "addition",
+
                  "line": "execution of this proposal, to provide liquidity at the correct market price.\n",
+
                  "lineNum": 20
+
                },
+
                {
+
                  "type": "context",
+
                  "line": "\n",
+
                  "lineNumOld": 17,
+
                  "lineNumNew": 21
+
                },
+
                {
+
                  "type": "context",
+
                  "line": "## Notes\n",
+
                  "lineNumOld": 18,
+
                  "lineNumNew": 22
+
                },
+
                {
+
                  "type": "context",
+
                  "line": "\n",
+
                  "lineNumOld": 19,
+
                  "lineNumNew": 23
+
                }
+
              ]
+
            }
+
          ]
+
        }
+
      }
+
    ]
+
  },
+
  "branches": [
+
    "main",
+
    "master"
+
  ]
+
}
added cypress/fixtures/projectCommits.json
@@ -0,0 +1,23 @@
+
{
+
  "headers": [
+
    {
+
      "sha1": "cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      "author": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "summary": "initial commit",
+
      "description": "",
+
      "committer": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "committerTime": 1646236685
+
    }
+
  ],
+
  "stats": {
+
    "commits": 1,
+
    "branches": 1,
+
    "contributors": 1
+
  }
+
}
added cypress/fixtures/projectHome.json
@@ -0,0 +1,23 @@
+
{
+
  "message": "Welcome!",
+
  "service": "radicle-http-api",
+
  "version": "0.2.0",
+
  "path": "/",
+
  "links": [
+
    {
+
      "href": "/v1/projects",
+
      "rel": "projects",
+
      "type": "GET"
+
    },
+
    {
+
      "href": "/v1/peer",
+
      "rel": "peer",
+
      "type": "GET"
+
    },
+
    {
+
      "href": "/v1/delegates/:urn/projects",
+
      "rel": "projects",
+
      "type": "GET"
+
    }
+
  ]
+
}
added cypress/fixtures/projectInfo.json
@@ -0,0 +1,16 @@
+
{
+
  "urn": "rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy",
+
  "name": "bright-forest-protocol",
+
  "description": "bfc-sc",
+
  "defaultBranch": "main",
+
  "delegates": [
+
    {
+
      "type": "indirect",
+
      "urn": "rad:git:hnrkqz68g6nddigodpgjmc1u8ydpzb8fqq8ro",
+
      "ids": [
+
        "hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"
+
      ]
+
    }
+
  ],
+
  "head": "56e4e029c294b08546386e1fb706b772c7433c49"
+
}
added cypress/fixtures/projectList.json
@@ -0,0 +1,16 @@
+
[{
+
  "urn": "rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy",
+
  "name": "mocked-seed-protocol",
+
  "description": "bfc-sc",
+
  "defaultBranch": "main",
+
  "delegates": [
+
    {
+
      "type": "indirect",
+
      "urn": "rad:git:hnrkqz68g6nddigodpgjmc1u8ydpzb8fqq8ro",
+
      "ids": [
+
        "hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"
+
      ]
+
    }
+
  ],
+
  "head": "56e4e029c294b08546386e1fb706b772c7433c49"
+
}]
added cypress/fixtures/projectPeer.json
@@ -0,0 +1 @@
+
{ "id": "hyy841u4phudmr8s5rg1jjwd1ct7x7438wmjwtsm464y8uyxyhyi6c" }
added cypress/fixtures/projectReadme.json
@@ -0,0 +1,24 @@
+
{
+
  "binary": false,
+
  "html": false,
+
  "content": "# Basic Sample Hardhat Project\n\nThis project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts.\n\nTry running some of the following tasks:\n\n```shell\nnpx hardhat accounts\nnpx hardhat compile\nnpx hardhat clean\nnpx hardhat test\nnpx hardhat node\nnode scripts/sample-script.js\nnpx hardhat help\n```\n",
+
  "info": {
+
    "name": "README.md",
+
    "objectType": "BLOB",
+
    "lastCommit": {
+
      "sha1": "cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      "author": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "summary": "initial commit",
+
      "description": "",
+
      "committer": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "committerTime": 1646236685
+
    }
+
  },
+
  "path": "README.md"
+
}
added cypress/fixtures/projectRemotes.json
@@ -0,0 +1,7 @@
+
[
+
  {
+
    "id": "hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke",
+
    "name": "dabit3",
+
    "delegate": true
+
  }
+
]
added cypress/fixtures/projectTree56e4e02.json
@@ -0,0 +1,100 @@
+
{
+
  "path": "",
+
  "entries": [
+
    {
+
      "path": "contracts",
+
      "info": {
+
        "name": "contracts",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "scripts",
+
      "info": {
+
        "name": "scripts",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "test",
+
      "info": {
+
        "name": "test",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": ".gitignore",
+
      "info": {
+
        "name": ".gitignore",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "README.md",
+
      "info": {
+
        "name": "README.md",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "hardhat.config.js",
+
      "info": {
+
        "name": "hardhat.config.js",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "package-lock.json",
+
      "info": {
+
        "name": "package-lock.json",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "package.json",
+
      "info": {
+
        "name": "package.json",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "yarn.lock",
+
      "info": {
+
        "name": "yarn.lock",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    }
+
  ],
+
  "info": {
+
    "name": "",
+
    "objectType": "TREE",
+
    "lastCommit": {
+
      "sha1": "56e4e029c294b08546386e1fb706b772c7433c49",
+
      "author": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "summary": "emit event",
+
      "description": "",
+
      "committer": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "committerTime": 1646237176
+
    }
+
  },
+
  "stats": {
+
    "commits": 3,
+
    "branches": 1,
+
    "contributors": 1
+
  }
+
}
added cypress/fixtures/projectTreecbf5df4.json
@@ -0,0 +1,100 @@
+
{
+
  "path": "",
+
  "entries": [
+
    {
+
      "path": "contracts",
+
      "info": {
+
        "name": "contracts",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "scripts",
+
      "info": {
+
        "name": "scripts",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "test",
+
      "info": {
+
        "name": "test",
+
        "objectType": "TREE",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": ".gitignore",
+
      "info": {
+
        "name": ".gitignore",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "README.md",
+
      "info": {
+
        "name": "README.md",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "hardhat.config.js",
+
      "info": {
+
        "name": "hardhat.config.js",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "package-lock.json",
+
      "info": {
+
        "name": "package-lock.json",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "package.json",
+
      "info": {
+
        "name": "package.json",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    },
+
    {
+
      "path": "yarn.lock",
+
      "info": {
+
        "name": "yarn.lock",
+
        "objectType": "BLOB",
+
        "lastCommit": null
+
      }
+
    }
+
  ],
+
  "info": {
+
    "name": "",
+
    "objectType": "TREE",
+
    "lastCommit": {
+
      "sha1": "56e4e029c294b08546386e1fb706b772c7433c49",
+
      "author": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "summary": "emit event",
+
      "description": "",
+
      "committer": {
+
        "name": "dabit3",
+
        "email": "dabit3@gmail.com"
+
      },
+
      "committerTime": 1646237176
+
    }
+
  },
+
  "stats": {
+
    "commits": 3,
+
    "branches": 1,
+
    "contributors": 1
+
  }
+
}
added cypress/integration/home.spec.ts
@@ -0,0 +1,29 @@
+
/// <reference types="cypress" />
+
import { MockExtensionProvider } from "../support";
+

+
describe("Landing page", () => {
+
  it("Loads user, orgs and seeds", () => {
+
    cy.intercept("https://willow.radicle.garden:8777/", { fixture: "projectHome.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
+
    cy.intercept("https://pine.radicle.garden:8777/", { fixture: "projectHome.json" });
+
    cy.intercept("https://pine.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
+
    cy.intercept("https://maple.radicle.garden:8777/", { fixture: "projectHome.json" });
+
    cy.intercept("https://maple.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
+
    cy.intercept("https://gateway.thegraph.com/api/1758a78ae257ad4906f9c638e4a68c19/subgraphs/id/0x2f0963e77ca6ac0c2dad1bf4147b6b40e0dd8728-0", {
+
      "data": { "safe": null }
+
    });
+
    cy.intercept("https://maple.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
+
    cy.intercept("https://pine.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects", { fixture: "projectList.json" });
+
    cy.visit("/", {
+
      onBeforeLoad(win) {
+
        win.ethereum = new MockExtensionProvider();
+
      },
+
    });
+
    cy.get("div.card-label")
+
      .first()
+
      .should("have.text", "willow.radicle.garden")
+
      .next()
+
      .should("have.text", "1 project(s)");
+
  });
+
});
added cypress/integration/project.spec.ts
@@ -0,0 +1,115 @@
+
/* eslint-disable @typescript-eslint/no-unused-vars */
+
/// <reference types="cypress" />
+
import { MockExtensionProvider } from "../support";
+

+
describe("Project View", () => {
+
  before(() => {
+
    cy.intercept("https://willow.radicle.garden:8777/", { fixture: "projectHome.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/bright-forest-protocol", { fixture: "projectInfo.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy", { fixture: "projectInfo.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes", { fixture: "projectRemotes.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/56e4e029c294b08546386e1fb706b772c7433c49", { fixture: "projectTree56e4e02.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectTreecbf5df4.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke", { fixture: "projectBranches.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/56e4e029c294b08546386e1fb706b772c7433c49", { fixture: "projectReadme.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits?parent=cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectCommits.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectReadme.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectCommit.json" });
+
  });
+

+
  it("Load project view and use peer and branch selector ", () => {
+
    cy.visit("/seeds/willow.radicle.garden/bright-forest-protocol", {
+
      onBeforeLoad(win) {
+
        win.ethereum = new MockExtensionProvider();
+
      },
+
    });
+

+
    cy.log("Check that the project view is loaded and populated with the stubbed data");
+
    cy.log("Page Header");
+
    cy.get("div.title").contains("bright-forest-protocol");
+
    cy.get("div.urn").contains("rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy");
+
    cy.get("div.description").contains("bfc-sc");
+

+
    cy.log("README");
+
    cy.get("div.column-right article div.markdown h1").should("have.text", "Basic Sample Hardhat Project");
+

+
    cy.log("Project Header");
+
    cy.get("div.stat.seed span").should("have.text", "willow.radicle.garden");
+
    cy.get("div.stat.commit-count").should("have.text", "3 commit(s)");
+
    cy.get("div.stat.contributor-count").should("have.text", "1 contributor(s)");
+
    cy.get("div.anchor span.anchor-label").contains("not anchored 🔓");
+
    cy.get("div.stat.branch").should("have.class", "not-allowed").should("have.text", "main");
+
    cy.get("div.hash.desktop").should("have.text", "56e4e02");
+

+
    cy.get("div.clone").click();
+

+
    cy.log("Peer Selection");
+
    cy.get("div.selector").click();
+
    cy.get("div.dropdown-item").contains("dabit3 hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke delegate").click();
+
    cy.location().should((location) => {
+
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree');
+
    });
+
    cy.get("span.peer-id span[title='hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke']").should("have.text", "hyndc7…j9oeke");
+
    cy.get("div.stat.peer span.peer-id").should("have.text", "dabit3");
+
    cy.get("div.stat.peer span.badge").should("have.text", "delegate");
+

+
    cy.log("Branch Selection");
+
    cy.get("div.commit div.stat.branch").click();
+
    cy.get("div.dropdown-item")
+
      .first()
+
      .contains("main")
+
      .next()
+
      .contains("master")
+
      .click();
+
    cy.location().should((location) => {
+
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree/master');
+
    });
+
    cy.get("div.stat.branch").should("have.text", "master");
+
    cy.get("div.hash.desktop").should("have.text", "cbf5df4");
+

+
    cy.log("Commit history view");
+
    cy.get("div.stat.commit-count")
+
      .should("not.have.class", "active")
+
      .click();
+
    cy.location().should((location) => {
+
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/history/master');
+
    });
+
    cy.get("div.stat.commit-count").should("have.class", "active");
+

+
    cy.log("Commit Group");
+
    cy.get("header.commit-date").should("have.text", "Wednesday, March 2, 2022");
+

+
    cy.log("Commit Trailer");
+
    cy.get("div.summary span.hash")
+
      .should("have.text", "cbf5df4")
+
      .next()
+
      .should("have.text", "initial commit");
+
    cy.get("div.commit div.right span")
+
      .first()
+
      .should("have.text", "dabit3")
+
      .next()
+
      .should("have.text", "Wed, 02 Mar 2022 15:58:05 GMT");
+
    cy.get("div.summary").click();
+

+
    cy.log("Commit Detail View");
+
    cy.location().should((location) => {
+
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a');
+
    });
+
    cy.get("header div.summary h3").should("have.text", "initial commit");
+
    cy.get("header pre.description").should("have.text", "this is the first commit of many");
+
    cy.get("header div.meta")
+
      .first()
+
      .should("have.text", "Committed by dabit3 <dabit3@gmail.com> Wed, 02 Mar 2022 15:58:05 GMT")
+
      .next()
+
      .should("have.text", "Authored by dabit3 <dabit3@gmail.com>");
+
    cy.get("div.changeset-summary").should("have.text", "1 file(s) changed\n    with\n    0 addition(s)\n    and\n    0 deletion(s)");
+
    cy.get("header.file-header:nth-child(1) div.file-data > *")
+
      .first()
+
      .should("have.text", "README.md")
+
      .next()
+
      .should("have.text", "created");
+
    cy.get("tr.diff-line td.diff-line-number").contains("16");
+
    cy.get("tr.diff-line td.diff-line-content").contains("To prevent front-running, the RAD/USDC balances are set through the Uniswap router *proxy* contract");
+
  });
+
});
modified cypress/support/index.ts
@@ -1,19 +1,49 @@
+
/* eslint-disable @typescript-eslint/no-unused-vars */
import '@cypress/code-coverage/support';
import '@testing-library/cypress/add-commands';
+
import { ethers } from 'ethers';

-
export const styles = {
-
  cssFile: "public/index.css",
-
  style: `
-
  .wrapper {
-
    height: 100vh;
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
+
declare global {
+
  interface Window {
+
    ethereum: any;
  }
-
  `,
-
  html: `
-
    <div class="wrapper">
-
      <div id="here" />
-
    </div>
-
  `
-
};
+
}
+

+
export class MockExtensionProvider extends ethers.providers.BaseProvider {
+
  isMetaMask = true;
+

+
  constructor() {
+
    super({ name: "homestead", chainId: 1 });
+
    console.log("Creating Mock provider");
+
  }
+
  async request({ method, params }: { method: string; params: any }): Promise<any> {
+
    switch (method) {
+
      case 'eth_chainId':
+
        return "1";
+
      case 'net_version':
+
        return "1";
+
      case 'eth_call':
+
        return resolveEthCall(params);
+
      case 'eth_getCode':
+
        return "0x";
+
      default:
+
        console.log("Unknown method", method);
+
        break;
+
    }
+
  }
+
}
+

+
function resolveEthCall(params: { to: string; data: string }[]): string {
+
  // Get Resolver
+
  if (params[0].to === "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e") {
+
    return "0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41";
+
  // Get ENS Attributes
+
  } else if (params[0].to === "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41" && params[0].data === "0x59d1d43c567c364804de7bbedb53f583e483f6b73513fd2f44299e281024e4719da0b332000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066176617461720000000000000000000000000000000000000000000000000000") {
+
    return "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001f68747470733a2f2f636c6f7564686561642e696f2f6176617461722e706e6700";
+
  // Get Org informations
+
  } else if (params[0].to === "0x8152237402e0f194176154c3a6ea1eb99b611482" && params[0].data === "0x8da5cb5b") {
+
    return "0x000000000000000000000000ceab094641905c209cc796fc8037dd9ecc87ca2f";
+
  } else {
+
    return "0x000000000000000000000000394b920c5d39e0ca40fca2871569b6b90d750c7c";
+
  }
+
}
modified package-lock.json
@@ -40,20 +40,22 @@
        "@cypress/vite-dev-server": "^2.2.2",
        "@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
        "@testing-library/cypress": "^8.0.2",
+
        "@testing-library/svelte": "^3.1.0",
        "@tsconfig/svelte": "^1.0.13",
        "@types/sanitize-html": "^2.6.2",
        "@typescript-eslint/eslint-plugin": "^4.26.1",
        "@typescript-eslint/parser": "^4.26.1",
-
        "cypress": "^9.3.1",
+
        "cypress": "^9.5.1",
        "eslint": "^7.28.0",
        "eslint-plugin-radicle": "^0.2.0",
        "eslint-plugin-svelte3": "^3.2.0",
-
        "radicle-svelte-unit-test": "^1.0.0",
+
        "happy-dom": "^2.50.0",
        "svelte-check": "^2.4.1",
        "typescript": "^4.2.4",
        "vite": "^2.7.13",
-
        "vite-plugin-istanbul": "^2.4.0",
-
        "vite-plugin-rewrite-all": "^0.1.2"
+
        "vite-plugin-istanbul": "^2.5.1",
+
        "vite-plugin-rewrite-all": "^0.1.2",
+
        "vitest": "^0.6.3"
      },
      "engines": {
        "node": ">=14"
@@ -1758,6 +1760,19 @@
        "node": ">=6.9.0"
      }
    },
+
    "node_modules/@babel/runtime-corejs3": {
+
      "version": "7.17.8",
+
      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz",
+
      "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "core-js-pure": "^3.20.2",
+
        "regenerator-runtime": "^0.13.4"
+
      },
+
      "engines": {
+
        "node": ">=6.9.0"
+
      }
+
    },
    "node_modules/@babel/template": {
      "version": "7.16.7",
      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
@@ -1806,28 +1821,6 @@
        "node": ">=6.9.0"
      }
    },
-
    "node_modules/@bahmutov/cy-rollup": {
-
      "version": "2.0.0",
-
      "resolved": "https://registry.npmjs.org/@bahmutov/cy-rollup/-/cy-rollup-2.0.0.tgz",
-
      "integrity": "sha512-KJlvqB3U1nvKSq7djkXcvM4WpBLP6HEWKKzChI8s/aw0pYm0gVc+sphpyz5CmGpsNE9+/1QB6yoDfTe9Q97SJw==",
-
      "dev": true,
-
      "dependencies": {
-
        "debug": "4.1.1"
-
      },
-
      "peerDependencies": {
-
        "rollup": "2"
-
      }
-
    },
-
    "node_modules/@bahmutov/cy-rollup/node_modules/debug": {
-
      "version": "4.1.1",
-
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-
      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-
      "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
-
      "dev": true,
-
      "dependencies": {
-
        "ms": "^2.1.1"
-
      }
-
    },
    "node_modules/@ceramicnetwork/3id-did-resolver": {
      "version": "1.4.16",
      "resolved": "https://registry.npmjs.org/@ceramicnetwork/3id-did-resolver/-/3id-did-resolver-1.4.16.tgz",
@@ -3013,6 +3006,22 @@
        "node": ">=8"
      }
    },
+
    "node_modules/@jest/types": {
+
      "version": "26.6.2",
+
      "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+
      "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/istanbul-lib-coverage": "^2.0.0",
+
        "@types/istanbul-reports": "^3.0.0",
+
        "@types/node": "*",
+
        "@types/yargs": "^15.0.0",
+
        "chalk": "^4.0.0"
+
      },
+
      "engines": {
+
        "node": ">= 10.14.2"
+
      }
+
    },
    "node_modules/@json-rpc-tools/provider": {
      "version": "1.7.6",
      "resolved": "https://registry.npmjs.org/@json-rpc-tools/provider/-/provider-1.7.6.tgz",
@@ -3356,6 +3365,68 @@
        "node": ">=12"
      }
    },
+
    "node_modules/@testing-library/svelte": {
+
      "version": "3.1.0",
+
      "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-3.1.0.tgz",
+
      "integrity": "sha512-xTN6v4xRLQb75GTJn2mrjSUJN4PkhpNZFjwvtdzbOTS6OvxMrkRdm6hFRGauwiFd0LPV7/SqdWbbtMAOC7a+Dg==",
+
      "dev": true,
+
      "dependencies": {
+
        "@testing-library/dom": "^7.0.3"
+
      },
+
      "engines": {
+
        "node": ">= 8"
+
      },
+
      "peerDependencies": {
+
        "svelte": "3.x"
+
      }
+
    },
+
    "node_modules/@testing-library/svelte/node_modules/@testing-library/dom": {
+
      "version": "7.31.2",
+
      "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz",
+
      "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "@babel/code-frame": "^7.10.4",
+
        "@babel/runtime": "^7.12.5",
+
        "@types/aria-query": "^4.2.0",
+
        "aria-query": "^4.2.2",
+
        "chalk": "^4.1.0",
+
        "dom-accessibility-api": "^0.5.6",
+
        "lz-string": "^1.4.4",
+
        "pretty-format": "^26.6.2"
+
      },
+
      "engines": {
+
        "node": ">=10"
+
      }
+
    },
+
    "node_modules/@testing-library/svelte/node_modules/aria-query": {
+
      "version": "4.2.2",
+
      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+
      "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+
      "dev": true,
+
      "dependencies": {
+
        "@babel/runtime": "^7.10.2",
+
        "@babel/runtime-corejs3": "^7.10.2"
+
      },
+
      "engines": {
+
        "node": ">=6.0"
+
      }
+
    },
+
    "node_modules/@testing-library/svelte/node_modules/pretty-format": {
+
      "version": "26.6.2",
+
      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
+
      "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
+
      "dev": true,
+
      "dependencies": {
+
        "@jest/types": "^26.6.2",
+
        "ansi-regex": "^5.0.0",
+
        "ansi-styles": "^4.0.0",
+
        "react-is": "^17.0.1"
+
      },
+
      "engines": {
+
        "node": ">= 10"
+
      }
+
    },
    "node_modules/@tsconfig/svelte": {
      "version": "1.0.13",
      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-1.0.13.tgz",
@@ -3376,6 +3447,63 @@
        "@types/node": "*"
      }
    },
+
    "node_modules/@types/chai": {
+
      "version": "4.3.0",
+
      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
+
      "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==",
+
      "dev": true
+
    },
+
    "node_modules/@types/chai-subset": {
+
      "version": "1.3.3",
+
      "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz",
+
      "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/chai": "*"
+
      }
+
    },
+
    "node_modules/@types/concat-stream": {
+
      "version": "1.6.1",
+
      "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz",
+
      "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/node": "*"
+
      }
+
    },
+
    "node_modules/@types/form-data": {
+
      "version": "0.0.33",
+
      "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
+
      "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/node": "*"
+
      }
+
    },
+
    "node_modules/@types/istanbul-lib-coverage": {
+
      "version": "2.0.4",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+
      "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+
      "dev": true
+
    },
+
    "node_modules/@types/istanbul-lib-report": {
+
      "version": "3.0.0",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+
      "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/istanbul-lib-coverage": "*"
+
      }
+
    },
+
    "node_modules/@types/istanbul-reports": {
+
      "version": "3.0.1",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
+
      "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/istanbul-lib-report": "*"
+
      }
+
    },
    "node_modules/@types/json-schema": {
      "version": "7.0.9",
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@@ -3405,6 +3533,12 @@
      "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
      "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg=="
    },
+
    "node_modules/@types/qs": {
+
      "version": "6.9.7",
+
      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+
      "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+
      "dev": true
+
    },
    "node_modules/@types/sanitize-html": {
      "version": "2.6.2",
      "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.6.2.tgz",
@@ -3442,6 +3576,21 @@
      "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
      "dev": true
    },
+
    "node_modules/@types/yargs": {
+
      "version": "15.0.14",
+
      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz",
+
      "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/yargs-parser": "*"
+
      }
+
    },
+
    "node_modules/@types/yargs-parser": {
+
      "version": "21.0.0",
+
      "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
+
      "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
+
      "dev": true
+
    },
    "node_modules/@types/yauzl": {
      "version": "2.9.2",
      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
@@ -4042,6 +4191,12 @@
        "node": ">=8"
      }
    },
+
    "node_modules/asap": {
+
      "version": "2.0.6",
+
      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+
      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+
      "dev": true
+
    },
    "node_modules/asn1": {
      "version": "0.2.6",
      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -4097,6 +4252,15 @@
        "inherits": "2.0.1"
      }
    },
+
    "node_modules/assertion-error": {
+
      "version": "1.1.0",
+
      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+
      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+
      "dev": true,
+
      "engines": {
+
        "node": "*"
+
      }
+
    },
    "node_modules/astral-regex": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -4801,6 +4965,24 @@
      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
      "dev": true
    },
+
    "node_modules/chai": {
+
      "version": "4.3.6",
+
      "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz",
+
      "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==",
+
      "dev": true,
+
      "dependencies": {
+
        "assertion-error": "^1.1.0",
+
        "check-error": "^1.0.2",
+
        "deep-eql": "^3.0.1",
+
        "get-func-name": "^2.0.0",
+
        "loupe": "^2.3.1",
+
        "pathval": "^1.1.1",
+
        "type-detect": "^4.0.5"
+
      },
+
      "engines": {
+
        "node": ">=4"
+
      }
+
    },
    "node_modules/chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -4817,6 +4999,15 @@
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
+
    "node_modules/check-error": {
+
      "version": "1.0.2",
+
      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+
      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+
      "dev": true,
+
      "engines": {
+
        "node": "*"
+
      }
+
    },
    "node_modules/check-more-types": {
      "version": "2.24.0",
      "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
@@ -5179,6 +5370,17 @@
        "semver": "bin/semver.js"
      }
    },
+
    "node_modules/core-js-pure": {
+
      "version": "3.21.1",
+
      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
+
      "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==",
+
      "dev": true,
+
      "hasInstallScript": true,
+
      "funding": {
+
        "type": "opencollective",
+
        "url": "https://opencollective.com/core-js"
+
      }
+
    },
    "node_modules/core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -5264,10 +5466,16 @@
        "node": "*"
      }
    },
+
    "node_modules/css.escape": {
+
      "version": "1.5.1",
+
      "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+
      "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
+
      "dev": true
+
    },
    "node_modules/cypress": {
-
      "version": "9.4.1",
-
      "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz",
-
      "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==",
+
      "version": "9.5.2",
+
      "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.5.2.tgz",
+
      "integrity": "sha512-gYiQYvJozMzDOriUV1rCt6CeRM/pRK4nhwGJj3nJQyX2BoUdTCVwp30xDMKc771HiNVhBtgj5o5/iBdVDVXQUg==",
      "dev": true,
      "hasInstallScript": true,
      "dependencies": {
@@ -5449,6 +5657,18 @@
      "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
      "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU="
    },
+
    "node_modules/deep-eql": {
+
      "version": "3.0.1",
+
      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+
      "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+
      "dev": true,
+
      "dependencies": {
+
        "type-detect": "^4.0.0"
+
      },
+
      "engines": {
+
        "node": ">=0.12"
+
      }
+
    },
    "node_modules/deep-is": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -5866,38 +6086,60 @@
      "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM="
    },
    "node_modules/esbuild": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
-
      "integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz",
+
      "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==",
      "dev": true,
      "hasInstallScript": true,
      "bin": {
        "esbuild": "bin/esbuild"
      },
+
      "engines": {
+
        "node": ">=12"
+
      },
      "optionalDependencies": {
-
        "esbuild-android-arm64": "0.13.15",
-
        "esbuild-darwin-64": "0.13.15",
-
        "esbuild-darwin-arm64": "0.13.15",
-
        "esbuild-freebsd-64": "0.13.15",
-
        "esbuild-freebsd-arm64": "0.13.15",
-
        "esbuild-linux-32": "0.13.15",
-
        "esbuild-linux-64": "0.13.15",
-
        "esbuild-linux-arm": "0.13.15",
-
        "esbuild-linux-arm64": "0.13.15",
-
        "esbuild-linux-mips64le": "0.13.15",
-
        "esbuild-linux-ppc64le": "0.13.15",
-
        "esbuild-netbsd-64": "0.13.15",
-
        "esbuild-openbsd-64": "0.13.15",
-
        "esbuild-sunos-64": "0.13.15",
-
        "esbuild-windows-32": "0.13.15",
-
        "esbuild-windows-64": "0.13.15",
-
        "esbuild-windows-arm64": "0.13.15"
+
        "esbuild-android-64": "0.14.27",
+
        "esbuild-android-arm64": "0.14.27",
+
        "esbuild-darwin-64": "0.14.27",
+
        "esbuild-darwin-arm64": "0.14.27",
+
        "esbuild-freebsd-64": "0.14.27",
+
        "esbuild-freebsd-arm64": "0.14.27",
+
        "esbuild-linux-32": "0.14.27",
+
        "esbuild-linux-64": "0.14.27",
+
        "esbuild-linux-arm": "0.14.27",
+
        "esbuild-linux-arm64": "0.14.27",
+
        "esbuild-linux-mips64le": "0.14.27",
+
        "esbuild-linux-ppc64le": "0.14.27",
+
        "esbuild-linux-riscv64": "0.14.27",
+
        "esbuild-linux-s390x": "0.14.27",
+
        "esbuild-netbsd-64": "0.14.27",
+
        "esbuild-openbsd-64": "0.14.27",
+
        "esbuild-sunos-64": "0.14.27",
+
        "esbuild-windows-32": "0.14.27",
+
        "esbuild-windows-64": "0.14.27",
+
        "esbuild-windows-arm64": "0.14.27"
+
      }
+
    },
+
    "node_modules/esbuild-android-64": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz",
+
      "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==",
+
      "cpu": [
+
        "x64"
+
      ],
+
      "dev": true,
+
      "optional": true,
+
      "os": [
+
        "android"
+
      ],
+
      "engines": {
+
        "node": ">=12"
      }
    },
    "node_modules/esbuild-android-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
-
      "integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz",
+
      "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==",
      "cpu": [
        "arm64"
      ],
@@ -5905,12 +6147,15 @@
      "optional": true,
      "os": [
        "android"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-darwin-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
-
      "integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz",
+
      "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==",
      "cpu": [
        "x64"
      ],
@@ -5918,12 +6163,15 @@
      "optional": true,
      "os": [
        "darwin"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-darwin-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
-
      "integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz",
+
      "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==",
      "cpu": [
        "arm64"
      ],
@@ -5931,12 +6179,15 @@
      "optional": true,
      "os": [
        "darwin"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-freebsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
-
      "integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz",
+
      "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==",
      "cpu": [
        "x64"
      ],
@@ -5944,12 +6195,15 @@
      "optional": true,
      "os": [
        "freebsd"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-freebsd-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
-
      "integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz",
+
      "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==",
      "cpu": [
        "arm64"
      ],
@@ -5957,12 +6211,15 @@
      "optional": true,
      "os": [
        "freebsd"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-32": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
-
      "integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz",
+
      "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==",
      "cpu": [
        "ia32"
      ],
@@ -5970,12 +6227,15 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
-
      "integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz",
+
      "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==",
      "cpu": [
        "x64"
      ],
@@ -5983,12 +6243,15 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-arm": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
-
      "integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz",
+
      "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==",
      "cpu": [
        "arm"
      ],
@@ -5996,12 +6259,15 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
-
      "integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz",
+
      "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==",
      "cpu": [
        "arm64"
      ],
@@ -6009,12 +6275,15 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-mips64le": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
-
      "integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz",
+
      "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==",
      "cpu": [
        "mips64el"
      ],
@@ -6022,12 +6291,15 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-linux-ppc64le": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
-
      "integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz",
+
      "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==",
      "cpu": [
        "ppc64"
      ],
@@ -6035,12 +6307,47 @@
      "optional": true,
      "os": [
        "linux"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
+
    },
+
    "node_modules/esbuild-linux-riscv64": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz",
+
      "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==",
+
      "cpu": [
+
        "riscv64"
+
      ],
+
      "dev": true,
+
      "optional": true,
+
      "os": [
+
        "linux"
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
+
    },
+
    "node_modules/esbuild-linux-s390x": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz",
+
      "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==",
+
      "cpu": [
+
        "s390x"
+
      ],
+
      "dev": true,
+
      "optional": true,
+
      "os": [
+
        "linux"
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-netbsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
-
      "integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz",
+
      "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==",
      "cpu": [
        "x64"
      ],
@@ -6048,12 +6355,15 @@
      "optional": true,
      "os": [
        "netbsd"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-openbsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
-
      "integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz",
+
      "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==",
      "cpu": [
        "x64"
      ],
@@ -6061,12 +6371,15 @@
      "optional": true,
      "os": [
        "openbsd"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-sunos-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
-
      "integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz",
+
      "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==",
      "cpu": [
        "x64"
      ],
@@ -6074,12 +6387,15 @@
      "optional": true,
      "os": [
        "sunos"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-windows-32": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
-
      "integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz",
+
      "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==",
      "cpu": [
        "ia32"
      ],
@@ -6087,12 +6403,15 @@
      "optional": true,
      "os": [
        "win32"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-windows-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
-
      "integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz",
+
      "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==",
      "cpu": [
        "x64"
      ],
@@ -6100,12 +6419,15 @@
      "optional": true,
      "os": [
        "win32"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/esbuild-windows-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
-
      "integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz",
+
      "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==",
      "cpu": [
        "arm64"
      ],
@@ -6113,7 +6435,10 @@
      "optional": true,
      "os": [
        "win32"
-
      ]
+
      ],
+
      "engines": {
+
        "node": ">=12"
+
      }
    },
    "node_modules/escalade": {
      "version": "3.1.1",
@@ -7029,6 +7354,15 @@
        "node": "6.* || 8.* || >= 10.*"
      }
    },
+
    "node_modules/get-func-name": {
+
      "version": "2.0.0",
+
      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+
      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+
      "dev": true,
+
      "engines": {
+
        "node": "*"
+
      }
+
    },
    "node_modules/get-intrinsic": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
@@ -7193,6 +7527,30 @@
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
      "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
    },
+
    "node_modules/happy-dom": {
+
      "version": "2.50.0",
+
      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-2.50.0.tgz",
+
      "integrity": "sha512-3boJ9i4AbSMUvI4f6N4UEuDDOdKMTCXlnsjoB7KCyhkN1VD+vR9kQEmHLLDHrIPdLn65zA/XhhaFZhCkoWoLQQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "css.escape": "^1.5.1",
+
        "he": "^1.2.0",
+
        "node-fetch": "^2.x.x",
+
        "sync-request": "^6.1.0",
+
        "webidl-conversions": "^7.0.0",
+
        "whatwg-encoding": "^2.0.0",
+
        "whatwg-mimetype": "^3.0.0"
+
      }
+
    },
+
    "node_modules/happy-dom/node_modules/webidl-conversions": {
+
      "version": "7.0.0",
+
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+
      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=12"
+
      }
+
    },
    "node_modules/has": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -7310,6 +7668,15 @@
        "node": ">=8"
      }
    },
+
    "node_modules/he": {
+
      "version": "1.2.0",
+
      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+
      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+
      "dev": true,
+
      "bin": {
+
        "he": "bin/he"
+
      }
+
    },
    "node_modules/hmac-drbg": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -7353,6 +7720,36 @@
        "entities": "^2.0.0"
      }
    },
+
    "node_modules/http-basic": {
+
      "version": "8.1.3",
+
      "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz",
+
      "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==",
+
      "dev": true,
+
      "dependencies": {
+
        "caseless": "^0.12.0",
+
        "concat-stream": "^1.6.2",
+
        "http-response-object": "^3.0.1",
+
        "parse-cache-control": "^1.0.1"
+
      },
+
      "engines": {
+
        "node": ">=6.0.0"
+
      }
+
    },
+
    "node_modules/http-response-object": {
+
      "version": "3.0.2",
+
      "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
+
      "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/node": "^10.0.3"
+
      }
+
    },
+
    "node_modules/http-response-object/node_modules/@types/node": {
+
      "version": "10.17.60",
+
      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+
      "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+
      "dev": true
+
    },
    "node_modules/http-signature": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
@@ -7382,6 +7779,18 @@
        "node": ">=8.12.0"
      }
    },
+
    "node_modules/iconv-lite": {
+
      "version": "0.6.3",
+
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+
      "dev": true,
+
      "dependencies": {
+
        "safer-buffer": ">= 2.1.2 < 3.0.0"
+
      },
+
      "engines": {
+
        "node": ">=0.10.0"
+
      }
+
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -8319,6 +8728,18 @@
        }
      }
    },
+
    "node_modules/local-pkg": {
+
      "version": "0.4.1",
+
      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz",
+
      "integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=14"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/antfu"
+
      }
+
    },
    "node_modules/locate-path": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -8455,6 +8876,15 @@
        "logfmt": "bin/logfmt"
      }
    },
+
    "node_modules/loupe": {
+
      "version": "2.3.4",
+
      "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
+
      "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "get-func-name": "^2.0.0"
+
      }
+
    },
    "node_modules/lower-case": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@@ -9166,6 +9596,12 @@
        "safe-buffer": "^5.1.1"
      }
    },
+
    "node_modules/parse-cache-control": {
+
      "version": "1.0.1",
+
      "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
+
      "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=",
+
      "dev": true
+
    },
    "node_modules/parse-srcset": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
@@ -9236,6 +9672,15 @@
        "node": ">=8"
      }
    },
+
    "node_modules/pathval": {
+
      "version": "1.1.1",
+
      "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+
      "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+
      "dev": true,
+
      "engines": {
+
        "node": "*"
+
      }
+
    },
    "node_modules/pbkdf2": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
@@ -9418,6 +9863,15 @@
        "node": ">=0.4.0"
      }
    },
+
    "node_modules/promise": {
+
      "version": "8.1.0",
+
      "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
+
      "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==",
+
      "dev": true,
+
      "dependencies": {
+
        "asap": "~2.0.6"
+
      }
+
    },
    "node_modules/proxy-from-env": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
@@ -9733,19 +10187,6 @@
        }
      ]
    },
-
    "node_modules/radicle-svelte-unit-test": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/radicle-svelte-unit-test/-/radicle-svelte-unit-test-1.0.0.tgz",
-
      "integrity": "sha512-BU3u3R+3bOve6sp8n3bRU4Dj6cuSz0fojPo3FPJOqq9F45Rn8PSZBz4pii5bRYTKkSNb83VFfPmJ+nlkD828oQ==",
-
      "dev": true,
-
      "dependencies": {
-
        "@bahmutov/cy-rollup": "2.0.0",
-
        "unfetch": "4.1.0"
-
      },
-
      "engines": {
-
        "node": ">=8"
-
      }
-
    },
    "node_modules/randombytes": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -10805,6 +11246,38 @@
        "typescript": "^4.1.2"
      }
    },
+
    "node_modules/sync-request": {
+
      "version": "6.1.0",
+
      "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz",
+
      "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==",
+
      "dev": true,
+
      "dependencies": {
+
        "http-response-object": "^3.0.1",
+
        "sync-rpc": "^1.2.1",
+
        "then-request": "^6.0.0"
+
      },
+
      "engines": {
+
        "node": ">=8.0.0"
+
      }
+
    },
+
    "node_modules/sync-rpc": {
+
      "version": "1.3.6",
+
      "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz",
+
      "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==",
+
      "dev": true,
+
      "dependencies": {
+
        "get-port": "^3.1.0"
+
      }
+
    },
+
    "node_modules/sync-rpc/node_modules/get-port": {
+
      "version": "3.2.0",
+
      "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+
      "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=4"
+
      }
+
    },
    "node_modules/syntax-error": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
@@ -10867,6 +11340,34 @@
      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
      "dev": true
    },
+
    "node_modules/then-request": {
+
      "version": "6.0.2",
+
      "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz",
+
      "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/concat-stream": "^1.6.0",
+
        "@types/form-data": "0.0.33",
+
        "@types/node": "^8.0.0",
+
        "@types/qs": "^6.2.31",
+
        "caseless": "~0.12.0",
+
        "concat-stream": "^1.6.0",
+
        "form-data": "^2.2.0",
+
        "http-basic": "^8.1.1",
+
        "http-response-object": "^3.0.1",
+
        "promise": "^8.0.0",
+
        "qs": "^6.4.0"
+
      },
+
      "engines": {
+
        "node": ">=6.0.0"
+
      }
+
    },
+
    "node_modules/then-request/node_modules/@types/node": {
+
      "version": "8.10.66",
+
      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz",
+
      "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==",
+
      "dev": true
+
    },
    "node_modules/throttleit": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
@@ -10900,6 +11401,24 @@
        "node": ">=0.6.0"
      }
    },
+
    "node_modules/tinypool": {
+
      "version": "0.1.2",
+
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.2.tgz",
+
      "integrity": "sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=14.0.0"
+
      }
+
    },
+
    "node_modules/tinyspy": {
+
      "version": "0.3.0",
+
      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.0.tgz",
+
      "integrity": "sha512-c5uFHqtUp74R2DJE3/Efg0mH5xicmgziaQXMm/LvuuZn3RdpADH32aEGDRyCzObXT1DNfwDMqRQ/Drh1MlO12g==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=14.0.0"
+
      }
+
    },
    "node_modules/tmp": {
      "version": "0.2.1",
      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -11024,7 +11543,16 @@
        "prelude-ls": "^1.2.1"
      },
      "engines": {
-
        "node": ">= 0.8.0"
+
        "node": ">= 0.8.0"
+
      }
+
    },
+
    "node_modules/type-detect": {
+
      "version": "4.0.8",
+
      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+
      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=4"
      }
    },
    "node_modules/type-fest": {
@@ -11118,12 +11646,6 @@
        "undeclared-identifiers": "bin.js"
      }
    },
-
    "node_modules/unfetch": {
-
      "version": "4.1.0",
-
      "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
-
      "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==",
-
      "dev": true
-
    },
    "node_modules/unicode-canonical-property-names-ecmascript": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -11273,14 +11795,14 @@
      "dev": true
    },
    "node_modules/vite": {
-
      "version": "2.7.13",
-
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.13.tgz",
-
      "integrity": "sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==",
+
      "version": "2.8.6",
+
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz",
+
      "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==",
      "dev": true,
      "dependencies": {
-
        "esbuild": "^0.13.12",
-
        "postcss": "^8.4.5",
-
        "resolve": "^1.20.0",
+
        "esbuild": "^0.14.14",
+
        "postcss": "^8.4.6",
+
        "resolve": "^1.22.0",
        "rollup": "^2.59.0"
      },
      "bin": {
@@ -11310,9 +11832,9 @@
      }
    },
    "node_modules/vite-plugin-istanbul": {
-
      "version": "2.4.0",
-
      "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-2.4.0.tgz",
-
      "integrity": "sha512-Oq2FcZZcxtMVLtkG3SPi3M+OMBwt8J765+1mT6+R2K1VKpmfZhqZnsISgSrFNvUjCEcXko/bKrwqPUp0Jz+dtA==",
+
      "version": "2.5.1",
+
      "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-2.5.1.tgz",
+
      "integrity": "sha512-5ZR2/haQalUXVqHrjatZ0QKxzF3e0hBiJe6VhjGYxE7Yc4WFs7ZE56oazYB4jnPXvlirOvgLxG0LJQoHFdMxMg==",
      "dev": true,
      "dependencies": {
        "chalk": "^4.1.2",
@@ -11335,6 +11857,50 @@
        "vite": "^2.0.2"
      }
    },
+
    "node_modules/vitest": {
+
      "version": "0.6.3",
+
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.6.3.tgz",
+
      "integrity": "sha512-0x5UeYQ5Mqniurxv9KT0+FI4IszLBbNBiBPSXSdyQAEodeq400Q00YB0f1qNbf7YsxyLqFJvGpoOTKJWUYm6mQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "@types/chai": "^4.3.0",
+
        "@types/chai-subset": "^1.3.3",
+
        "chai": "^4.3.6",
+
        "local-pkg": "^0.4.1",
+
        "tinypool": "^0.1.2",
+
        "tinyspy": "^0.3.0",
+
        "vite": "^2.7.10"
+
      },
+
      "bin": {
+
        "vitest": "vitest.mjs"
+
      },
+
      "engines": {
+
        "node": ">=14.14.0"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/antfu"
+
      },
+
      "peerDependencies": {
+
        "@vitest/ui": "*",
+
        "c8": "*",
+
        "happy-dom": "*",
+
        "jsdom": "*"
+
      },
+
      "peerDependenciesMeta": {
+
        "@vitest/ui": {
+
          "optional": true
+
        },
+
        "c8": {
+
          "optional": true
+
        },
+
        "happy-dom": {
+
          "optional": true
+
        },
+
        "jsdom": {
+
          "optional": true
+
        }
+
      }
+
    },
    "node_modules/vm-browserify": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -11525,6 +12091,27 @@
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
      "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
    },
+
    "node_modules/whatwg-encoding": {
+
      "version": "2.0.0",
+
      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+
      "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+
      "dev": true,
+
      "dependencies": {
+
        "iconv-lite": "0.6.3"
+
      },
+
      "engines": {
+
        "node": ">=12"
+
      }
+
    },
+
    "node_modules/whatwg-mimetype": {
+
      "version": "3.0.0",
+
      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+
      "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=12"
+
      }
+
    },
    "node_modules/whatwg-url": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -12898,6 +13485,16 @@
        "regenerator-runtime": "^0.13.4"
      }
    },
+
    "@babel/runtime-corejs3": {
+
      "version": "7.17.8",
+
      "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz",
+
      "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==",
+
      "dev": true,
+
      "requires": {
+
        "core-js-pure": "^3.20.2",
+
        "regenerator-runtime": "^0.13.4"
+
      }
+
    },
    "@babel/template": {
      "version": "7.16.7",
      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
@@ -12937,26 +13534,6 @@
        "to-fast-properties": "^2.0.0"
      }
    },
-
    "@bahmutov/cy-rollup": {
-
      "version": "2.0.0",
-
      "resolved": "https://registry.npmjs.org/@bahmutov/cy-rollup/-/cy-rollup-2.0.0.tgz",
-
      "integrity": "sha512-KJlvqB3U1nvKSq7djkXcvM4WpBLP6HEWKKzChI8s/aw0pYm0gVc+sphpyz5CmGpsNE9+/1QB6yoDfTe9Q97SJw==",
-
      "dev": true,
-
      "requires": {
-
        "debug": "4.1.1"
-
      },
-
      "dependencies": {
-
        "debug": {
-
          "version": "4.1.1",
-
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-
          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-
          "dev": true,
-
          "requires": {
-
            "ms": "^2.1.1"
-
          }
-
        }
-
      }
-
    },
    "@ceramicnetwork/3id-did-resolver": {
      "version": "1.4.16",
      "resolved": "https://registry.npmjs.org/@ceramicnetwork/3id-did-resolver/-/3id-did-resolver-1.4.16.tgz",
@@ -13767,6 +14344,19 @@
      "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
      "dev": true
    },
+
    "@jest/types": {
+
      "version": "26.6.2",
+
      "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
+
      "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
+
      "dev": true,
+
      "requires": {
+
        "@types/istanbul-lib-coverage": "^2.0.0",
+
        "@types/istanbul-reports": "^3.0.0",
+
        "@types/node": "*",
+
        "@types/yargs": "^15.0.0",
+
        "chalk": "^4.0.0"
+
      }
+
    },
    "@json-rpc-tools/provider": {
      "version": "1.7.6",
      "resolved": "https://registry.npmjs.org/@json-rpc-tools/provider/-/provider-1.7.6.tgz",
@@ -14068,6 +14658,55 @@
        "pretty-format": "^27.0.2"
      }
    },
+
    "@testing-library/svelte": {
+
      "version": "3.1.0",
+
      "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-3.1.0.tgz",
+
      "integrity": "sha512-xTN6v4xRLQb75GTJn2mrjSUJN4PkhpNZFjwvtdzbOTS6OvxMrkRdm6hFRGauwiFd0LPV7/SqdWbbtMAOC7a+Dg==",
+
      "dev": true,
+
      "requires": {
+
        "@testing-library/dom": "^7.0.3"
+
      },
+
      "dependencies": {
+
        "@testing-library/dom": {
+
          "version": "7.31.2",
+
          "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz",
+
          "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==",
+
          "dev": true,
+
          "requires": {
+
            "@babel/code-frame": "^7.10.4",
+
            "@babel/runtime": "^7.12.5",
+
            "@types/aria-query": "^4.2.0",
+
            "aria-query": "^4.2.2",
+
            "chalk": "^4.1.0",
+
            "dom-accessibility-api": "^0.5.6",
+
            "lz-string": "^1.4.4",
+
            "pretty-format": "^26.6.2"
+
          }
+
        },
+
        "aria-query": {
+
          "version": "4.2.2",
+
          "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+
          "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+
          "dev": true,
+
          "requires": {
+
            "@babel/runtime": "^7.10.2",
+
            "@babel/runtime-corejs3": "^7.10.2"
+
          }
+
        },
+
        "pretty-format": {
+
          "version": "26.6.2",
+
          "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
+
          "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
+
          "dev": true,
+
          "requires": {
+
            "@jest/types": "^26.6.2",
+
            "ansi-regex": "^5.0.0",
+
            "ansi-styles": "^4.0.0",
+
            "react-is": "^17.0.1"
+
          }
+
        }
+
      }
+
    },
    "@tsconfig/svelte": {
      "version": "1.0.13",
      "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-1.0.13.tgz",
@@ -14088,6 +14727,63 @@
        "@types/node": "*"
      }
    },
+
    "@types/chai": {
+
      "version": "4.3.0",
+
      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
+
      "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==",
+
      "dev": true
+
    },
+
    "@types/chai-subset": {
+
      "version": "1.3.3",
+
      "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz",
+
      "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==",
+
      "dev": true,
+
      "requires": {
+
        "@types/chai": "*"
+
      }
+
    },
+
    "@types/concat-stream": {
+
      "version": "1.6.1",
+
      "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz",
+
      "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==",
+
      "dev": true,
+
      "requires": {
+
        "@types/node": "*"
+
      }
+
    },
+
    "@types/form-data": {
+
      "version": "0.0.33",
+
      "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz",
+
      "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=",
+
      "dev": true,
+
      "requires": {
+
        "@types/node": "*"
+
      }
+
    },
+
    "@types/istanbul-lib-coverage": {
+
      "version": "2.0.4",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
+
      "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==",
+
      "dev": true
+
    },
+
    "@types/istanbul-lib-report": {
+
      "version": "3.0.0",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz",
+
      "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==",
+
      "dev": true,
+
      "requires": {
+
        "@types/istanbul-lib-coverage": "*"
+
      }
+
    },
+
    "@types/istanbul-reports": {
+
      "version": "3.0.1",
+
      "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz",
+
      "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==",
+
      "dev": true,
+
      "requires": {
+
        "@types/istanbul-lib-report": "*"
+
      }
+
    },
    "@types/json-schema": {
      "version": "7.0.9",
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@@ -14117,6 +14813,12 @@
      "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.6.tgz",
      "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg=="
    },
+
    "@types/qs": {
+
      "version": "6.9.7",
+
      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+
      "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+
      "dev": true
+
    },
    "@types/sanitize-html": {
      "version": "2.6.2",
      "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.6.2.tgz",
@@ -14154,6 +14856,21 @@
      "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
      "dev": true
    },
+
    "@types/yargs": {
+
      "version": "15.0.14",
+
      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz",
+
      "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==",
+
      "dev": true,
+
      "requires": {
+
        "@types/yargs-parser": "*"
+
      }
+
    },
+
    "@types/yargs-parser": {
+
      "version": "21.0.0",
+
      "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
+
      "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
+
      "dev": true
+
    },
    "@types/yauzl": {
      "version": "2.9.2",
      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
@@ -14602,6 +15319,12 @@
      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
      "dev": true
    },
+
    "asap": {
+
      "version": "2.0.6",
+
      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+
      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+
      "dev": true
+
    },
    "asn1": {
      "version": "0.2.6",
      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -14656,6 +15379,12 @@
      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
      "dev": true
    },
+
    "assertion-error": {
+
      "version": "1.1.0",
+
      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+
      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+
      "dev": true
+
    },
    "astral-regex": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@@ -15238,6 +15967,21 @@
      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
      "dev": true
    },
+
    "chai": {
+
      "version": "4.3.6",
+
      "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz",
+
      "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==",
+
      "dev": true,
+
      "requires": {
+
        "assertion-error": "^1.1.0",
+
        "check-error": "^1.0.2",
+
        "deep-eql": "^3.0.1",
+
        "get-func-name": "^2.0.0",
+
        "loupe": "^2.3.1",
+
        "pathval": "^1.1.1",
+
        "type-detect": "^4.0.5"
+
      }
+
    },
    "chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -15248,6 +15992,12 @@
        "supports-color": "^7.1.0"
      }
    },
+
    "check-error": {
+
      "version": "1.0.2",
+
      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+
      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+
      "dev": true
+
    },
    "check-more-types": {
      "version": "2.24.0",
      "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
@@ -15542,6 +16292,12 @@
        }
      }
    },
+
    "core-js-pure": {
+
      "version": "3.21.1",
+
      "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz",
+
      "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==",
+
      "dev": true
+
    },
    "core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -15621,10 +16377,16 @@
        "randomfill": "^1.0.3"
      }
    },
+
    "css.escape": {
+
      "version": "1.5.1",
+
      "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+
      "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
+
      "dev": true
+
    },
    "cypress": {
-
      "version": "9.4.1",
-
      "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz",
-
      "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==",
+
      "version": "9.5.2",
+
      "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.5.2.tgz",
+
      "integrity": "sha512-gYiQYvJozMzDOriUV1rCt6CeRM/pRK4nhwGJj3nJQyX2BoUdTCVwp30xDMKc771HiNVhBtgj5o5/iBdVDVXQUg==",
      "dev": true,
      "requires": {
        "@cypress/request": "^2.88.10",
@@ -15758,6 +16520,15 @@
      "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
      "integrity": "sha1-vuX7fJ5yfYXf+iRZDRDsGrElUwU="
    },
+
    "deep-eql": {
+
      "version": "3.0.1",
+
      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+
      "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+
      "dev": true,
+
      "requires": {
+
        "type-detect": "^4.0.0"
+
      }
+
    },
    "deep-is": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -16107,146 +16878,170 @@
      "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM="
    },
    "esbuild": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
-
      "integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
-
      "dev": true,
-
      "requires": {
-
        "esbuild-android-arm64": "0.13.15",
-
        "esbuild-darwin-64": "0.13.15",
-
        "esbuild-darwin-arm64": "0.13.15",
-
        "esbuild-freebsd-64": "0.13.15",
-
        "esbuild-freebsd-arm64": "0.13.15",
-
        "esbuild-linux-32": "0.13.15",
-
        "esbuild-linux-64": "0.13.15",
-
        "esbuild-linux-arm": "0.13.15",
-
        "esbuild-linux-arm64": "0.13.15",
-
        "esbuild-linux-mips64le": "0.13.15",
-
        "esbuild-linux-ppc64le": "0.13.15",
-
        "esbuild-netbsd-64": "0.13.15",
-
        "esbuild-openbsd-64": "0.13.15",
-
        "esbuild-sunos-64": "0.13.15",
-
        "esbuild-windows-32": "0.13.15",
-
        "esbuild-windows-64": "0.13.15",
-
        "esbuild-windows-arm64": "0.13.15"
-
      }
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz",
+
      "integrity": "sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==",
+
      "dev": true,
+
      "requires": {
+
        "esbuild-android-64": "0.14.27",
+
        "esbuild-android-arm64": "0.14.27",
+
        "esbuild-darwin-64": "0.14.27",
+
        "esbuild-darwin-arm64": "0.14.27",
+
        "esbuild-freebsd-64": "0.14.27",
+
        "esbuild-freebsd-arm64": "0.14.27",
+
        "esbuild-linux-32": "0.14.27",
+
        "esbuild-linux-64": "0.14.27",
+
        "esbuild-linux-arm": "0.14.27",
+
        "esbuild-linux-arm64": "0.14.27",
+
        "esbuild-linux-mips64le": "0.14.27",
+
        "esbuild-linux-ppc64le": "0.14.27",
+
        "esbuild-linux-riscv64": "0.14.27",
+
        "esbuild-linux-s390x": "0.14.27",
+
        "esbuild-netbsd-64": "0.14.27",
+
        "esbuild-openbsd-64": "0.14.27",
+
        "esbuild-sunos-64": "0.14.27",
+
        "esbuild-windows-32": "0.14.27",
+
        "esbuild-windows-64": "0.14.27",
+
        "esbuild-windows-arm64": "0.14.27"
+
      }
+
    },
+
    "esbuild-android-64": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz",
+
      "integrity": "sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==",
+
      "dev": true,
+
      "optional": true
    },
    "esbuild-android-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
-
      "integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz",
+
      "integrity": "sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==",
      "dev": true,
      "optional": true
    },
    "esbuild-darwin-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
-
      "integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz",
+
      "integrity": "sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==",
      "dev": true,
      "optional": true
    },
    "esbuild-darwin-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
-
      "integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz",
+
      "integrity": "sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==",
      "dev": true,
      "optional": true
    },
    "esbuild-freebsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
-
      "integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz",
+
      "integrity": "sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==",
      "dev": true,
      "optional": true
    },
    "esbuild-freebsd-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
-
      "integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz",
+
      "integrity": "sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-32": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
-
      "integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz",
+
      "integrity": "sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
-
      "integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz",
+
      "integrity": "sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-arm": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
-
      "integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz",
+
      "integrity": "sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
-
      "integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz",
+
      "integrity": "sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-mips64le": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
-
      "integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz",
+
      "integrity": "sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==",
      "dev": true,
      "optional": true
    },
    "esbuild-linux-ppc64le": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
-
      "integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz",
+
      "integrity": "sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==",
+
      "dev": true,
+
      "optional": true
+
    },
+
    "esbuild-linux-riscv64": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz",
+
      "integrity": "sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==",
+
      "dev": true,
+
      "optional": true
+
    },
+
    "esbuild-linux-s390x": {
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz",
+
      "integrity": "sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==",
      "dev": true,
      "optional": true
    },
    "esbuild-netbsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
-
      "integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz",
+
      "integrity": "sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==",
      "dev": true,
      "optional": true
    },
    "esbuild-openbsd-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
-
      "integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz",
+
      "integrity": "sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==",
      "dev": true,
      "optional": true
    },
    "esbuild-sunos-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
-
      "integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz",
+
      "integrity": "sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==",
      "dev": true,
      "optional": true
    },
    "esbuild-windows-32": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
-
      "integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz",
+
      "integrity": "sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==",
      "dev": true,
      "optional": true
    },
    "esbuild-windows-64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
-
      "integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz",
+
      "integrity": "sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==",
      "dev": true,
      "optional": true
    },
    "esbuild-windows-arm64": {
-
      "version": "0.13.15",
-
      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
-
      "integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
+
      "version": "0.14.27",
+
      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz",
+
      "integrity": "sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==",
      "dev": true,
      "optional": true
    },
@@ -16939,6 +17734,12 @@
      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
    },
+
    "get-func-name": {
+
      "version": "2.0.0",
+
      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+
      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+
      "dev": true
+
    },
    "get-intrinsic": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
@@ -17055,6 +17856,29 @@
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
      "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
    },
+
    "happy-dom": {
+
      "version": "2.50.0",
+
      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-2.50.0.tgz",
+
      "integrity": "sha512-3boJ9i4AbSMUvI4f6N4UEuDDOdKMTCXlnsjoB7KCyhkN1VD+vR9kQEmHLLDHrIPdLn65zA/XhhaFZhCkoWoLQQ==",
+
      "dev": true,
+
      "requires": {
+
        "css.escape": "^1.5.1",
+
        "he": "^1.2.0",
+
        "node-fetch": "^2.x.x",
+
        "sync-request": "^6.1.0",
+
        "webidl-conversions": "^7.0.0",
+
        "whatwg-encoding": "^2.0.0",
+
        "whatwg-mimetype": "^3.0.0"
+
      },
+
      "dependencies": {
+
        "webidl-conversions": {
+
          "version": "7.0.0",
+
          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+
          "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+
          "dev": true
+
        }
+
      }
+
    },
    "has": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -17140,6 +17964,12 @@
        }
      }
    },
+
    "he": {
+
      "version": "1.2.0",
+
      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+
      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+
      "dev": true
+
    },
    "hmac-drbg": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -17173,6 +18003,35 @@
        "entities": "^2.0.0"
      }
    },
+
    "http-basic": {
+
      "version": "8.1.3",
+
      "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz",
+
      "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==",
+
      "dev": true,
+
      "requires": {
+
        "caseless": "^0.12.0",
+
        "concat-stream": "^1.6.2",
+
        "http-response-object": "^3.0.1",
+
        "parse-cache-control": "^1.0.1"
+
      }
+
    },
+
    "http-response-object": {
+
      "version": "3.0.2",
+
      "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
+
      "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
+
      "dev": true,
+
      "requires": {
+
        "@types/node": "^10.0.3"
+
      },
+
      "dependencies": {
+
        "@types/node": {
+
          "version": "10.17.60",
+
          "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+
          "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+
          "dev": true
+
        }
+
      }
+
    },
    "http-signature": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
@@ -17196,6 +18055,15 @@
      "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
      "dev": true
    },
+
    "iconv-lite": {
+
      "version": "0.6.3",
+
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+
      "dev": true,
+
      "requires": {
+
        "safer-buffer": ">= 2.1.2 < 3.0.0"
+
      }
+
    },
    "ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -17874,6 +18742,12 @@
        "wrap-ansi": "^7.0.0"
      }
    },
+
    "local-pkg": {
+
      "version": "0.4.1",
+
      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.1.tgz",
+
      "integrity": "sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==",
+
      "dev": true
+
    },
    "locate-path": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -17985,6 +18859,15 @@
        "through": "2.3.x"
      }
    },
+
    "loupe": {
+
      "version": "2.3.4",
+
      "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
+
      "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==",
+
      "dev": true,
+
      "requires": {
+
        "get-func-name": "^2.0.0"
+
      }
+
    },
    "lower-case": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@@ -18542,6 +19425,12 @@
        "safe-buffer": "^5.1.1"
      }
    },
+
    "parse-cache-control": {
+
      "version": "1.0.1",
+
      "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
+
      "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=",
+
      "dev": true
+
    },
    "parse-srcset": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
@@ -18597,6 +19486,12 @@
      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
      "dev": true
    },
+
    "pathval": {
+
      "version": "1.1.1",
+
      "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+
      "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+
      "dev": true
+
    },
    "pbkdf2": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
@@ -18725,6 +19620,15 @@
      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
      "dev": true
    },
+
    "promise": {
+
      "version": "8.1.0",
+
      "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
+
      "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==",
+
      "dev": true,
+
      "requires": {
+
        "asap": "~2.0.6"
+
      }
+
    },
    "proxy-from-env": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
@@ -18962,16 +19866,6 @@
      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
      "dev": true
    },
-
    "radicle-svelte-unit-test": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/radicle-svelte-unit-test/-/radicle-svelte-unit-test-1.0.0.tgz",
-
      "integrity": "sha512-BU3u3R+3bOve6sp8n3bRU4Dj6cuSz0fojPo3FPJOqq9F45Rn8PSZBz4pii5bRYTKkSNb83VFfPmJ+nlkD828oQ==",
-
      "dev": true,
-
      "requires": {
-
        "@bahmutov/cy-rollup": "2.0.0",
-
        "unfetch": "4.1.0"
-
      }
-
    },
    "randombytes": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -19759,6 +20653,34 @@
        "pascal-case": "^3.1.1"
      }
    },
+
    "sync-request": {
+
      "version": "6.1.0",
+
      "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz",
+
      "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==",
+
      "dev": true,
+
      "requires": {
+
        "http-response-object": "^3.0.1",
+
        "sync-rpc": "^1.2.1",
+
        "then-request": "^6.0.0"
+
      }
+
    },
+
    "sync-rpc": {
+
      "version": "1.3.6",
+
      "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz",
+
      "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==",
+
      "dev": true,
+
      "requires": {
+
        "get-port": "^3.1.0"
+
      },
+
      "dependencies": {
+
        "get-port": {
+
          "version": "3.2.0",
+
          "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
+
          "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=",
+
          "dev": true
+
        }
+
      }
+
    },
    "syntax-error": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
@@ -19811,6 +20733,33 @@
      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
      "dev": true
    },
+
    "then-request": {
+
      "version": "6.0.2",
+
      "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz",
+
      "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==",
+
      "dev": true,
+
      "requires": {
+
        "@types/concat-stream": "^1.6.0",
+
        "@types/form-data": "0.0.33",
+
        "@types/node": "^8.0.0",
+
        "@types/qs": "^6.2.31",
+
        "caseless": "~0.12.0",
+
        "concat-stream": "^1.6.0",
+
        "form-data": "^2.2.0",
+
        "http-basic": "^8.1.1",
+
        "http-response-object": "^3.0.1",
+
        "promise": "^8.0.0",
+
        "qs": "^6.4.0"
+
      },
+
      "dependencies": {
+
        "@types/node": {
+
          "version": "8.10.66",
+
          "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz",
+
          "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==",
+
          "dev": true
+
        }
+
      }
+
    },
    "throttleit": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
@@ -19841,6 +20790,18 @@
        "process": "~0.11.0"
      }
    },
+
    "tinypool": {
+
      "version": "0.1.2",
+
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.2.tgz",
+
      "integrity": "sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==",
+
      "dev": true
+
    },
+
    "tinyspy": {
+
      "version": "0.3.0",
+
      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.0.tgz",
+
      "integrity": "sha512-c5uFHqtUp74R2DJE3/Efg0mH5xicmgziaQXMm/LvuuZn3RdpADH32aEGDRyCzObXT1DNfwDMqRQ/Drh1MlO12g==",
+
      "dev": true
+
    },
    "tmp": {
      "version": "0.2.1",
      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -19945,6 +20906,12 @@
        "prelude-ls": "^1.2.1"
      }
    },
+
    "type-detect": {
+
      "version": "4.0.8",
+
      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+
      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+
      "dev": true
+
    },
    "type-fest": {
      "version": "0.21.3",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
@@ -20014,12 +20981,6 @@
        "xtend": "^4.0.1"
      }
    },
-
    "unfetch": {
-
      "version": "4.1.0",
-
      "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
-
      "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==",
-
      "dev": true
-
    },
    "unicode-canonical-property-names-ecmascript": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -20150,22 +21111,22 @@
      }
    },
    "vite": {
-
      "version": "2.7.13",
-
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.7.13.tgz",
-
      "integrity": "sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==",
+
      "version": "2.8.6",
+
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.8.6.tgz",
+
      "integrity": "sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==",
      "dev": true,
      "requires": {
-
        "esbuild": "^0.13.12",
+
        "esbuild": "^0.14.14",
        "fsevents": "~2.3.2",
-
        "postcss": "^8.4.5",
-
        "resolve": "^1.20.0",
+
        "postcss": "^8.4.6",
+
        "resolve": "^1.22.0",
        "rollup": "^2.59.0"
      }
    },
    "vite-plugin-istanbul": {
-
      "version": "2.4.0",
-
      "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-2.4.0.tgz",
-
      "integrity": "sha512-Oq2FcZZcxtMVLtkG3SPi3M+OMBwt8J765+1mT6+R2K1VKpmfZhqZnsISgSrFNvUjCEcXko/bKrwqPUp0Jz+dtA==",
+
      "version": "2.5.1",
+
      "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-2.5.1.tgz",
+
      "integrity": "sha512-5ZR2/haQalUXVqHrjatZ0QKxzF3e0hBiJe6VhjGYxE7Yc4WFs7ZE56oazYB4jnPXvlirOvgLxG0LJQoHFdMxMg==",
      "dev": true,
      "requires": {
        "chalk": "^4.1.2",
@@ -20182,6 +21143,21 @@
        "connect-history-api-fallback": "^1.6.0"
      }
    },
+
    "vitest": {
+
      "version": "0.6.3",
+
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.6.3.tgz",
+
      "integrity": "sha512-0x5UeYQ5Mqniurxv9KT0+FI4IszLBbNBiBPSXSdyQAEodeq400Q00YB0f1qNbf7YsxyLqFJvGpoOTKJWUYm6mQ==",
+
      "dev": true,
+
      "requires": {
+
        "@types/chai": "^4.3.0",
+
        "@types/chai-subset": "^1.3.3",
+
        "chai": "^4.3.6",
+
        "local-pkg": "^0.4.1",
+
        "tinypool": "^0.1.2",
+
        "tinyspy": "^0.3.0",
+
        "vite": "^2.7.10"
+
      }
+
    },
    "vm-browserify": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -20359,6 +21335,21 @@
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
      "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
    },
+
    "whatwg-encoding": {
+
      "version": "2.0.0",
+
      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+
      "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+
      "dev": true,
+
      "requires": {
+
        "iconv-lite": "0.6.3"
+
      }
+
    },
+
    "whatwg-mimetype": {
+
      "version": "3.0.0",
+
      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+
      "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+
      "dev": true
+
    },
    "whatwg-url": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
modified package.json
@@ -5,7 +5,10 @@
    "serve": "vite preview",
    "build": "scripts/build",
    "check": "scripts/check",
-
    "test": "cypress run-ct"
+
    "coverage": "npx nyc report",
+
    "test:unit": "vitest run",
+
    "test:components": "cypress run-ct",
+
    "test:e2e": "cypress run"
  },
  "type": "module",
  "engines": {
@@ -16,20 +19,22 @@
    "@cypress/vite-dev-server": "^2.2.2",
    "@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
    "@testing-library/cypress": "^8.0.2",
+
    "@testing-library/svelte": "^3.1.0",
    "@tsconfig/svelte": "^1.0.13",
    "@types/sanitize-html": "^2.6.2",
    "@typescript-eslint/eslint-plugin": "^4.26.1",
    "@typescript-eslint/parser": "^4.26.1",
-
    "cypress": "^9.3.1",
+
    "cypress": "^9.5.1",
    "eslint": "^7.28.0",
    "eslint-plugin-radicle": "^0.2.0",
    "eslint-plugin-svelte3": "^3.2.0",
-
    "radicle-svelte-unit-test": "^1.0.0",
+
    "happy-dom": "^2.50.0",
    "svelte-check": "^2.4.1",
    "typescript": "^4.2.4",
    "vite": "^2.7.13",
-
    "vite-plugin-istanbul": "^2.4.0",
-
    "vite-plugin-rewrite-all": "^0.1.2"
+
    "vite-plugin-istanbul": "^2.5.1",
+
    "vite-plugin-rewrite-all": "^0.1.2",
+
    "vitest": "^0.6.3"
  },
  "dependencies": {
    "@datamodels/identity-profile-basic": "^0.1.2",
modified scripts/build
@@ -1,4 +1,5 @@
#!/bin/sh
+
set -e

vite build
cp _redirects build/_redirects
modified scripts/check
@@ -1,3 +1,4 @@
#!/bin/sh
+
set -e

npx tsc --noEmit && npx svelte-check
modified scripts/check-format
@@ -1,4 +1,5 @@
#!/bin/sh
+
set -e

# Checks local /node_modules,
# if not found will download that package and execute it.
deleted scripts/test
@@ -1,3 +0,0 @@
-
#!/bin/sh
-

-
npm test && npx nyc report
added scripts/unit-test
@@ -0,0 +1,5 @@
+
#!/bin/sh
+
set -e
+

+
npm run test:unit
+
npm run test:components
added src/BlockTimer.spec.ts
@@ -0,0 +1,46 @@
+
import BlockTimer from "./BlockTimer.svelte";
+
import { render } from "@testing-library/svelte";
+
import "@public/index.css";
+
import type { EventType, Listener } from "@ethersproject/abstract-provider";
+

+

+
describe('BlockTimer', function () {
+
  it("increases correctly the loading bar", () => {
+
    let block = 1;
+
    const props = {
+
      config: {
+
        provider: {
+
          on: (event: EventType, listener: Listener) => {
+
            if (event === "block") {
+
              listener(block);
+
            }
+
          }
+
        }
+
      },
+
      startBlock: 1,
+
      duration: 3
+
    };
+

+
    const { rerender } = render(BlockTimer, props);
+

+
    cy.get("div.loader").should("have.attr", "style", "width: 0%;").then(() => {
+
      block += 1;
+
      rerender(props);
+
    });
+

+
    cy.get("div.loader").last().should("have.attr", "style", "width: 33%;").then(() => {
+
      block += 1;
+
      rerender(props);
+
    });
+

+
    cy.get("div.loader").last().should("have.attr", "style", "width: 66%;").then(() => {
+
      block += 1;
+
      rerender(props);
+
    });
+

+
    cy.get("div.loader").last().should("have.attr", "style", "width: 99%;").then(() => {
+
      block += 1;
+
      rerender(props);
+
    });
+
  });
+
});
modified src/BlockTimer.svelte
@@ -29,5 +29,5 @@
</style>

<div class="parent">
-
  <div class="loader" style="width: {(currentBlock - startBlock) * 10}%" />
+
  <div class="loader" style="width: {(currentBlock - startBlock) * Math.floor(100 / duration)}%" />
</div>
modified src/Error.spec.ts
@@ -1,11 +1,11 @@
import Error from "./Error.svelte";
-
import { mount } from "radicle-svelte-unit-test";
+
import { render } from "@testing-library/svelte";
import { Failure } from '@app/error';
-
import { styles } from "@test/support/index";
+
import "@public/index.css";

describe('Error', function () {
-
  it("Open Error modal with default props", () => {
-
    mount(Error, { props: {
+
  it("should show passed in props", () => {
+
    render(Error, { props: {
      subtitle: "Subtitle of Modal",
      error: {
        type: Failure.InsufficientBalance,
@@ -13,30 +13,28 @@ describe('Error', function () {
        message: "Not enough RAD"
      }
    }
-
    }, styles);
-
    cy.get("[slot=title]").should("have.text", " Error");
-
    cy.get("[slot=subtitle]").should("have.text", "Subtitle of Modal");
-
    cy.get("[slot=body]").should("have.text", "Error: Not enough RAD");
-
    cy.get("button").should("have.text", "Back");
+
    });
+
    cy.findByText("Error");
+
    cy.findByText("Subtitle of Modal");
+
    cy.findByText("Not enough RAD");
+
    cy.findByText("Back");
  });

-
  it("Open Error modal with custom message", () => {
-
    mount(Error, { props: {
+
  it("should show custom error message", () => {
+
    render(Error, { props: {
      subtitle: "Subtitle of Modal",
      message: "Error message to check for",
-
    } }, styles);
-
    cy.get("[slot=subtitle]").should("have.text", "Subtitle of Modal");
-
    cy.get("[slot=body]").should("have.text", "Error: Error message to check for");
-
    cy.get("button").should("have.text", "Back");
+
    } });
+
    cy.findByText("Error message to check for");
  });

-
  it("Check floating modal changes button label to Close", () => {
-
    mount(Error, { props: {
+
  it("should change button label to Close when floating", () => {
+
    render(Error, { props: {
      title: "Title of Modal",
      subtitle: "Subtitle of Modal",
      message: "Error message to check for",
      floating: true
-
    } }, styles);
-
    cy.get("button").should("have.text", "Close");
+
    } });
+
    cy.findByText("Close");
  });
});
added src/NotFound.spec.ts
@@ -0,0 +1,17 @@
+
import NotFound from "./NotFound.svelte";
+
import { render } from "@testing-library/svelte";
+
import "@public/index.css";
+

+
describe('NotFound', function () {
+
  it("shows passed props correctly", () => {
+
    render(NotFound, {
+
      props: {
+
        title: "nakamoto",
+
        subtitle: "Sorry, the requested project was not found."
+
      }
+
    });
+
    cy.findByText("nakamoto");
+
    cy.findByText("Sorry, the requested project was not found.");
+
    cy.findByText("Back");
+
  });
+
});
modified src/Search.spec.ts
@@ -1,18 +1,30 @@
import Search from "./Search.svelte";
-
import { mount } from "radicle-svelte-unit-test";
-
import { styles } from "@test/support/index";
+
import { fireEvent, render } from "@testing-library/svelte";
+
import "@public/index.css";

-
describe('Search', function () {
-
  it("Renders correctly", () => {
-
    mount(Search, {}, styles);
-
    cy.get("input").should("exist");
+
describe('Logic', () => {
+
  it("show a appropiate placeholder", () => {
+
    render(Search);
    cy.get("input").should("have.attr", "placeholder", "Search a name or address...");
  });

-
  it("Search input displays and navigates correctly", () => {
-
    mount(Search, {}, styles);
-
    cy.get("input").type("cloudhead{enter}");
-
    cy.get("input").should("have.value", "cloudhead");
-
    cy.url().should("contain", "/resolver/query?q=cloudhead");
+
  it("allow input a query and navigates accordingly", () => {
+
    render(Search);
+
    cy.get("input").type("cloudhead.radicle.eth{enter}");
+
    cy.get("input").should("have.value", "cloudhead.radicle.eth");
+
    cy.url().should("contain", "/resolver/query?q=cloudhead.radicle.eth");
+
  });
+
});
+

+
describe("Events", () => {
+
  it("should fire an event when the input changes", () => {
+
    const { component } = render(Search);
+
    const mock = cy.spy();
+
    component.$on("search", mock);
+

+
    cy.get("input").then(([input]) => {
+
      fireEvent.keyDown(input, { key: "Enter" });
+
      expect(mock).to.have.been.calledOnce;
+
    });
  });
});
modified src/SeedAddress.spec.ts
@@ -1,18 +1,37 @@
import SeedAddress from "./SeedAddress.svelte";
-
import { mount } from "radicle-svelte-unit-test";
-
import { styles } from "@test/support/index";
+
import { render } from "@testing-library/svelte";
+
import "@public/index.css";

describe('SeedAddress', function () {
-
  it("Renders correctly", () => {
-
    mount(SeedAddress, {
+
  it("shows the seed emoji and seed host", () => {
+
    render(SeedAddress, {
      props: {
        seed: {
          id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
+
          emoji: "🐱",
          host: "seed.cloudhead.io",
        },
        port: 8776
      }
-
    }, styles);
-
    cy.findByText("seed.cloudhead.io").should("exist").should("have.attr", "href", "/seeds/seed.cloudhead.io");
+
    });
+
    cy.get("span.seed-icon").should("have.text", "🐱");
+
    cy.findByText("seed.cloudhead.io").should("have.attr", "href", "/seeds/seed.cloudhead.io").should("be.visible");
+
  });
+

+
  it("shows the full seed id", () => {
+
    render(SeedAddress, {
+
      props: {
+
        seed: {
+
          id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
+
          emoji: "🐱",
+
          host: "seed.cloudhead.io",
+
        },
+
        port: 8776,
+
        full: true
+
      }
+
    });
+
    cy.get("span.seed-icon").should("have.text", "🐱");
+
    cy.findByText("hydkkk…coygh1@seed.cloudhead.io").should("be.visible");
+
    cy.findByText(":8776").should("be.visible");
  });
});
modified src/base/projects/BranchSelector.spec.ts
@@ -1,6 +1,6 @@
import BranchSelector from "./BranchSelector.svelte";
-
import { mount } from "radicle-svelte-unit-test";
-
import { styles } from "@test/support/index";
+
import { fireEvent, render } from "@testing-library/svelte";
+
import "@public/index.css";

const defaultProps = {
  project: {
@@ -18,41 +18,63 @@ const defaultProps = {
  },
  branches: { "master": "e678629cd37c770c640a2cd997fc76303c815772" },
  revision: "e678629cd37c770c640a2cd997fc76303c815772",
+
  toggleDropdown: () => "branch"
};

-
describe('BranchSelector', function () {
-
  it("Render with commit = head and branch listing", () => {
-
    mount(BranchSelector, {
-
      props: defaultProps
-
    }, styles);
-
    cy.findByText("master").should("exist");
-
    cy.get("div.commit > div.hash.desktop").should("have.text", "e678629");
+
describe('Logic', () => {
+
  it("should show defaultBranch label and head commit if revision == head", () => {
+
    const { rerender } = render(BranchSelector, {
+
      props: defaultProps,
+
    });
+
    cy.get("div.stat.branch").should("be.visible").should("have.text", "master");
+
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "e678629");
+

+
    // If project.head is null we should get the head from branches.
+
    rerender({
+
      props: {
+
        ...defaultProps,
+
        project: {
+
          head: null,
+
          urn: "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio",
+
          name: "nakamoto",
+
          description: "Privacy-preserving Bitcoin light-client implementation in Rust",
+
          defaultBranch: "master",
+
          maintainers: [
+
            "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"
+
          ],
+
          delegates: [
+
            "hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"
+
          ]
+
        }
+
      }
+
    });
+
    cy.get("div.stat.branch").should("be.visible").should("have.text", "master");
+
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "e678629");
  });

-
  it("Test Branch selection", () => {
-
    // The viewport here is to simulate desktop behaviour
-
    cy.viewport(800, 300);
-
    mount(BranchSelector, {
+
  it("should show the branch dropdown if branches available", () => {
+
    render(BranchSelector, {
      props: {
-
        ...defaultProps, branches: {
+
        ...defaultProps,
+
        branches: {
          "master": "e678629cd37c770c640a2cd997fc76303c815772",
          "feature-branch": "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5",
          "xyz": "debf82ef3623ec11751a993bda85bac2ff1c6f00",
        },
-
        branchesDropdown: true,
-
      }, callbacks: {
-
        branchChanged: cy.stub().as("branchChanged")
+
        branchesDropdown: true
      }
-
    }, styles);
-
    cy.get("div.dropdown > div").first().click();
-
    cy.get("@branchChanged")
-
      .should("be.calledOnce")
-
      .its("firstCall.args.0.detail")
-
      .should("equal", "feature-branch");
+
    });
+
    cy.get("div.dropdown div.dropdown-item")
+
      .first()
+
      .should("contain.text", "feature-branch")
+
      .next()
+
      .should("contain.text", "master").should("have.class", "selected")
+
      .next()
+
      .should("contain.text", "xyz");
  });

-
  it("Render with commit != head, passing a branch as rev and branch listing", () => {
-
    mount(BranchSelector, {
+
  it("should show feature-branch label and head commit, if branch label is passed as revision", () => {
+
    render(BranchSelector, {
      props: {
        ...defaultProps, branches: {
          "master": "e678629cd37c770c640a2cd997fc76303c815772",
@@ -61,42 +83,84 @@ describe('BranchSelector', function () {
        },
        revision: "feature-branch"
      }
-
    }, styles);
-
    cy.findByText("feature-branch").should("exist");
-
    cy.get("div.commit > div.hash.desktop").should("have.text", "29e8b7b");
+
    });
+
    cy.get("div.stat.branch").should("be.visible").should("have.text", "feature-branch");
+
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "29e8b7b");
+
  });
+

+
  it("should show only commit if no branchLabel nor branches are available", () => {
+
    render(BranchSelector, {
+
      props: {
+
        ...defaultProps,
+
        revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
+
        branches: {}
+
      }
+
    });
+
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "debf82e");
+
    cy.viewport("macbook-13");
+
    cy.get("div.hash.desktop").should("be.visible").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
  });

-
  it("Render with commit != head passing a commit as rev and branch listing", () => {
-
    mount(BranchSelector, {
+
  it("should show only commit if branches are available but no branchLabel", () => {
+
    render(BranchSelector, {
      props: {
        ...defaultProps,
-
        revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00"
+
        revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
      }
-
    }, styles
-
    );
-
    cy.get("div.commit > div.hash.desktop").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
+
    });
+
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "debf82e");
+
    cy.viewport("macbook-13");
+
    cy.get("div.hash.desktop").should("be.visible").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
  });

-
  it("Render with commit = head, without branch listing", () => {
-
    mount(BranchSelector, {
+
  it("should show defaultBranch label if revision == head", () => {
+
    render(BranchSelector, {
      props: {
        ...defaultProps,
        revision: "e678629cd37c770c640a2cd997fc76303c815772",
-
        branches: {},
+
        branches: {}
      }
-
    }, styles
-
    );
-
    cy.get("div.commit > div.hash.desktop").should("have.text", "e678629cd37c770c640a2cd997fc76303c815772");
+
    });
+
    cy.get("div.stat.branch.not-allowed").should("be.visible").should("have.text", "master");
  });
+
});
+

+
describe("Layout", () => {
+
  it("should show shortened commit when on mobile, and full hash when on desktop", () => {
+
    render(BranchSelector, {
+
      props: {
+
        ...defaultProps,
+
        revision: "e678629cd37c770c640a2cd997fc76303c815772"
+
      }
+
    });
+
    cy.viewport("iphone-x");
+
    cy.get("div.hash.mobile").should("be.visible");
+
    cy.get("div.hash.desktop").should("not.be.visible");
+
    cy.viewport("macbook-15");
+
    cy.get("div.hash.mobile").should("not.be.visible");
+
    cy.get("div.hash.desktop").should("be.visible");
+
  });
+
});

-
  it("Render without branch listing, commit != head", () => {
-
    mount(BranchSelector, {
+
describe("Events", () => {
+
  it("should dispatch a 'branchChanged' event on click", () => {
+
    const { getByText, component } = render(BranchSelector, {
      props: {
        ...defaultProps,
-
        revision: "6b84e519d3c535879eb2b9ee8457bb70ca275a75",
-
        branches: {},
+
        revision: "feature-branch",
+
        branchesDropdown: true,
+
        branches: {
+
          "feature-branch": "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5",
+
          "xyz": "debf82ef3623ec11751a993bda85bac2ff1c6f00",
+
        }
      }
-
    }, styles);
-
    cy.get("div.commit > div.hash.desktop").should("have.text", "6b84e519d3c535879eb2b9ee8457bb70ca275a75");
+
    });
+
    const branchLabel = getByText("xyz");
+

+
    const mock = cy.spy();
+
    component.$on("branchChanged", mock);
+

+
    fireEvent.click(branchLabel);
+
    expect(mock).to.have.been.calledOnce;
  });
});
modified src/base/projects/BranchSelector.svelte
@@ -19,7 +19,7 @@

  $: branchList = Object.keys(branches).sort().map(b => ({ key: b, value: b, badge: null }));
  $: showSelector = branchList.length > 1;
-
  $: head = branches[project.defaultBranch];
+
  $: head = project.head ?? branches[project.defaultBranch];
  $: commit = getOid(revision, branches) || head;
  $: if (commit == head) {
    branchLabel = project.defaultBranch;
modified src/base/projects/Header.svelte
@@ -188,7 +188,7 @@
  <div class="stat commit-count" class:active={content == ProjectContent.History} on:click={() => toggleContent(ProjectContent.History)}>
    <strong>{tree.stats.commits}</strong> commit(s)
  </div>
-
  <div class="stat">
+
  <div class="stat contributor-count">
    <strong>{tree.stats.contributors}</strong> contributor(s)
  </div>
</header>
modified src/base/projects/History.svelte
@@ -57,7 +57,7 @@
  <div class="history">
    {#each history.headers as group (group.time)}
      <div class="commit-group">
-
        <header>
+
        <header class="commit-date">
          <p>{group.time}</p>
        </header>
        <div class="commit-group-headers">
modified src/base/projects/PeerSelector.spec.ts
@@ -1,6 +1,6 @@
import PeerSelector from "./PeerSelector.svelte";
-
import { mount } from "radicle-svelte-unit-test";
-
import { styles } from "@test/support/index";
+
import { fireEvent, render } from "@testing-library/svelte";
+
import "@public/index.css";

const defaultProps = {
  peer: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
@@ -14,67 +14,82 @@ const defaultProps = {
  toggleDropdown: () => console.log("toggle"),
};

-
describe('PeerSelector', function () {
-
  it("Render correctly with default props", () => {
-
    mount(PeerSelector, {
+
describe('Logic', function () {
+
  it("show delegate name and badge", () => {
+
    render(PeerSelector, {
      props: defaultProps
-
    }, styles);
-
    cy.get("span.peer-id").should("has.text", "sebastinez");
-
    cy.get("span.badge").should("has.text", "delegate");
+
    });
+
    cy.get("span.peer-id").should("have.text", "sebastinez");
+
    cy.get("span.badge.primary").should("have.text", "delegate");
  });

-
  it("Test Peer selection", () => {
-
    mount(PeerSelector, {
+
  it("show peer id with badge if no name available", () => {
+
    render(PeerSelector, {
      props: {
-
        ...defaultProps, peers: [
+
        ...defaultProps,
+
        peers: [
          {
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
-
            "name": "sebastinez",
-
            "delegate": false
-
          },
-
          {
-
            "id": "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
-
            "name": "cloudhead",
            "delegate": true
-
          },
+
          }
        ],
-
        peersDropdown: true
-
      }, callbacks: {
-
        peerChanged: cy.stub().as("peerChanged")
      }
-
    }, styles);
-
    cy.get("div.dropdown > div").last().click();
-
    cy.get("@peerChanged")
-
      .should("be.calledOnce")
-
      .its("firstCall.args.0.detail")
-
      .should("equal", "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1");
+
    });
+
    cy.get("span.peer-id").should("have.text", "hyyg55…p7ofue");
+
    cy.get("span.badge.primary").should("have.text", "delegate");
  });
-
  it("If no peers are provided, no dropdown should be showed", () => {
-
    mount(PeerSelector, {
+

+
  it("show only peer id if no additional data available", () => {
+
    render(PeerSelector, {
      props: {
        ...defaultProps,
-
        peers: [],
-
        peersDropdown: true
+
        peers: [
+
          {
+
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
          }
+
        ]
      }
-
    }, styles);
-
    cy.get("div.dropdown > div.dropdown-item").should("not.exist");
+
    });
+
    cy.get("span.peer-id").should("have.text", "hyyg55…p7ofue");
  });
-
  it("If peer identity is not being resolved, fallback to peer id", () => {
-
    mount(PeerSelector, {
+
});
+

+
describe("Layout", () => {
+
  it("should highlight the current peer", () => {
+
    render(PeerSelector, {
+
      props: { ...defaultProps, peersDropdown: true }
+
    });
+
    cy.get("div.dropdown-item").should("have.class", "selected");
+
  });
+
});
+

+
describe('Events', () => {
+
  it("dispatch peerChanged event if clicking on a peer", () => {
+
    cy.viewport("macbook-13");
+
    const { getByText, component } = render(PeerSelector, {
      props: {
        ...defaultProps,
+
        peersDropdown: true,
        peers: [
          {
+
            "id": "hyy841u4phudmr8s5rg1jjwd1ct7x7438wmjwtsm464y8uyxyhyi6c",
+
            "name": "cloudhead",
+
            "delegate": true
+
          },
+
          {
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
            "name": "sebastinez",
+
            "delegate": true
          }
-
        ],
-
        peersDropdown: true
+
        ]
      }
-
    }, styles);
-
    cy.get("span.peer-id").should("has.text", "hyyg55…p7ofue");
-
    cy.get("span.badge").should("not.exist");
-
    cy.get("div.dropdown > .dropdown-item")
-
      .first()
-
      .should("contain", "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue");
+
    });
+

+
    const peer = getByText("cloudhead");
+
    const mock = cy.spy();
+
    component.$on("peerChanged", mock);
+

+
    fireEvent.click(peer);
+
    expect(mock).to.have.been.calledOnce;
  });
});
deleted src/base/seeds/Seed.spec.ts
@@ -1,48 +0,0 @@
-
import { Seed } from "./Seed";
-
import { Config } from "@app/config";
-

-
describe('Seed', function () {
-
  it("Constructs git host correctly", () => {
-
    const cfg = new Config({ name: "homestead", chainId: 1 }, null as any, null);
-
    const seed = new Seed({
-
      host: "seed.cloudhead.io",
-
      id: "hydzjm73tstmdcwhach3psfgkunbjpih7ztnuswhw9w6h9pi5sffd6",
-
      git: "git.seed.cloudhead.io",
-
    }, cfg);
-

-
    expect(seed.git.host).to.eq("git.seed.cloudhead.io");
-
    expect(seed.git.port).to.eq(443);
-

-
    expect(seed.host).to.eq("seed.cloudhead.io");
-
    expect(seed.api.host).to.eq(seed.host);
-
    expect(seed.api.port).to.eq(8777);
-
  });
-

-
  it("Constructs git host correctly with custom port", () => {
-
    const cfg = new Config({ name: "homestead", chainId: 1 }, null as any, null);
-
    const seed = new Seed({
-
      host: "seed.cloudhead.io",
-
      id: "hydzjm73tstmdcwhach3psfgkunbjpih7ztnuswhw9w6h9pi5sffd6",
-
      git: "https://git.seed.cloudhead.io:8778",
-
    }, cfg);
-

-
    expect(seed.git.host).to.eq("git.seed.cloudhead.io");
-
    expect(seed.git.port).to.eq(8778);
-
  });
-

-
  it("Constructs api host correctly", () => {
-
    const cfg = new Config({ name: "homestead", chainId: 1 }, null as any, null);
-
    const seed = new Seed({
-
      host: "seed.cloudhead.io",
-
      id: "hydzjm73tstmdcwhach3psfgkunbjpih7ztnuswhw9w6h9pi5sffd6",
-
      api: "https://api.seed.cloudhead.io:8080",
-
    }, cfg);
-

-
    expect(seed.api.host).to.eq("api.seed.cloudhead.io");
-
    expect(seed.api.port).to.eq(8080);
-

-
    expect(seed.host).to.eq("api.seed.cloudhead.io");
-
    expect(seed.git.host).to.eq("seed.cloudhead.io");
-
    expect(seed.git.port).to.eq(443);
-
  });
-
});
modified src/config.ts
@@ -8,8 +8,11 @@ import WalletConnect from "@walletconnect/client";
import config from "@app/config.json";
import { WalletConnectSigner } from "./WalletConnectSigner";

+

declare global {
  interface Window {
+
    // eslint-disable-next-line @typescript-eslint/naming-convention
+
    Cypress: any;
    ethereum: any;
    registrarState: any;
  }
@@ -246,8 +249,7 @@ export async function getConfig(): Promise<Config> {
  if (metamask) {
    // If Metamask is detected, we use the network configured there.
    const ready = await checkMetaMask(metamask);
-
    if (ready === null) throw new Error("Not able to connect to Metamask, check for multiple Web3 providers");
-
    network = ready;
+
    if (ready) network = ready;
  }

  const networkConfig = (<Record<string, any>> config)[network.name];
added src/polyfills/canvas.js
@@ -0,0 +1,7 @@
+
// A simple mock for some HTMLCanvasElement functions to run in a node env.
+

+
HTMLCanvasElement.prototype.getContext = () => ({
+
  fillRect: () => null
+
});
+

+
HTMLCanvasElement.prototype.toDataURL = () => null;
added src/polyfills/fetch.js
@@ -0,0 +1,9 @@
+
/* eslint-disable no-undef */
+
import fetch from 'node-fetch';
+

+
if (! globalThis.fetch) {
+
  globalThis.fetch = fetch;
+
  globalThis.Headers = fetch.Headers;
+
  globalThis.Request = fetch.Request;
+
  globalThis.Response = fetch.Response;
+
}
modified src/project.ts
@@ -169,7 +169,6 @@ export function path(opts: PathOptions): string {
}

// We need a SHA1 commit in some places, so we return early if the revision is a SHA and else we look into branches.
-
// As fallback we use the head commit.
export function getOid(revision: string, branches?: Branches): string | null {
  if (isOid(revision)) return revision;

added src/utils.test.ts
@@ -0,0 +1,276 @@
+
import { BigNumber } from "ethers";
+
import { describe, expect, test } from "vitest";
+
import type { Config } from "./config";
+
import * as utils from "./utils";
+

+
describe("Conversions", () => {
+
  test("toWei", () => {
+
    expect(
+
      utils.toWei("10")
+
    ).toEqual(BigNumber.from("10000000000000000000"));
+
  });
+
});
+

+
describe("Format functions", () => {
+
  test.each([
+
    { cid: "Qm1234567890123456789012345678901234567890", expected: "https://ipfs.io/ipfs/Qm1234567890123456789012345678901234567890" },
+
    { cid: undefined, expected: undefined }
+
  ])("formatIpfsFile $cid => $expected", ({ cid, expected }) => {
+
    expect(
+
      utils.formatIpfsFile(cid)
+
    ).toEqual(expected);
+
  });
+

+
  test.each([
+
    { amount: "1000", digits: 2, expected: "10.0" },
+
    { amount: "10000000000000000000", expected: "10.0" },
+
  ])("formatBalance", ({ amount, digits, expected }) => {
+
    expect(utils.formatBalance(BigNumber.from(amount), digits)).toEqual(expected);
+
  });
+

+
  test("formatRadicleId", () => {
+
    expect(
+
      utils.formatRadicleId(
+
        new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86])
+
      ))
+
      .toEqual("rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy");
+
  });
+

+
  test("formatProjectHash", () => {
+
    expect(
+
      utils.formatProjectHash(
+
        new Uint8Array([17, 20, 69, 234, 87, 208, 172, 127, 33, 22, 110, 216, 52, 69, 104, 219, 88, 66, 50, 152, 115, 23])
+
      )
+
    ).toEqual("45ea57d0ac7f21166ed8344568db584232987317");
+
  });
+

+
  test.each([
+
    { hash: "#L42", expected: 42 },
+
    { hash: "#ETH", expected: null },
+
  ])("formatLocationHash $hash => $expected", ({ hash, expected }) => {
+
    expect(
+
      utils.formatLocationHash(hash))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1", expected: "hydkkk…coygh1" }
+
  ])("formatSeedId $id => $expected", ({ id, expected }) => {
+
    expect(
+
      utils.formatSeedId(id))
+
      .toEqual(expected);
+
  });
+

+
  test("formatRadicleUrn", () => {
+
    expect(
+
      utils.formatRadicleUrn("rad:git:hnrkemobagsicpf9sr95o3g551otspcd84c9o"))
+
      .toEqual("rad:git:hnrkem…d84c9o");
+
  });
+

+
  test("formatRadicleUrn throw when wrong URN", () => {
+
    expect(() => utils.formatRadicleUrn("hnrkemobagsicpf9sr95o3g551otspcd84c9o"))
+
      .toThrow();
+
  });
+

+
  test("formatCAIP10Address", () => {
+
    expect(
+
      utils.formatCAIP10Address(
+
        "0x1234567890123456789012345678901234567890",
+
        "eip155",
+
        1
+
      ))
+
      .toEqual("0x1234567890123456789012345678901234567890@eip155:1");
+
  });
+

+
  test("formatAddress", () => {
+
    expect(
+
      utils.formatAddress(
+
        "0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511",
+
      )
+
    ).toEqual("b5d8 – F511");
+

+
    expect(
+
      () => utils.formatAddress(
+
        "0x8f91813",
+
      )
+
    ).toThrowError('invalid address (argument="address", value="0x8f91813", code=INVALID_ARGUMENT, version=address/5.5.0)');
+
  });
+

+
  test.each([
+
    { hash: "0x8f918133b56bb85c18ea192549503f0ea59e3beb1f88023f442656c660018e3a", expected: "0x8f91...8e3a" },
+
    { hash: "0x8f91813", expected: "0x8f91813" }, // If the string length is less than 10 characters the entire string is returned.
+
  ])("formatHash $hash => $expected", ({ hash, expected }) => {
+
    expect(
+
      utils.formatHash(hash)
+
    ).toEqual(expected);
+
  });
+

+
  test.each([
+
    { commit: "a8a6a979a6261a2ec1ea85fc9a65a4a30aa22cc8", expected: "a8a6a97" },
+
    { commit: "a8a6a97", expected: "a8a6a97" }
+
  ])("formatCommit $commit => $expected", ({ commit, expected }) => {
+
    expect(
+
      utils.formatCommit(commit)
+
    ).toEqual(expected);
+
  });
+
});
+

+
describe("String Assertions", () => {
+
  test.each([
+
    { a: "0x1234567890123456789012345678901234567890", b: "0x1234567890123456789012345678901234567890", expected: true },
+
    { a: "0x1234567890123456789012345678901234567890", b: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", expected: false }
+
  ])("isAddressEqual", ({ a, b, expected }) => {
+
    expect(
+
      utils.isAddressEqual(a, b))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { domain: "alt-clients.radicle.xyz", expected: true },
+
    { domain: "0.0.0.0", expected: true }, // Pass as true since we are not in production
+
    { domain: "", expected: false },
+
  ])("isDomain $domain => $expected", ({ domain, expected }) => {
+
    expect(
+
      utils.isDomain(domain))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { path: "README.md", expected: true },
+
    { path: "README.mkd", expected: true },
+
    { path: "README.markdown", expected: true },
+
    { path: "", expected: false },
+
  ])("isMarkdownPath $path => $expected", ({ path, expected }) => {
+
    expect(
+
      utils.isMarkdownPath(path))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { id: "rad:git:hnrkemobagsicpf9sr95o3g551otspcd84c9o", expected: true },
+
    { id: "0x1234567890123456789012345678901234567890", expected: false },
+
  ])("isRadicleId $id => $expected", ({ id, expected }) => {
+
    expect(
+
      utils.isRadicleId(id))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { oid: "a64ae9c6d572e0ad906faa9a4a7a8d43f113278c", expected: true },
+
    { oid: "a64ae9c", expected: false }
+
  ])("isOid $oid => $expected", ({ oid, expected }) => {
+
    expect(
+
      utils.isOid(oid))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { address: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", expected: true },
+
    { address: "0x5E813e48a81977c6fdd565ed5097eb600c73c4f0", expected: false }, // If address is badly checksummed => false
+
    { address: "0x5e813e48a81977c6fdd565ed5097eb600c73c4f0", expected: true },
+
  ])("isAddress $address => $expected", ({ address, expected }) => {
+
    expect(
+
      utils.isAddress(
+
        address,
+
      ))
+
      .toBe(expected);
+
  });
+

+
  test.each([
+
    { url: "https://app.radicle.network", expected: true },
+
    { url: "http://app.radicle.network", expected: true },
+
    { url: "http://app", expected: true },
+
    { url: "://app", expected: false },
+
    { url: "//app", expected: false },
+
    { url: "app", expected: false },
+
  ])("isUrl $url => $expected", ({ url, expected }) => {
+
    expect(
+
      utils.isUrl(
+
        url,
+
      ))
+
      .toBe(expected);
+
  });
+

+
  test.each([
+
    { did: "did:3:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383", expected: true },
+
    { did: "did:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383", expected: false },
+
  ])("isDid $did => $expected", ({ did, expected }) => {
+
    expect(
+
      utils.isDid(did))
+
      .toBe(expected);
+
  });
+
});
+

+
describe("Others", () => {
+
  test.each([
+
    { viewer: "https://gnosis-safe.io/app/#/safes", name: "", expected: "https://gnosis-safe.io/app/#/safes/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
    { viewer: null, name: "", expected: "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
  ])("safeLink $viewer => $expected", ({ name, viewer, expected }) => {
+
    expect(
+
      utils.safeLink(
+
        "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
        {
+
          network: {
+
            name
+
          },
+
          safe: {
+
            viewer
+
          }
+
        } as Config
+
      ))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { name: "rinkeby", expected: "https://rinkeby.etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
    { name: "", expected: "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
  ])("explorerLink $name => $expected", ({ name, expected }) => {
+
    expect(
+
      utils.explorerLink(
+
        "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
        {
+
          network: {
+
            name,
+
          }
+
        } as Config
+
      ))
+
      .toEqual(expected);
+
  });
+

+
  test.each([
+
    { id: "rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy", expected: new Uint8Array([156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86]) },
+
    { id: "hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy", expected: new Uint8Array([156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86]) }
+
  ])("decodeRadicleId", ({ id, expected }) => {
+
    expect(
+
      utils.decodeRadicleId(id))
+
      .toEqual(expected);
+
  });
+
});
+

+
describe("Parse Strings", () => {
+
  test.each([
+
    { label: "sebastinez.radicle.eth", expected: "sebastinez" },
+
    { label: "sebastinez", expected: "sebastinez" },
+
  ])("parseEnsLabel", ({ label, expected }) => {
+
    expect(
+
      utils.parseEnsLabel(
+
        label,
+
        {
+
          registrar: {
+
            address: "0x1234567890123456789012345678901234567890",
+
            domain: "radicle.eth"
+
          }
+
        } as Config
+
      )
+
    ).toEqual(expected);
+
  });
+

+
  test.each([
+
    { input: "https://twitter.com/cloudhead", expected: "cloudhead" },
+
    { input: "sebastinez", expected: "sebastinez" }
+
  ])("parseUsername", ({ input, expected }) => {
+
    expect(
+
      utils.parseUsername(input)
+
    ).toEqual(expected);
+
  });
+
});
modified src/utils.ts
@@ -98,6 +98,8 @@ export function formatSeedId(id: string): string {
}

export function formatRadicleUrn(id: string): string {
+
  assert(isRadicleId(id));
+

  return id.substring(0, 14)
    + '…'
    + id.substring(id.length - 6, id.length);
@@ -125,6 +127,7 @@ export function formatIpfsFile(ipfs: string | undefined): string | undefined {
  return undefined;
}

+
// If the string is less than 10 characters the entire string is returned.
export function formatHash(hash: string): string {
  if (hash.length < 10) return hash;
  return hash.substring(0, 6)
@@ -149,8 +152,7 @@ export function capitalize(s: string): string {
  return s[0].toUpperCase() + s.substring(1);
}

-
// Takes a domain name, eg. 'cloudhead.radicle.eth' and
-
// returns the label, eg. 'cloudhead', otherwise `undefined`.
+
// Takes a domain name, eg. 'cloudhead.radicle.eth' and returns the label, eg. 'cloudhead'.
export function parseEnsLabel(name: string, config: Config): string {
  const domain = config.registrar.domain.replace(".", "\\.");
  const label = name.replace(new RegExp(`\\.${domain}$`), "");
modified tsconfig.json
@@ -1,8 +1,7 @@
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "include": [
-
    "src",
-
    "cypress"
+
    "src"
  ],
  "exclude": [
    "node_modules/*"
@@ -26,8 +25,8 @@
    "importsNotUsedAsValues": "error",
    "skipLibCheck": true,
    "paths": {
+
      "@public/*": ["./public/*"],
      "@app/*": ["./src/*"],
-
      "@test/*": ["./cypress/*"]
    }
  },
  "noEmit": true
modified vite.config.ts
@@ -1,3 +1,4 @@
+
///<reference types="vitest" />
import path from 'path';
import { UserConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
@@ -8,8 +9,26 @@ const config: UserConfig = {
  optimizeDeps: {
    exclude: ['svelte-routing', '@pedrouid/environment', '@pedrouid/iso-crypto']
  },
+
  test: {
+
    deps: {
+
      inline: [
+
        "@ethersproject/signing-key",
+
        "@ethersproject/basex",
+
      ]
+
    },
+
    environment: "happy-dom",
+
    include: ["**/*.test.ts"],
+
    reporters: "verbose",
+
    coverage: {
+
      reporter: ["html"],
+
      all: true,
+
      excludeNodeModules: true,
+
      extension: [".svelte", ".ts", ".js"]
+
    },
+
  },
  plugins: [
    svelte({
+
      hot: !process.env.VITEST,
      compilerOptions: {
        dev: process.env.NODE_ENV !== "production"
      }
@@ -26,8 +45,8 @@ const config: UserConfig = {
    alias: {
      // This is needed for vite not to choke.
      "caip": path.resolve("./node_modules/caip/dist/umd/index.min.js"),
+
      '@public': path.resolve('./public'),
      '@app': path.resolve('./src'),
-
      '@test': path.resolve('./cypress'),
      // Polyfill for Node.js 'stream' library.
      'stream': path.resolve('./src/polyfills/stream.ts'),
      'typedarray-to-buffer': path.resolve('./src/polyfills/typedarray-to-buffer.js'),
@@ -46,4 +65,9 @@ const config: UserConfig = {
  }
};

+
// For Vitest to work we need to unset READABLE_STREAM.
+
if (process.env.VITEST) {
+
  config.define = undefined;
+
}
+

export default config;