diff --git a/jest-setup.ts b/jest-setup.ts new file mode 100644 index 0000000..b58164c --- /dev/null +++ b/jest-setup.ts @@ -0,0 +1,2 @@ +import './test-utils/to-have-pattern'; +import './test-utils/to-match-groups'; diff --git a/package.json b/package.json index 7a9f5cc..86a00c7 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,12 @@ }, "jest": { "preset": "react-native", + "setupFilesAfterEnv": [ + "/jest-setup.ts" + ], + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}" + ], "modulePathIgnorePatterns": [ "/lib/" ] diff --git a/src/__tests__/builder.test.ts b/src/__tests__/builder.test.ts index f9ed336..6aa5f59 100644 --- a/src/__tests__/builder.test.ts +++ b/src/__tests__/builder.test.ts @@ -1,6 +1,6 @@ import { buildRegex } from '../builders'; -test('"regexBuilder" flags', () => { +test('`regexBuilder` flags', () => { expect(buildRegex('a').flags).toBe(''); expect(buildRegex({}, 'a').flags).toBe(''); diff --git a/src/components/__tests__/capture.test.tsx b/src/components/__tests__/capture.test.tsx index 02b166d..a969eee 100644 --- a/src/components/__tests__/capture.test.tsx +++ b/src/components/__tests__/capture.test.tsx @@ -1,19 +1,17 @@ -import { buildPattern } from '../../builders'; import { capture } from '../capture'; import { oneOrMore } from '../quantifiers'; -import { execRegex } from '../../test-utils'; -test('"capture" base cases', () => { - expect(buildPattern(capture('a'))).toBe('(a)'); - expect(buildPattern(capture('abc'))).toBe('(abc)'); - expect(buildPattern(capture(oneOrMore('abc')))).toBe('((?:abc)+)'); - expect(buildPattern(oneOrMore(capture('abc')))).toBe('(abc)+'); +test('`capture` base cases', () => { + expect(capture('a')).toHavePattern('(a)'); + expect(capture('abc')).toHavePattern('(abc)'); + expect(capture(oneOrMore('abc'))).toHavePattern('((?:abc)+)'); + expect(oneOrMore(capture('abc'))).toHavePattern('(abc)+'); }); -test('"capture" captures group', () => { - expect(execRegex('ab', [capture('b')])).toEqual(['b', 'b']); - expect(execRegex('ab', ['a', capture('b')])).toEqual(['ab', 'b']); - expect(execRegex('abc', ['a', capture('b'), capture('c')])).toEqual([ +test('`capture` captures group', () => { + expect(capture('b')).toMatchGroups('ab', ['b', 'b']); + expect(['a', capture('b')]).toMatchGroups('ab', ['ab', 'b']); + expect(['a', capture('b'), capture('c')]).toMatchGroups('abc', [ 'abc', 'b', 'c', diff --git a/src/components/__tests__/character-class.test.ts b/src/components/__tests__/character-class.test.ts index f73481b..4e8ab2c 100644 --- a/src/components/__tests__/character-class.test.ts +++ b/src/components/__tests__/character-class.test.ts @@ -1,5 +1,4 @@ -import { buildPattern } from '../../builders'; -import { one, oneOrMore } from '../quantifiers'; +import { oneOrMore, optionally, zeroOrMore } from '../quantifiers'; import { any, anyOf, @@ -9,81 +8,75 @@ import { whitespace, word, } from '../character-class'; -import { execRegex } from '../../test-utils'; -test('"whitespace" character class', () => { - expect(buildPattern(whitespace)).toEqual(`\\s`); - - expect(buildPattern(one('ab'), whitespace)).toEqual(`ab\\s`); - - expect(buildPattern(one('ab'), whitespace, one('c'))).toEqual(`ab\\sc`); +test('`any` character class', () => { + expect(any).toHavePattern('.'); + expect(['x', any]).toHavePattern('x.'); + expect(['x', any, 'x']).toHavePattern('x.x'); }); -test('"digit" character class', () => { - expect(buildPattern(digit)).toEqual(`\\d`); - - expect(buildPattern(one('ab'), digit)).toEqual(`ab\\d`); - - expect(buildPattern(one('ab'), digit, one('c'))).toEqual(`ab\\dc`); +test('`digit` character class', () => { + expect(digit).toHavePattern('\\d'); + expect(['x', digit]).toHavePattern('x\\d'); + expect(['x', digit, 'x']).toHavePattern('x\\dx'); }); -test('"word" character class', () => { - expect(buildPattern(word)).toEqual(`\\w`); - - expect(buildPattern(one('ab'), word)).toEqual(`ab\\w`); - - expect(buildPattern(one('ab'), word, one('c'))).toEqual(`ab\\wc`); +test('`word` character class', () => { + expect(word).toHavePattern('\\w'); + expect(['x', word]).toHavePattern('x\\w'); + expect(['x', word, 'x']).toHavePattern('x\\wx'); }); -test('"any" character class', () => { - expect(buildPattern(any)).toEqual(`.`); - - expect(buildPattern(one('ab'), any)).toEqual(`ab.`); - - expect(buildPattern(one('ab'), any, one('c'))).toEqual(`ab.c`); +test('`whitespace` character class', () => { + expect(whitespace).toHavePattern('\\s'); + expect(['x', whitespace]).toHavePattern('x\\s'); + expect(['x', whitespace, 'x']).toHavePattern('x\\sx'); }); -test('"anyOf" base cases', () => { - expect(buildPattern(anyOf('a'))).toBe('a'); - expect(buildPattern(anyOf('abc'))).toBe('[abc]'); +test('`anyOf` base cases', () => { + expect(anyOf('a')).toHavePattern('a'); + expect(['x', anyOf('a'), 'x']).toHavePattern('xax'); + expect(anyOf('ab')).toHavePattern('[ab]'); + expect(['x', anyOf('ab')]).toHavePattern('x[ab]'); + expect(['x', anyOf('ab'), 'x']).toHavePattern('x[ab]x'); }); -test('"anyOf" in context', () => { - expect(buildPattern('x', anyOf('a'), 'x')).toBe('xax'); - expect(buildPattern('x', anyOf('abc'), 'x')).toBe('x[abc]x'); - expect(buildPattern('x', oneOrMore(anyOf('abc')), 'x')).toBe('x[abc]+x'); +test('`anyOf` with quantifiers', () => { + expect(['x', oneOrMore(anyOf('abc')), 'x']).toHavePattern('x[abc]+x'); + expect(['x', optionally(anyOf('abc')), 'x']).toHavePattern('x[abc]?x'); + expect(['x', zeroOrMore(anyOf('abc')), 'x']).toHavePattern('x[abc]*x'); }); -test('"anyOf" escapes special characters', () => { - expect(buildPattern(anyOf('abc-+.'))).toBe('[-abc\\+\\.]'); +test('`anyOf` escapes special characters', () => { + expect(anyOf('abc-+.')).toHavePattern('[-abc\\+\\.]'); }); -test('"anyOf" moves hyphen to the first position', () => { - expect(buildPattern(anyOf('a-bc'))).toBe('[-abc]'); +test('`anyOf` moves hyphen to the first position', () => { + expect(anyOf('a-bc')).toHavePattern('[-abc]'); }); -test('"anyOf" throws on empty text', () => { +test('`anyOf` throws on empty text', () => { expect(() => anyOf('')).toThrowErrorMatchingInlineSnapshot( `"\`anyOf\` should received at least one character"` ); }); -test('"inverted" character class', () => { - expect(buildPattern(inverted(anyOf('a')))).toBe('[^a]'); - expect(buildPattern(inverted(anyOf('abc')))).toBe('[^abc]'); +test('`inverted` character class', () => { + expect(inverted(anyOf('a'))).toHavePattern('[^a]'); + expect(inverted(anyOf('abc'))).toHavePattern('[^abc]'); }); -test('"inverted" character class double inversion', () => { - expect(buildPattern(inverted(inverted(anyOf('a'))))).toBe('a'); - expect(buildPattern(inverted(inverted(anyOf('abc'))))).toBe('[abc]'); +test('`inverted` character class double inversion', () => { + expect(inverted(inverted(anyOf('a')))).toHavePattern('a'); + expect(inverted(inverted(anyOf('abc')))).toHavePattern('[abc]'); }); -test('"inverted" character class execution', () => { - expect(execRegex('aa', [inverted(anyOf('a'))])).toBeNull(); - expect(execRegex('aba', [inverted(anyOf('a'))])).toEqual(['b']); +test('`inverted` character class execution', () => { + expect(inverted(anyOf('a'))).toMatchGroups('aa', []); + expect(inverted(anyOf('a'))).toMatchGroups('aba', ['b']); }); -test('buildPattern throws on empty text', () => { +test('`encodeCharacterClass` throws on empty text', () => { expect(() => encodeCharacterClass({ type: 'characterClass', diff --git a/src/components/__tests__/choice-of.test.ts b/src/components/__tests__/choice-of.test.ts index 3748dab..30502c8 100644 --- a/src/components/__tests__/choice-of.test.ts +++ b/src/components/__tests__/choice-of.test.ts @@ -1,37 +1,34 @@ -import { buildPattern } from '../../builders'; import { oneOrMore, zeroOrMore } from '../quantifiers'; import { repeat } from '../repeat'; import { choiceOf } from '../choice-of'; -test('"choiceOf" using basic strings', () => { - expect(buildPattern(choiceOf('a'))).toEqual('a'); - expect(buildPattern(choiceOf('a', 'b'))).toEqual('a|b'); - expect(buildPattern(choiceOf('a', 'b', 'c'))).toEqual('a|b|c'); - expect(buildPattern(choiceOf('aaa', 'bbb'))).toEqual('aaa|bbb'); +test('`choiceOf` using basic strings', () => { + expect(choiceOf('a')).toHavePattern('a'); + expect(choiceOf('a', 'b')).toHavePattern('a|b'); + expect(choiceOf('a', 'b', 'c')).toHavePattern('a|b|c'); + expect(choiceOf('aaa', 'bbb')).toHavePattern('aaa|bbb'); }); -test('"choiceOf" used in sequence', () => { - expect(buildPattern('x', choiceOf('a'), 'x')).toEqual('xax'); - expect(buildPattern(choiceOf('a', 'b'), 'x')).toEqual('(?:a|b)x'); - expect(buildPattern('x', choiceOf('a', 'b'))).toEqual('x(?:a|b)'); +test('`choiceOf` used in sequence', () => { + expect(['x', choiceOf('a'), 'x']).toHavePattern('xax'); + expect([choiceOf('a', 'b'), 'x']).toHavePattern('(?:a|b)x'); + expect(['x', choiceOf('a', 'b')]).toHavePattern('x(?:a|b)'); - expect(buildPattern(choiceOf('a', 'b', 'c'))).toEqual('a|b|c'); - expect(buildPattern('x', choiceOf('a', 'b', 'c'))).toEqual('x(?:a|b|c)'); - expect(buildPattern(choiceOf('a', 'b', 'c'), 'x')).toEqual('(?:a|b|c)x'); + expect(choiceOf('a', 'b', 'c')).toHavePattern('a|b|c'); + expect(['x', choiceOf('a', 'b', 'c')]).toHavePattern('x(?:a|b|c)'); + expect([choiceOf('a', 'b', 'c'), 'x']).toHavePattern('(?:a|b|c)x'); - expect(buildPattern(choiceOf('aaa', 'bbb'))).toEqual('aaa|bbb'); + expect(choiceOf('aaa', 'bbb')).toHavePattern('aaa|bbb'); }); -test('"choiceOf" using nested regex', () => { - expect(buildPattern(choiceOf(oneOrMore('a'), zeroOrMore('b')))).toBe('a+|b*'); +test('`choiceOf` using nested regex', () => { + expect(choiceOf(oneOrMore('a'), zeroOrMore('b'))).toHavePattern('a+|b*'); expect( - buildPattern( - choiceOf(repeat({ min: 1, max: 3 }, 'a'), repeat({ count: 5 }, 'bx')) - ) - ).toBe('a{1,3}|(?:bx){5}'); + choiceOf(repeat({ min: 1, max: 3 }, 'a'), repeat({ count: 5 }, 'bx')) + ).toHavePattern('a{1,3}|(?:bx){5}'); }); -test('`anyOf` throws on empty options', () => { +test('`choiceOf` throws on empty options', () => { expect(() => choiceOf()).toThrowErrorMatchingInlineSnapshot( `"\`choiceOf\` should receive at least one option"` ); diff --git a/src/components/__tests__/quantifiers.test.tsx b/src/components/__tests__/quantifiers.test.tsx index f2fdddb..22934ff 100644 --- a/src/components/__tests__/quantifiers.test.tsx +++ b/src/components/__tests__/quantifiers.test.tsx @@ -1,54 +1,58 @@ -import { buildPattern, buildRegex } from '../../builders'; +import { buildRegex } from '../../builders'; import { digit } from '../character-class'; import { one, oneOrMore, optionally, zeroOrMore } from '../quantifiers'; -test('"oneOrMore" quantifier', () => { - expect(buildPattern(oneOrMore('a'))).toEqual('a+'); - expect(buildPattern(oneOrMore('ab'))).toEqual('(?:ab)+'); +test('`oneOrMore` quantifier', () => { + expect(oneOrMore('a')).toHavePattern('a+'); + expect(oneOrMore('ab')).toHavePattern('(?:ab)+'); }); -test('"one" quantifier', () => { - expect(buildPattern(one('a'))).toEqual('a'); - expect(buildPattern(one('ab'))).toEqual('ab'); +test('`one` quantifier', () => { + expect(one('a')).toHavePattern('a'); + expect(one('ab')).toHavePattern('ab'); }); -test('"optionally" quantifier', () => { - expect(buildPattern(optionally('a'))).toEqual('a?'); - expect(buildPattern(optionally('ab'))).toEqual('(?:ab)?'); +test('`optionally` quantifier', () => { + expect(optionally('a')).toHavePattern('a?'); + expect(optionally('ab')).toHavePattern('(?:ab)?'); }); -test('"zeroOrMore" quantifier', () => { - expect(buildPattern(zeroOrMore('a'))).toEqual('a*'); - expect(buildPattern(zeroOrMore('ab'))).toEqual('(?:ab)*'); +test('`zeroOrMore` quantifier', () => { + expect(zeroOrMore('a')).toHavePattern('a*'); + expect(zeroOrMore('ab')).toHavePattern('(?:ab)*'); }); -test('oneOrMore does not generate capture when grouping', () => { +test('`oneOrMore` does not generate capture when grouping', () => { const regex = buildRegex(oneOrMore('aa')); const groups = [...'aa'.match(regex)!]; expect(groups).toEqual(['aa']); }); -test('one does not generate capture when grouping', () => { +test('`one` does not generate capture when grouping', () => { const regex = buildRegex(one('aa')); const groups = [...'aa'.match(regex)!]; expect(groups).toEqual(['aa']); }); -test('optionally does not generate capture when grouping', () => { +test('`optionally` does not generate capture when grouping', () => { const regex = buildRegex(optionally('aa')); const groups = [...'aa'.match(regex)!]; expect(groups).toEqual(['aa']); }); -test('zeroOrMore does not generate capture when grouping', () => { +test('`zeroOrMore` does not generate capture when grouping', () => { const regex = buildRegex(zeroOrMore('aa')); const groups = [...'aa'.match(regex)!]; expect(groups).toEqual(['aa']); }); test('base quantifiers optimize grouping for atoms', () => { - expect(buildPattern(one(digit))).toBe('\\d'); - expect(buildPattern(oneOrMore(digit))).toBe('\\d+'); - expect(buildPattern(optionally(digit))).toBe('\\d?'); - expect(buildPattern(zeroOrMore(digit))).toBe('\\d*'); + expect(one(digit)).toHavePattern('\\d'); + expect(oneOrMore(digit)).toHavePattern('\\d+'); + expect(optionally(digit)).toHavePattern('\\d?'); + expect(zeroOrMore(digit)).toHavePattern('\\d*'); + + expect(oneOrMore('a')).toHavePattern('a+'); + expect(optionally('a')).toHavePattern('a?'); + expect(zeroOrMore('a')).toHavePattern('a*'); }); diff --git a/src/components/__tests__/repeat.test.tsx b/src/components/__tests__/repeat.test.tsx index 0bb2225..0243930 100644 --- a/src/components/__tests__/repeat.test.tsx +++ b/src/components/__tests__/repeat.test.tsx @@ -1,25 +1,24 @@ -import { buildPattern } from '../../builders'; import { digit } from '../character-class'; import { oneOrMore, zeroOrMore } from '../quantifiers'; import { repeat } from '../repeat'; -test('"repeat" quantifier', () => { - expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}'); - expect(buildPattern('a', repeat({ min: 1 }, 'b'))).toEqual('ab{1,}'); - expect(buildPattern('a', repeat({ count: 1 }, 'b'))).toEqual('ab{1}'); +test('`repeat` quantifier', () => { + expect(['a', repeat({ min: 1, max: 5 }, 'b')]).toHavePattern('ab{1,5}'); + expect(['a', repeat({ min: 1 }, 'b')]).toHavePattern('ab{1,}'); + expect(['a', repeat({ count: 1 }, 'b')]).toHavePattern('ab{1}'); - expect(buildPattern('a', repeat({ count: 1 }, 'a', zeroOrMore('b')))).toEqual( + expect(['a', repeat({ count: 1 }, 'a', zeroOrMore('b'))]).toHavePattern( 'a(?:ab*){1}' ); - expect( - buildPattern(repeat({ count: 5 }, 'text', ' ', oneOrMore('d'))) - ).toEqual('(?:text d+){5}'); + expect(repeat({ count: 5 }, 'text', ' ', oneOrMore('d'))).toHavePattern( + '(?:text d+){5}' + ); }); -test('"repeat"" optimizes grouping for atoms', () => { - expect(buildPattern(repeat({ count: 2 }, digit))).toBe('\\d{2}'); - expect(buildPattern(repeat({ min: 2 }, digit))).toBe('\\d{2,}'); - expect(buildPattern(repeat({ min: 1, max: 5 }, digit))).toBe('\\d{1,5}'); +test('`repeat` optimizes grouping for atoms', () => { + expect(repeat({ count: 2 }, digit)).toHavePattern('\\d{2}'); + expect(repeat({ min: 2 }, digit)).toHavePattern('\\d{2,}'); + expect(repeat({ min: 1, max: 5 }, digit)).toHavePattern('\\d{1,5}'); }); test('`repeat` throws on no children', () => { diff --git a/src/components/character-class.ts b/src/components/character-class.ts index 1de3e6f..880030b 100644 --- a/src/components/character-class.ts +++ b/src/components/character-class.ts @@ -8,12 +8,6 @@ export const any: CharacterClass = { inverted: false, }; -export const whitespace: CharacterClass = { - type: 'characterClass', - characters: ['\\s'], - inverted: false, -}; - export const digit: CharacterClass = { type: 'characterClass', characters: ['\\d'], @@ -26,6 +20,12 @@ export const word: CharacterClass = { inverted: false, }; +export const whitespace: CharacterClass = { + type: 'characterClass', + characters: ['\\s'], + inverted: false, +}; + export function anyOf(characters: string): CharacterClass { const charactersArray = characters.split('').map(escapeText); if (charactersArray.length === 0) { diff --git a/src/encoder/__tests__/encoder.test.tsx b/src/encoder/__tests__/encoder.test.tsx index 5867c20..919573a 100644 --- a/src/encoder/__tests__/encoder.test.tsx +++ b/src/encoder/__tests__/encoder.test.tsx @@ -8,25 +8,25 @@ import { import { repeat } from '../../components/repeat'; test('basic quantifies', () => { - expect(buildPattern('a')).toEqual('a'); - expect(buildPattern('a', 'b')).toEqual('ab'); + expect('a').toHavePattern('a'); + expect(['a', 'b']).toHavePattern('ab'); - expect(buildPattern(oneOrMore('a'))).toEqual('a+'); - expect(buildPattern(optionally('a'))).toEqual('a?'); + expect(oneOrMore('a')).toHavePattern('a+'); + expect(optionally('a')).toHavePattern('a?'); - expect(buildPattern('a', oneOrMore('b'))).toEqual('ab+'); - expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+'); - expect(buildPattern('a', oneOrMore('bc'))).toEqual('a(?:bc)+'); + expect(['a', oneOrMore('b')]).toHavePattern('ab+'); + expect(['a', oneOrMore('bc')]).toHavePattern('a(?:bc)+'); + expect(['a', oneOrMore('bc')]).toHavePattern('a(?:bc)+'); - expect(buildPattern('a', repeat({ min: 1, max: 5 }, 'b'))).toEqual('ab{1,5}'); + expect(['a', repeat({ min: 1, max: 5 }, 'b')]).toHavePattern('ab{1,5}'); - expect(buildPattern('a', zeroOrMore('b'))).toEqual('ab*'); - expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*'); - expect(buildPattern('a', zeroOrMore('bc'))).toEqual('a(?:bc)*'); + expect(['a', zeroOrMore('b')]).toHavePattern('ab*'); + expect(['a', zeroOrMore('bc')]).toHavePattern('a(?:bc)*'); + expect(['a', zeroOrMore('bc')]).toHavePattern('a(?:bc)*'); - expect(buildPattern(optionally('a'), 'b')).toEqual('a?b'); + expect([optionally('a'), 'b']).toHavePattern('a?b'); - expect(buildPattern(optionally('a'), 'b', one('d'))).toEqual('a?bd'); + expect([optionally('a'), 'b', one('d')]).toHavePattern('a?bd'); }); test('regex constructor', () => { @@ -34,35 +34,35 @@ test('regex constructor', () => { expect(buildRegex('a').test('b')).toBeFalsy(); }); -test('"buildPattern" escapes special characters', () => { - expect(buildPattern('.')).toBe('\\.'); - expect(buildPattern('*')).toBe('\\*'); - expect(buildPattern('+')).toBe('\\+'); - expect(buildPattern('?')).toBe('\\?'); - expect(buildPattern('^')).toBe('\\^'); - expect(buildPattern('$')).toBe('\\$'); - expect(buildPattern('{')).toBe('\\{'); - expect(buildPattern('}')).toBe('\\}'); - expect(buildPattern('|')).toBe('\\|'); - expect(buildPattern('[')).toBe('\\['); - expect(buildPattern(']')).toBe('\\]'); - expect(buildPattern('\\')).toBe('\\\\'); +test('`buildPattern` escapes special characters', () => { + expect('.').toHavePattern('\\.'); + expect('*').toHavePattern('\\*'); + expect('+').toHavePattern('\\+'); + expect('?').toHavePattern('\\?'); + expect('^').toHavePattern('\\^'); + expect('$').toHavePattern('\\$'); + expect('{').toHavePattern('\\{'); + expect('}').toHavePattern('\\}'); + expect('|').toHavePattern('\\|'); + expect('[').toHavePattern('\\['); + expect(']').toHavePattern('\\]'); + expect('\\').toHavePattern('\\\\'); - expect(buildPattern('*.*')).toBe('\\*\\.\\*'); + expect('*.*').toHavePattern('\\*\\.\\*'); - expect(buildPattern(oneOrMore('.*'), zeroOrMore('[]{}'))).toBe( + expect([oneOrMore('.*'), zeroOrMore('[]{}')]).toHavePattern( '(?:\\.\\*)+(?:\\[\\]\\{\\})*' ); }); -test('buildRegex throws error on unknown element', () => { +test('`buildRegex` throws error on unknown element', () => { expect(() => // @ts-expect-error intentionally passing incorrect object buildRegex({ type: 'unknown' }) - ).toThrowErrorMatchingInlineSnapshot(`"Unknown elements type unknown"`); + ).toThrowErrorMatchingInlineSnapshot(`"Unknown element type unknown"`); }); -test('buildPattern throws on empty text', () => { +test('`buildPattern` throws on empty text', () => { expect(() => buildPattern('')).toThrowErrorMatchingInlineSnapshot( `"\`encodeText\`: received text should not be empty"` ); diff --git a/src/encoder/encoder.ts b/src/encoder/encoder.ts index 5a586aa..61c49a3 100644 --- a/src/encoder/encoder.ts +++ b/src/encoder/encoder.ts @@ -56,7 +56,7 @@ export function encodeElement(element: RegexElement | string): EncoderNode { } // @ts-expect-error User passed incorrect type - throw new Error(`Unknown elements type ${element.type}`); + throw new Error(`Unknown element type ${element.type}`); } function encodeText(text: string): EncoderNode { diff --git a/src/test-utils.ts b/src/test-utils.ts deleted file mode 100644 index 520f235..0000000 --- a/src/test-utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { buildRegex } from './builders'; -import type { RegexElement } from './components/types'; - -export function execRegex( - text: string, - elements: Array -) { - const regex = buildRegex(...elements); - const result = regex.exec(text); - return result ? [...result] : null; -} diff --git a/src/utils.ts b/src/utils.ts index d0d930d..ef5b9dc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -34,6 +34,10 @@ export function isRegexElement(element: unknown): element is RegexElement { return typeof element === 'object' && element !== null && 'type' in element; } +export function isRegexElementOrString(element: unknown): boolean { + return typeof element === 'string' || isRegexElement(element); +} + // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping export function escapeText(text: string) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string diff --git a/test-utils/to-have-pattern.ts b/test-utils/to-have-pattern.ts new file mode 100644 index 0000000..2ed2459 --- /dev/null +++ b/test-utils/to-have-pattern.ts @@ -0,0 +1,49 @@ +import { buildPattern } from '../src/builders'; +import type { RegexElement } from '../src/components/types'; +import { isRegexElementOrString } from '../src/utils'; + +export function toHavePattern( + this: jest.MatcherContext, + elements: Array | RegexElement | string, + expected: string +) { + if (!Array.isArray(elements)) { + elements = [elements]; + } + + elements.forEach((e) => { + if (!isRegexElementOrString(e)) { + throw new Error( + `\`toHavePattern()\` received an array of RegexElements and strings.` + ); + } + }); + + const received = buildPattern(...elements); + + const options = { + isNot: this.isNot, + }; + + return { + pass: expected === received, + message: () => + this.utils.matcherHint('toHavePattern', undefined, undefined, options) + + '\n\n' + + `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected( + expected + )}\n` + + `Received: ${this.utils.printReceived(received)}`, + }; +} + +expect.extend({ toHavePattern }); + +declare global { + namespace jest { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Matchers { + toHavePattern(expected: string): R; + } + } +} diff --git a/test-utils/to-match-groups.ts b/test-utils/to-match-groups.ts new file mode 100644 index 0000000..b453a7d --- /dev/null +++ b/test-utils/to-match-groups.ts @@ -0,0 +1,52 @@ +import { buildRegex } from '../src/builders'; +import type { RegexElement } from '../src/components/types'; +import { isRegexElementOrString } from '../src/utils'; + +export function toMatchGroups( + this: jest.MatcherContext, + elements: Array | RegexElement | string, + input: string, + expected: string[] +) { + if (!Array.isArray(elements)) { + elements = [elements]; + } + + elements.forEach((e) => { + if (!isRegexElementOrString(e)) { + throw new Error( + `\`toHavePattern()\` received an array of RegexElements and strings.` + ); + } + }); + + const regex = buildRegex(...elements); + const options = { + isNot: this.isNot, + }; + + const execResult = regex.exec(input); + const actual = execResult ? [...execResult] : []; + + return { + pass: this.equals(actual, expected), + message: () => + this.utils.matcherHint('toMatchGroups', undefined, undefined, options) + + '\n\n' + + `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected( + expected + )}\n` + + `Received: ${this.utils.printReceived(actual)}`, + }; +} + +expect.extend({ toMatchGroups }); + +declare global { + namespace jest { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Matchers { + toMatchGroups(input: string, expected: string[]): R; + } + } +}