From 74f3516272499950c76adf216d9ec24cd75732c8 Mon Sep 17 00:00:00 2001 From: Keming He Date: Fri, 26 Jul 2024 15:59:56 -0400 Subject: [PATCH 1/7] test(__tests__ dir): added vitest and benchmarking to workflow --- .github/workflows/node-ci.yml | 38 ++ __tests__/Fuzzy.test.ts | 50 +++ __tests__/benchmark.json | 110 ++++++ __tests__/utils.bench.ts | 70 ++++ __tests__/utils.test.ts | 88 +++++ package.json | 13 +- tsconfig.json | 6 +- vitest.config.ts | 33 ++ yarn.lock | 718 +++++++++++++++++++++++++++++++++- 9 files changed, 1120 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/node-ci.yml create mode 100644 __tests__/Fuzzy.test.ts create mode 100644 __tests__/benchmark.json create mode 100644 __tests__/utils.bench.ts create mode 100644 __tests__/utils.test.ts create mode 100644 vitest.config.ts diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml new file mode 100644 index 0000000..810f4bf --- /dev/null +++ b/.github/workflows/node-ci.yml @@ -0,0 +1,38 @@ +# ./.github/workflows/node-ci.yml +# +# Auto CI/CD for Node.js projects using GitHub Actions. +name: Node CI + +# Enable the workflow for auto push, pull_request +# and manual workflow_dispatch events. +on: + push: + pull_request: + workflow_dispatch: + +jobs: + yarn-vitest: + name: Yarn Vitest + + permissions: + contents: read + packages: read + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set Node.js 18.x + uses: actions/setup-node@v4 + with: + node-version: 18.x + + - name: Install dependencies + run: yarn install + + - name: Run tests + run: yarn test + + - name: Run Benchmarks Compare + run: yarn test:bench:compare diff --git a/__tests__/Fuzzy.test.ts b/__tests__/Fuzzy.test.ts new file mode 100644 index 0000000..b1852ef --- /dev/null +++ b/__tests__/Fuzzy.test.ts @@ -0,0 +1,50 @@ +// ./__tests__/Fuzzy.test.ts +// +// Unittests for the Fuzzy class. + +// Vitest essential imports. +import { describe, expect, it } from 'vitest'; + +// Local Fuzzy class import. +import Fuzzy from '../src/Fuzzy'; + +// Fuzzy class test suite. +describe('Fuzzy', () => { + const list = ['apple', 'banana', 'grape', 'orange', 'pineapple']; + const fuzzy = new Fuzzy(list); + + it('returns correct results for a query', () => { + const query = 'apple'; + const results = fuzzy.search(query); + + expect(results).toBeInstanceOf(Array); + expect(results.length).toBeGreaterThan(0); + + const firstResult = results[0]; + expect(firstResult.text).toBe('apple'); + expect(firstResult.distance).toBe(0); + }); + + it('includes matches if includeMatches option is true', () => { + const fuzzyWithMatches = new Fuzzy(list, { includeMatches: true }); + const query = 'apple'; + const results = fuzzyWithMatches.search(query); + + expect(results).toBeInstanceOf(Array); + expect(results.length).toBeGreaterThan(0); + + const firstResult = results[0]; + expect(firstResult.text).toBe('apple'); + expect(firstResult.distance).toBe(0); + + expect(firstResult.matches).toBeDefined(); + }); + + it('returns empty array for no matches', () => { + const query = 'xyz'; + const results = fuzzy.search(query); + + expect(results).toBeInstanceOf(Array); + expect(results.length).toBe(0); + }); +}); diff --git a/__tests__/benchmark.json b/__tests__/benchmark.json new file mode 100644 index 0000000..514c6f5 --- /dev/null +++ b/__tests__/benchmark.json @@ -0,0 +1,110 @@ +{ + "files": [ + { + "filepath": "/home/keminghe/proj-work/contrib-fuzzify/__tests__/utils.bench.ts", + "groups": [ + { + "fullName": "__tests__/utils.bench.ts", + "benchmarks": [ + { + "id": "-1248884344_0", + "sampleCount": 134, + "median": 3.6381399631500244, + "name": "levenshteinFullMatrixSearch", + "rank": 2, + "rme": 1.9125015686828117, + "totalTime": 500.86785477399826, + "min": 3.4747620224952698, + "max": 6.656813979148865, + "hz": 267.535635842439, + "period": 3.7378198117462555, + "mean": 3.7378198117462555, + "variance": 0.17825141208411566, + "sd": 0.42219830895459026, + "sem": 0.036472378843971455, + "df": 133, + "critical": 1.96, + "moe": 0.07148586253418406, + "p75": 3.7468050122261047, + "p99": 5.7775309681892395, + "p995": 6.656813979148865, + "p999": 6.656813979148865 + }, + { + "id": "-1248884344_1", + "sampleCount": 13647, + "median": 0.035607993602752686, + "name": "getMaxLevenshteinDistance", + "rank": 1, + "rme": 0.3789746761222862, + "totalTime": 500.0110992193222, + "min": 0.03521198034286499, + "max": 0.5219640135765076, + "hz": 27293.394129265023, + "period": 0.03663890226564975, + "mean": 0.03663890226564975, + "variance": 0.00006849049996385367, + "sd": 0.008275898740551003, + "sem": 0.00007084293938571793, + "df": 13646, + "critical": 1.96, + "moe": 0.00013885216119600714, + "p75": 0.035928964614868164, + "p99": 0.06894999742507935, + "p995": 0.08079302310943604, + "p999": 0.139756977558136 + }, + { + "id": "-1248884344_2", + "sampleCount": 115, + "median": 4.351763963699341, + "name": "getMatchingIndices", + "rank": 3, + "rme": 0.6832604876119398, + "totalTime": 503.44462782144547, + "min": 4.210349977016449, + "max": 5.024766981601715, + "hz": 228.42631273599875, + "period": 4.377779372360395, + "mean": 4.377779372360395, + "variance": 0.026213291438407327, + "sd": 0.16190519274688916, + "sem": 0.015097737071554892, + "df": 114, + "critical": 1.9812, + "moe": 0.029911636686164554, + "p75": 4.440456986427307, + "p99": 4.984890997409821, + "p995": 5.024766981601715, + "p999": 5.024766981601715 + }, + { + "id": "-1248884344_3", + "sampleCount": 111, + "median": 4.520973980426788, + "name": "calculateScore", + "rank": 4, + "rme": 0.6229958739017899, + "totalTime": 503.89105755090714, + "min": 4.340812027454376, + "max": 4.979479014873505, + "hz": 220.2857112398465, + "period": 4.539559077035199, + "mean": 4.539559077035199, + "variance": 0.02260025627126498, + "sd": 0.15033381612686142, + "sem": 0.01426905436087965, + "df": 110, + "critical": 1.982, + "moe": 0.028281265743263468, + "p75": 4.6429190039634705, + "p99": 4.974684000015259, + "p995": 4.979479014873505, + "p999": 4.979479014873505 + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/__tests__/utils.bench.ts b/__tests__/utils.bench.ts new file mode 100644 index 0000000..ba73e82 --- /dev/null +++ b/__tests__/utils.bench.ts @@ -0,0 +1,70 @@ +// ./__tests__/utils.bench.ts +// +// Benchmarks for the L-Dist utility functions. + +// Vitest essentail imports. +import { bench } from 'vitest'; + +// Local utility functions import. +import { + calculateScore, + getMatchingIndices, + getMaxLevenshteinDistance, + levenshteinFullMatrixSearch, +} from '../src/utils'; + +// Helper function to generate random pairs +function getRandomPairs( + arr : string[], + numPairs: number +) : [string, string][] { + + const pairs: [string, string][] = []; + for (let i = 0; i < numPairs; i++) { + const firstIndex = Math.floor(Math.random() * arr.length); + const secondIndex = Math.floor(Math.random() * arr.length); + pairs.push([arr[firstIndex], arr[secondIndex]]); + } + return pairs; +} +// Copied from ./playground/countries.ts +const countries = [ + "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", + "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", + "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", + "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", + "Bulgaria", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", + "Canada", "Central African Republic", "Chad", "Chile", "China", "Colombia", + "Comoros", "Congo, Democratic Republic of the", "Congo, Republic of the", + "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", + "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", +]; +const randomPairs = getRandomPairs(countries, 1000); + +bench('levenshteinFullMatrixSearch', () => { + for (const [query, target] of randomPairs) { + levenshteinFullMatrixSearch(query, target); + } +}); + +bench('getMaxLevenshteinDistance', () => { + for (const [query, target] of randomPairs) { + getMaxLevenshteinDistance(query, target); + } +}); + +bench('getMatchingIndices', () => { + for (const [query, target] of randomPairs) { + const matrix = levenshteinFullMatrixSearch(query, target); + getMatchingIndices(matrix, query, target); + } +}); + +bench('calculateScore', () => { + for (const [query, target] of randomPairs) { + const matrix = levenshteinFullMatrixSearch(query, target); + const matches = getMatchingIndices(matrix, query, target); + const distance = matrix[query.length][target.length]; + calculateScore(query, target, matches, distance); + } +}); diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts new file mode 100644 index 0000000..04739cd --- /dev/null +++ b/__tests__/utils.test.ts @@ -0,0 +1,88 @@ +// ./__tests__/utils.test.ts +// +// Unittests for the L-Dist utility functions. + +// Vitest essentail imports. +import { describe, expect, it } from 'vitest'; + +// Local utility functions import. +import { + calculateScore, + getMatchingIndices, + getMaxLevenshteinDistance, + levenshteinFullMatrixSearch, +} from '../src/utils'; + +// levenshteinFullMatrixSearch test suite. +describe('levenshteinFullMatrixSearch', () => { + it('returns a matrix of the correct dimensions', () => { + const query = 'hello'; + const target = 'world'; + const matrix = levenshteinFullMatrixSearch(query, target); + expect(matrix.length).toBe(query.length + 1); + expect(matrix[0].length).toBe(target.length + 1); + }); + + it('returns a matrix with the correct values', () => { + const query = 'hello'; + const target = 'world'; + const matrix = levenshteinFullMatrixSearch(query, target); + expect(matrix[0][0]).toBe(0); + expect(matrix[0][1]).toBe(1); + expect(matrix[1][0]).toBe(1); + expect(matrix[1][1]).toBe(1); + expect(matrix[1][2]).toBe(2); + expect(matrix[5][5]).toBe(4); + }); +}); + +// getMaxLevenshteinDistance test suite. +describe('getMaxLevenshteinDistance', () => { + it('returns strictly 3 for short len 0~5 strings', () => { + expect(getMaxLevenshteinDistance('', '')).toBe(3); // Empty str edge case. + expect(getMaxLevenshteinDistance('a', '12')).toBe(3); // Routine case. + expect(getMaxLevenshteinDistance('12345', 'cd')).toBe(3); // Len 5 edge case. + }); + + it('returns strictly 10 for medium len 6~15 strings', () => { + expect(getMaxLevenshteinDistance('123456', 'hey')).toBe(10); // Len 6 edge case. + expect(getMaxLevenshteinDistance('hello world', 'world')).toBe(10); // Routine case. + expect(getMaxLevenshteinDistance('hello world', '123456789012345')).toBe(10); // Len 15 edge case. + }); + + it('returns strictly 15 for long len >=16 strings', () => { + expect(getMaxLevenshteinDistance('hello world, world hello', '')).toBe(15); + }); +}); + +// getMatchingIndices test suite. +describe('getMatchingIndices', () => { + it('returns the correct matching indices', () => { + const query = 'hello'; + const target = 'world'; + const matrix = levenshteinFullMatrixSearch(query, target); + const matches = getMatchingIndices(matrix, query, target); + expect(matches).toEqual([[3, 3]]); + }); +}); + +// calculateScore test suite. +describe('calculateScore', () => { + it('returns the correct score when param dist <= max dist', () => { + const query = 'hello'; + const target = 'world'; + const matrix = levenshteinFullMatrixSearch(query, target); + const matches = getMatchingIndices(matrix, query, target); + const score = calculateScore(query, target, matches, 0); + expect(score).toBe(0.6); + }); + + it('returns strictly 0 when param dist > max dist', () => { + const query = 'hello'; + const target = 'world'; + const matrix = levenshteinFullMatrixSearch(query, target); + const matches = getMatchingIndices(matrix, query, target); + const score = calculateScore(query, target, matches, 9999); + expect(score).toBe(0); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index 0b80669..bba9c5d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "dist/*" ], "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "vitest", + "test:bench": "vitest bench --outputJson __tests__/benchmark.json", + "test:bench:compare": "vitest bench --compare __tests__/benchmark.json", "build": "rm -rf dist && tsc", "build:playground": "vite build playground", "start": "vite playground", @@ -30,11 +32,18 @@ "url": "https://github.com/ad1992/fuzzify/issues" }, "homepage": "https://github.com/ad1992/fuzzify#readme", + "packageManager": "yarn@1.22.22", "devDependencies": { + "@types/node": "^20.14.12", + "@vitest/coverage-v8": "^2.0.4", "fuzzify": "^0.1.0-test5", "sass": "^1.77.8", "ts-node": "^10.9.2", "typescript": "^5.5.3", - "vite": "^5.3.4" + "vite": "^5.3.4", + "vitest": "^2.0.4" + }, + "engines": { + "node": ">=18.0.0" } } diff --git a/tsconfig.json b/tsconfig.json index 73b7037..ccb78b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,13 @@ { - "exclude": ["**/*.test.*", "tests", "types", "playground", "dist"], + "exclude": ["**/*.test.*", "__tests__", "types", "playground", "dist"], "compilerOptions": { "target": "ESNext", "outDir": "dist", "module": "ESNext", + "moduleResolution": "node", + "paths": { + "vitest/config": ["./node_modules/vitest/config"] + }, "esModuleInterop": true, "declaration": true, "forceConsistentCasingInFileNames": true, diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..a41d25f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,33 @@ +// ./vitest.config.ts +// +// Global config file for vitest. + +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + exclude: ["src", "node_modules", "dist", "playground", "public"], + include: ["__tests__/**/*.test.ts"], + typecheck: { + enabled: true, + }, + benchmark: { + include: ["__tests__/**/*.bench.ts"], + }, + coverage: { + enabled: true, + include: ["src/**/*"], + provider: "v8", + reporter: [ "text", "html", "clover", "json"], + reportsDirectory: "coverage", + thresholds: { + statements: 80, + branches: 80, + functions: 80, + lines: 80, + }, + }, + reporters: "default", + watch: false, + }, +}); diff --git a/yarn.lock b/yarn.lock index 8f664d0..2b57716 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,43 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/parser@^7.24.4": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" + integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== + +"@babel/types@^7.24.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" + integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -124,12 +161,43 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@jridgewell/resolve-uri@^3.0.3": +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -142,6 +210,19 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@rollup/rollup-android-arm-eabi@4.18.1": version "4.18.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz#f0da481244b7d9ea15296b35f7fe39cd81157396" @@ -242,11 +323,87 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== -"@types/estree@1.0.5": +"@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/node@^20.14.12": + version "20.14.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" + integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== + dependencies: + undici-types "~5.26.4" + +"@vitest/coverage-v8@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.0.4.tgz#a90605b6ce4243bb9c4be05e3bbeac72f956f792" + integrity sha512-i4lx/Wpg5zF1h2op7j0wdwuEQxaL/YTwwQaKuKMHYj7MMh8c7I4W7PNfOptZBCSBZI0z1qwn64o0pM/pA8Tz1g== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@bcoe/v8-coverage" "^0.2.3" + debug "^4.3.5" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^5.0.6" + istanbul-reports "^3.1.7" + magic-string "^0.30.10" + magicast "^0.3.4" + std-env "^3.7.0" + test-exclude "^7.0.1" + tinyrainbow "^1.2.0" + +"@vitest/expect@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.0.4.tgz#d365c106c84f2a3aae96000e95be21956acc099c" + integrity sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw== + dependencies: + "@vitest/spy" "2.0.4" + "@vitest/utils" "2.0.4" + chai "^5.1.1" + tinyrainbow "^1.2.0" + +"@vitest/pretty-format@2.0.4", "@vitest/pretty-format@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.0.4.tgz#9a3934932e7f8ddd836b38c34ddaeec91bd0f82e" + integrity sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.0.4.tgz#0b1edb8ab5f81a1c7dfd50090e5e7e971a117891" + integrity sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ== + dependencies: + "@vitest/utils" "2.0.4" + pathe "^1.1.2" + +"@vitest/snapshot@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.0.4.tgz#7d7dea9df17c5c13386f1a7a433b99dc0ffe3c14" + integrity sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw== + dependencies: + "@vitest/pretty-format" "2.0.4" + magic-string "^0.30.10" + pathe "^1.1.2" + +"@vitest/spy@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.0.4.tgz#19083386a741a158c2f142beffe43be68b1375cf" + integrity sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q== + dependencies: + tinyspy "^3.0.0" + +"@vitest/utils@2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.0.4.tgz#2db1df35aaeb5caa932770a190df636a68d284d5" + integrity sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ== + dependencies: + "@vitest/pretty-format" "2.0.4" + estree-walker "^3.0.3" + loupe "^3.1.1" + tinyrainbow "^1.2.0" + acorn-walk@^8.1.1: version "8.3.3" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" @@ -259,6 +416,28 @@ acorn@^8.11.0, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -272,11 +451,28 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -284,6 +480,27 @@ braces@~3.0.2: dependencies: fill-range "^7.1.1" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +chai@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" + integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + "chokidar@>=3.0.0 <4.0.0": version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -299,16 +516,64 @@ braces@~3.0.2: optionalDependencies: fsevents "~2.3.2" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-spawn@^7.0.0, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.1, debug@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + esbuild@^0.21.3: version "0.21.5" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" @@ -338,6 +603,28 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -345,6 +632,14 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -355,6 +650,16 @@ fuzzify@^0.1.0-test5: resolved "https://registry.yarnpkg.com/fuzzify/-/fuzzify-0.1.0-test5.tgz#b46b64c4a9727d80d4c8dc542df95477c1237ccc" integrity sha512-23GzYVrKcGhXdSVJ1YYhIhvDGOzBrlkeRkq0BCyq662khsDsXdN7PZgYYnNsUxPOIkutVP/UtMoyI4QoCMyU5A== +get-func-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -362,6 +667,33 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob@^10.4.1: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + immutable@^4.0.0: version "4.3.7" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" @@ -379,6 +711,11 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -391,11 +728,123 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +loupe@^3.1.0, loupe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.1.tgz#71d038d59007d890e3247c5db97c1ec5a92edc54" + integrity sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw== + dependencies: + get-func-name "^2.0.1" + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +magic-string@^0.30.10: + version "0.30.10" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" + integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +magicast@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.4.tgz#bbda1791d03190a24b00ff3dd18151e7fd381d19" + integrity sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q== + dependencies: + "@babel/parser" "^7.24.4" + "@babel/types" "^7.24.0" + source-map-js "^1.2.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -406,6 +855,53 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -466,11 +962,142 @@ sass@^1.77.8: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +semver@^7.5.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^4.0.1, signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +test-exclude@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" + integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^10.4.1" + minimatch "^9.0.4" + +tinybench@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.8.0.tgz#30e19ae3a27508ee18273ffed9ac7018949acd7b" + integrity sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw== + +tinypool@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238" + integrity sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585" + integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -502,11 +1129,38 @@ typescript@^5.5.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +vite-node@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.0.4.tgz#5600cc9f0d9c3ff9a64050c6858e7e1b62fb3fcd" + integrity sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA== + dependencies: + cac "^6.7.14" + debug "^4.3.5" + pathe "^1.1.2" + tinyrainbow "^1.2.0" + vite "^5.0.0" + +vite@^5.0.0: + version "5.3.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8" + integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.39" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + vite@^5.3.4: version "5.3.4" resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.4.tgz#b36ebd47c8a5e3a8727046375d5f10bf9fdf8715" @@ -518,6 +1172,64 @@ vite@^5.3.4: optionalDependencies: fsevents "~2.3.3" +vitest@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.0.4.tgz#ac6bfbaee53e502cee864b07a5b2edf1fcba793e" + integrity sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@vitest/expect" "2.0.4" + "@vitest/pretty-format" "^2.0.4" + "@vitest/runner" "2.0.4" + "@vitest/snapshot" "2.0.4" + "@vitest/spy" "2.0.4" + "@vitest/utils" "2.0.4" + chai "^5.1.1" + debug "^4.3.5" + execa "^8.0.1" + magic-string "^0.30.10" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.8.0" + tinypool "^1.0.0" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.0.4" + why-is-node-running "^2.3.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 9bfc05cecbedb3b9835fbbe5d877b977333d2f20 Mon Sep 17 00:00:00 2001 From: Keming He Date: Sun, 28 Jul 2024 15:10:24 -0400 Subject: [PATCH 2/7] test(__tests__): refactored according to feedback. --- .github/workflows/node-ci.yml | 7 +- __tests__/Fuzzy.test.ts | 36 ++----- __tests__/__snapshots__/Fuzzy.test.ts.snap | 115 ++++++++++++++++++++ __tests__/__snapshots__/utils.test.ts.snap | 116 +++++++++++++++++++++ __tests__/benchmark.json | 110 ------------------- __tests__/index.test.ts | 12 +++ __tests__/utils.bench.ts | 70 ------------- __tests__/utils.test.ts | 31 ++---- package.json | 2 - tsconfig.json | 3 + vitest.config.ts | 37 ++++--- 11 files changed, 297 insertions(+), 242 deletions(-) create mode 100644 __tests__/__snapshots__/Fuzzy.test.ts.snap create mode 100644 __tests__/__snapshots__/utils.test.ts.snap delete mode 100644 __tests__/benchmark.json create mode 100644 __tests__/index.test.ts delete mode 100644 __tests__/utils.bench.ts diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 810f4bf..33f53bb 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -16,7 +16,7 @@ jobs: permissions: contents: read - packages: read + pull-requests: write runs-on: ubuntu-latest @@ -34,5 +34,6 @@ jobs: - name: Run tests run: yarn test - - name: Run Benchmarks Compare - run: yarn test:bench:compare + - name: Run coverage + if: always() + uses: davelosert/vitest-coverage-report-action@v2 diff --git a/__tests__/Fuzzy.test.ts b/__tests__/Fuzzy.test.ts index b1852ef..1d1418d 100644 --- a/__tests__/Fuzzy.test.ts +++ b/__tests__/Fuzzy.test.ts @@ -2,9 +2,6 @@ // // Unittests for the Fuzzy class. -// Vitest essential imports. -import { describe, expect, it } from 'vitest'; - // Local Fuzzy class import. import Fuzzy from '../src/Fuzzy'; @@ -13,38 +10,27 @@ describe('Fuzzy', () => { const list = ['apple', 'banana', 'grape', 'orange', 'pineapple']; const fuzzy = new Fuzzy(list); - it('returns correct results for a query', () => { + it('should set this.list to an empty array if list is undefined', () => { + const badFuzzy = new Fuzzy(undefined as any); // Simulate absence of list + expect(badFuzzy).toMatchSnapshot(); + }); + + it('should return correct results for a query', () => { const query = 'apple'; const results = fuzzy.search(query); - - expect(results).toBeInstanceOf(Array); - expect(results.length).toBeGreaterThan(0); - - const firstResult = results[0]; - expect(firstResult.text).toBe('apple'); - expect(firstResult.distance).toBe(0); + expect(results).toMatchSnapshot(); }); - it('includes matches if includeMatches option is true', () => { + it('should include matches if includeMatches option is true', () => { const fuzzyWithMatches = new Fuzzy(list, { includeMatches: true }); const query = 'apple'; const results = fuzzyWithMatches.search(query); - - expect(results).toBeInstanceOf(Array); - expect(results.length).toBeGreaterThan(0); - - const firstResult = results[0]; - expect(firstResult.text).toBe('apple'); - expect(firstResult.distance).toBe(0); - - expect(firstResult.matches).toBeDefined(); + expect(results).toMatchSnapshot(); }); - it('returns empty array for no matches', () => { + it('should return empty array for no matches', () => { const query = 'xyz'; const results = fuzzy.search(query); - - expect(results).toBeInstanceOf(Array); - expect(results.length).toBe(0); + expect(results).toMatchSnapshot(); }); }); diff --git a/__tests__/__snapshots__/Fuzzy.test.ts.snap b/__tests__/__snapshots__/Fuzzy.test.ts.snap new file mode 100644 index 0000000..238e43e --- /dev/null +++ b/__tests__/__snapshots__/Fuzzy.test.ts.snap @@ -0,0 +1,115 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Fuzzy > should include matches if includeMatches option is true 1`] = ` +[ + { + "distance": 0, + "matches": [ + [ + 0, + 0, + ], + [ + 1, + 1, + ], + [ + 2, + 2, + ], + [ + 3, + 3, + ], + [ + 4, + 4, + ], + ], + "text": "apple", + }, + { + "distance": 4, + "matches": [ + [ + 0, + 4, + ], + [ + 1, + 5, + ], + [ + 2, + 6, + ], + [ + 3, + 7, + ], + [ + 4, + 8, + ], + ], + "text": "pineapple", + }, + { + "distance": 5, + "matches": [ + [ + 0, + 2, + ], + [ + 4, + 5, + ], + ], + "text": "orange", + }, + { + "distance": 5, + "matches": [ + [ + 0, + 1, + ], + ], + "text": "banana", + }, +] +`; + +exports[`Fuzzy > should return correct results for a query 1`] = ` +[ + { + "distance": 0, + "text": "apple", + }, + { + "distance": 4, + "text": "pineapple", + }, + { + "distance": 5, + "text": "orange", + }, + { + "distance": 5, + "text": "banana", + }, +] +`; + +exports[`Fuzzy > should return empty array for no matches 1`] = `[]`; + +exports[`Fuzzy > should set this.list to an empty array if list is undefined 1`] = ` +Fuzzy { + "list": [], + "options": { + "includeMatches": false, + }, + "search": [Function], +} +`; diff --git a/__tests__/__snapshots__/utils.test.ts.snap b/__tests__/__snapshots__/utils.test.ts.snap new file mode 100644 index 0000000..42b087a --- /dev/null +++ b/__tests__/__snapshots__/utils.test.ts.snap @@ -0,0 +1,116 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`getMatchingIndices > should return the correct matching indices 1`] = ` +[ + [ + 3, + 3, + ], +] +`; + +exports[`levenshteinFullMatrixSearch > should return a matrix of the correct dimensions 1`] = ` +[ + [ + 0, + 1, + 2, + 3, + 4, + 5, + ], + [ + 1, + 1, + 2, + 3, + 4, + 5, + ], + [ + 2, + 2, + 2, + 3, + 4, + 5, + ], + [ + 3, + 3, + 3, + 3, + 3, + 4, + ], + [ + 4, + 4, + 4, + 4, + 3, + 4, + ], + [ + 5, + 5, + 4, + 5, + 4, + 4, + ], +] +`; + +exports[`levenshteinFullMatrixSearch > should return a matrix with the correct values 1`] = ` +[ + [ + 0, + 1, + 2, + 3, + 4, + 5, + ], + [ + 1, + 1, + 2, + 3, + 4, + 5, + ], + [ + 2, + 2, + 2, + 3, + 4, + 5, + ], + [ + 3, + 3, + 3, + 3, + 3, + 4, + ], + [ + 4, + 4, + 4, + 4, + 3, + 4, + ], + [ + 5, + 5, + 4, + 5, + 4, + 4, + ], +] +`; diff --git a/__tests__/benchmark.json b/__tests__/benchmark.json deleted file mode 100644 index 514c6f5..0000000 --- a/__tests__/benchmark.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "files": [ - { - "filepath": "/home/keminghe/proj-work/contrib-fuzzify/__tests__/utils.bench.ts", - "groups": [ - { - "fullName": "__tests__/utils.bench.ts", - "benchmarks": [ - { - "id": "-1248884344_0", - "sampleCount": 134, - "median": 3.6381399631500244, - "name": "levenshteinFullMatrixSearch", - "rank": 2, - "rme": 1.9125015686828117, - "totalTime": 500.86785477399826, - "min": 3.4747620224952698, - "max": 6.656813979148865, - "hz": 267.535635842439, - "period": 3.7378198117462555, - "mean": 3.7378198117462555, - "variance": 0.17825141208411566, - "sd": 0.42219830895459026, - "sem": 0.036472378843971455, - "df": 133, - "critical": 1.96, - "moe": 0.07148586253418406, - "p75": 3.7468050122261047, - "p99": 5.7775309681892395, - "p995": 6.656813979148865, - "p999": 6.656813979148865 - }, - { - "id": "-1248884344_1", - "sampleCount": 13647, - "median": 0.035607993602752686, - "name": "getMaxLevenshteinDistance", - "rank": 1, - "rme": 0.3789746761222862, - "totalTime": 500.0110992193222, - "min": 0.03521198034286499, - "max": 0.5219640135765076, - "hz": 27293.394129265023, - "period": 0.03663890226564975, - "mean": 0.03663890226564975, - "variance": 0.00006849049996385367, - "sd": 0.008275898740551003, - "sem": 0.00007084293938571793, - "df": 13646, - "critical": 1.96, - "moe": 0.00013885216119600714, - "p75": 0.035928964614868164, - "p99": 0.06894999742507935, - "p995": 0.08079302310943604, - "p999": 0.139756977558136 - }, - { - "id": "-1248884344_2", - "sampleCount": 115, - "median": 4.351763963699341, - "name": "getMatchingIndices", - "rank": 3, - "rme": 0.6832604876119398, - "totalTime": 503.44462782144547, - "min": 4.210349977016449, - "max": 5.024766981601715, - "hz": 228.42631273599875, - "period": 4.377779372360395, - "mean": 4.377779372360395, - "variance": 0.026213291438407327, - "sd": 0.16190519274688916, - "sem": 0.015097737071554892, - "df": 114, - "critical": 1.9812, - "moe": 0.029911636686164554, - "p75": 4.440456986427307, - "p99": 4.984890997409821, - "p995": 5.024766981601715, - "p999": 5.024766981601715 - }, - { - "id": "-1248884344_3", - "sampleCount": 111, - "median": 4.520973980426788, - "name": "calculateScore", - "rank": 4, - "rme": 0.6229958739017899, - "totalTime": 503.89105755090714, - "min": 4.340812027454376, - "max": 4.979479014873505, - "hz": 220.2857112398465, - "period": 4.539559077035199, - "mean": 4.539559077035199, - "variance": 0.02260025627126498, - "sd": 0.15033381612686142, - "sem": 0.01426905436087965, - "df": 110, - "critical": 1.982, - "moe": 0.028281265743263468, - "p75": 4.6429190039634705, - "p99": 4.974684000015259, - "p995": 4.979479014873505, - "p999": 4.979479014873505 - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts new file mode 100644 index 0000000..c3cc319 --- /dev/null +++ b/__tests__/index.test.ts @@ -0,0 +1,12 @@ +// ./__tests__/index.test.ts +// +// Integration test for the package entrypoint. + +import Fuzzy from "../src/Fuzzy"; +import * as IndexExport from "../src/index"; + +describe("Package entrypoint", () => { + it("should correctly exports the Fuzzy class", () => { + expect(IndexExport.default).toBe(Fuzzy); + }); +}); diff --git a/__tests__/utils.bench.ts b/__tests__/utils.bench.ts deleted file mode 100644 index ba73e82..0000000 --- a/__tests__/utils.bench.ts +++ /dev/null @@ -1,70 +0,0 @@ -// ./__tests__/utils.bench.ts -// -// Benchmarks for the L-Dist utility functions. - -// Vitest essentail imports. -import { bench } from 'vitest'; - -// Local utility functions import. -import { - calculateScore, - getMatchingIndices, - getMaxLevenshteinDistance, - levenshteinFullMatrixSearch, -} from '../src/utils'; - -// Helper function to generate random pairs -function getRandomPairs( - arr : string[], - numPairs: number -) : [string, string][] { - - const pairs: [string, string][] = []; - for (let i = 0; i < numPairs; i++) { - const firstIndex = Math.floor(Math.random() * arr.length); - const secondIndex = Math.floor(Math.random() * arr.length); - pairs.push([arr[firstIndex], arr[secondIndex]]); - } - return pairs; -} -// Copied from ./playground/countries.ts -const countries = [ - "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", - "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", - "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", - "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", - "Bulgaria", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", - "Canada", "Central African Republic", "Chad", "Chile", "China", "Colombia", - "Comoros", "Congo, Democratic Republic of the", "Congo, Republic of the", - "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", - "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", -]; -const randomPairs = getRandomPairs(countries, 1000); - -bench('levenshteinFullMatrixSearch', () => { - for (const [query, target] of randomPairs) { - levenshteinFullMatrixSearch(query, target); - } -}); - -bench('getMaxLevenshteinDistance', () => { - for (const [query, target] of randomPairs) { - getMaxLevenshteinDistance(query, target); - } -}); - -bench('getMatchingIndices', () => { - for (const [query, target] of randomPairs) { - const matrix = levenshteinFullMatrixSearch(query, target); - getMatchingIndices(matrix, query, target); - } -}); - -bench('calculateScore', () => { - for (const [query, target] of randomPairs) { - const matrix = levenshteinFullMatrixSearch(query, target); - const matches = getMatchingIndices(matrix, query, target); - const distance = matrix[query.length][target.length]; - calculateScore(query, target, matches, distance); - } -}); diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index 04739cd..ed48f2c 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -2,9 +2,6 @@ // // Unittests for the L-Dist utility functions. -// Vitest essentail imports. -import { describe, expect, it } from 'vitest'; - // Local utility functions import. import { calculateScore, @@ -15,60 +12,54 @@ import { // levenshteinFullMatrixSearch test suite. describe('levenshteinFullMatrixSearch', () => { - it('returns a matrix of the correct dimensions', () => { + it('should return a matrix of the correct dimensions', () => { const query = 'hello'; const target = 'world'; const matrix = levenshteinFullMatrixSearch(query, target); - expect(matrix.length).toBe(query.length + 1); - expect(matrix[0].length).toBe(target.length + 1); + expect(matrix).toMatchSnapshot(); }); - it('returns a matrix with the correct values', () => { + it('should return a matrix with the correct values', () => { const query = 'hello'; const target = 'world'; const matrix = levenshteinFullMatrixSearch(query, target); - expect(matrix[0][0]).toBe(0); - expect(matrix[0][1]).toBe(1); - expect(matrix[1][0]).toBe(1); - expect(matrix[1][1]).toBe(1); - expect(matrix[1][2]).toBe(2); - expect(matrix[5][5]).toBe(4); + expect(matrix).toMatchSnapshot(); }); }); // getMaxLevenshteinDistance test suite. describe('getMaxLevenshteinDistance', () => { - it('returns strictly 3 for short len 0~5 strings', () => { + it('should return strictly 3 for short len 0~5 strings', () => { expect(getMaxLevenshteinDistance('', '')).toBe(3); // Empty str edge case. expect(getMaxLevenshteinDistance('a', '12')).toBe(3); // Routine case. expect(getMaxLevenshteinDistance('12345', 'cd')).toBe(3); // Len 5 edge case. }); - it('returns strictly 10 for medium len 6~15 strings', () => { + it('should return strictly 10 for medium len 6~15 strings', () => { expect(getMaxLevenshteinDistance('123456', 'hey')).toBe(10); // Len 6 edge case. expect(getMaxLevenshteinDistance('hello world', 'world')).toBe(10); // Routine case. expect(getMaxLevenshteinDistance('hello world', '123456789012345')).toBe(10); // Len 15 edge case. }); - it('returns strictly 15 for long len >=16 strings', () => { + it('should return strictly 15 for long len >=16 strings', () => { expect(getMaxLevenshteinDistance('hello world, world hello', '')).toBe(15); }); }); // getMatchingIndices test suite. describe('getMatchingIndices', () => { - it('returns the correct matching indices', () => { + it('should return the correct matching indices', () => { const query = 'hello'; const target = 'world'; const matrix = levenshteinFullMatrixSearch(query, target); const matches = getMatchingIndices(matrix, query, target); - expect(matches).toEqual([[3, 3]]); + expect(matches).toMatchSnapshot(); }); }); // calculateScore test suite. describe('calculateScore', () => { - it('returns the correct score when param dist <= max dist', () => { + it('should return the correct score when param dist <= max dist', () => { const query = 'hello'; const target = 'world'; const matrix = levenshteinFullMatrixSearch(query, target); @@ -77,7 +68,7 @@ describe('calculateScore', () => { expect(score).toBe(0.6); }); - it('returns strictly 0 when param dist > max dist', () => { + it('should return strictly 0 when param dist > max dist', () => { const query = 'hello'; const target = 'world'; const matrix = levenshteinFullMatrixSearch(query, target); diff --git a/package.json b/package.json index bba9c5d..4cd055c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,6 @@ ], "scripts": { "test": "vitest", - "test:bench": "vitest bench --outputJson __tests__/benchmark.json", - "test:bench:compare": "vitest bench --compare __tests__/benchmark.json", "build": "rm -rf dist && tsc", "build:playground": "vite build playground", "start": "vite playground", diff --git a/tsconfig.json b/tsconfig.json index ccb78b3..f9a9def 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,9 @@ "paths": { "vitest/config": ["./node_modules/vitest/config"] }, + "types": [ + "vitest/globals" + ], "esModuleInterop": true, "declaration": true, "forceConsistentCasingInFileNames": true, diff --git a/vitest.config.ts b/vitest.config.ts index a41d25f..59fb850 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,28 +6,41 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - exclude: ["src", "node_modules", "dist", "playground", "public"], + + // Files to include and exclude. Exlusion takes precedence. include: ["__tests__/**/*.test.ts"], + exclude: ["src", "node_modules", "dist", "playground", "public"], + + // Enable globals to avoid redundant imports. + globals: true, + + // Enable typescript typechecking. typecheck: { enabled: true, }, - benchmark: { - include: ["__tests__/**/*.bench.ts"], - }, + + // Enable coverage generation and reporting via the + // @vitest/coverage-v8 dev dependency. coverage: { enabled: true, - include: ["src/**/*"], + include: ["src/**/*.ts"], provider: "v8", - reporter: [ "text", "html", "clover", "json"], + + // Json and json-summary are required for GitHub vitest coverage report. + // For better debugging, enable generate coverage even on failure. + reporter: ["text", "html", "clover", "json", "json-summary"], + reportOnFailure: true, + reportsDirectory: "coverage", thresholds: { - statements: 80, - branches: 80, - functions: 80, - lines: 80, - }, + statements: 90, + branches: 90, + functions: 90, + lines: 90, + } }, - reporters: "default", + + // Disable watch mode to simplify workflow. watch: false, }, }); From dea26e8f9363dd5b93935217f2ac762600eb3a4f Mon Sep 17 00:00:00 2001 From: Keming He Date: Sun, 28 Jul 2024 15:21:48 -0400 Subject: [PATCH 3/7] chore(index.test.ts): fixed typo. --- __tests__/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index c3cc319..f6c2a70 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -6,7 +6,7 @@ import Fuzzy from "../src/Fuzzy"; import * as IndexExport from "../src/index"; describe("Package entrypoint", () => { - it("should correctly exports the Fuzzy class", () => { + it("should correctly export the Fuzzy class", () => { expect(IndexExport.default).toBe(Fuzzy); }); }); From c4762140b1238614bce97a40d8a14a7ec9c332ff Mon Sep 17 00:00:00 2001 From: Keming He Date: Sun, 28 Jul 2024 19:51:40 -0400 Subject: [PATCH 4/7] feat(Fuzzy.ts): added query result in-mem caching. Implemented testing for both cache hit and cache miss cases. done #10 --- __tests__/Fuzzy.test.ts | 25 +++++++++++----- __tests__/__snapshots__/Fuzzy.test.ts.snap | 22 ++++++++++++++ src/Fuzzy.ts | 35 +++++++++++++++++++--- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/__tests__/Fuzzy.test.ts b/__tests__/Fuzzy.test.ts index 1d1418d..bb558f5 100644 --- a/__tests__/Fuzzy.test.ts +++ b/__tests__/Fuzzy.test.ts @@ -10,21 +10,16 @@ describe('Fuzzy', () => { const list = ['apple', 'banana', 'grape', 'orange', 'pineapple']; const fuzzy = new Fuzzy(list); - it('should set this.list to an empty array if list is undefined', () => { - const badFuzzy = new Fuzzy(undefined as any); // Simulate absence of list - expect(badFuzzy).toMatchSnapshot(); - }); - it('should return correct results for a query', () => { const query = 'apple'; const results = fuzzy.search(query); expect(results).toMatchSnapshot(); }); - it('should include matches if includeMatches option is true', () => { - const fuzzyWithMatches = new Fuzzy(list, { includeMatches: true }); + // Branch coverage: cache hit. + it('should NOT update cache for repeated same query', () => { const query = 'apple'; - const results = fuzzyWithMatches.search(query); + const results = fuzzy.search(query); expect(results).toMatchSnapshot(); }); @@ -33,4 +28,18 @@ describe('Fuzzy', () => { const results = fuzzy.search(query); expect(results).toMatchSnapshot(); }); + + // Branch coverage: includeMatches option is true. + it('should include matches if includeMatches option is true', () => { + const fuzzyWithMatches = new Fuzzy(list, { includeMatches: true }); + const query = 'apple'; + const results = fuzzyWithMatches.search(query); + expect(results).toMatchSnapshot(); + }); + + // Edge case: list is undefined. + it('should set this.list to an empty array if list is undefined', () => { + const badFuzzy = new Fuzzy(undefined as any); // Simulate absence of list + expect(badFuzzy).toMatchSnapshot(); + }); }); diff --git a/__tests__/__snapshots__/Fuzzy.test.ts.snap b/__tests__/__snapshots__/Fuzzy.test.ts.snap index 238e43e..2612ebc 100644 --- a/__tests__/__snapshots__/Fuzzy.test.ts.snap +++ b/__tests__/__snapshots__/Fuzzy.test.ts.snap @@ -1,5 +1,26 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`Fuzzy > should NOT update cache for repeated same query 1`] = ` +[ + { + "distance": 0, + "text": "apple", + }, + { + "distance": 4, + "text": "pineapple", + }, + { + "distance": 5, + "text": "orange", + }, + { + "distance": 5, + "text": "banana", + }, +] +`; + exports[`Fuzzy > should include matches if includeMatches option is true 1`] = ` [ { @@ -106,6 +127,7 @@ exports[`Fuzzy > should return empty array for no matches 1`] = `[]`; exports[`Fuzzy > should set this.list to an empty array if list is undefined 1`] = ` Fuzzy { + "cache": {}, "list": [], "options": { "includeMatches": false, diff --git a/src/Fuzzy.ts b/src/Fuzzy.ts index 2765628..6c1ac8c 100644 --- a/src/Fuzzy.ts +++ b/src/Fuzzy.ts @@ -14,19 +14,42 @@ export type SingleResult = { }; export type Result = Array; +/** + * Represents a Fuzzy search class. + */ class Fuzzy { + /** + * The list of strings to search within. + * @type {Array} + */ private list: Array; + + /** + * The options for the fuzzy search. + * @type {Options} + */ private options: Options; + /** + * In-memory cache for query results. + * @type {Object.} + */ + private cache: { [query: string]: Result }; + constructor(list: Array, options?: Options) { - this.list = list || []; - this.options = options || { - includeMatches: false, - }; + this.list = list || []; + this.options = options || { includeMatches: false }; + this.cache = {}; } // Search for the query in the list public search = (query: string) => { + + // If incoming query is already cached, return the cached result + if (this.cache[query]) { + return this.cache[query]; + } + const result: (SingleResult & { score: number })[] = []; for (let i = 0; i < this.list.length; i++) { const matrix = levenshteinFullMatrixSearch( @@ -49,6 +72,7 @@ class Fuzzy { score, }; } + // Sort by score in descending order result.sort((x, y) => { return y.score - x.score; @@ -64,6 +88,9 @@ class Fuzzy { approxMatches[index] = obj; } }); + + // Cache the result and return. + this.cache[query] = approxMatches; return approxMatches; }; } From e3264f8f7017cd9ba6f7ea9d8ba29c8689b90586 Mon Sep 17 00:00:00 2001 From: Keming He Date: Mon, 29 Jul 2024 10:25:29 -0400 Subject: [PATCH 5/7] chore(git): hard reset to only testing implementation. --- .../{node-ci.yml => test-coverage-pr.yml} | 10 +++---- __tests__/Fuzzy.test.ts | 2 +- __tests__/__snapshots__/Fuzzy.test.ts.snap | 29 +++---------------- __tests__/__snapshots__/utils.test.ts.snap | 6 ++-- __tests__/index.test.ts | 2 +- __tests__/utils.test.ts | 8 ++--- 6 files changed, 18 insertions(+), 39 deletions(-) rename .github/workflows/{node-ci.yml => test-coverage-pr.yml} (78%) diff --git a/.github/workflows/node-ci.yml b/.github/workflows/test-coverage-pr.yml similarity index 78% rename from .github/workflows/node-ci.yml rename to .github/workflows/test-coverage-pr.yml index 33f53bb..348fa2d 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/test-coverage-pr.yml @@ -1,7 +1,7 @@ -# ./.github/workflows/node-ci.yml +# ./.github/workflows/test-coverage-pr.yml # -# Auto CI/CD for Node.js projects using GitHub Actions. -name: Node CI +# Automated GitHub Actions workflow to run tests and coverage +name: Test Coverage Pull Request # Enable the workflow for auto push, pull_request # and manual workflow_dispatch events. @@ -11,8 +11,8 @@ on: workflow_dispatch: jobs: - yarn-vitest: - name: Yarn Vitest + coverage: + name: Test Coverage permissions: contents: read diff --git a/__tests__/Fuzzy.test.ts b/__tests__/Fuzzy.test.ts index bb558f5..0727a01 100644 --- a/__tests__/Fuzzy.test.ts +++ b/__tests__/Fuzzy.test.ts @@ -6,7 +6,7 @@ import Fuzzy from '../src/Fuzzy'; // Fuzzy class test suite. -describe('Fuzzy', () => { +describe('Test Fuzzy', () => { const list = ['apple', 'banana', 'grape', 'orange', 'pineapple']; const fuzzy = new Fuzzy(list); diff --git a/__tests__/__snapshots__/Fuzzy.test.ts.snap b/__tests__/__snapshots__/Fuzzy.test.ts.snap index 2612ebc..2acdbb2 100644 --- a/__tests__/__snapshots__/Fuzzy.test.ts.snap +++ b/__tests__/__snapshots__/Fuzzy.test.ts.snap @@ -1,27 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Fuzzy > should NOT update cache for repeated same query 1`] = ` -[ - { - "distance": 0, - "text": "apple", - }, - { - "distance": 4, - "text": "pineapple", - }, - { - "distance": 5, - "text": "orange", - }, - { - "distance": 5, - "text": "banana", - }, -] -`; - -exports[`Fuzzy > should include matches if includeMatches option is true 1`] = ` +exports[`Test Fuzzy > should include matches if includeMatches option is true 1`] = ` [ { "distance": 0, @@ -102,7 +81,7 @@ exports[`Fuzzy > should include matches if includeMatches option is true 1`] = ` ] `; -exports[`Fuzzy > should return correct results for a query 1`] = ` +exports[`Test Fuzzy > should return correct results for a query 1`] = ` [ { "distance": 0, @@ -123,9 +102,9 @@ exports[`Fuzzy > should return correct results for a query 1`] = ` ] `; -exports[`Fuzzy > should return empty array for no matches 1`] = `[]`; +exports[`Test Fuzzy > should return empty array for no matches 1`] = `[]`; -exports[`Fuzzy > should set this.list to an empty array if list is undefined 1`] = ` +exports[`Test Fuzzy > should set this.list to an empty array if list is undefined 1`] = ` Fuzzy { "cache": {}, "list": [], diff --git a/__tests__/__snapshots__/utils.test.ts.snap b/__tests__/__snapshots__/utils.test.ts.snap index 42b087a..db1a2e0 100644 --- a/__tests__/__snapshots__/utils.test.ts.snap +++ b/__tests__/__snapshots__/utils.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`getMatchingIndices > should return the correct matching indices 1`] = ` +exports[`Test getMatchingIndices > should return the correct matching indices 1`] = ` [ [ 3, @@ -9,7 +9,7 @@ exports[`getMatchingIndices > should return the correct matching indices 1`] = ` ] `; -exports[`levenshteinFullMatrixSearch > should return a matrix of the correct dimensions 1`] = ` +exports[`Test levenshteinFullMatrixSearch > should return a matrix of the correct dimensions 1`] = ` [ [ 0, @@ -62,7 +62,7 @@ exports[`levenshteinFullMatrixSearch > should return a matrix of the correct dim ] `; -exports[`levenshteinFullMatrixSearch > should return a matrix with the correct values 1`] = ` +exports[`Test levenshteinFullMatrixSearch > should return a matrix with the correct values 1`] = ` [ [ 0, diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index f6c2a70..8d52a56 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -5,7 +5,7 @@ import Fuzzy from "../src/Fuzzy"; import * as IndexExport from "../src/index"; -describe("Package entrypoint", () => { +describe("Test package entrypoint", () => { it("should correctly export the Fuzzy class", () => { expect(IndexExport.default).toBe(Fuzzy); }); diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index ed48f2c..82b063a 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -11,7 +11,7 @@ import { } from '../src/utils'; // levenshteinFullMatrixSearch test suite. -describe('levenshteinFullMatrixSearch', () => { +describe('Test levenshteinFullMatrixSearch', () => { it('should return a matrix of the correct dimensions', () => { const query = 'hello'; const target = 'world'; @@ -28,7 +28,7 @@ describe('levenshteinFullMatrixSearch', () => { }); // getMaxLevenshteinDistance test suite. -describe('getMaxLevenshteinDistance', () => { +describe('Test getMaxLevenshteinDistance', () => { it('should return strictly 3 for short len 0~5 strings', () => { expect(getMaxLevenshteinDistance('', '')).toBe(3); // Empty str edge case. expect(getMaxLevenshteinDistance('a', '12')).toBe(3); // Routine case. @@ -47,7 +47,7 @@ describe('getMaxLevenshteinDistance', () => { }); // getMatchingIndices test suite. -describe('getMatchingIndices', () => { +describe('Test getMatchingIndices', () => { it('should return the correct matching indices', () => { const query = 'hello'; const target = 'world'; @@ -58,7 +58,7 @@ describe('getMatchingIndices', () => { }); // calculateScore test suite. -describe('calculateScore', () => { +describe('Test calculateScore', () => { it('should return the correct score when param dist <= max dist', () => { const query = 'hello'; const target = 'world'; From d21b62bd568c2c686c5a746d69d3885a8fe11b96 Mon Sep 17 00:00:00 2001 From: Keming He Date: Mon, 29 Jul 2024 10:51:59 -0400 Subject: [PATCH 6/7] test(__tests__/__snapshots/): updated snapshots. --- __tests__/__snapshots__/Fuzzy.test.ts.snap | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/__tests__/__snapshots__/Fuzzy.test.ts.snap b/__tests__/__snapshots__/Fuzzy.test.ts.snap index 2acdbb2..987636c 100644 --- a/__tests__/__snapshots__/Fuzzy.test.ts.snap +++ b/__tests__/__snapshots__/Fuzzy.test.ts.snap @@ -1,5 +1,26 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`Test Fuzzy > should NOT update cache for repeated same query 1`] = ` +[ + { + "distance": 0, + "text": "apple", + }, + { + "distance": 4, + "text": "pineapple", + }, + { + "distance": 5, + "text": "orange", + }, + { + "distance": 5, + "text": "banana", + }, +] +`; + exports[`Test Fuzzy > should include matches if includeMatches option is true 1`] = ` [ { From ca5420fb87e036993784ed1ebc25f4347a8bf392 Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Mon, 29 Jul 2024 22:09:39 +0530 Subject: [PATCH 7/7] remove caching related stuff --- __tests__/Fuzzy.test.ts | 39 ++++++++-------------- __tests__/__snapshots__/Fuzzy.test.ts.snap | 24 ------------- src/Fuzzy.ts | 21 +----------- vitest.config.ts | 5 ++- 4 files changed, 17 insertions(+), 72 deletions(-) diff --git a/__tests__/Fuzzy.test.ts b/__tests__/Fuzzy.test.ts index 0727a01..44e65fb 100644 --- a/__tests__/Fuzzy.test.ts +++ b/__tests__/Fuzzy.test.ts @@ -1,45 +1,34 @@ -// ./__tests__/Fuzzy.test.ts -// -// Unittests for the Fuzzy class. +import Fuzzy from "../src/Fuzzy"; -// Local Fuzzy class import. -import Fuzzy from '../src/Fuzzy'; - -// Fuzzy class test suite. -describe('Test Fuzzy', () => { - const list = ['apple', 'banana', 'grape', 'orange', 'pineapple']; +describe("Test Fuzzy", () => { + const list = ["apple", "banana", "grape", "orange", "pineapple"]; const fuzzy = new Fuzzy(list); - it('should return correct results for a query', () => { - const query = 'apple'; - const results = fuzzy.search(query); - expect(results).toMatchSnapshot(); - }); - - // Branch coverage: cache hit. - it('should NOT update cache for repeated same query', () => { - const query = 'apple'; + it("should return correct results for a query", () => { + const query = "apple"; const results = fuzzy.search(query); expect(results).toMatchSnapshot(); }); - it('should return empty array for no matches', () => { - const query = 'xyz'; + it("should return empty array for no matches", () => { + const query = "xyz"; const results = fuzzy.search(query); - expect(results).toMatchSnapshot(); + expect(results).toMatchInlineSnapshot(`[]`); }); // Branch coverage: includeMatches option is true. - it('should include matches if includeMatches option is true', () => { + it("should include matches if includeMatches option is true", () => { const fuzzyWithMatches = new Fuzzy(list, { includeMatches: true }); - const query = 'apple'; + const query = "apple"; const results = fuzzyWithMatches.search(query); expect(results).toMatchSnapshot(); }); // Edge case: list is undefined. - it('should set this.list to an empty array if list is undefined', () => { - const badFuzzy = new Fuzzy(undefined as any); // Simulate absence of list + it("should set this.list to an empty array if list is undefined", () => { + // Simulate absence of list + const badFuzzy = new Fuzzy(undefined as any); expect(badFuzzy).toMatchSnapshot(); }); + ``; }); diff --git a/__tests__/__snapshots__/Fuzzy.test.ts.snap b/__tests__/__snapshots__/Fuzzy.test.ts.snap index 987636c..7d7a9ef 100644 --- a/__tests__/__snapshots__/Fuzzy.test.ts.snap +++ b/__tests__/__snapshots__/Fuzzy.test.ts.snap @@ -1,26 +1,5 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Test Fuzzy > should NOT update cache for repeated same query 1`] = ` -[ - { - "distance": 0, - "text": "apple", - }, - { - "distance": 4, - "text": "pineapple", - }, - { - "distance": 5, - "text": "orange", - }, - { - "distance": 5, - "text": "banana", - }, -] -`; - exports[`Test Fuzzy > should include matches if includeMatches option is true 1`] = ` [ { @@ -123,11 +102,8 @@ exports[`Test Fuzzy > should return correct results for a query 1`] = ` ] `; -exports[`Test Fuzzy > should return empty array for no matches 1`] = `[]`; - exports[`Test Fuzzy > should set this.list to an empty array if list is undefined 1`] = ` Fuzzy { - "cache": {}, "list": [], "options": { "includeMatches": false, diff --git a/src/Fuzzy.ts b/src/Fuzzy.ts index 6c1ac8c..b6d1e45 100644 --- a/src/Fuzzy.ts +++ b/src/Fuzzy.ts @@ -13,10 +13,6 @@ export type SingleResult = { matches?: number[][]; }; export type Result = Array; - -/** - * Represents a Fuzzy search class. - */ class Fuzzy { /** * The list of strings to search within. @@ -30,26 +26,13 @@ class Fuzzy { */ private options: Options; - /** - * In-memory cache for query results. - * @type {Object.} - */ - private cache: { [query: string]: Result }; - constructor(list: Array, options?: Options) { - this.list = list || []; + this.list = list || []; this.options = options || { includeMatches: false }; - this.cache = {}; } // Search for the query in the list public search = (query: string) => { - - // If incoming query is already cached, return the cached result - if (this.cache[query]) { - return this.cache[query]; - } - const result: (SingleResult & { score: number })[] = []; for (let i = 0; i < this.list.length; i++) { const matrix = levenshteinFullMatrixSearch( @@ -89,8 +72,6 @@ class Fuzzy { } }); - // Cache the result and return. - this.cache[query] = approxMatches; return approxMatches; }; } diff --git a/vitest.config.ts b/vitest.config.ts index 59fb850..caef478 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,11 +6,10 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - // Files to include and exclude. Exlusion takes precedence. include: ["__tests__/**/*.test.ts"], exclude: ["src", "node_modules", "dist", "playground", "public"], - + // Enable globals to avoid redundant imports. globals: true, @@ -37,7 +36,7 @@ export default defineConfig({ branches: 90, functions: 90, lines: 90, - } + }, }, // Disable watch mode to simplify workflow.