webamp/js/utils.test.ts
2018-12-22 12:15:34 -08:00

431 lines
12 KiB
TypeScript

import fs from "fs";
import {
getTimeObj,
getTimeStr,
clamp,
parseViscolors,
parseIni,
normalize,
denormalize,
segment,
moveSelected,
spliceIn,
getFileExtension,
makeCachingFilterFunction
} from "./utils";
const fixture = (filename: string) =>
fs.readFileSync(`./js/__tests__/fixtures/${filename}`, "utf8");
describe("getTimeObj", () => {
it("expresses seconds as an object", () => {
const actual = getTimeObj(1234);
const expected = {
minutesFirstDigit: "2",
minutesSecondDigit: "0",
secondsFirstDigit: "3",
secondsSecondDigit: "4"
};
expect(actual).toEqual(expected);
});
});
describe("getTimeStr", () => {
it("expresses seconds as string", () => {
const actual = getTimeStr(1234);
const expected = "20:34";
expect(actual).toEqual(expected);
});
it("pads with only one zero", () => {
const actual = getTimeStr(5);
const expected = "0:05";
expect(actual).toEqual(expected);
});
it("truncates extra minutes", () => {
const actual = getTimeStr(540000);
const expected = "9000:00";
expect(actual).toEqual(expected);
});
});
describe("getFileExtension", () => {
it("can get bmp", () => {
expect(getFileExtension("foo.bmp")).toBe("bmp");
});
it("can match four char extension", () => {
expect(getFileExtension("foo.html")).toBe("html");
});
it("converts to lower case", () => {
expect(getFileExtension("foo.BMP")).toBe("bmp");
});
it("returns null if a match is not found", () => {
expect(getFileExtension("foo")).toBe(null);
});
});
describe("clamp", () => {
it("respects the max value", () => {
const actual = clamp(101, 0, 100);
const expected = 100;
expect(actual).toEqual(expected);
});
it("respects the min value", () => {
const actual = clamp(0, 1, 100);
const expected = 1;
expect(actual).toEqual(expected);
});
it("respects the given value if in range", () => {
const actual = clamp(50, 0, 100);
const expected = 50;
expect(actual).toEqual(expected);
});
});
describe("parseViscolors", () => {
it("can parse the default viscolors file", () => {
const viscolors = fixture("VISCOLOR.TXT");
const actual = parseViscolors(viscolors);
const expected = [
"rgb(0,0,0)",
"rgb(24,33,41)",
"rgb(239,49,16)",
"rgb(206,41,16)",
"rgb(214,90,0)",
"rgb(214,102,0)",
"rgb(214,115,0)",
"rgb(198,123,8)",
"rgb(222,165,24)",
"rgb(214,181,33)",
"rgb(189,222,41)",
"rgb(148,222,33)",
"rgb(41,206,16)",
"rgb(50,190,16)",
"rgb(57,181,16)",
"rgb(49,156,8)",
"rgb(41,148,0)",
"rgb(24,132,8)",
"rgb(255,255,255)",
"rgb(214,214,222)",
"rgb(181,189,189)",
"rgb(160,170,175)",
"rgb(148,156,165)",
"rgb(150,150,150)"
];
expect(actual).toEqual(expected);
});
it("can parse a malformed viscolors file", () => {
// From https://skins.webamp.org/skin/018ddb394f2bfe49efa70bce27b71cb2/Centra_CSS-102_104-3.wsz/
const viscolors = fixture("CENTRA_VISCOLOR.TXT");
const actual = parseViscolors(viscolors);
const expected = [
"rgb(110,150,176)", // 0
"rgb(165,165,165)", // 1
"rgb(55,55,67)", // 2
"rgb(55,55,67)", // 3
"rgb(55,55,67)", // 4
"rgb(55,55,67)", // 5
"rgb(55,55,67)", // 6
"rgb(55,55,67)", // 7
"rgb(55,55,67)", // 8
"rgb(55,55,67)", // 9
"rgb(55,55,67)", // 10
"rgb(55,55,67)", // 11
"rgb(55,55,67)", // 12
"rgb(55,55,67)", // 13
"rgb(55,55,67)", // 14
"rgb(55,55,67)", // 15
"rgb(55,55,67)", // 16
"rgb(55,55,67)", // 17
"rgb(55,55,67)", // 18
"rgb(55,55,67)", // 19
"rgb(55,55,67)", // 20
"rgb(55,55,67)", // 21
"rgb(55,55,67)", // 22
//c 2
"rgb(181,189,189)", // 20 = osc 3
"rgb(148,156,165)", // 21 = osc 4
"rgb(148,156,165)" // 2) = osc 5 (dimmest)
];
expect(actual).toEqual(expected);
});
it("does not require commas to separate values", () => {
// From https://skins.webamp.org/skin/99c6227d8880e00813a9aa6c4e808c37/valgaav_by_dreamcass-d85bqwp.wsz/
const viscolors = fixture("viscolor_valgaav.txt");
const actual = parseViscolors(viscolors);
const expected = [
"rgb(98,111,123)", // color 0 = background
"rgb(98,111,123)", // color 1 = dots
"rgb(21,21,21)", // 2 = top of spec
"rgb(223,176,176)", // 16
"rgb(218,168,168)", // 15
"rgb(211,158,158)", // 14
"rgb(204,147,147)", // 13
"rgb(198,137,137)", // 12
"rgb(191,127,127)", // 11
"rgb(185,117,117)", // 10
"rgb(178,107,107)", // 9
"rgb(172,97,97)", // 8
"rgb(165,86,86)", // 7
"rgb(158,76,76)", // 6
"rgb(152,66,66)", // 5
"rgb(145,56,56)", // 4
"rgb(138,46,46)", // 3
"rgb(138,46,46)", // 17 = bottom of spec
"rgb(138,46,46)", // 18 = osc 1
"rgb(158,76,76)", // 19 = osc 2
"rgb(178,107,107)", // 20 = osc 3
"rgb(198,137,137)", // 21 = osc4
"rgb(218,168,168)", // 22 = osc5
"rgb(223,176,176)" // 23 = analyzer peak dots
];
expect(actual).toEqual(expected);
});
});
describe("parseIni", () => {
it("can parse the default pledit.txt file", () => {
const pledit = fixture("PLEDIT.TXT");
const actual = parseIni(pledit);
const expected = {
text: {
normal: "#00FF00",
current: "#FFFFFF",
normalbg: "#000000",
selectedbg: "#0000FF",
font: "Arial"
}
};
expect(actual).toEqual(expected);
});
it("can parse TopazAmp's pledit.txt file", () => {
const pledit = fixture("PLEDIT_TOPAZ.TXT");
const actual = parseIni(pledit);
const expected = {
text: {
normal: "#319593",
current: "#89D8D1",
normalbg: "#000000",
selectedbg: "#2B4242",
font: "Arial",
mbbg: "#000000",
mbfg: "#89D8D1"
}
};
expect(actual).toEqual(expected);
});
it("allows space around =", () => {
const actual = parseIni(`
[foo]
bar = baz
`);
const expected = {
foo: {
bar: "baz"
}
};
expect(actual).toEqual(expected);
});
it("can parse a pledit.txt file with quotes", () => {
const pledit = fixture("PLEDIT_WITH_QUOTES.TXT");
const actual = parseIni(pledit);
const expected = {
text: {
normal: "#00FF00",
current: "#FFFFFF",
normalbg: "#000000",
selectedbg: "#0000FF",
font: "Ricky's cool font!"
}
};
expect(actual).toEqual(expected);
});
it("allows quotes around values", () => {
const actual = parseIni(`
[foo]
bar = "baz"
`);
const expected = {
foo: {
bar: "baz"
}
};
expect(actual).toEqual(expected);
});
});
test("normalize", () => {
expect(normalize(1)).toBe(1);
expect(normalize(64)).toBe(100);
});
test("denormalize", () => {
expect(denormalize(1)).toBe(1);
expect(denormalize(100)).toBe(64);
});
describe("segment", () => {
it("can handle min", () => {
expect(segment(0, 100, 0, [0, 1, 2])).toBe(0);
expect(segment(1, 100, 1, [0, 1, 2])).toBe(0);
expect(segment(-1, 100, -1, [0, 1, 2])).toBe(0);
});
it("can handle max", () => {
//expect(segment(0, 100, 100, [0, 1, 2])).toBe(2);
//expect(segment(1, 100, 100, [0, 1, 2])).toBe(2);
expect(segment(-1, 100, 100, [0, 1, 2])).toBe(2);
});
it("can handle mid", () => {
expect(segment(0, 2, 1, [0, 1, 2])).toBe(1);
expect(segment(0, 2, 1.5, [0, 1, 2])).toBe(2);
expect(segment(1, 3, 2.5, [0, 1, 2])).toBe(2);
expect(segment(-1, 2, 0.5, [0, 1, 2])).toBe(1);
});
it("can handle various real wold cases", () => {
expect(segment(-100, 100, -100, ["left", "center", "right"])).toBe("left");
expect(segment(0, 100, 88, ["left", "center", "right"])).toBe("right");
expect(segment(0, 100, 50, ["left", "center", "right"])).toBe("center");
});
});
describe("moveSelected", () => {
it("can drag a single item 1", () => {
expect(
moveSelected(
["a", "b", "c", "d", "e", "f", "g", "h"],
i => new Set([1]).has(i),
1
)
).toEqual(["a", "c", "b", "d", "e", "f", "g", "h"]);
});
it("can drag a single item", () => {
expect(
moveSelected(
["a", "b", "c", "d", "e", "f", "g", "h"],
i => new Set([1]).has(i),
3
)
).toEqual(["a", "c", "d", "e", "b", "f", "g", "h"]);
});
it("can drag consecutive items", () => {
expect(
moveSelected(
["a", "b", "c", "d", "e", "f", "g", "h"],
i => new Set([1, 2]).has(i),
3
)
).toEqual(["a", "d", "e", "f", "b", "c", "g", "h"]);
});
it("works for a simple example", () => {
const arr = [true, false, false];
expect(moveSelected(arr, i => arr[i], 1)).toEqual([false, true, false]);
});
it("works for a simple negative example", () => {
const arr = [false, false, true];
expect(moveSelected(arr, i => arr[i], -1)).toEqual([false, true, false]);
});
});
describe("spliceIn", () => {
it("is immutable", () => {
const original = [1, 2, 3];
const spliced = spliceIn(original, 1, [200]);
expect(spliced).not.toBe(original);
expect(original).toEqual([1, 2, 3]);
});
it("adds values at the given index", () => {
const spliced = spliceIn([1, 2, 3], 1, [200]);
expect(spliced).toEqual([1, 200, 2, 3]);
});
});
describe("makeCachingFilterFunction", () => {
test("caches exact queries", () => {
const values = ["abc", "b", "c"];
const includes = jest.fn((v, query) => v.includes(query));
const filter = makeCachingFilterFunction(values, includes);
expect(filter("c")).toEqual(["abc", "c"]);
expect(includes.mock.calls.length).toBe(3);
expect(filter("c")).toEqual(["abc", "c"]);
expect(includes.mock.calls.length).toBe(3);
});
test("caches sub queries", () => {
const values = ["a--", "ab-", "abc"];
const includes = jest.fn((v, query) => v.includes(query));
let comparisons = 0;
const newComparisons = () => {
const recent = includes.mock.calls.length - comparisons;
comparisons += recent;
return recent;
};
const filter = makeCachingFilterFunction(values, includes);
// Intial search
expect(filter("ab")).toEqual(["ab-", "abc"]);
expect(newComparisons()).toBe(3); // Looks at all elements
// Second search where original search is a prefix
expect(filter("abc")).toEqual(["abc"]);
expect(newComparisons()).toBe(2); // Only reconsiders the previous matches
// Unique search
expect(filter("b")).toEqual(["ab-", "abc"]); // Looks at all elements
expect(newComparisons()).toBe(3); // Reconsiders all elements
expect(filter("bc")).toEqual(["abc"]); // Only reconsidres the matches that already include `b`
expect(newComparisons()).toBe(2);
// Go back to the initial serach
expect(filter("ab")).toEqual(["ab-", "abc"]);
expect(newComparisons()).toBe(0); // Result is cached
// A variation on the second search
expect(filter("abcd")).toEqual([]);
expect(newComparisons()).toBe(1); // Only recondsiders the results of `abc`
});
test("big data", () => {
const values = [...Array(10000)].map((val, i) => String(i));
const includes = jest.fn((v, query) => v.includes(query));
let comparisons = 0;
const newComparisons = () => {
const recent = includes.mock.calls.length - comparisons;
comparisons += recent;
return recent;
};
const filter = makeCachingFilterFunction(values, includes);
// Intial search
expect(filter("").length).toEqual(10000);
expect(newComparisons()).toBe(0); // Looks at zero
expect(filter("1").length).toEqual(3439);
expect(newComparisons()).toBe(10000); // Looks at all elements
expect(filter("12").length).toEqual(299);
expect(newComparisons()).toBe(3439);
expect(filter("123").length).toEqual(20);
expect(newComparisons()).toBe(299);
expect(filter("1234").length).toEqual(1);
expect(newComparisons()).toBe(20);
expect(filter("12345").length).toEqual(0);
expect(newComparisons()).toBe(1);
// A variation on the initial non-empty query
expect(filter("11").length).toEqual(280);
expect(newComparisons()).toBe(3439);
expect(filter("111").length).toEqual(19);
expect(newComparisons()).toBe(280);
expect(filter("1111").length).toEqual(1);
expect(newComparisons()).toBe(19);
});
});