Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Fix out-of-state commit listing when refreshing page after push
Sebastian Martinez committed 2 years ago
commit 99f27350c1a236975cd6fcbe96b69b1142bb3d5b
parent 277c8d23fd97ebcdf43e512211cd946c578c4b4a
16 files changed +202 -136
modified src/lib/router.ts
@@ -109,6 +109,41 @@ export async function replace(newRoute: Route): Promise<void> {
  await navigate("replace", newRoute);
}

+
// We need a SHA1 commit in some places, so we return early if the revision is
+
// a SHA and else we look into branches.
+
export function getOid(
+
  revision: string,
+
  branches?: Record<string, string>,
+
): string | undefined {
+
  if (isOid(revision)) return revision;
+

+
  if (branches) {
+
    const oid = branches[revision];
+
    if (oid) return oid;
+
  }
+
  return undefined;
+
}
+

+
// Check whether the input is a SHA1 commit.
+
export function isOid(input: string): boolean {
+
  return /^[a-fA-F0-9]{40}$/.test(input);
+
}
+

+
export function parseRevisionToOid(
+
  revision: string | undefined,
+
  defaultBranch: string,
+
  branches: Record<string, string>,
+
): string {
+
  if (revision) {
+
    const oid = getOid(revision, branches);
+
    if (!oid) {
+
      throw new Error(`Revision ${revision} not found`);
+
    }
+
    return oid;
+
  }
+
  return branches[defaultBranch];
+
}
+

function pathToRoute(path: string): Route | null {
  // This matches e.g. an empty string
  if (!path) {
@@ -266,4 +301,4 @@ export function routeToPath(route: Route) {
  }
}

-
export const testExports = { pathToRoute };
+
export const testExports = { pathToRoute, isOid, routeToPath };
modified src/lib/utils.ts
@@ -19,24 +19,6 @@ export function getRawBasePath(
  return `${baseUrl.scheme}://${baseUrl.hostname}:${baseUrl.port}/raw/${id}/${commit}`;
}

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

-
  if (branches) {
-
    const oid = branches[revision];
-

-
    if (oid) {
-
      return oid;
-
    }
-
  }
-
  return null;
-
}
-

