@player-ui/player
Version:
209 lines (167 loc) • 5.29 kB
text/typescript
import { describe, test, expect, vitest } from "vitest";
import { BindingParser } from "..";
test("caches bindings", () => {
const parser = new BindingParser();
const b1 = parser.parse("foo.bar");
const b2 = parser.parse("foo.bar");
const b3 = parser.parse(["foo", "bar"]);
expect(b1).toBe(b2);
expect(b1).toBe(b3);
});
test("does not call the update hook when readOnly is true", () => {
const onSetHook = vitest.fn();
const onGetHook = vitest.fn();
const parser = new BindingParser({
get: (b) => {
onGetHook(b);
return [{ bar: "blah" }];
},
set: onSetHook,
readOnly: true,
});
parser.parse('foo[bar="baz"].blah');
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
expect(onSetHook).not.toHaveBeenCalled();
});
test("calls the update hook when data needs to be changed", () => {
const onSetHook = vitest.fn();
const onGetHook = vitest.fn();
const parser = new BindingParser({
get: (b) => {
onGetHook(b);
return [{ bar: "blah" }];
},
set: onSetHook,
});
parser.parse('foo[bar="baz"].blah');
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
expect(onSetHook).toBeCalledWith([[parser.parse("foo.1.bar"), "baz"]]);
});
test("skips the update hook when data does not need to be changed", () => {
const onSetHook = vitest.fn();
const onGetHook = vitest.fn();
const parser = new BindingParser({
get: (b) => {
onGetHook(b);
return [{ bar: "baz" }];
},
set: onSetHook,
});
parser.parse('foo[bar="baz"].blah');
expect(onGetHook).toBeCalledWith(parser.parse("foo"));
expect(onSetHook).not.toBeCalledWith(parser.parse("foo"));
});
test("throws error on bad binding syntax", () => {
const parser = new BindingParser();
expect(() => parser.parse("foo.bar[")).toThrowError(
/Cannot normalize path "foo.bar\[/,
);
});
test("works for binding with nested refs", () => {
let callCount = 0;
const parser = new BindingParser({
get: () => {
return callCount++;
},
});
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.0.baz");
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.1.baz");
});
test("works for bindings with nested boolean refs", () => {
const parser = new BindingParser({
get: () => {
return true;
},
});
expect(parser.parse("foo.{{bar}}.baz").asString()).toBe("foo.true.baz");
});
test("works for binding with partial-nested refs", () => {
const parser = new BindingParser({
get: (binding) => {
if (binding.asString() === "hello") {
return "bar";
}
return "not-{{hello}}";
},
});
expect(parser.parse("foo.{{hello}}_world.baz").asString()).toBe(
"foo.bar_world.baz",
);
expect(parser.parse("foo.{{hello}}_other_{{world}}.baz").asString()).toBe(
"foo.bar_other_not-bar.baz",
);
});
test("works for expanded nested refs", () => {
const parser = new BindingParser({
get: (binding) => {
if (binding.asString() === "nested") {
return "nested.ref[1]";
}
},
});
expect(parser.parse("foo.{{nested}}.baz").asString()).toBe(
"foo.nested.ref.1.baz",
);
});
test("returns a binding if its already parsed", () => {
const parser = new BindingParser();
const binding = parser.parse("foo.bar");
expect(parser.parse(binding)).toBe(binding);
});
test("top level parser returns empty key set", () => {
const parser = new BindingParser();
const binding = parser.parse("");
expect(binding.asArray()).toHaveLength(0);
expect(binding.asString()).toBe("");
});
describe("errors", () => {
test("throws when it gets an undefined nested path", () => {
const parser = new BindingParser({
get: () => undefined,
});
expect(() => parser.parse("foo.{{nested}}.bar")).toThrowError();
});
test("throws when get is used but not wired up", () => {
const parser = new BindingParser({
get: () => undefined,
});
expect(() =>
parser.parse("foo.{{nested}}.bar"),
).toThrowErrorMatchingInlineSnapshot(
`[NestedError: Cannot resolve binding: foo.{{nested}}.bar]`,
);
});
test("throws when set is used but not wired up", () => {
const parser = new BindingParser({
get: () => [],
});
expect(() => parser.parse("foo[foo=bar].bar")).toThrowError();
});
test("throws when eval is used but not wired up", () => {
const parser = new BindingParser();
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
});
test("throws when a path is not binding serializable", () => {
const parser = new BindingParser({
get: () => ({
foo: "bar",
}),
});
expect(() => parser.parse("foo.{{nested}}.bar")).toThrowError();
});
// When exp() === '' -> ??? (foo.bar) | Error
describe("expressions", () => {
test("throws when expression returns undef", () => {
const parser = new BindingParser({
evaluate: () => undefined,
});
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
});
test("throws when expression returns empty binding", () => {
const parser = new BindingParser({
evaluate: () => "",
});
expect(() => parser.parse("foo.`exp()`.bar")).toThrowError();
});
});
});