// SPDX-License-Identifier: LGPL-2.1-or-later import cockpit from "cockpit"; import QUnit from "qunit-tests"; QUnit.test("basic", function (assert) { window.location.hash = ""; assert.equal(typeof cockpit.location, "object", "cockpit.location exists"); assert.ok(Array.isArray(cockpit.location.path), "cockpit.location.path exists"); assert.equal(typeof cockpit.location.options, "object", "cockpit.location.options exists"); assert.equal(typeof cockpit.location.go, "function", "cockpit.location.go exists"); assert.equal(typeof cockpit.location.replace, "function", "cockpit.location.replace exists"); assert.equal(typeof cockpit.location.decode, "function", "cockpit.location.decode exists"); assert.equal(typeof cockpit.location.encode, "function", "cockpit.location.encode exists"); assert.deepEqual(cockpit.location.path, [], "path is empty"); assert.deepEqual(cockpit.location.options, { }, "options are empty"); }); QUnit.test("decode", function (assert) { window.location.hash = "#/base/test"; const checks = [ ["#/host/path/sub?a=1&b=2", { path: ["host", "path", "sub"], options: { a: "1", b: "2" } } ], ["", { path: [], options: { } } ], ["#", { path: [], options: { } } ], ["#/", { path: [], options: { } } ], ["/horst", { path: ["horst"], options: { } } ], ["//one", { path: ["one"], options: { } } ], ["//one/", { path: ["one"], options: { } } ], ["///two", { path: ["two"], options: { } } ], ["/slash/%2f", { path: ["slash", "/"], options: { } } ], ["?a=1", { path: [], options: { a: "1" } } ], ["?a=1&a=2", { path: [], options: { a: ["1", "2"] } } ], ["?%3f=%3d", { path: [], options: { "?": "=" } } ], ["#?=", { path: [], options: { "": "" } } ], ["?=", { path: [], options: { "": "" } } ], ["relative/sub", { path: ["base", "relative", "sub"], options: { } } ], ["./relative/sub", { path: ["base", "relative", "sub"], options: { } } ], ["../relative/sub", { path: ["relative", "sub"], options: { } } ], ["/top/../sub", { path: ["sub"], options: { } } ], ["/top/./sub/./", { path: ["top", "sub"], options: { } } ], ["relative/../sub", { path: ["base", "sub"], options: { } } ] ]; assert.expect(checks.length); for (let i = 0; i < checks.length; i++) { const options = { }; const path = cockpit.location.decode(checks[i][0], options); assert.deepEqual({ path, options }, checks[i][1], "decode(\"" + checks[i][0] + "\")"); } }); QUnit.test("encode", function (assert) { /* We don't check options here since we can't predict the order in which they appear in the hash. Encoding of options is covered in the "roundtrip" test. */ const checks = [ ["/host/path/sub?a=1&b=2", { path: ["host", "path", "sub"], options: { a: "1", b: "2" } } ], ["/one", { path: ["one"], options: { } } ], ["/one/two", { path: ["one", "two"], options: { } } ], ["/slash/%2F", { path: ["slash", "/"], options: { } } ], ["/p?a=1", { path: ["p"], options: { a: "1" } } ], ["/p?%3F=%3D", { path: ["p"], options: { "?": "=" } } ], ["/p?=", { path: ["p"], options: { "": "" } } ], ["/p?value=one&value=two", { path: ["p"], options: { value: ["one", "two"] } }] ]; assert.expect(checks.length); for (let i = 0; i < checks.length; i++) { const encoded = cockpit.location.encode(checks[i][1].path, checks[i][1].options); assert.strictEqual(encoded, checks[i][0], "encode(" + JSON.stringify(checks[i][1]) + ")"); } }); QUnit.test("roundtrip", function (assert) { const checks = [ { path: ["path", "sub"], options: { a: "1", b: "2" } }, { path: ["päth", "süb"], options: { a: "1", b: "2" } }, { path: ["/=()?", "$%&/"], options: { "": "=$&%", b: "=2%34" } }, ]; assert.expect(checks.length); for (let i = 0; i < checks.length; i++) { const encoded = cockpit.location.encode(checks[i].path, checks[i].options); const decoded = { options: { } }; decoded.path = cockpit.location.decode(encoded, decoded.options); assert.deepEqual(decoded, checks[i], "roundtrip(" + JSON.stringify(checks[i]) + ")"); } }); QUnit.test("external change", function (assert) { const location = cockpit.location; window.location.hash = "#/a/b/c?x=1&y=2"; assert.notStrictEqual(cockpit.location, location, "cockpit.location is different object"); assert.deepEqual(cockpit.location.path, ["a", "b", "c"], "path is correct"); assert.strictEqual(cockpit.location.options.x, "1", "option x is correct"); assert.strictEqual(cockpit.location.options.y, "2", "option y is correct"); }); QUnit.test("internal change", function (assert) { cockpit.location.go(["x", "y", "z"]); assert.strictEqual(window.location.hash, "#/x/y/z", "hash is correct"); assert.deepEqual(cockpit.location.path, ["x", "y", "z"], "path is correct"); assert.deepEqual(cockpit.location.options, { }, "options are empty"); }); QUnit.test("string change", function (assert) { cockpit.location = "/p/x/../q/r?a=b"; assert.strictEqual(window.location.hash, "#/p/q/r?a=b", "hash is correct"); assert.deepEqual(cockpit.location.path, ["p", "q", "r"], "path is correct"); assert.deepEqual(cockpit.location.options, { a: "b" }, "options are empty"); }); QUnit.test("string change", function (assert) { window.location.href = "#/top/file"; cockpit.location = "another"; assert.strictEqual(window.location.hash, "#/top/another", "hash is correct"); assert.deepEqual(cockpit.location.path, ["top", "another"], "path is correct"); }); QUnit.test("change options", function (assert) { window.location.hash = ""; assert.deepEqual(cockpit.location.path, [], "path is empty"); assert.deepEqual(cockpit.location.options, { }, "options are empty"); cockpit.location.go(cockpit.location.path, { x: "1" }); assert.deepEqual(cockpit.location.options, { x: "1" }, "options are correct"); assert.strictEqual(window.location.hash, "#/?x=1", "hash is correct"); cockpit.location.go(cockpit.location.path); assert.deepEqual(cockpit.location.options, { }, "options are empty"); assert.strictEqual(window.location.hash, "#/", "hash is correct"); }); QUnit.test("test", function (assert) { const done = assert.async(); window.location.hash = "#/hello"; let triggered = false; assert.deepEqual(cockpit.location.path, ["hello"], "path is right"); const onLocationChanged = () => { assert.strictEqual(window.location.hash, "#/gonna-happen", "hash has changed"); cockpit.removeEventListener("locationchanged", onLocationChanged); triggered = true; done(); }; cockpit.addEventListener("locationchanged", onLocationChanged); cockpit.location.go(["gonna-happen"]); assert.ok(!triggered, "not yet triggered"); }); QUnit.test("test", function (assert) { const done = assert.async(); window.location.hash = "#/hello"; const location = cockpit.location; assert.deepEqual(cockpit.location.path, ["hello"], "path is right"); window.setTimeout(function() { /* window.location.hash has changed so the old `location` object is no longer valid and .go/replace are ignored */ location.go(["not-gonna-happen"]); assert.strictEqual(window.location.hash, "#/other", "hash is correct"); cockpit.location.go(["gonna-happen"]); assert.strictEqual(window.location.hash, "#/gonna-happen", "hash has changed"); done(); }, 100); /* User or something else navigates */ window.location.hash = "#/other"; }); /* Set url_root in beforeEach so the test does not flake */ QUnit.module("url_root tests", { beforeEach: () => { window.mock = { url_root: "cockpit" }; }, afterEach: () => { window.mock = null; } }); QUnit.test("encode", assert => { let path = cockpit.location.encode("path", "", true); assert.equal(path, "/cockpit/path", "path is correct"); /* an existing url_root should not be prepended */ path = cockpit.location.encode("/cockpit/path", "", true); assert.equal(path, "/cockpit/path", "path is correct"); }); QUnit.start();