UNPKG

openrosa-xpath-evaluator

Version:

Wrapper for browsers' XPath evaluator with added support for OpenRosa extensions.

405 lines (371 loc) 14.8 kB
const { initDoc, filterAttributes, assertThrow, assertTrue, assertFalse, assertString, assertStringValue, assertNumber, } = require('../helpers'); describe('native string functions', () => { it('string() conversion of strings, numbers, booleans', () => { assertString("string('-1.0')", '-1.0'); assertString("string('1')", '1'); assertString("string(' \nhello \n\r')", ' \nhello \n\r'); assertString("string('')", ''); assertString("string('As Df')", 'As Df'); // of numbers // assertString("string(number('-1.0a'))", "NaN"); assertString('string(0)', '0'); assertString('string(-0)', '0'); assertString('string(1 div 0)', 'Infinity'); assertString('string(-1 div 0)', '-Infinity'); assertString('string(-123)', '-123'); assertString('string(123)', '123'); assertString('string(123.)', '123'); assertString('string(123.0)', '123'); assertString('string(.123)', '0.123'); assertString('string(-0.1000)', '-0.1'); assertString('string(1.1)', '1.1'); assertString('string(-1.1)', '-1.1'); // of booleans assertString('string(true())', 'true'); assertString('string(false())', 'false'); }); describe('string() conversion of nodesets', () => { const doc = initDoc(` <div id="FunctionStringCase"> <div id="FunctionStringCaseStringNodesetElement">aaa</div> <div id="FunctionStringCaseStringNodesetElementNested"><span>bbb</span>sss<span></span><div>ccc<span>ddd</span></div></div> <div id="FunctionStringCaseStringNodesetComment"><!-- hello world --></div> <div id="FunctionStringCaseStringNodesetText">here is some text</div> <div id="FunctionStringCaseStringNodesetProcessingInstruction"><?xml-stylesheet type="text/xml" href="test.xsl"?></div> <div id="FunctionStringCaseStringNodesetCData"><![CDATA[some cdata]]></div> <div id="FunctionStringCaseStringNodesetAttribute" class="123" width=" 1 00% "></div> <div id="FunctionStringCaseStringNodesetNamespace" xmlns:asdf="http://www.123.com/"></div> <div id="FunctionStringCaseStringLength1"></div> <div id="FunctionStringCaseStringLength2">asdf</div> <div id="FunctionStringCaseStringNormalizeSpace1"></div> <div id="FunctionStringCaseStringNormalizeSpace2"> </div> <div id="FunctionStringCaseStringNormalizeSpace3"> a b </div> <div id="FunctionStringCaseStringNormalizeSpace4"> a bc c </div> </div>`); let node; const nodeWithAttributes = doc.getElementById( 'FunctionStringCaseStringNodesetAttribute' ); const input = [ ['string(/htmlnot)', doc, ''], // empty [ 'string(self::node())', doc.getElementById('FunctionStringCaseStringNodesetElement'), 'aaa', ], // element [ 'string()', doc.getElementById('FunctionStringCaseStringNodesetElement'), 'aaa', ], // element [ 'string(node())', doc.getElementById( 'FunctionStringCaseStringNodesetElementNested' ), 'bbb', ], // element nested [ 'string(self::node())', doc.getElementById( 'FunctionStringCaseStringNodesetElementNested' ), 'bbbssscccddd', ], // element nested [ 'string()', doc.getElementById( 'FunctionStringCaseStringNodesetElementNested' ), 'bbbssscccddd', ], // element nested [ 'string()', doc.getElementById('FunctionStringCaseStringNodesetComment') .firstChild, ' hello world ', ], // comment [ 'string()', doc.getElementById('FunctionStringCaseStringNodesetText') .firstChild, 'here is some text', ], // text [ 'string(attribute::node()[1])', nodeWithAttributes, filterAttributes(nodeWithAttributes.attributes)[0].nodeValue, ], // attribute [ 'string(attribute::node()[3])', nodeWithAttributes, filterAttributes(nodeWithAttributes.attributes)[2].nodeValue, ], // attribute ]; // Processing Instruction node = doc.getElementById( 'FunctionStringCaseStringNodesetProcessingInstruction' ).firstChild; if (node && node.nodeType === 7) { input.push(['string()', node, 'type="text/xml" href="test.xsl"']); } // CDATASection node = doc.getElementById( 'FunctionStringCaseStringNodesetCData' ).firstChild; if (node && node.nodeType === 4) { input.push(['string()', node, 'some cdata']); } input.forEach(([expr, node, expected]) => { it(`should convert ${expr} to ${expected}`, () => { assertString(node, null, expr, expected); }); }); }); it('string conversion of nodeset with namepace', () => { const doc = initDoc(` <!DOCTYPE html> <html xml:lang="en-us" xmlns="http://www.w3.org/1999/xhtml" xmlns:ev="http://some-namespace.com/nss"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>xpath-test</title> </head> <body class="yui3-skin-sam" id="body"> <div id="FunctionStringCaseStringNodesetNamespace" xmlns:asdf="http://www.123.com/"></div> </body> </html>`); const node = doc.getElementById( 'FunctionStringCaseStringNodesetNamespace' ); assertStringValue( node, null, 'string(namespace-uri(/*))', 'http://www.w3.org/1999/xhtml' ); }); it('string conversion fails when too many arguments are provided', () => { assertThrow('string(1, 2)'); }); it('concat()', () => { assertString('concat(0, 0)', '00'); assertString("concat('', '', 'b')", 'b'); assertString("concat('a', '', 'c')", 'ac'); assertString("concat('a', 'b', 'c', 'd', 'e')", 'abcde'); }); it('starts-with', () => { assertTrue("starts-with('', '')"); assertTrue("starts-with('a', '')"); assertTrue("starts-with('a', 'a')"); assertFalse("starts-with('a', 'b')"); assertTrue("starts-with('ba', 'b')"); assertFalse("starts-with('', 'b')"); }); it('starts-with() fails when too many arguments are provided', () => { assertThrow('starts-with(1, 2, 3)'); }); it('start-with() fails when not enough arguments are provided', () => { assertThrow('starts-with()'); assertThrow('starts-with(1)'); }); it('contains()', () => { assertTrue("contains('', '')"); assertFalse("contains('', 'a')"); assertTrue("contains('a', 'a')"); assertTrue("contains('a', '')"); assertTrue("contains('asdf', 'sd')"); assertFalse("contains('asdf', 'af')"); }); it('contains() fails when too many arguments are provided', () => { assertThrow('contains(1, 2, 3)'); }); it('contains() fails when too few arguments are provided', () => { assertThrow('contains()'); assertThrow('contains(1)'); }); it('substring-before()', () => { assertString("substring-before('', '')", ''); assertString("substring-before('', 'a')", ''); assertString("substring-before('a', '')", ''); assertString("substring-before('a', 'a')", ''); assertString("substring-before('ab', 'a')", ''); assertString("substring-before('ab', 'b')", 'a'); assertString("substring-before('abb', 'b')", 'a'); assertString("substring-before('ab', 'c')", ''); }); it('substring-before() fails with too many arguments', () => { assertThrow('substring-before(1, 2, 3)'); }); it('substring-before() with too few arguments', () => { assertThrow('substring-before()'); assertThrow('substring-before(1)'); }); it('substring-after()', () => { assertString("substring-after('', '')", ''); assertString("substring-after('', 'a')", ''); assertString("substring-after('a', '')", 'a'); assertString("substring-after('a', 'a')", ''); assertString("substring-after('ab', 'a')", 'b'); assertString("substring-after('aab', 'a')", 'ab'); assertString("substring-after('ab', 'b')", ''); assertString("substring-after('ab', 'c')", ''); }); it('substring-after() fails when too many arguments are provided', () => { assertThrow('substring-after(1, 2, 3)'); }); it('substring-after() fails when too few arguments are provided', () => { assertThrow('substring-after()'); assertThrow('substring-after(1)'); }); describe('substring()', () => { [ ["substring('12345', 2, 3)", '234'], ["substring('12345', 2)", '2345'], ["substring('12345', -1)", '12345'], ["substring('12345', 1 div 0)", ''], ["substring('12345', 0 div 0)", ''], ["substring('12345', -1 div 0)", '12345'], // this diverges from Firefox and Chrome implementations, but seems to follow the spec ["substring('12345', 1.5, 2.6)", '234'], ["substring('12345', 1.3, 2.3)", '12'], ["substring('12345', 0, 3)", '12'], ["substring('12345', 0, -1 div 0)", ''], ["substring('12345', 0 div 0, 3)", ''], ["substring('12345', 1, 0 div 0)", ''], ["substring('12345', -42, 1 div 0)", '12345'], ["substring('12345', -1 div 0, 1 div 0)", ''], ].forEach(([expr, expected]) => { it(`should evaluate "${expr}" to "${expected}"`, () => { assertString(expr, expected); }); }); }); it('substring() fails when too many arguments are provided', () => { assertThrow('substring(1, 2, 3, 4)'); }); it('substring() fails when too few arguments are provided', () => { assertThrow('substring()'); assertThrow('substring(1)'); }); it('string-length()', () => { const doc = initDoc(` <div> <div id="FunctionStringCaseStringLength1"></div> <div id="FunctionStringCaseStringLength2">asdf</div> </div>`); [ ["string-length('')", 0, doc], ["string-length(' ')", 1, doc], ["string-length('\r\n')", 2, doc], ["string-length('a')", 1, doc], [ 'string-length()', 0, doc.getElementById('FunctionStringCaseStringLength1'), ], [ 'string-length()', 4, doc.getElementById('FunctionStringCaseStringLength2'), ], ].forEach((t) => { assertNumber(t[2], null, t[0], t[1]); }); }); it('string-length() fails when too many arguments are provided', () => { assertThrow('string-length(1, 2)'); }); describe('normalize-space()', () => { const doc = initDoc(` <div> <div id="FunctionStringCaseStringNormalizeSpace1"></div> <div id="FunctionStringCaseStringNormalizeSpace2"> </div> <div id="FunctionStringCaseStringNormalizeSpace3"> a b </div> <div id="FunctionStringCaseStringNormalizeSpace4"> a bc c </div> </div>`); [ ["normalize-space('')", '', doc], ["normalize-space(' ')", '', doc], ["normalize-space(' a')", 'a', doc], ["normalize-space(' a ')", 'a', doc], ["normalize-space(' a b ')", 'a b', doc], ["normalize-space(' a b ')", 'a b', doc], ["normalize-space(' \r\n\t')", '', doc], ["normalize-space(' \f\v ')", '\f\v', doc], ["normalize-space('\na \f \r\v b\r\n ')", 'a \f \v b', doc], [ 'normalize-space()', '', doc.getElementById('FunctionStringCaseStringNormalizeSpace1'), ], [ 'normalize-space()', '', doc.getElementById('FunctionStringCaseStringNormalizeSpace2'), ], [ 'normalize-space()', 'a b', doc.getElementById('FunctionStringCaseStringNormalizeSpace3'), ], [ 'normalize-space()', 'a bc c', doc.getElementById('FunctionStringCaseStringNormalizeSpace4'), ], ].forEach(([expr, expected, node]) => { it(`should evaluate ${expr} to ${expected}`, () => { assertString(node, null, expr, expected); }); }); }); it('normalize-space() fails when too many arguments are provided', () => { assertThrow('normalize-space(1,2)'); }); it('translate()', () => { [ ["translate('', '', '')", ''], ["translate('a', '', '')", 'a'], ["translate('a', 'a', '')", ''], ["translate('a', 'b', '')", 'a'], ["translate('ab', 'a', 'A')", 'Ab'], ["translate('ab', 'a', 'AB')", 'Ab'], ["translate('aabb', 'ab', 'ba')", 'bbaa'], ["translate('aa', 'aa', 'bc')", 'bb'], ].forEach(([expr, expected]) => { assertString(expr, expected); }); }); it('translate() with a node parameter', () => { const doc = initDoc(` <div> <a id="A">TAXIcab</a> </div>`); assertString( doc.getElementById('A'), null, 'translate( ., "abc", "ABC")', 'TAXICAB' ); }); it('translate() fails when too many arguments are provided', () => { assertThrow('translate(1, 2, 3, 4)'); }); it('translate() fails when too few arguments are provided', () => { assertThrow('translate()'); assertThrow('translate(1)'); assertThrow('translate(1, 2)'); }); });