mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 02:08:13 +00:00
JS files had wrong copyright owner (The Forgejo Authors → Bruno Postle). Python files lacked SPDX-License-Identifier: LGPL-3.0-or-later. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
219 lines
8.8 KiB
JavaScript
219 lines
8.8 KiB
JavaScript
// Copyright 2026 Bruno Postle <bruno@postle.net>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import { test, describe } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { parseIfcUrl, buildIfcUrl, toRawUrl } from "../custom/public/assets/viewer-url.js";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// parseIfcUrl
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("parseIfcUrl", () => {
|
|
test("returns null for non-ifc URLs", () => {
|
|
assert.equal(parseIfcUrl("https://example.com"), null);
|
|
assert.equal(parseIfcUrl(""), null);
|
|
assert.equal(parseIfcUrl(null), null);
|
|
assert.equal(parseIfcUrl(undefined), null);
|
|
});
|
|
|
|
test("parses host and repo path", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(r.host, "example.com");
|
|
assert.equal(r.repoSuffix, "org/repo");
|
|
assert.equal(r.userAt, "");
|
|
});
|
|
|
|
test("parses user@ authority", () => {
|
|
const r = parseIfcUrl("ifc://git@example.com/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(r.userAt, "git@");
|
|
assert.equal(r.host, "example.com");
|
|
assert.equal(r.repo, "git@example.com/org/repo");
|
|
});
|
|
|
|
test("parses heads/ ref", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?path=a.ifc");
|
|
assert.equal(r.ref, "heads/main");
|
|
});
|
|
|
|
test("parses tags/ ref", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@tags/v1.0?path=a.ifc");
|
|
assert.equal(r.ref, "tags/v1.0");
|
|
});
|
|
|
|
test("parses bare commit hash ref", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@abc123def456?path=a.ifc");
|
|
assert.equal(r.ref, "abc123def456");
|
|
});
|
|
|
|
test("parses path parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?path=subdir/model.ifc");
|
|
assert.equal(r.path, "subdir/model.ifc");
|
|
});
|
|
|
|
test("parses selector parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?selector=IfcWall");
|
|
assert.equal(r.selector, "IfcWall");
|
|
});
|
|
|
|
test("parses query parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?selector=IfcWall&query=Pset_WallCommon.FireRating");
|
|
assert.equal(r.query, "Pset_WallCommon.FireRating");
|
|
});
|
|
|
|
test("parses camera parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?camera=1,2,3,4,5,6,7,8,9");
|
|
assert.equal(r.camera, "1,2,3,4,5,6,7,8,9");
|
|
});
|
|
|
|
test("parses fov parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?fov=60");
|
|
assert.equal(r.fov, "60");
|
|
});
|
|
|
|
test("parses scale parameter", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main?scale=0.01");
|
|
assert.equal(r.scale, "0.01");
|
|
});
|
|
|
|
test("defaults missing parameters to empty string", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main");
|
|
assert.equal(r.path, "");
|
|
assert.equal(r.selector, "");
|
|
assert.equal(r.query, "");
|
|
assert.equal(r.camera, "");
|
|
assert.equal(r.fov, "");
|
|
assert.equal(r.scale, "");
|
|
});
|
|
|
|
test("builds repo field as userAt + host + / + repoSuffix", () => {
|
|
const r = parseIfcUrl("ifc://example.com/org/repo@heads/main");
|
|
assert.equal(r.repo, "example.com/org/repo");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// buildIfcUrl
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("buildIfcUrl", () => {
|
|
test("returns null when repo is missing", () => {
|
|
assert.equal(buildIfcUrl("", "heads/main", "model.ifc", ""), null);
|
|
assert.equal(buildIfcUrl(null, "heads/main", "", ""), null);
|
|
});
|
|
|
|
test("returns null when ref is missing", () => {
|
|
assert.equal(buildIfcUrl("example.com/org/repo", "", "model.ifc", ""), null);
|
|
assert.equal(buildIfcUrl("example.com/org/repo", null, "", ""), null);
|
|
});
|
|
|
|
test("builds minimal URL with no path or selector", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "", "");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main");
|
|
});
|
|
|
|
test("includes path in query string", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "model.ifc", "");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main?path=model.ifc");
|
|
});
|
|
|
|
test("includes selector in query string", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "", "IfcWall");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main?selector=IfcWall");
|
|
});
|
|
|
|
test("includes both path and selector", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "model.ifc", "IfcWall");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main?path=model.ifc&selector=IfcWall");
|
|
});
|
|
|
|
test("includes query parameter", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "model.ifc", "IfcWall", "Pset_WallCommon.FireRating");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main?path=model.ifc&selector=IfcWall&query=Pset_WallCommon.FireRating");
|
|
});
|
|
|
|
test("omits query when empty", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "model.ifc", "IfcWall", "");
|
|
assert.equal(url, "ifc://example.com/org/repo@heads/main?path=model.ifc&selector=IfcWall");
|
|
});
|
|
|
|
test("does not percent-encode commas in selector", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "", "IfcWall,Name=\"Core\"");
|
|
assert.ok(url.includes(","), "comma should not be encoded");
|
|
assert.ok(!url.includes("%2C"), "%2C should not appear");
|
|
});
|
|
|
|
test("does not percent-encode + in selector", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "", "IfcWall+IfcSlab");
|
|
assert.ok(!url.includes("%2B"), "%2B should not appear");
|
|
});
|
|
|
|
test("does not percent-encode $ in selector (GlobalId syntax)", () => {
|
|
const url = buildIfcUrl("example.com/org/repo", "heads/main", "", "325Q7Fhnf67OZC$$r43uzK");
|
|
assert.ok(!url.includes("%24"), "%24 should not appear");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// toRawUrl
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("toRawUrl", () => {
|
|
// GitHub
|
|
test("generates GitHub raw URL for heads/ ref", () => {
|
|
const url = toRawUrl("ifc://github.com/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(url, "https://raw.githubusercontent.com/org/repo/main/model.ifc");
|
|
});
|
|
|
|
test("generates GitHub raw URL for tags/ ref", () => {
|
|
const url = toRawUrl("ifc://github.com/org/repo@tags/v1.0?path=model.ifc");
|
|
assert.equal(url, "https://raw.githubusercontent.com/org/repo/v1.0/model.ifc");
|
|
});
|
|
|
|
test("generates GitHub raw URL for bare commit hash", () => {
|
|
const url = toRawUrl("ifc://github.com/org/repo@abc123?path=model.ifc");
|
|
assert.equal(url, "https://raw.githubusercontent.com/org/repo/abc123/model.ifc");
|
|
});
|
|
|
|
// GitLab
|
|
test("generates GitLab raw URL for heads/ ref", () => {
|
|
const url = toRawUrl("ifc://gitlab.com/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(url, "https://gitlab.com/org/repo/-/raw/main/model.ifc");
|
|
});
|
|
|
|
test("generates GitLab raw URL for tags/ ref", () => {
|
|
const url = toRawUrl("ifc://gitlab.com/org/repo@tags/v1.0?path=model.ifc");
|
|
assert.equal(url, "https://gitlab.com/org/repo/-/raw/v1.0/model.ifc");
|
|
});
|
|
|
|
// Forgejo / Gitea (codeberg.org, self-hosted, etc.)
|
|
test("generates Forgejo raw URL for heads/ ref", () => {
|
|
const url = toRawUrl("ifc://codeberg.org/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(url, "https://codeberg.org/org/repo/raw/branch/main/model.ifc");
|
|
});
|
|
|
|
test("generates Forgejo raw URL for tags/ ref", () => {
|
|
const url = toRawUrl("ifc://codeberg.org/org/repo@tags/v1.0?path=model.ifc");
|
|
assert.equal(url, "https://codeberg.org/org/repo/raw/tag/v1.0/model.ifc");
|
|
});
|
|
|
|
test("generates Forgejo raw URL for bare commit hash", () => {
|
|
const url = toRawUrl("ifc://codeberg.org/org/repo@abc123?path=model.ifc");
|
|
assert.equal(url, "https://codeberg.org/org/repo/raw/commit/abc123/model.ifc");
|
|
});
|
|
|
|
test("generates Forgejo raw URL for self-hosted instance", () => {
|
|
const url = toRawUrl("ifc://git.example.com/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(url, "https://git.example.com/org/repo/raw/branch/main/model.ifc");
|
|
});
|
|
|
|
test("uses http for localhost Forgejo (local dev instance)", () => {
|
|
const url = toRawUrl("ifc://localhost:3000/org/repo@heads/main?path=model.ifc");
|
|
assert.equal(url, "http://localhost:3000/org/repo/raw/branch/main/model.ifc");
|
|
});
|
|
|
|
test("includes subdirectory file path", () => {
|
|
const url = toRawUrl("ifc://codeberg.org/org/repo@heads/main?path=subdir/model.ifc");
|
|
assert.ok(url.endsWith("/subdir/model.ifc"), `expected path suffix, got: ${url}`);
|
|
});
|
|
});
|