export function formatLocationHash(hash: string | null): number | null {
  if (hash && hash.match(/^#L[0-9]+$/)) return parseInt(hash.slice(2));
  return null;
@@ -180,11 +162,6 @@ export const formatTimestamp = (
  return new Date(timestamp).toUTCString();
};

-
// Check whether the input is a SHA1 commit.
-
export function isOid(input: string): boolean {
-
  return /^[a-fA-F0-9]{40}$/.test(input);
-
}
-

// Check whether the input is a URL.
export function isUrl(input: string): boolean {
  return /^https?:\/\//.test(input);
modified src/views/projects/BranchSelector.svelte
@@ -1,5 +1,6 @@
<script lang="ts" strictEvents>
  import * as utils from "@app/lib/utils";
+
  import { parseRevisionToOid } from "@app/lib/router";

  import Dropdown from "@app/components/Dropdown.svelte";
  import DropdownItem from "@app/components/Dropdown/DropdownItem.svelte";
@@ -8,8 +9,7 @@

  export let branches: Record<string, string>;
  export let projectDefaultBranch: string;
-
  export let projectHead: string | undefined = undefined;
-
  export let revision: string;
+
  export let revision: string | undefined;

  let branchLabel: string | null = null;

@@ -17,12 +17,11 @@
    .sort()
    .map(b => ({ key: b, value: b, title: `Switch to ${b}`, badge: null }));
  $: showSelector = branchList.length > 1;
-
  $: head = projectHead ?? branches[projectDefaultBranch];
-
  $: commit = utils.getOid(revision, branches) || head;
-
  $: if (commit === head) {
-
    branchLabel = projectDefaultBranch;
-
  } else if (branches[revision]) {
+
  $: commit = parseRevisionToOid(revision, projectDefaultBranch, branches);
+
  $: if (revision && branches[revision]) {
    branchLabel = revision;
+
  } else if (commit === branches[projectDefaultBranch]) {
+
    branchLabel = projectDefaultBranch;
  } else {
    branchLabel = null;
  }
@@ -104,7 +103,7 @@
      {utils.formatCommit(commit)}
    </div>
    <!-- If there is no branch listing available, show default branch name if commit is head and else show entire commit -->
-
  {:else if commit === head}
+
  {:else if commit === branches[projectDefaultBranch]}
    <div class="stat branch not-allowed">
      {projectDefaultBranch}
    </div>
modified src/views/projects/Browser.svelte
@@ -14,6 +14,7 @@

  import * as utils from "@app/lib/utils";
  import { HttpdClient } from "@httpd-client";
+
  import { parseRevisionToOid } from "@app/lib/router";

  import Button from "@app/components/Button.svelte";
  import Loading from "@app/components/Loading.svelte";
@@ -34,11 +35,13 @@
  export let project: Project;
  export let baseUrl: BaseUrl;
  export let tree: Tree;
-
  export let commit: string;
+
  export let revision: string | undefined;
+
  export let branches: Record<string, string>;
  export let activeRoute: ProjectRoute;

  $: path = activeRoute.params.path || "/";
  $: line = activeRoute.params.line;
+
  $: commit = parseRevisionToOid(revision, project.defaultBranch, branches);

  // When the component is loaded the first time, the blob is yet to be loaded.
  let state: State = { status: Status.Loading, path };
@@ -196,6 +199,7 @@
            {path}
            {fetchTree}
            {loadingPath}
+
            revision={revision ?? project.defaultBranch}
            on:select={() => {
              // Close mobile tree if user navigates to other file
              mobileFileTree = false;
modified src/views/projects/SourceBrowsingHeader.svelte
@@ -12,7 +12,7 @@

  export let project: Project;
  export let activeRoute: ProjectRoute;
-
  export let revision: string;
+
  export let revision: string | undefined;
  export let peers: Remote[];
  export let branches: Record<string, string>;

@@ -51,11 +51,10 @@
  {/if}

  <BranchSelector
-
    projectDefaultBranch={project.defaultBranch}
-
    projectHead={project.head}
    {branches}
    {revision}
-
    on:click={() => closeFocused()} />
+
    on:click={() => closeFocused()}
+
    projectDefaultBranch={project.defaultBranch} />

  <ProjectLink
    projectParams={{
@@ -63,7 +62,7 @@
      view: {
        resource: "history",
      },
-
      revision: revision,
+
      revision,
      search: undefined,
    }}>
    <SquareButton
modified src/views/projects/Tree.svelte
@@ -10,6 +10,7 @@
  export let fetchTree: (path: string) => Promise<Tree | undefined>;
  export let path: string;
  export let tree: Tree;
+
  export let revision: string;
  export let loadingPath: string | null = null;

  const dispatch = createEventDispatcher<{ select: string }>();
@@ -23,16 +24,14 @@
    <Folder
      {fetchTree}
      {loadingPath}
+
      {revision}
      name={entry.name}
      prefix={`${entry.path}/`}
      currentPath={path}
      on:select={onSelect} />
  {:else}
    <ProjectLink
-
      projectParams={{
-
        view: { resource: "tree" },
-
        path: entry.path,
-
      }}
+
      projectParams={{ view: { resource: "tree" }, path: entry.path, revision }}
      on:click={() => onSelect({ detail: entry.path })}>
      <File
        active={entry.path === path}
modified src/views/projects/Tree/Folder.svelte
@@ -13,6 +13,7 @@
  export let prefix: string;
  export let currentPath: string;
  export let loadingPath: string | null = null;
+
  export let revision: string;

  let expanded = currentPath.indexOf(prefix) === 0;
  let tree: Promise<Tree | undefined> = fetchTree(prefix).then(tree => {
@@ -83,12 +84,14 @@
              on:select={onSelectFile}
              prefix={`${entry.path}/`}
              {loadingPath}
+
              {revision}
              {currentPath} />
          {:else}
            <ProjectLink
              projectParams={{
                view: { resource: "tree" },
                path: entry.path,
+
                revision,
              }}
              on:click={() => onSelectFile({ detail: entry.path })}>
              <File
modified src/views/projects/View.svelte
@@ -32,6 +32,7 @@

  $: id = activeRoute.params.id;
  $: peer = activeRoute.params.peer;
+
  $: revision = activeRoute.params.revision;

  $: searchParams = new URLSearchParams(activeRoute.params.search || "");
  $: issueFilter = (searchParams.get("state") as IssueStatus) || "open";
@@ -48,23 +49,22 @@
    input: string,
    branches: Record<string, string>,
  ): { path?: string; revision?: string } {
+
    const parsed: { path?: string; revision?: string } = {};
+
    const commitPath = [input.slice(0, 40), input.slice(41)];
    const branch = Object.entries(branches).find(([branchName]) =>
      input.startsWith(branchName),
    );
-
    const commitPath = [input.slice(0, 40), input.slice(41)];
-
    const parsed: { path?: string; revision?: string } = {};

    if (branch) {
      const [rev, path] = [
        input.slice(0, branch[0].length),
        input.slice(branch[0].length + 1),
      ];
-

      parsed.revision = rev;
-
      parsed.path = path ? path : "/";
-
    } else if (utils.isOid(commitPath[0])) {
+
      parsed.path = path || "/";
+
    } else if (router.isOid(commitPath[0])) {
      parsed.revision = commitPath[0];
-
      parsed.path = commitPath[1] ? commitPath[1] : "/";
+
      parsed.path = commitPath[1] || "/";
    } else {
      parsed.path = input;
    }
@@ -98,28 +98,19 @@
        { replace: true },
      );
    }
-
    if (!activeRoute.params.revision) {
-
      // We need a revision to fetch `getRoot`.
-
      // Don't use router.updateProjectRoute, to avoid changing the URL.
-
      activeRoute.params.revision = project.defaultBranch;
-
    }

    return { project, branches, peers };
  };

  async function getRoot(
-
    revision: string | null,
    branches: Record<string, string>,
-
    head: string,
-
  ): Promise<{ tree: Tree; commit: string }> {
-
    const commit = revision ? utils.getOid(revision, branches) : head;
-

-
    if (!commit) {
-
      throw new Error(`Revision ${revision} not found`);
-
    }
+
    defaultBranch: string,
+
    revision?: string,
+
  ): Promise<{ tree: Tree }> {
+
    const commit = router.parseRevisionToOid(revision, defaultBranch, branches);
    const tree = await api.project.getTree(id, commit);

-
    return { tree, commit };
+
    return { tree };
  }

  function handleIssueCreation({ detail: issueId }: CustomEvent<string>) {
@@ -144,9 +135,6 @@

  // React to peer changes
  $: projectPromise = getProject(id, peer);
-

-
  // Content can be altered in child components.
-
  $: revision = activeRoute.params.revision || null;
</script>

<style>
@@ -184,11 +172,16 @@
    </header>
  </main>
{:then { project, peers, branches }}
+
  {@const commit = router.parseRevisionToOid(
+
    revision,
+
    project.defaultBranch,
+
    branches,
+
  )}
  <main>
    <ProjectMeta {project} nodeId={peer} />
-
    {#await getRoot(revision, branches, project.head)}
+
    {#await getRoot(branches, project.defaultBranch, revision)}
      <Loading center />
-
    {:then { tree, commit }}
+
    {:then { tree }}
      <Header
        projectId={project.id}
        projectName={project.name}
@@ -201,15 +194,21 @@
        <SourceBrowsingHeader
          {project}
          {activeRoute}
-
          revision={activeRoute.params.revision ?? commit}
          {peers}
          {branches}
          commitCount={tree.stats.commits}
-
          contributorCount={tree.stats.contributors} />
+
          contributorCount={tree.stats.contributors}
+
          revision={activeRoute.params.revision} />
      {/if}

      {#if activeRoute.params.view.resource === "tree"}
-
        <Browser {baseUrl} {project} {commit} {tree} {activeRoute} />
+
        <Browser
+
          {baseUrl}
+
          {project}
+
          {revision}
+
          {branches}
+
          {tree}
+
          {activeRoute} />
      {:else if activeRoute.params.view.resource === "history"}
        <History projectId={project.id} {baseUrl} parentCommit={commit} />
      {:else if activeRoute.params.view.resource === "commits"}
modified tests/e2e/hashRouter.spec.ts
@@ -61,7 +61,7 @@ test.describe("project page navigation", () => {
    await expect(page).toHaveURL(projectTreeURL);

    await page.locator('role=link[name="8 commits"]').click();
-
    await expect(page).toHaveURL(`/#${projectFixtureUrl}/history/main`);
+
    await expect(page).toHaveURL(`/#${projectFixtureUrl}/history`);

    await expectBackAndForwardNavigationWorks(projectTreeURL, page);
    await expectUrlPersistsReload(page);
modified tests/e2e/project/commits.spec.ts
@@ -127,12 +127,41 @@ test("pushing changes while viewing history", async ({ page, peerManager }) => {
  const { rid, projectFolder } = await alice.createProject("alice-project");
  await page.goto(`/seeds/127.0.0.1:8090/${rid}`);
  await page.locator('role=link[name="1 commit"]').click();
+
  await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}/history`);

-
  alice.setCwd(projectFolder);
-
  await alice.git(["commit", "--allow-empty", "--message", "first change"]);
-
  await alice.git(["push", "rad", "main"]);
+
  await alice.git(["commit", "--allow-empty", "--message", "first change"], {
+
    cwd: projectFolder,
+
  });
+
  await alice.git(["push", "rad", "main"], {
+
    cwd: projectFolder,
+
  });
  await page.reload();
-
  await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}/history/main`);
-
  await page.locator('role=link[name="2 commits"]').click();
+
  await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}/history`);
+
  await expect(page.locator('role=link[name="2 commits"]')).toBeVisible();
  await expect(page.getByTitle("Current branch")).toContainText("main 516fa74");
+

+
  await page.locator("text=alice-project").click();
+
  await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}`);
+
  await page.locator('role=link[name="2 commits"]').click();
+

+
  await alice.git(
+
    [
+
      "commit",
+
      "--allow-empty",
+
      "--message",
+
      "after clicking the project title",
+
    ],
+
    {
+
      cwd: projectFolder,
+
    },
+
  );
+
  await alice.git(["push", "rad", "main"], {
+
    cwd: projectFolder,
+
  });
+
  await page.reload();
+
  await expect(page).toHaveURL(`/seeds/127.0.0.1:8090/${rid}/history`);
+
  await expect(page.locator('role=link[name="3 commits"]')).toHaveText(
+
    "3 commits",
+
  );
+
  await expect(page.getByTitle("Current branch")).toContainText("main bb9089a");
});
modified tests/support/fixtures.ts
@@ -141,16 +141,9 @@ export const test = base.extend<{

  // eslint-disable-next-line no-empty-pattern
  stateDir: async ({}, use, testInfo) => {
-
    // Ok, we're moving the state dir to /tmp to avoid hitting the SUN_LEN limit, due to socket files.
-
    // We still create a symlink in the testInfo.outputDir, so that we can easily find the state dir.
-
    const stateDir = Path.join(
-
      "/tmp",
-
      Path.basename(testInfo.testId.substring(0, 8)),
-
    );
+
    const stateDir = testInfo.outputDir;
    await Fs.rm(stateDir, { recursive: true, force: true });
-
    await Fs.rm(testInfo.outputDir, { recursive: true, force: true });
    await Fs.mkdir(stateDir, { recursive: true });
-
    await Fs.symlink(stateDir, testInfo.outputDir);

    await use(stateDir);
    if (
@@ -158,7 +151,6 @@ export const test = base.extend<{
      (testInfo.status === "passed" || testInfo.status === "skipped")
    ) {
      await Fs.rm(stateDir, { recursive: true });
-
      await Fs.rm(testInfo.outputDir, { recursive: true });
    }
  },
});
@@ -242,10 +234,12 @@ export async function createSeedFixture() {
    name: "alice",
    gitOptions: gitOptions["alice"],
  });
+
  const aliceProjectPath = Path.join(alice.checkoutPath, "source-browsing");
  const bob = await peerManager.startPeer({
    name: "bob",
    gitOptions: gitOptions["bob"],
  });
+
  const bobProjectPath = Path.join(bob.checkoutPath, "source-browsing");
  await palm.startNode({ trackingPolicy: "track", trackingScope: "all" });
  await alice.startNode();
  await bob.startNode();
@@ -253,23 +247,25 @@ export async function createSeedFixture() {
  await bob.connect(palm);

  await alice.git(["clone", sourceBrowsingDir], { cwd: alice.checkoutPath });
-
  alice.setCwd(Path.join(alice.checkoutPath, "source-browsing"));
-
  await alice.git(["checkout", "feature/branch"]);
-
  await alice.git(["checkout", "orphaned-branch"]);
-
  await alice.git(["checkout", "main"]);
-
  await alice.rad([
-
    "init",
-
    "--name",
-
    projectName,
-
    "--default-branch",
-
    "main",
-
    "--description",
-
    "Git repository for source browsing tests",
-
    "--announce",
-
  ]);
+
  await alice.git(["checkout", "feature/branch"], { cwd: aliceProjectPath });
+
  await alice.git(["checkout", "orphaned-branch"], { cwd: aliceProjectPath });
+
  await alice.git(["checkout", "main"], { cwd: aliceProjectPath });
+
  await alice.rad(
+
    [
+
      "init",
+
      "--name",
+
      projectName,
+
      "--default-branch",
+
      "main",
+
      "--description",
+
      "Git repository for source browsing tests",
+
      "--announce",
+
    ],
+
    { cwd: aliceProjectPath },
+
  );
  // Needed due to rad init not pushing all branches.
-
  await alice.git(["push", "rad", "--all"]);
-
  await alice.rad(["track", bob.nodeId]);
+
  await alice.git(["push", "rad", "--all"], { cwd: aliceProjectPath });
+
  await alice.rad(["track", bob.nodeId], { cwd: aliceProjectPath });

  await alice.waitForRoutes(rid, alice.nodeId, palm.nodeId);
  await bob.waitForRoutes(rid, alice.nodeId, palm.nodeId);
@@ -285,16 +281,27 @@ export async function createSeedFixture() {
    Path.join(bob.checkoutPath, "source-browsing", "README.md"),
    "Updated readme",
  );
-
  bob.setCwd(Path.join(bob.checkoutPath, "source-browsing"));
-
  await bob.git(["add", "README.md"]);
-
  await bob.git([
-
    "commit",
-
    "--message",
-
    "Update readme",
-
    "--date",
-
    "Mon Dec 21 14:00 2022 +0100",
-
  ]);
-
  await bob.git(["push", "rad"]);
+
  await bob.git(["add", "README.md"], { cwd: bobProjectPath });
+
  await bob.git(
+
    [
+
      "commit",
+
      "--message",
+
      "Update readme",
+
      "--date",
+
      "Mon Dec 21 14:00 2022 +0100",
+
    ],
+
    { cwd: bobProjectPath },
+
  );
+
  await bob.git(["push", "rad"], { cwd: bobProjectPath });
+

+
  await bob.waitForEvent(
+
    { type: "refs-synced", remote: palm.nodeId, rid },
+
    2000,
+
  );
+
  await bob.waitForEvent(
+
    { type: "refs-synced", remote: alice.nodeId, rid },
+
    2000,
+
  );
}

export const aliceMainHead = "fcc929424b82984b7cbff9c01d2e20d9b1249842";
modified tests/support/heartwood-version
@@ -1 +1 @@
-
29507b550d6dcea5082baf1f5049ef93b8cc34a8
+
97f122c4512fbc5ea0b04a11db01d0d2d0977527
modified tests/support/peerManager.ts
@@ -2,6 +2,7 @@
import type { ExecaChildProcess, Options } from "execa";

import * as Fs from "node:fs/promises";
+
import * as Os from "node:os";
import * as Path from "node:path";
import * as Stream from "node:stream";
import getPort from "get-port";
@@ -10,6 +11,7 @@ import waitOn from "wait-on";
import { execa } from "execa";

import * as Process from "./process.js";
+
import { randomTag } from "@tests/support/support.js";
import { sleep } from "@app/lib/sleep.js";

export type RefsUpdate =
@@ -108,8 +110,8 @@ export class RadiclePeer {
  public nodeId: string;

  #seed: string;
+
  #socket: string;
  #radHome: string;
-
  #cwd?: string;
  #eventRecords: NodeEvent[] = [];
  #outputLog: Stream.Writable;
  #gitOptions?: Record<string, string>;
@@ -119,6 +121,7 @@ export class RadiclePeer {
    checkoutPath: string;
    nodeId: string;
    seed: string;
+
    socket: string;
    gitOptions?: Record<string, string>;
    radHome: string;
    logFile: Stream.Writable;
@@ -127,6 +130,7 @@ export class RadiclePeer {
    this.nodeId = props.nodeId;
    this.#gitOptions = props.gitOptions;
    this.#seed = props.seed;
+
    this.#socket = props.socket;
    this.#radHome = props.radHome;
    this.#outputLog = props.logFile;
  }
@@ -152,11 +156,17 @@ export class RadiclePeer {
    const radHome = Path.join(dataPath, name, "home");
    await Fs.mkdir(radHome, { recursive: true });

+
    const socketDir = await Fs.mkdtemp(
+
      Path.join(Os.tmpdir(), `radicle-${randomTag()}`),
+
    );
+
    const socket = Path.join(socketDir, "control.sock");
+

    const env = {
      ...gitOptions,
      RAD_HOME: radHome,
      RAD_PASSPHRASE: "asdf",
      RAD_SEED: seed,
+
      RAD_SOCKET: socket,
    };

    await execa("rad", ["auth"], { env });
@@ -166,6 +176,7 @@ export class RadiclePeer {
      checkoutPath,
      gitOptions,
      seed,
+
      socket,
      nodeId,
      radHome,
      logFile,
@@ -177,7 +188,7 @@ export class RadiclePeer {
    this.spawn("radicle-httpd", ["--listen", `0.0.0.0:${port}`]);

    await waitOn({
-
      resources: [`tcp:0.0.0.0:${port}`],
+
      resources: [`tcp:127.0.0.1:${port}`],
      timeout: 7000,
    });
  }
@@ -205,10 +216,10 @@ export class RadiclePeer {
    }

    // eslint-disable-next-line @typescript-eslint/no-floating-promises
-
    this.spawn("radicle-node", args);
+
    void this.spawn("radicle-node", args);

    await waitOn({
-
      resources: [`socket:${Path.join(this.#radHome, "node", "control.sock")}`],
+
      resources: [`socket:${this.#socket}`],
    });

    this.rad(["node", "events"], { cwd: this.#radHome }).stdout?.on(
@@ -272,12 +283,13 @@ export class RadiclePeer {
    let remaining = nodes;

    while (remaining.length > 0) {
-
      const { stdout: entries } = await this.rad(
-
        ["node", "routing", "--rid", rid, "--json"],
-
        {
-
          cwd: this.#radHome,
-
        },
-
      );
+
      const { stdout: entries } = await this.rad([
+
        "node",
+
        "routing",
+
        "--rid",
+
        rid,
+
        "--json",
+
      ]);

      entries.split("\n").forEach(entry => {
        if (entry && entry.trim() !== "") {
@@ -323,10 +335,6 @@ export class RadiclePeer {
    return `/seeds/127.0.0.1:8080/${rid}`;
  }

-
  public setCwd(cwd: string) {
-
    this.#cwd = cwd;
-
  }
-

  public git(args: string[] = [], opts?: Options): ExecaChildProcess {
    return this.spawn("git", args, { ...opts });
  }
@@ -341,7 +349,6 @@ export class RadiclePeer {
    opts?: Options,
  ): ExecaChildProcess {
    opts = {
-
      cwd: this.#cwd,
      ...opts,
      env: {
        ...opts?.env,
@@ -352,6 +359,7 @@ export class RadiclePeer {
        RAD_PASSPHRASE: "asdf",
        RAD_COMMIT_TIME: "1671125284",
        RAD_SEED: this.#seed,
+
        RAD_SOCKET: this.#socket,
      },
    };
    const childProcess = Process.spawn(cmd, args, opts);
added tests/support/support.ts
@@ -0,0 +1,6 @@
+
import * as Crypto from "node:crypto";
+

+
// Generate string of 12 random characters with 8 bits of entropy.
+
export function randomTag(): string {
+
  return Crypto.randomBytes(8).toString("hex");
+
}
modified tests/unit/router.test.ts
@@ -1,10 +1,18 @@
import { describe, expect, test } from "vitest";
-
import { routeToPath } from "@app/lib/router";
import { testExports } from "@app/lib/router";

// Defining the window.origin value, since vitest doesn't provide one.
window.origin = "http://localhost:3000";

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

describe("routeToPath", () => {
  test.each([
    { input: { resource: "home" }, output: "/", description: "Home Route" },
@@ -29,7 +37,7 @@ describe("routeToPath", () => {
      description: "Seed Project Route",
    },
  ])("$description", (route: any) => {
-
    expect(routeToPath(route.input)).toEqual(route.output);
+
    expect(testExports.routeToPath(route.input)).toEqual(route.output);
  });
});

modified tests/unit/utils.test.ts
@@ -108,13 +108,6 @@ describe("String Assertions", () => {
  });

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

-
  test.each([
    { url: "https://app.radicle.xyz", expected: true },
    { url: "http://app.radicle.xyz", expected: true },
    { url: "http://app", expected: true },