test/tsv.test.js (3336 bytes)
1 "use strict"; 2 const test = require("node:test"); 3 const assert = require("node:assert/strict"); 4 const fc = require("fast-check"); 5 const Tsv = require("../lib/tsv.js"); 6 7 const url = fc.stringMatching(/^[!-~]{1,40}$/).filter((u) => !u.startsWith("#")); 8 const desc = fc.string({ maxLength: 40 }).filter((d) => !/[\t\r\n]/.test(d)); 9 const tag = fc.stringMatching(/^[a-z0-9-]{1,10}$/); 10 const bookmark = fc.record({ url, desc, tags: fc.array(tag, { maxLength: 5 }) }); 11 12 test("a formatted bookmark parses back to itself", () => { 13 fc.assert(fc.property(bookmark, (b) => { 14 // fc.record gives objects without a prototype; parse gives plain objects. 15 assert.deepEqual(Tsv.parse(Tsv.format(b) + "\n"), [{ ...b }]); 16 }), { numRuns: 1000 }); 17 }); 18 19 test("a file of bookmarks parses back to its bookmarks, with LF or CRLF", () => { 20 fc.assert(fc.property(fc.array(bookmark, { maxLength: 20 }), fc.boolean(), (bs, crlf) => { 21 const end = crlf ? "\r\n" : "\n"; 22 const text = bs.map((b) => Tsv.format(b) + end).join(""); 23 assert.deepEqual(Tsv.parse(text), bs.map((b) => ({ ...b }))); 24 }), { numRuns: 1000 }); 25 }); 26 27 test("norm ignores the scheme, www., trailing slashes and the case of the host", () => { 28 const host = fc.stringMatching(/^[a-z0-9]{1,10}\.(org|com)$/); 29 const path = fc.stringMatching(/^(\/[A-Za-z0-9]{1,8}){0,3}$/); 30 fc.assert(fc.property(host, path, fc.constantFrom("http://", "https://", ""), fc.boolean(), fc.nat(3), 31 (h, p, s, www, slashes) => { 32 const other = s + (www ? "WWW." : "") + h.toUpperCase() + p + "/".repeat(slashes); 33 assert.equal(Tsv.norm(other), Tsv.norm("https://" + h + p)); 34 }), { numRuns: 1000 }); 35 }); 36 37 test("the case of the path counts", () => { 38 assert.notEqual(Tsv.norm("https://example.org/A"), Tsv.norm("https://example.org/a")); 39 }); 40 41 test("comments, blank lines and old lines", () => { 42 const text = "# notes\n\nhttps://a.example\tA\tcode sec\nhttps://b.example B site | tag\n"; 43 assert.deepEqual(Tsv.parse(text), [ 44 { url: "https://a.example", desc: "A", tags: ["code", "sec"] }, 45 { url: "https://b.example", desc: "B site | tag", tags: [] }, 46 ]); 47 }); 48 49 test("format cleans the fields", () => { 50 const b = { url: " https://a.example/x ", desc: "one\ttwo\nthree", tags: ["a b", "c"] }; 51 assert.equal(Tsv.format(b), "https://a.example/x\tone two three\ta b c"); 52 }); 53 54 test("find gives the same page, as bm finds it", () => { 55 const known = Tsv.parse("https://www.example.com/a/\tA\t\n"); 56 assert.equal(Tsv.find(known, "http://example.com/a"), known[0]); 57 assert.equal(Tsv.find(known, "https://example.com/b"), undefined); 58 }); 59 60 test("appendix puts the line on a line of its own", () => { 61 assert.equal(Tsv.appendix("", "x"), "x\n"); 62 assert.equal(Tsv.appendix("a\n", "x"), "x\n"); 63 assert.equal(Tsv.appendix("a", "x"), "\nx\n"); 64 }); 65 66 test("target: an address, or a web search, as in bm", () => { 67 assert.equal(Tsv.target("suckless.org/dwm"), "https://suckless.org/dwm"); 68 assert.equal(Tsv.target("gopher://x.org"), "gopher://x.org"); 69 assert.equal(Tsv.target("posix sh printf"), "https://duckduckgo.com/?q=posix+sh+printf"); 70 assert.equal(Tsv.target("a&b"), "https://duckduckgo.com/?q=a%26b"); 71 }); 72 73 test("host", () => { 74 assert.equal(Tsv.host("https://jisho.org/word/x?y#z"), "jisho.org"); 75 assert.equal(Tsv.host("example.org?q=1"), "example.org"); 76 });