@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
243 lines (215 loc) • 9.25 kB
JavaScript
import {Parser} from '../src/parser';
import {
LiteralString,
LiteralPrimitive,
LiteralObject,
ValueConverter,
BindingBehavior,
AccessScope,
AccessMember,
AccessKeyed,
CallScope,
CallMember,
CallFunction,
AccessThis,
AccessAncestor
} from '../src/ast';
describe('Parser', () => {
let parser;
beforeAll(() => {
parser = new Parser();
});
it('parses literal primitives', () => {
// http://es5.github.io/x7.html#x7.8.4
let tests = [
{ expression: '\'foo\'', value: 'foo', type: LiteralString },
{ expression: '\'\\\\\'', value: '\\', type: LiteralString },
{ expression: '\'\\\'\'', value: '\'', type: LiteralString },
{ expression: '\'"\'', value: '"', type: LiteralString },
{ expression: '\'\\f\'', value: '\f', type: LiteralString },
{ expression: '\'\\n\'', value: '\n', type: LiteralString },
{ expression: '\'\\r\'', value: '\r', type: LiteralString },
{ expression: '\'\\t\'', value: '\t', type: LiteralString },
{ expression: '\'\\v\'', value: '\v', type: LiteralString },
{ expression: 'true', value: true, type: LiteralPrimitive },
{ expression: 'false', value: false, type: LiteralPrimitive },
{ expression: 'null', value: null, type: LiteralPrimitive },
{ expression: 'undefined', value: undefined, type: LiteralPrimitive },
{ expression: '0', value: 0, type: LiteralPrimitive },
{ expression: '1', value: 1, type: LiteralPrimitive },
{ expression: '2.2', value: 2.2, type: LiteralPrimitive }
];
for (let i = 0; i < tests.length; i++) {
let test = tests[i];
let expression = parser.parse(test.expression);
expect(expression instanceof test.type).toBe(true);
expect(expression.value).toEqual(test.value);
}
});
it('parses binding behaviors', () => {
let expression = parser.parse('foo & bar');
expect(expression instanceof BindingBehavior).toBe(true);
expect(expression.name).toBe('bar');
expect(expression.expression instanceof AccessScope).toBe(true);
expression = parser.parse('foo & bar:x:y:z & baz:a:b:c');
expect(expression instanceof BindingBehavior).toBe(true);
expect(expression.name).toBe('baz');
expect(expression.args).toEqual([new AccessScope('a', 0), new AccessScope('b', 0), new AccessScope('c', 0)])
expect(expression.expression instanceof BindingBehavior).toBe(true);
expect(expression.expression.name).toBe('bar');
expect(expression.expression.args).toEqual([new AccessScope('x', 0), new AccessScope('y', 0), new AccessScope('z', 0)]);
expect(expression.expression.expression instanceof AccessScope).toBe(true);
});
it('parses value converters', () => {
let expression = parser.parse('foo | bar');
expect(expression instanceof ValueConverter).toBe(true);
expect(expression.name).toBe('bar');
expect(expression.expression instanceof AccessScope).toBe(true);
expression = parser.parse('foo | bar:x:y:z | baz:a:b:c');
expect(expression instanceof ValueConverter).toBe(true);
expect(expression.name).toBe('baz');
expect(expression.args).toEqual([new AccessScope('a', 0), new AccessScope('b', 0), new AccessScope('c', 0)]);
expect(expression.expression instanceof ValueConverter).toBe(true);
expect(expression.expression.name).toBe('bar');
expect(expression.expression.args).toEqual([new AccessScope('x', 0), new AccessScope('y', 0), new AccessScope('z', 0)]);
expect(expression.expression.expression instanceof AccessScope).toBe(true);
});
it('parses value converters and binding behaviors', () => {
let expression = parser.parse('foo | bar:x:y:z & baz:a:b:c');
expect(expression instanceof BindingBehavior).toBe(true);
expect(expression.name).toBe('baz');
expect(expression.args).toEqual([new AccessScope('a', 0), new AccessScope('b', 0), new AccessScope('c', 0)])
expect(expression.expression instanceof ValueConverter).toBe(true);
expect(expression.expression.name).toBe('bar');
expect(expression.expression.args).toEqual([new AccessScope('x', 0), new AccessScope('y', 0), new AccessScope('z', 0)]);
expect(expression.expression.expression instanceof AccessScope).toBe(true);
});
it('parses AccessScope', () => {
let expression = parser.parse('foo');
expect(expression instanceof AccessScope).toBe(true);
expect(expression.name).toBe('foo');
});
it('parses AccessMember', () => {
let expression = parser.parse('foo.bar');
expect(expression instanceof AccessMember).toBe(true);
expect(expression.name).toBe('bar');
expect(expression.object instanceof AccessScope).toBe(true);
expect(expression.object.name).toBe('foo');
});
it('parses CallScope', () => {
let expression = parser.parse('foo(x)');
expect(expression instanceof CallScope).toBe(true);
expect(expression.name).toBe('foo');
expect(expression.args).toEqual([new AccessScope('x', 0)]);
});
it('parses CallMember', () => {
let expression = parser.parse('foo.bar(x)');
expect(expression instanceof CallMember).toBe(true);
expect(expression.name).toBe('bar');
expect(expression.args).toEqual([new AccessScope('x', 0)]);
expect(expression.object instanceof AccessScope).toBe(true);
expect(expression.object.name).toBe('foo');
});
it('parses $this', () => {
let expression = parser.parse('$this');
expect(expression instanceof AccessThis).toBe(true);
});
it('translates $this.member to AccessScope', () => {
let expression = parser.parse('$this.foo');
expect(expression instanceof AccessScope).toBe(true);
expect(expression.name).toBe('foo');
});
it('translates $this() to CallFunction', () => {
let expression = parser.parse('$this()');
expect(expression instanceof CallFunction).toBe(true);
expect(expression.func instanceof AccessThis).toBe(true);
});
it('translates $this.member() to CallScope', () => {
let expression = parser.parse('$this.foo(x)');
expect(expression instanceof CallScope).toBe(true);
expect(expression.name).toBe('foo');
expect(expression.args).toEqual([new AccessScope('x', 0)]);
});
it('parses $parent', () => {
let s = '$parent';
for (let i = 1; i < 10; i++) {
let expression = parser.parse(s);
expect(expression instanceof AccessThis).toBe(true);
expect(expression.ancestor).toBe(i);
s += '.$parent';
}
});
it('translates $parent.foo to AccessScope', () => {
let s = '$parent.foo';
for (let i = 1; i < 10; i++) {
let expression = parser.parse(s);
expect(expression instanceof AccessScope).toBe(true);
expect(expression.name).toBe('foo');
expect(expression.ancestor).toBe(i);
s = '$parent.' + s;
}
});
it('translates $parent.foo() to CallScope', () => {
let s = '$parent.foo()';
for (let i = 1; i < 10; i++) {
let expression = parser.parse(s);
expect(expression instanceof CallScope).toBe(true);
expect(expression.name).toBe('foo');
expect(expression.ancestor).toBe(i);
s = '$parent.' + s;
}
});
it('translates $parent() to CallFunction', () => {
let s = '$parent()';
for (let i = 1; i < 10; i++) {
let expression = parser.parse(s);
expect(expression instanceof CallFunction).toBe(true);
expect(expression.func instanceof AccessThis).toBe(true);
expect(expression.func.ancestor).toBe(i);
s = '$parent.' + s;
}
});
it('translates $parent[0] to AccessKeyed', () => {
let s = '$parent[0]';
for (let i = 1; i < 10; i++) {
let expression = parser.parse(s);
expect(expression instanceof AccessKeyed).toBe(true);
expect(expression.object instanceof AccessThis).toBe(true);
expect(expression.object.ancestor).toBe(i);
expect(expression.key instanceof LiteralPrimitive).toBe(true);
expect(expression.key.value).toBe(0);
s = '$parent.' + s;
}
});
it('parses $parent in LiteralObject', () => {
let expression = parser.parse('{parent: $parent}');
expect(expression instanceof LiteralObject).toBe(true);
expect(expression.keys.length).toBe(1);
expect(expression.values.length).toBe(1);
expect(expression.values[0] instanceof AccessThis).toBe(true);
expect(expression.values[0].ancestor).toBe(1);
});
it('parses es6 shorthand LiteralObject', () => {
let expression = parser.parse('{ foo, bar }');
expect(expression instanceof LiteralObject).toBe(true);
expect(expression.keys.length).toBe(2);
expect(expression.values.length).toBe(2);
expect(expression.values[0] instanceof AccessScope).toBe(true);
expect(expression.values[0].name).toBe('foo');
expect(expression.values[1] instanceof AccessScope).toBe(true);
expect(expression.values[1].name).toBe('bar');
});
it('does not parse invalid shorthand properties', () => {
let pass = false;
try {
parser.parse('{ foo.bar, bar.baz }');
pass = true;
} catch (e) { pass = false; }
expect(pass).toBe(false);
try {
parser.parse('{ "foo.bar" }');
pass = true;
} catch (e) { pass = false; }
expect(pass).toBe(false);
});
});