|  | 
|  | 1 | +import {readFile} from 'node:fs/promises'; | 
|  | 2 | +import * as path from 'node:path'; | 
|  | 3 | +import {globCompile} from './glob.ts'; | 
|  | 4 | + | 
|  | 5 | +async function loadGlobTestData(): Promise<{caseNames: string[], caseDataMap: Record<string, string>}> { | 
|  | 6 | +  const fileContent = await readFile(path.join(import.meta.dirname, 'glob.test.txt'), 'utf8'); | 
|  | 7 | +  const fileLines = fileContent.split('\n'); | 
|  | 8 | +  const caseDataMap: Record<string, string> = {}; | 
|  | 9 | +  const caseNameMap: Record<string, boolean> = {}; | 
|  | 10 | +  for (let line of fileLines) { | 
|  | 11 | +    line = line.trim(); | 
|  | 12 | +    if (!line || line.startsWith('#')) continue; | 
|  | 13 | +    const parts = line.split('=', 2); | 
|  | 14 | +    if (parts.length !== 2) throw new Error(`Invalid test case line: ${line}`); | 
|  | 15 | + | 
|  | 16 | +    const key = parts[0].trim(); | 
|  | 17 | +    let value = parts[1].trim(); | 
|  | 18 | +    value = value.substring(1, value.length - 1); // remove quotes | 
|  | 19 | +    value = value.replace(/\\\\/g, '\\').replaceAll(/\\\//g, '/'); | 
|  | 20 | +    caseDataMap[key] = value; | 
|  | 21 | +    if (key.startsWith('pattern_')) caseNameMap[key.substring('pattern_'.length)] = true; | 
|  | 22 | +  } | 
|  | 23 | +  return {caseNames: Object.keys(caseNameMap), caseDataMap}; | 
|  | 24 | +} | 
|  | 25 | + | 
|  | 26 | +function loadGlobGolangCases() { | 
|  | 27 | +  // https://github.com/gobwas/glob/blob/master/glob_test.go | 
|  | 28 | +  function glob(matched: boolean, pattern: string, input: string, separators: string = '') { | 
|  | 29 | +    return {matched, pattern, input, separators}; | 
|  | 30 | +  } | 
|  | 31 | +  return [ | 
|  | 32 | +    glob(true, '* ?at * eyes', 'my cat has very bright eyes'), | 
|  | 33 | + | 
|  | 34 | +    glob(true, '', ''), | 
|  | 35 | +    glob(false, '', 'b'), | 
|  | 36 | + | 
|  | 37 | +    glob(true, '*ä', 'åä'), | 
|  | 38 | +    glob(true, 'abc', 'abc'), | 
|  | 39 | +    glob(true, 'a*c', 'abc'), | 
|  | 40 | +    glob(true, 'a*c', 'a12345c'), | 
|  | 41 | +    glob(true, 'a?c', 'a1c'), | 
|  | 42 | +    glob(true, 'a.b', 'a.b', '.'), | 
|  | 43 | +    glob(true, 'a.*', 'a.b', '.'), | 
|  | 44 | +    glob(true, 'a.**', 'a.b.c', '.'), | 
|  | 45 | +    glob(true, 'a.?.c', 'a.b.c', '.'), | 
|  | 46 | +    glob(true, 'a.?.?', 'a.b.c', '.'), | 
|  | 47 | +    glob(true, '?at', 'cat'), | 
|  | 48 | +    glob(true, '?at', 'fat'), | 
|  | 49 | +    glob(true, '*', 'abc'), | 
|  | 50 | +    glob(true, `\\*`, '*'), | 
|  | 51 | +    glob(true, '**', 'a.b.c', '.'), | 
|  | 52 | + | 
|  | 53 | +    glob(false, '?at', 'at'), | 
|  | 54 | +    glob(false, '?at', 'fat', 'f'), | 
|  | 55 | +    glob(false, 'a.*', 'a.b.c', '.'), | 
|  | 56 | +    glob(false, 'a.?.c', 'a.bb.c', '.'), | 
|  | 57 | +    glob(false, '*', 'a.b.c', '.'), | 
|  | 58 | + | 
|  | 59 | +    glob(true, '*test', 'this is a test'), | 
|  | 60 | +    glob(true, 'this*', 'this is a test'), | 
|  | 61 | +    glob(true, '*is *', 'this is a test'), | 
|  | 62 | +    glob(true, '*is*a*', 'this is a test'), | 
|  | 63 | +    glob(true, '**test**', 'this is a test'), | 
|  | 64 | +    glob(true, '**is**a***test*', 'this is a test'), | 
|  | 65 | + | 
|  | 66 | +    glob(false, '*is', 'this is a test'), | 
|  | 67 | +    glob(false, '*no*', 'this is a test'), | 
|  | 68 | +    glob(true, '[!a]*', 'this is a test3'), | 
|  | 69 | + | 
|  | 70 | +    glob(true, '*abc', 'abcabc'), | 
|  | 71 | +    glob(true, '**abc', 'abcabc'), | 
|  | 72 | +    glob(true, '???', 'abc'), | 
|  | 73 | +    glob(true, '?*?', 'abc'), | 
|  | 74 | +    glob(true, '?*?', 'ac'), | 
|  | 75 | +    glob(false, 'sta', 'stagnation'), | 
|  | 76 | +    glob(true, 'sta*', 'stagnation'), | 
|  | 77 | +    glob(false, 'sta?', 'stagnation'), | 
|  | 78 | +    glob(false, 'sta?n', 'stagnation'), | 
|  | 79 | + | 
|  | 80 | +    glob(true, '{abc,def}ghi', 'defghi'), | 
|  | 81 | +    glob(true, '{abc,abcd}a', 'abcda'), | 
|  | 82 | +    glob(true, '{a,ab}{bc,f}', 'abc'), | 
|  | 83 | +    glob(true, '{*,**}{a,b}', 'ab'), | 
|  | 84 | +    glob(false, '{*,**}{a,b}', 'ac'), | 
|  | 85 | + | 
|  | 86 | +    glob(true, '/{rate,[a-z][a-z][a-z]}*', '/rate'), | 
|  | 87 | +    glob(true, '/{rate,[0-9][0-9][0-9]}*', '/rate'), | 
|  | 88 | +    glob(true, '/{rate,[a-z][a-z][a-z]}*', '/usd'), | 
|  | 89 | + | 
|  | 90 | +    glob(true, '{*.google.*,*.yandex.*}', 'www.google.com', '.'), | 
|  | 91 | +    glob(true, '{*.google.*,*.yandex.*}', 'www.yandex.com', '.'), | 
|  | 92 | +    glob(false, '{*.google.*,*.yandex.*}', 'yandex.com', '.'), | 
|  | 93 | +    glob(false, '{*.google.*,*.yandex.*}', 'google.com', '.'), | 
|  | 94 | + | 
|  | 95 | +    glob(true, '{*.google.*,yandex.*}', 'www.google.com', '.'), | 
|  | 96 | +    glob(true, '{*.google.*,yandex.*}', 'yandex.com', '.'), | 
|  | 97 | +    glob(false, '{*.google.*,yandex.*}', 'www.yandex.com', '.'), | 
|  | 98 | +    glob(false, '{*.google.*,yandex.*}', 'google.com', '.'), | 
|  | 99 | + | 
|  | 100 | +    glob(true, '*//{,*.}example.com', 'https://www.example.com'), | 
|  | 101 | +    glob(true, '*//{,*.}example.com', 'http://example.com'), | 
|  | 102 | +    glob(false, '*//{,*.}example.com', 'http://example.com.net'), | 
|  | 103 | +  ]; | 
|  | 104 | +} | 
|  | 105 | + | 
|  | 106 | +test('GlobCompiler', async () => { | 
|  | 107 | +  const {caseNames, caseDataMap} = await loadGlobTestData(); | 
|  | 108 | +  expect(caseNames.length).toBe(10); // should have 10 test cases | 
|  | 109 | +  for (const caseName of caseNames) { | 
|  | 110 | +    const pattern = caseDataMap[`pattern_${caseName}`]; | 
|  | 111 | +    const regexp = caseDataMap[`regexp_${caseName}`]; | 
|  | 112 | +    expect(globCompile(pattern).regexpPattern).toBe(regexp); | 
|  | 113 | +  } | 
|  | 114 | + | 
|  | 115 | +  const golangCases = loadGlobGolangCases(); | 
|  | 116 | +  expect(golangCases.length).toBe(60); | 
|  | 117 | +  for (const c of golangCases) { | 
|  | 118 | +    const compiled = globCompile(c.pattern, c.separators); | 
|  | 119 | +    const msg = `pattern: ${c.pattern}, input: ${c.input}, separators: ${c.separators || '(none)'}, compiled: ${compiled.regexpPattern}`; | 
|  | 120 | +    // eslint-disable-next-line @vitest/valid-expect -- Unlike Jest, Vitest supports a message as the second argument | 
|  | 121 | +    expect(compiled.regexp.test(c.input), msg).toBe(c.matched); | 
|  | 122 | +  } | 
|  | 123 | + | 
|  | 124 | +  // then our cases | 
|  | 125 | +  expect(globCompile('*/**/x').regexpPattern).toBe('^.*/.*/x$'); | 
|  | 126 | +  expect(globCompile('*/**/x', '/').regexpPattern).toBe('^[^/]*/.*/x$'); | 
|  | 127 | +  expect(globCompile('[a-b][^-\\]]', '/').regexpPattern).toBe('^[a-b][^-\\]]$'); | 
|  | 128 | +  expect(globCompile('.+^$()|', '/').regexpPattern).toBe('^\\.\\+\\^\\$\\(\\)\\|$'); | 
|  | 129 | +}); | 
0 commit comments