UNPKG

fontoxpath

Version:

A minimalistic XPath 3.1 engine in JavaScript

207 lines (166 loc) 8.43 kB
import * as slimdom from 'slimdom'; import evaluateXPathToAsyncSingleton from 'test-helpers/evaluateXPathToAsyncSingleton'; import { evaluateXPathToMap, evaluateXPathToNumber, evaluateXPathToBoolean, evaluateXPathToStrings } from 'fontoxpath'; let documentNode; beforeEach(() => { documentNode = new slimdom.Document(); }); describe('functions over maps', () => { describe('map:get', () => { it('can get a value from a map using a string key', () => chai.assert.equal(evaluateXPathToNumber('map:get(map {"a": 1, "b":2}, "a")', documentNode), 1)); it('can get a value from a map multiple times', () => chai.assert.equal(evaluateXPathToNumber('let $map := map {"a": (1,2,3,4)} return count($map("a")) + count($map("a") => reverse())', documentNode), 8)); it('can get a value from a map using a boolean key', () => chai.assert.equal(evaluateXPathToNumber('map:get(map {true(): 1, false():2}, true())', documentNode), 1)); it('can get a value from a map using a numeric key', () => chai.assert.equal(evaluateXPathToNumber('map:get(map {1: 1, 2:2}, 1)', documentNode), 1)); it('returns the empty sequence if nothing matches: numeric vs other', () => chai.assert.equal(evaluateXPathToNumber('count(map:get(map {1: 1, 2:2}, false()))', documentNode), 0)); it('returns the empty sequence if nothing matches: other vs numeric', () => chai.assert.equal(evaluateXPathToNumber('count(map:get(map {true(): 1, 2:2}, 1))', documentNode), 0)); it('is aliased to "executing the map function"', () => chai.assert.equal(evaluateXPathToNumber('(map {"a": 1, "b":2})("a")', documentNode), 1)); it('returns the correct value when passes NaN', () => chai.assert.equal(evaluateXPathToNumber('(map {number("NaN"): 1, "b":2})(number("NaN"))', documentNode), 1)); it('can be chained', () => chai.assert.equal(evaluateXPathToNumber('(map {"a": map{1:1}})("a")(1)', documentNode), 1)); it('works with async params', async () => { chai.assert.equal(await evaluateXPathToAsyncSingleton(` (map { "a" => fontoxpath:sleep(): map{ 1: 1 => fontoxpath:sleep() } => fontoxpath:sleep() })("a")(1 => fontoxpath:sleep()) `, documentNode), 1); }); }); describe('map:merge', () => { it('can merge two maps', () => chai.assert.deepEqual(evaluateXPathToMap('map:merge((map {"a": 1}, map{"b":2}))', documentNode), { a: 1, b: 2 })); it('can merge two maps asynchronously', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:merge((map {"a": 1}, map{"b":2} => fontoxpath:sleep()))'), { a: 1, b: 2 }); }); it('can handle duplicates: use-first', () => chai.assert.deepEqual(evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ), map {"duplicates": "use-first"})`, documentNode), { a: 1 })); it('defaults to use-first duplication handling if no options are passed', () => chai.assert.deepEqual(evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ))`, documentNode), { a: 1 })); it('defaults to use-first duplication handling if no duplicates entry is present in the map', () => chai.assert.deepEqual(evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ), map{})`, documentNode), { a: 1 })); it('can handle duplicates: use-last', () => chai.assert.deepEqual(evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ), map {"duplicates": "use-last"})`, documentNode), { a: 2 })); it('can handle duplicates: use-any', () => chai.assert.deepEqual(evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ), map {"duplicates": "use-any"})`, documentNode), { a: 1 })); it('can handle duplicates: combine', () => chai.assert.isTrue(evaluateXPathToBoolean(` map:merge(( map {"a": 1}, map {"a": 2} ), map {"duplicates": "combine"}) => deep-equal(map{"a": (1,2)})`, documentNode))); it('can handle duplicates: reject', () => chai.assert.throws(() => evaluateXPathToMap(` map:merge(( map {"a": 1}, map {"a": 2} ), map {"duplicates": "reject"})`, documentNode), 'FOJS0003')); }); describe('map:put', () => { it('adds an item to a map', () => chai.assert.deepEqual(evaluateXPathToMap('map{"a": 1} => map:put("b", 2)', documentNode), { a: 1, b: 2 })); it('replaces the old value', () => chai.assert.deepEqual(evaluateXPathToMap('map{"a": 1} => map:put("a", 2)', documentNode), { a: 2 })); it('does not mutate the map', () => chai.assert.deepEqual(evaluateXPathToMap('let $mapA := map{"a": 1}, $mapB := map:put($mapA, "a", 2) return $mapA', documentNode), { a: 1 })); it('can put an item into a map which is resolved asynchronously', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:put(map {"a": 1} => fontoxpath:sleep(), "b", 2)'), { a: 1, b: 2 }); }); it('can put an item into a map whereby the key is resolved asynchronously', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:put(map {"a": 1}, "b" => fontoxpath:sleep(), 2)'), { a: 1, b: 2 }); }); it('can put an item into a map whereby the value is resolved asynchronously', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:put(map {"a": 1}, "b", 2 => fontoxpath:sleep())'), { a: 1, b: 2 }); }); }); describe('map:entry', () => { it('returns a single entry map', () => chai.assert.deepEqual(evaluateXPathToMap('map:entry(1, 2)', documentNode), { 1: 2 })); }); describe('map:size', () => { it('returns 0 for an empty map', () => chai.assert.equal(evaluateXPathToNumber('map:size(map{})', documentNode), 0)); it('returns 2 for a map with two values', () => chai.assert.equal(evaluateXPathToNumber('map:size(map{1:1, 2:2})', documentNode), 2)); it('works with async params', async () => { chai.assert.equal(await evaluateXPathToAsyncSingleton(` (map { "a" => fontoxpath:sleep(): map{ 1: 1 => fontoxpath:sleep() } => fontoxpath:sleep() })("a")(1 => fontoxpath:sleep()) `, documentNode), 1); }); }); describe('map:keys', () => { it('returns the empty sequence for an empty map', () => chai.assert.equal(evaluateXPathToNumber('count(map:keys(map{}))', documentNode), 0)); it('returns the keys for a map with values', () => chai.assert.deepEqual(evaluateXPathToStrings('map:keys(map{"a":1, "b":2})', documentNode), ['a', 'b'])); it('returns the keys of merged maps', () => chai.assert.isTrue(evaluateXPathToBoolean('let $result := map:keys(map:merge((map:entry("a", "1"), map:entry("b", 2)))) return $result = "a"', documentNode))); it('works with async params', async () => { chai.assert.isTrue(await evaluateXPathToAsyncSingleton('let $result := map:keys(map{"a": 1, "b": 2} => fontoxpath:sleep()) return $result = "a"', documentNode)); }); }); describe('map:contains', () => { it('returns false if the key is not present', () => chai.assert.isFalse(evaluateXPathToBoolean('map:contains(map{}, "a")', documentNode))); it('returns true if the key is present', () => chai.assert.isTrue(evaluateXPathToBoolean('map:contains(map{"a":1}, "a")', documentNode))); it('works with async params', async () => { chai.assert.isTrue(await evaluateXPathToAsyncSingleton('map:contains(map{"a":1} => fontoxpath:sleep(), "a")', documentNode)); }); }); describe('map:remove', () => { it('removes an item from the map', () => chai.assert.deepEqual(evaluateXPathToMap('map:remove(map{"a": 1}, "a")', documentNode), {})); it('works with async params', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:remove(map{"a":1} => fontoxpath:sleep(), "a")', documentNode), {}); }); it('does nothing if the key is not present', () => chai.assert.deepEqual(evaluateXPathToMap('map:remove(map{"a":1}, "b")', documentNode), { a: 1 })); it('removes multiple items if multiple keys are passed', () => chai.assert.deepEqual(evaluateXPathToMap('map:remove(map{"a":1, "b": 2, "c": 3}, ("a", "b"))', documentNode), { c: 3 })); }); describe('map:for-each', () => { it('executes a function over every item', () => chai.assert.deepEqual(evaluateXPathToMap('map:for-each(map{"a":"b", "b": "c"}, concat#2)', documentNode), { a: 'ab', b: 'bc' })); it('works with async params', async () => { chai.assert.deepEqual(await evaluateXPathToAsyncSingleton('map:for-each(map{"a":1, "b": 2} => fontoxpath:sleep(), function ($key, $val) {$key || $val})', documentNode), { a: 'a1', b: 'b2' }); }); }); });