UNPKG

@v4fire/core

Version:
527 lines (460 loc) 12.8 kB
/* eslint-disable max-lines-per-function */ /*! * V4Fire Core * https://github.com/V4Fire/Core * * Released under the MIT license * https://github.com/V4Fire/Core/blob/master/LICENSE */ import Parser from 'core/json/stream/parser'; describe('core/json/stream/parser', () => { describe('parsing of primitive values', () => { it('integer numbers', async () => { { const tokens = []; for await (const token of Parser.from('100500')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '0'}, {name: 'numberChunk', value: '0'}, {name: 'numberChunk', value: '5'}, {name: 'numberChunk', value: '0'}, {name: 'numberChunk', value: '0'}, {name: 'endNumber'}, {name: 'numberValue', value: '100500'} ]); } { const tokens = []; for await (const token of Parser.from('-12')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '-12'} ]); } }); it('numbers with a floating points', async () => { { const tokens = []; for await (const token of Parser.from('12.3')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '3'}, {name: 'endNumber'}, {name: 'numberValue', value: '12.3'} ]); } { const tokens = []; for await (const token of Parser.from('-12.300')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '3'}, {name: 'numberChunk', value: '0'}, {name: 'numberChunk', value: '0'}, {name: 'endNumber'}, {name: 'numberValue', value: '-12.300'} ]); } }); it('numbers in an exponential form', async () => { { const tokens = []; for await (const token of Parser.from('12e10')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: 'e'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '0'}, {name: 'endNumber'}, {name: 'numberValue', value: '12e10'} ]); } { const tokens = []; for await (const token of Parser.from('-1E2')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: 'E'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '-1E2'} ]); } { const tokens = []; for await (const token of Parser.from('-1.4e2')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: 'e'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '-1.4e2'} ]); } { const tokens = []; for await (const token of Parser.from('4.4e-2')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: 'e'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '4.4e-2'} ]); } { const tokens = []; for await (const token of Parser.from('-4.4e-2')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: 'e'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '-4.4e-2'} ]); } }); it('strings', async () => { { const tokens = []; for await (const token of Parser.from('"foo bar"')) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startString'}, {name: 'stringChunk', value: 'f'}, {name: 'stringChunk', value: 'o'}, {name: 'stringChunk', value: 'o'}, {name: 'stringChunk', value: ' '}, {name: 'stringChunk', value: 'b'}, {name: 'stringChunk', value: 'a'}, {name: 'stringChunk', value: 'r'}, {name: 'endString'}, {name: 'stringValue', value: 'foo bar'} ]); } { const tokens = []; for await (const token of Parser.from(['"foo\t\tbar"'])) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startString'}, {name: 'stringChunk', value: 'foo\t\tbar'}, {name: 'endString'}, {name: 'stringValue', value: 'foo\t\tbar'} ]); } }); it('booleans', () => { { const parser = new Parser(); expect([...parser.processChunk('true'), ...parser.finishChunkProcessing()]).toEqual([ { name: 'trueValue', value: true } ]); } { const parser = new Parser(); expect([...parser.processChunk('false'), ...parser.finishChunkProcessing()]).toEqual([ { name: 'falseValue', value: false } ]); } }); it('null', () => { const parser = new Parser(); expect([...parser.processChunk('null'), ...parser.finishChunkProcessing()]).toEqual([ { name: 'nullValue', value: null } ]); }); }); it('should parse JSON chunks to tokens with the specified bytes step', async () => { const input = { a: 0.14E10, b: true, c: ['foo'], d: { e: null, f: [135, 2e-10, -32.42152] } }; const tokens = []; for await (const token of Parser.from(createChunkIterator(input, 3))) { tokens.push(token); } expect(tokens).toEqual([ {name: 'startObject'}, {name: 'startKey'}, {name: 'stringChunk', value: 'a'}, {name: 'endKey'}, {name: 'keyValue', value: 'a'}, {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '400'}, {name: 'numberChunk', value: '000'}, {name: 'numberChunk', value: '000'}, {name: 'endNumber'}, {name: 'numberValue', value: '1400000000'}, {name: 'startKey'}, {name: 'stringChunk', value: 'b'}, {name: 'endKey'}, {name: 'keyValue', value: 'b'}, {name: 'trueValue', value: true}, {name: 'startKey'}, {name: 'stringChunk', value: 'c'}, {name: 'endKey'}, {name: 'keyValue', value: 'c'}, {name: 'startArray'}, {name: 'startString'}, {name: 'stringChunk', value: 'fo'}, {name: 'stringChunk', value: 'o'}, {name: 'endString'}, {name: 'stringValue', value: 'foo'}, {name: 'endArray'}, {name: 'startKey'}, {name: 'stringChunk', value: 'd'}, {name: 'endKey'}, {name: 'keyValue', value: 'd'}, {name: 'startObject'}, {name: 'startKey'}, {name: 'stringChunk', value: 'e'}, {name: 'endKey'}, {name: 'keyValue', value: 'e'}, {name: 'nullValue', value: null}, {name: 'startKey'}, {name: 'stringChunk', value: 'f'}, {name: 'endKey'}, {name: 'keyValue', value: 'f'}, {name: 'startArray'}, {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '35'}, {name: 'endNumber'}, {name: 'numberValue', value: '135'}, {name: 'startNumber'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: 'e'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '1'}, {name: 'numberChunk', value: '0'}, {name: 'endNumber'}, {name: 'numberValue', value: '2e-10'}, {name: 'startNumber'}, {name: 'numberChunk', value: '-'}, {name: 'numberChunk', value: '3'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: '.'}, {name: 'numberChunk', value: '4'}, {name: 'numberChunk', value: '2'}, {name: 'numberChunk', value: '152'}, {name: 'endNumber'}, {name: 'numberValue', value: '-32.42152'}, {name: 'endArray'}, {name: 'endObject'}, {name: 'endObject'} ]); }); it('should successfully parse JSON chunks with escaped fields', () => { const parser = new Parser(); const input = { stringWithTabsAndNewlines: "Did it work?\nNo...\t\tI don't think so...", array: [1, 2, true, 'tabs?\t\t\t\u0001\u0002\u0003', false] }; const iter = createChunkIterator(input, 10), tokens = []; for (const chunk of iter) { for (const token of parser.processChunk(chunk)) { tokens.push(token); } } expect(tokens).toEqual([ {name: 'startObject'}, {name: 'startKey'}, {name: 'stringChunk', value: 'stringWi'}, {name: 'stringChunk', value: 'thTabsAndN'}, {name: 'stringChunk', value: 'ewlines'}, {name: 'endKey'}, {name: 'keyValue', value: 'stringWithTabsAndNewlines'}, {name: 'startString'}, {name: 'stringChunk', value: 'Did it wor'}, {name: 'stringChunk', value: 'k?'}, {name: 'stringChunk', value: '\n'}, {name: 'stringChunk', value: 'No...'}, {name: 'stringChunk', value: '\t'}, {name: 'stringChunk', value: '\t'}, {name: 'stringChunk', value: "I don't"}, {name: 'stringChunk', value: ' think so.'}, {name: 'stringChunk', value: '..'}, {name: 'endString'}, {name: 'stringValue', value: "Did it work?\nNo...\t\tI don't think so..."}, {name: 'startKey'}, {name: 'stringChunk', value: 'array'}, {name: 'endKey'}, {name: 'keyValue', value: 'array'}, {name: 'startArray'}, {name: 'startNumber'}, {name: 'numberChunk', value: '1'}, {name: 'endNumber'}, {name: 'numberValue', value: '1'}, {name: 'startNumber'}, {name: 'numberChunk', value: '2'}, {name: 'endNumber'}, {name: 'numberValue', value: '2'}, {name: 'trueValue', value: true}, {name: 'startString'}, {name: 'stringChunk', value: 'tabs?'}, {name: 'stringChunk', value: '\t'}, {name: 'stringChunk', value: '\t'}, {name: 'stringChunk', value: '\t'}, {name: 'stringChunk', value: '\x01'}, {name: 'stringChunk', value: '\x02'}, {name: 'stringChunk', value: '\x03'}, {name: 'endString'}, {name: 'stringValue', value: 'tabs?\t\t\t\x01\x02\x03'}, {name: 'falseValue', value: false}, {name: 'endArray'}, {name: 'endObject'} ]); }); it('should throw an error if a JSON chunk is invalid', () => { const parser = new Parser(), tokens = []; const input = '{"key1":1}garbage{"key3":2}', iter = createChunkIterator(input); expect(iterate).toThrow(new SyntaxError('Can\'t parse the input: unexpected characters')); function iterate() { for (const chunk of iter) { for (const el of parser.processChunk(chunk)) { tokens.push(el); } } } }); it("should throw an error if double quotes don't wrap a JSON object key", () => { const parser = new Parser(), tokens = []; const input = '{key: 1}', iter = createChunkIterator(input); expect(iterate).toThrow(new SyntaxError("Can't parse the input: expected an object key")); function iterate() { for (const chunk of iter) { for (const el of parser.processChunk(chunk)) { tokens.push(el); } } } }); it('should throw an error if single quotes wrap a JSON object key', () => { const parser = new Parser(), tokens = []; const input = "{'key': 1}", iter = createChunkIterator(input); expect(iterate).toThrow(new SyntaxError("Can't parse the input: expected an object key")); function iterate() { for (const chunk of iter) { for (const el of parser.processChunk(chunk)) { tokens.push(el); } } } }); }); function createChunkIterator(input, step = 1) { const data = typeof input === 'string' ? input : JSON.stringify(input); let index = 0; return { [Symbol.iterator]() { return { next() { const start = index; index += step; return { value: data.substring(start, index), done: start > data.length }; } }; } }; }