import { parse, match, interpolation } from './core'; const expectToParse = (node, input, result, lastIndex = 0) => { const state = { quasis: [input], expressions: [], x: 0, y: 0 }; if (result) result.tag = 'node'; expect(node(state)).toEqual(result); // NOTE: After parsing we expect the current index to exactly match the // sum amount of matched characters if (result === undefined) { expect(state.y).toBe(0); } else { const index = lastIndex || result.reduce((acc, x) => acc + x.length, 0); expect(state.y).toBe(index); } }; describe('can create nameless matchers', () => { it('matches without tagging', () => { const state = { quasis: ['1'], expressions: [], x: 0, y: 0 }; const node = match(null)`${/1/}`; expect(node(state)).toEqual(['1']); }); }); describe('required matcher', () => { const node = match('node')`${/1/}`; it.each` input | result ${'1'} | ${['1']} ${''} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); it('matches empty regex patterns', () => { const node = match('node')`${/[ ]*/}`; expectToParse(node, '', ['']); }); }); describe('optional matcher', () => { const node = match('node')`${/1/}?`; it.each` input | result ${'1'} | ${['1']} ${'_'} | ${[]} ${''} | ${[]} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('star matcher', () => { const node = match('node')`${/1/}*`; it.each` input | result ${'1'} | ${['1']} ${'11'} | ${['1', '1']} ${'111'} | ${['1', '1', '1']} ${'_'} | ${[]} ${''} | ${[]} `('should return $result when "$input" is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus matcher', () => { const node = match('node')`${/1/}+`; it.each` input | result ${'1'} | ${['1']} ${'11'} | ${['1', '1']} ${'111'} | ${['1', '1', '1']} ${'_'} | ${undefined} ${''} | ${undefined} `('should return $result when "$input" is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('optional then required matcher', () => { const node = match('node')`${/1/}? ${/2/}`; it.each` input | result ${'12'} | ${['1', '2']} ${'2'} | ${['2']} ${''} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('star then required matcher', () => { const node = match('node')`${/1/}* ${/2/}`; it.each` input | result ${'12'} | ${['1', '2']} ${'112'} | ${['1', '1', '2']} ${'2'} | ${['2']} ${''} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus then required matcher', () => { const node = match('node')`${/1/}+ ${/2/}`; it.each` input | result ${'12'} | ${['1', '2']} ${'112'} | ${['1', '1', '2']} ${'2'} | ${undefined} ${''} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('optional group then required matcher', () => { const node = match('node')`(${/1/} ${/2/})? ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'3'} | ${['3']} ${'23'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('star group then required matcher', () => { const node = match('node')`(${/1/} ${/2/})* ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'12123'} | ${['1', '2', '1', '2', '3']} ${'3'} | ${['3']} ${'23'} | ${undefined} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus group then required matcher', () => { const node = match('node')`(${/1/} ${/2/})+ ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'12123'} | ${['1', '2', '1', '2', '3']} ${'23'} | ${undefined} ${'3'} | ${undefined} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('optional group with nested optional matcher, then required matcher', () => { const node = match('node')`(${/1/}? ${/2/})? ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'23'} | ${['2', '3']} ${'3'} | ${['3']} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('star group with nested optional matcher, then required matcher', () => { const node = match('node')`(${/1/}? ${/2/})* ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'23'} | ${['2', '3']} ${'223'} | ${['2', '2', '3']} ${'2123'} | ${['2', '1', '2', '3']} ${'3'} | ${['3']} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus group with nested optional matcher, then required matcher', () => { const node = match('node')`(${/1/}? ${/2/})+ ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'23'} | ${['2', '3']} ${'223'} | ${['2', '2', '3']} ${'2123'} | ${['2', '1', '2', '3']} ${'3'} | ${undefined} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus group with nested plus matcher, then required matcher', () => { const node = match('node')`(${/1/}+ ${/2/})+ ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'1123'} | ${['1', '1', '2', '3']} ${'12123'} | ${['1', '2', '1', '2', '3']} ${'121123'} | ${['1', '2', '1', '1', '2', '3']} ${'3'} | ${undefined} ${'23'} | ${undefined} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('plus group with nested required and plus matcher, then required matcher', () => { const node = match('node')`(${/1/} ${/2/}+)+ ${/3/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'1223'} | ${['1', '2', '2', '3']} ${'122123'} | ${['1', '2', '2', '1', '2', '3']} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('nested plus group with nested required and plus matcher, then required matcher or alternate', () => { const node = match('node')`(${/1/} ${/2/}+)+ ${/3/} | ${/1/}`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'1223'} | ${['1', '2', '2', '3']} ${'122123'} | ${['1', '2', '2', '1', '2', '3']} ${'1'} | ${['1']} ${'13'} | ${['1']} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('nested plus group with nested required and plus matcher, then alternate', () => { const node = match('node')`(${/1/} ${/2/}+)+ (${/3/} | ${/4/})`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'124'} | ${['1', '2', '4']} ${'1223'} | ${['1', '2', '2', '3']} ${'1224'} | ${['1', '2', '2', '4']} ${'1'} | ${undefined} ${'13'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('regular alternate', () => { const node = match('node')`${/1/} | ${/2/} | ${/3/} | ${/4/}`; it.each` input | result ${'1'} | ${['1']} ${'2'} | ${['2']} ${'3'} | ${['3']} ${'4'} | ${['4']} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('nested alternate in nested alternate in alternate', () => { const node = match('node')`((${/1/} | ${/2/}) | ${/3/}) | ${/4/}`; it.each` input | result ${'1'} | ${['1']} ${'2'} | ${['2']} ${'3'} | ${['3']} ${'4'} | ${['4']} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('alternate after required matcher', () => { const node = match('node')`${/1/} (${/2/} | ${/3/})`; it.each` input | result ${'12'} | ${['1', '2']} ${'13'} | ${['1', '3']} ${'14'} | ${undefined} ${'3'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('alternate with star group and required matcher after required matcher', () => { const node = match('node')`${/1/} (${/2/}* ${/3/} | ${/4/})`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'1223'} | ${['1', '2', '2', '3']} ${'13'} | ${['1', '3']} ${'14'} | ${['1', '4']} ${'12'} | ${undefined} ${'15'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('alternate with plus group and required matcher after required matcher', () => { const node = match('node')`${/1/} (${/2/}+ ${/3/} | ${/4/})`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'1223'} | ${['1', '2', '2', '3']} ${'14'} | ${['1', '4']} ${'13'} | ${undefined} ${'12'} | ${undefined} ${'15'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('alternate with optional and required matcher after required matcher', () => { const node = match('node')`${/1/} (${/2/}? ${/3/} | ${/4/})`; it.each` input | result ${'123'} | ${['1', '2', '3']} ${'13'} | ${['1', '3']} ${'14'} | ${['1', '4']} ${'12'} | ${undefined} ${'15'} | ${undefined} ${'_'} | ${undefined} `('should return $result when $input is passed', ({ input, result }) => { expectToParse(node, input, result); }); }); describe('non-capturing group', () => { const node = match('node')`${/1/} (?: ${/2/}+)`; it.each` input | result | lastIndex ${'12'} | ${['1']} | ${2} ${'122'} | ${['1']} | ${3} ${'13'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('non-capturing shorthand', () => { const node = match('node')`${/1/} :${/2/}+`; it.each` input | result | lastIndex ${'12'} | ${['1']} | ${2} ${'122'} | ${['1']} | ${3} ${'13'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('non-capturing group with plus matcher, then required matcher', () => { const node = match('node')`(?: ${/1/}+) ${/2/}`; it.each` input | result | lastIndex ${'12'} | ${['2']} | ${2} ${'112'} | ${['2']} | ${3} ${'1'} | ${undefined} | ${0} ${'13'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('non-capturing group with star group and required matcher, then required matcher', () => { const node = match('node')`(?: ${/1/}* ${/2/}) ${/3/}`; it.each` input | result | lastIndex ${'123'} | ${['3']} | ${3} ${'1123'} | ${['3']} | ${4} ${'23'} | ${['3']} | ${2} ${'13'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('non-capturing group with plus group and required matcher, then required matcher', () => { const node = match('node')`(?: ${/1/}+ ${/2/}) ${/3/}`; it.each` input | result | lastIndex ${'123'} | ${['3']} | ${3} ${'1123'} | ${['3']} | ${4} ${'23'} | ${undefined} | ${0} ${'13'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('non-capturing group with optional and required matcher, then required matcher', () => { const node = match('node')`(?: ${/1/}? ${/2/}) ${/3/}`; it.each` input | result | lastIndex ${'123'} | ${['3']} | ${3} ${'23'} | ${['3']} | ${2} ${'13'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('positive lookahead group', () => { const node = match('node')`(?= ${/1/}) ${/\d/}`; it.each` input | result | lastIndex ${'1'} | ${['1']} | ${1} ${'13'} | ${['1']} | ${1} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('positive lookahead shorthand', () => { const node = match('node')`=${/1/} ${/\d/}`; it.each` input | result | lastIndex ${'1'} | ${['1']} | ${1} ${'13'} | ${['1']} | ${1} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('positive lookahead group with plus matcher', () => { const node = match('node')`(?= ${/1/}+) ${/\d/}`; it.each` input | result | lastIndex ${'1'} | ${['1']} | ${1} ${'11'} | ${['1']} | ${1} ${'12'} | ${['1']} | ${1} ${'22'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('positive lookahead group with plus group and required matcher', () => { const node = match('node')`(?= ${/1/}+ ${/2/}) ${/\d/}`; it.each` input | result | lastIndex ${'12'} | ${['1']} | ${1} ${'112'} | ${['1']} | ${1} ${'1123'} | ${['1']} | ${1} ${'2'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('negative lookahead group', () => { const node = match('node')`(?! ${/1/}) ${/\d/}`; it.each` input | result | lastIndex ${'2'} | ${['2']} | ${1} ${'23'} | ${['2']} | ${1} ${'1'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('negative lookahead shorthand', () => { const node = match('node')`!${/1/} ${/\d/}`; it.each` input | result | lastIndex ${'2'} | ${['2']} | ${1} ${'23'} | ${['2']} | ${1} ${'1'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('longer negative lookahead group', () => { const node = match('node')`${/1/} (?! ${/2/} ${/3/}) ${/\d/} ${/\d/}`; it.each` input | result | lastIndex ${'145'} | ${['1', '4', '5']} | ${3} ${'124'} | ${['1', '2', '4']} | ${3} ${'123'} | ${undefined} | ${0} ${'2'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('negative lookahead group with plus matcher', () => { const node = match('node')`(?! ${/1/}+) ${/\d/}`; it.each` input | result | lastIndex ${'2'} | ${['2']} | ${1} ${'21'} | ${['2']} | ${1} ${'22'} | ${['2']} | ${1} ${'11'} | ${undefined} | ${0} ${'1'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('negative lookahead group with plus group and required matcher', () => { const node = match('node')`(?! ${/1/}+ ${/2/}) ${/\d/}`; it.each` input | result | lastIndex ${'21'} | ${['2']} | ${1} ${'211'} | ${['2']} | ${1} ${'113'} | ${['1']} | ${1} ${'1'} | ${['1']} | ${1} ${'112'} | ${undefined} | ${0} ${'12'} | ${undefined} | ${0} ${'_'} | ${undefined} | ${0} `( 'should return $result when $input is passed', ({ input, result, lastIndex }) => { expectToParse(node, input, result, lastIndex); } ); }); describe('interpolation parsing', () => { const node = match('node')` ${/1/} ${interpolation((x) => (x > 1 ? x : null))} ${/3/} `; it('matches interpolations', () => { const expected = ['1', 2, '3']; expected.tag = 'node'; expect(parse(node)`1${2}3`).toEqual(expected); }); it('does not match invalid inputs', () => { expect(parse(node)`13`).toBe(undefined); expect(parse(node)`13${2}`).toBe(undefined); expect(parse(node)`${2}13`).toBe(undefined); expect(parse(node)`1${1}3`).toBe(undefined); }); }); describe('string matching', () => { const node = match('node')` ${'1'} ${'2'} `; it('matches strings', () => { const expected = ['1', '2']; expected.tag = 'node'; expect(parse(node)('12')).toEqual(expected); expect(parse(node)('13')).toBe(undefined); }); });