// Copyright 2026 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT import { test, describe } from "node:test"; import assert from "node:assert/strict"; import { parseCommitHref, isSimpleTypeSelector, subtractIdMap, isCommitHash } from "../custom/public/assets/viewer-util.js"; // --------------------------------------------------------------------------- // parseCommitHref // --------------------------------------------------------------------------- describe("parseCommitHref", () => { test("parses a standard Forgejo browse path", () => { const r = parseCommitHref("/org/repo/src/commit/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/model.ifc"); assert.equal(r.repoPath, "org/repo"); assert.equal(r.hash, "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"); assert.equal(r.treePath, "model.ifc"); }); test("parses a path with subdirectories", () => { const r = parseCommitHref("/org/repo/src/commit/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2/subdir/model.ifc"); assert.equal(r.treePath, "subdir/model.ifc"); }); test("returns null for non-commit paths", () => { assert.equal(parseCommitHref("/org/repo/src/branch/main/model.ifc"), null); assert.equal(parseCommitHref("/org/repo"), null); assert.equal(parseCommitHref(""), null); }); test("returns null for a commit directory link (no file path)", () => { // Breadcrumb anchors on a file-view page link to the directory at the // commit, e.g. /org/repo/src/commit/{hash} with no trailing file path. // These must not be mistaken for a file permalink. assert.equal( parseCommitHref("/org/repo/src/commit/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"), null ); }); test("returns null if hash is not 40 hex chars", () => { assert.equal(parseCommitHref("/org/repo/src/commit/abc123/model.ifc"), null); }); test("is case-insensitive for hex hash", () => { const r = parseCommitHref("/org/repo/src/commit/A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2/model.ifc"); assert.ok(r !== null); assert.equal(r.hash, "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"); }); }); // --------------------------------------------------------------------------- // isSimpleTypeSelector // --------------------------------------------------------------------------- describe("isSimpleTypeSelector", () => { test("accepts a single IFC type", () => { assert.equal(isSimpleTypeSelector("IfcWall"), true); }); test("accepts multiple types joined by +", () => { assert.equal(isSimpleTypeSelector("IfcWall+IfcSlab"), true); assert.equal(isSimpleTypeSelector("IfcWall+IfcSlab+IfcBeam"), true); }); test("accepts subtypes", () => { assert.equal(isSimpleTypeSelector("IfcWallStandardCase"), true); }); test("accepts lowercase ifc prefix", () => { assert.equal(isSimpleTypeSelector("ifcWall"), true); }); test("rejects name filter", () => { assert.equal(isSimpleTypeSelector('IfcWall, Name="Core Wall"'), false); }); test("rejects property set filter", () => { assert.equal(isSimpleTypeSelector("IfcWall[Pset_WallCommon.IsExternal=true]"), false); }); test("rejects bare GlobalId (not an IFC type name)", () => { assert.equal(isSimpleTypeSelector("325Q7Fhnf67OZC$$r43uzK"), false); }); test("rejects empty string", () => { assert.equal(isSimpleTypeSelector(""), false); }); }); // --------------------------------------------------------------------------- // subtractIdMap // --------------------------------------------------------------------------- describe("subtractIdMap", () => { test("removes ids present in toRemove", () => { const all = { m1: [1, 2, 3, 4] }; const remove = { m1: [2, 4] }; const result = subtractIdMap(all, remove); assert.deepEqual(result, { m1: [1, 3] }); }); test("omits model keys where all ids are removed", () => { const all = { m1: [1, 2] }; const remove = { m1: [1, 2] }; const result = subtractIdMap(all, remove); assert.deepEqual(result, {}); }); test("leaves models untouched that are not in toRemove", () => { const all = { m1: [1, 2], m2: [3, 4] }; const remove = { m1: [1] }; const result = subtractIdMap(all, remove); assert.deepEqual(result, { m1: [2], m2: [3, 4] }); }); test("handles empty toRemove", () => { const all = { m1: [1, 2, 3] }; const result = subtractIdMap(all, {}); assert.deepEqual(result, { m1: [1, 2, 3] }); }); test("handles multiple models in toRemove", () => { const all = { m1: [1, 2], m2: [3, 4] }; const remove = { m1: [2], m2: [3] }; const result = subtractIdMap(all, remove); assert.deepEqual(result, { m1: [1], m2: [4] }); }); }); // --------------------------------------------------------------------------- // isCommitHash // --------------------------------------------------------------------------- describe("isCommitHash", () => { test("accepts a full 40-char SHA", () => { assert.equal(isCommitHash("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"), true); }); test("accepts a 7-char short SHA", () => { assert.equal(isCommitHash("a1b2c3d"), true); }); test("accepts lengths between 7 and 40", () => { assert.equal(isCommitHash("a1b2c3d4e5f6"), true); }); test("rejects heads/ ref", () => { assert.equal(isCommitHash("heads/main"), false); }); test("rejects tags/ ref", () => { assert.equal(isCommitHash("tags/v1.0"), false); }); test("rejects uppercase hex (hashes are lowercase)", () => { assert.equal(isCommitHash("A1B2C3D"), false); }); test("rejects fewer than 7 chars", () => { assert.equal(isCommitHash("a1b2c3"), false); }); test("rejects more than 40 chars", () => { assert.equal(isCommitHash("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2x"), false); }); test("rejects empty string", () => { assert.equal(isCommitHash(""), false); }); });