diff --git a/coverage/coverage-summary.json b/coverage/coverage-summary.json new file mode 100644 index 00000000..0313f0c1 --- /dev/null +++ b/coverage/coverage-summary.json @@ -0,0 +1,111 @@ +{ + "total": { + "lines": { "total": 909, "covered": 772, "skipped": 0, "pct": 84.92 }, + "statements": { "total": 997, "covered": 817, "skipped": 0, "pct": 81.94 }, + "functions": { "total": 172, "covered": 126, "skipped": 0, "pct": 73.25 }, + "branches": { "total": 691, "covered": 490, "skipped": 0, "pct": 70.91 }, + "branchesTrue": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/jest.monaco-mock.js": { + "lines": { "total": 5, "covered": 5, "skipped": 0, "pct": 100 }, + "functions": { "total": 4, "covered": 4, "skipped": 0, "pct": 100 }, + "statements": { "total": 6, "covered": 6, "skipped": 0, "pct": 100 }, + "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/jest.raw-mock.js": { + "lines": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, + "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, + "statements": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, + "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/core.ts": { + "lines": { "total": 12, "covered": 12, "skipped": 0, "pct": 100 }, + "functions": { "total": 6, "covered": 6, "skipped": 0, "pct": 100 }, + "statements": { "total": 18, "covered": 18, "skipped": 0, "pct": 100 }, + "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/import-map.ts": { + "lines": { "total": 22, "covered": 22, "skipped": 0, "pct": 100 }, + "functions": { "total": 8, "covered": 8, "skipped": 0, "pct": 100 }, + "statements": { "total": 23, "covered": 23, "skipped": 0, "pct": 100 }, + "branches": { "total": 28, "covered": 27, "skipped": 0, "pct": 96.42 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/index.ts": { + "lines": { "total": 21, "covered": 18, "skipped": 0, "pct": 85.71 }, + "functions": { "total": 8, "covered": 6, "skipped": 0, "pct": 75 }, + "statements": { "total": 29, "covered": 24, "skipped": 0, "pct": 82.75 }, + "branches": { "total": 31, "covered": 20, "skipped": 0, "pct": 64.51 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/sourcemap.ts": { + "lines": { "total": 39, "covered": 38, "skipped": 0, "pct": 97.43 }, + "functions": { "total": 4, "covered": 4, "skipped": 0, "pct": 100 }, + "statements": { "total": 40, "covered": 39, "skipped": 0, "pct": 97.5 }, + "branches": { "total": 8, "covered": 7, "skipped": 0, "pct": 87.5 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/store.ts": { + "lines": { "total": 225, "covered": 182, "skipped": 0, "pct": 80.88 }, + "functions": { "total": 47, "covered": 32, "skipped": 0, "pct": 68.08 }, + "statements": { "total": 244, "covered": 187, "skipped": 0, "pct": 76.63 }, + "branches": { "total": 169, "covered": 111, "skipped": 0, "pct": 65.68 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/transform.ts": { + "lines": { "total": 175, "covered": 139, "skipped": 0, "pct": 79.42 }, + "functions": { "total": 24, "covered": 13, "skipped": 0, "pct": 54.16 }, + "statements": { "total": 191, "covered": 141, "skipped": 0, "pct": 73.82 }, + "branches": { "total": 153, "covered": 106, "skipped": 0, "pct": 69.28 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/utils.ts": { + "lines": { "total": 20, "covered": 20, "skipped": 0, "pct": 100 }, + "functions": { "total": 5, "covered": 5, "skipped": 0, "pct": 100 }, + "statements": { "total": 20, "covered": 20, "skipped": 0, "pct": 100 }, + "branches": { "total": 5, "covered": 5, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/vue-dev-proxy.ts": { + "lines": { "total": 12, "covered": 10, "skipped": 0, "pct": 83.33 }, + "functions": { "total": 4, "covered": 3, "skipped": 0, "pct": 75 }, + "statements": { "total": 17, "covered": 14, "skipped": 0, "pct": 82.35 }, + "branches": { "total": 24, "covered": 16, "skipped": 0, "pct": 66.66 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/vue-server-renderer-dev-proxy.ts": { + "lines": { "total": 12, "covered": 10, "skipped": 0, "pct": 83.33 }, + "functions": { "total": 4, "covered": 3, "skipped": 0, "pct": 75 }, + "statements": { "total": 17, "covered": 14, "skipped": 0, "pct": 82.35 }, + "branches": { "total": 24, "covered": 16, "skipped": 0, "pct": 66.66 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/codemirror/codemirror.ts": { + "lines": { "total": 22, "covered": 22, "skipped": 0, "pct": 100 }, + "functions": { "total": 1, "covered": 1, "skipped": 0, "pct": 100 }, + "statements": { "total": 22, "covered": 22, "skipped": 0, "pct": 100 }, + "branches": { "total": 7, "covered": 6, "skipped": 0, "pct": 85.71 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/monaco/env.ts": { + "lines": { "total": 112, "covered": 78, "skipped": 0, "pct": 69.64 }, + "functions": { "total": 25, "covered": 12, "skipped": 0, "pct": 48 }, + "statements": { "total": 126, "covered": 85, "skipped": 0, "pct": 67.46 }, + "branches": { "total": 80, "covered": 42, "skipped": 0, "pct": 52.5 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/monaco/highlight.ts": { + "lines": { "total": 44, "covered": 38, "skipped": 0, "pct": 86.36 }, + "functions": { "total": 11, "covered": 8, "skipped": 0, "pct": 72.72 }, + "statements": { "total": 55, "covered": 45, "skipped": 0, "pct": 81.81 }, + "branches": { "total": 46, "covered": 31, "skipped": 0, "pct": 67.39 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/monaco/language-configs.ts": { + "lines": { "total": 7, "covered": 7, "skipped": 0, "pct": 100 }, + "functions": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 }, + "statements": { "total": 7, "covered": 7, "skipped": 0, "pct": 100 }, + "branches": { "total": 0, "covered": 0, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/output/PreviewProxy.ts": { + "lines": { "total": 42, "covered": 42, "skipped": 0, "pct": 100 }, + "functions": { "total": 9, "covered": 9, "skipped": 0, "pct": 100 }, + "statements": { "total": 43, "covered": 43, "skipped": 0, "pct": 100 }, + "branches": { "total": 21, "covered": 21, "skipped": 0, "pct": 100 } + }, + "/mnt/ext2/code/prompts/github/forks/repl/src/output/moduleCompiler.ts": { + "lines": { "total": 138, "covered": 128, "skipped": 0, "pct": 92.75 }, + "functions": { "total": 12, "covered": 12, "skipped": 0, "pct": 100 }, + "statements": { "total": 138, "covered": 128, "skipped": 0, "pct": 92.75 }, + "branches": { "total": 95, "covered": 82, "skipped": 0, "pct": 86.31 } + } +} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..61d951c3 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,21 @@ +import { createDefaultPreset } from 'ts-jest' + +const tsJestTransformCfg = createDefaultPreset().transform + +/** @type {import("jest").Config} **/ +export default { + testEnvironment: 'node', + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + isolatedModules: true, + }, + ], + }, + moduleNameMapper: { + '^.+\\?raw$': '/jest.raw-mock.js', + '\\.vue$': '/jest.raw-mock.js', + '^monaco-editor-core$': '/jest.monaco-mock.js', + }, +} diff --git a/jest.monaco-mock.js b/jest.monaco-mock.js new file mode 100644 index 00000000..16c8d039 --- /dev/null +++ b/jest.monaco-mock.js @@ -0,0 +1,21 @@ +// Manual mock for monaco-editor-core +const mockModels = new Map() + +module.exports = { + Uri: { + parse: jest.fn((uri) => ({ toString: () => uri })), + }, + editor: { + getModel: jest.fn((uri) => mockModels.get(uri.toString())), + getModels: jest.fn(() => Array.from(mockModels.values())), + createWebWorker: jest.fn(), + registerEditorOpener: jest.fn(), + }, + languages: { + register: jest.fn(), + setLanguageConfiguration: jest.fn(), + onLanguage: jest.fn(), + }, + // Export for test access + __mockModels: mockModels, +} diff --git a/jest.raw-mock.js b/jest.raw-mock.js new file mode 100644 index 00000000..b0c50903 --- /dev/null +++ b/jest.raw-mock.js @@ -0,0 +1 @@ +module.exports = '' diff --git a/package.json b/package.json index 4d38ce0f..835d1260 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/babel__standalone": "^7.1.9", "@types/codemirror": "^5.60.16", "@types/hash-sum": "^1.0.2", + "@types/jest": "^30.0.0", "@types/node": "^24.2.0", "@vitejs/plugin-vue": "^6.0.1", "@volar/jsdelivr": "2.4.23", @@ -104,6 +105,7 @@ "eslint-plugin-vue": "^10.4.0", "fflate": "^0.8.2", "hash-sum": "^2.0.0", + "jest": "^30.4.2", "lint-staged": "^16.1.4", "monaco-editor-core": "^0.52.2", "prettier": "^3.6.2", @@ -111,12 +113,13 @@ "simple-git-hooks": "^2.13.1", "source-map-js": "^1.2.1", "sucrase": "^3.35.0", + "ts-jest": "^29.4.10", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0", "vite": "^8.0.8", "vite-plugin-dts": "^4.5.4", - "vscode-uri": "^3.1.0", "volar-service-typescript": "0.0.65", + "vscode-uri": "^3.1.0", "vue": "^3.5.18", "vue-tsc": "3.0.8" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fccfd2f..4617214f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@types/hash-sum': specifier: ^1.0.2 version: 1.0.2 + '@types/jest': + specifier: ^30.0.0 + version: 30.0.0 '@types/node': specifier: ^24.2.0 version: 24.2.0 @@ -58,7 +61,7 @@ importers: version: 2.4.23 '@vue/babel-plugin-jsx': specifier: ^2.0.1 - version: 2.0.1(@babel/core@7.25.2) + version: 2.0.1(@babel/core@7.29.0) '@vue/language-core': specifier: 3.0.8 version: 3.0.8(typescript@5.9.2) @@ -92,6 +95,9 @@ importers: hash-sum: specifier: ^2.0.0 version: 2.0.0 + jest: + specifier: ^30.4.2 + version: 30.4.2(@types/node@24.2.0) lint-staged: specifier: ^16.1.4 version: 16.1.4 @@ -113,6 +119,9 @@ importers: sucrase: specifier: ^3.35.0 version: 3.35.0 + ts-jest: + specifier: ^29.4.10 + version: 29.4.10(@babel/core@7.29.0)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.0))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.2.0))(typescript@5.9.2) typescript: specifier: ^5.9.2 version: 5.9.2 @@ -152,10 +161,18 @@ packages: resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.25.2': resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} engines: {node: '>=6.9.0'} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} @@ -164,6 +181,10 @@ packages: resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} @@ -178,6 +199,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} @@ -194,21 +221,114 @@ packages: resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.26.0': resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/standalone@7.28.2': resolution: {integrity: sha512-1kjA8XzBRN68HoDDYKP38bucHtxYWCIX8XdYwe1drRNUOjOVNt8EMy9jiE6UwaGFfU7NOHCG+C8KgBc9CR08nA==} engines: {node: '>=6.9.0'} @@ -225,6 +345,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@conventional-changelog/git-client@1.0.1': resolution: {integrity: sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==} engines: {node: '>=18'} @@ -259,9 +382,15 @@ packages: '@emmetio/stream-reader@2.2.0': resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} @@ -342,12 +471,105 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@30.4.1': + resolution: {integrity: sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.4.2': + resolution: {integrity: sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@30.4.1': + resolution: {integrity: sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.4.1': + resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.4.1': + resolution: {integrity: sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.4.1': + resolution: {integrity: sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.4.1': + resolution: {integrity: sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.4.0': + resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.4.1': + resolution: {integrity: sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.4.1': + resolution: {integrity: sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.4.1': + resolution: {integrity: sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.4.1': + resolution: {integrity: sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.4.1': + resolution: {integrity: sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.4.1': + resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@johnsoncodehk/pug-beautify@0.2.2': resolution: {integrity: sha512-qqNS/YD0Nck5wtQLCPHAfGVgWbbGafxSPjNh0ekYPFSNNqnDH2kamnduzYly8IiADmeVx/MfAE1njMEjVeHTMA==} '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -377,6 +599,12 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -396,6 +624,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} @@ -669,6 +901,15 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -702,6 +943,18 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -717,12 +970,21 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/tern@0.23.9': resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.39.0': resolution: {integrity: sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -785,6 +1047,119 @@ packages: '@ungap/structured-clone@1.2.1': resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + '@vitejs/plugin-vue@6.0.1': resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -950,6 +1325,10 @@ packages: alien-signals@2.0.6: resolution: {integrity: sha512-P3TxJSe31bUHBiblg59oU1PpaWPtmxF9GhJ/cB7OkgJ0qN/ifFSKUI25/v8ZhsT+lIG6ac8DpTOplXxORX6F3Q==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} @@ -966,6 +1345,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -973,6 +1356,10 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -992,6 +1379,31 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + babel-jest@30.4.1: + resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-0 + + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@30.4.0: + resolution: {integrity: sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.4.0: + resolution: {integrity: sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1013,6 +1425,16 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bumpp@11.0.1: resolution: {integrity: sha512-X0ti27I/ewsx/u0EJSyl0IZWWOE95q+wIpAG/60kc5gqMNR4a23YJdd3lL7JsBN11TgLbCM4KpfGMuFfdigb4g==} engines: {node: '>=20.19.0'} @@ -1038,6 +1460,14 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001690: resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} @@ -1052,6 +1482,10 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1061,6 +1495,13 @@ packages: character-parser@2.2.0: resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1069,9 +1510,20 @@ packages: resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} engines: {node: '>=18'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + codemirror@5.65.18: resolution: {integrity: sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA==} + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1203,9 +1655,21 @@ packages: supports-color: optional: true + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1225,6 +1689,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -1242,6 +1710,10 @@ packages: electron-to-chromium@1.5.76: resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + emmet@2.4.11: resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} @@ -1266,6 +1738,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1282,6 +1757,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1331,6 +1810,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -1353,6 +1837,18 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@30.4.1: + resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -1372,6 +1868,9 @@ packages: fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1396,6 +1895,10 @@ packages: resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} engines: {node: '>=18'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1418,6 +1921,9 @@ packages: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1430,6 +1936,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.3.0: resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} @@ -1438,6 +1948,14 @@ packages: resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} engines: {node: '>= 0.4'} + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + git-raw-commits@5.0.0: resolution: {integrity: sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==} engines: {node: '>=18'} @@ -1463,6 +1981,15 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1482,6 +2009,11 @@ packages: engines: {node: '>=0.4.7'} hasBin: true + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1518,9 +2050,16 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1537,6 +2076,11 @@ packages: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1545,6 +2089,10 @@ packages: resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} engines: {node: '>=18'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1552,6 +2100,9 @@ packages: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -1579,6 +2130,10 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -1603,6 +2158,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} @@ -1610,9 +2169,157 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jest-changed-files@30.4.1: + resolution: {integrity: sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.4.2: + resolution: {integrity: sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.4.2: + resolution: {integrity: sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.4.2: + resolution: {integrity: sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.4.0: + resolution: {integrity: sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.4.1: + resolution: {integrity: sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-node@30.4.1: + resolution: {integrity: sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.4.1: + resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.4.1: + resolution: {integrity: sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.4.1: + resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.4.1: + resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.4.0: + resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.4.2: + resolution: {integrity: sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.4.1: + resolution: {integrity: sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.4.2: + resolution: {integrity: sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.4.2: + resolution: {integrity: sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.4.1: + resolution: {integrity: sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.4.1: + resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.4.1: + resolution: {integrity: sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.4.1: + resolution: {integrity: sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.4.1: + resolution: {integrity: sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.4.2: + resolution: {integrity: sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -1623,6 +2330,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1635,6 +2346,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1667,6 +2381,10 @@ packages: laplacenoma@0.0.3: resolution: {integrity: sha512-fsQMsMozEzGg/DhQG73nf8Nzx0XvU8RJFRViYuocM6pQSa33lucnHvJzsb4udQUzH5p5zdX63t9qRahKAhWtiQ==} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1761,10 +2479,17 @@ packages: resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} engines: {node: '>=14'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1788,6 +2513,16 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1799,6 +2534,9 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1822,6 +2560,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -1874,12 +2616,20 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -1887,6 +2637,14 @@ packages: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -1906,6 +2664,13 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -1920,14 +2685,26 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1938,6 +2715,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-json@8.1.0: resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} engines: {node: '>=18'} @@ -1949,6 +2730,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1986,6 +2771,14 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + pkg-types@1.3.0: resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} @@ -2013,6 +2806,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -2029,6 +2826,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} @@ -2038,6 +2838,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + read-package-up@11.0.0: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} @@ -2055,14 +2861,26 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2106,6 +2924,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2122,6 +2945,9 @@ packages: resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} engines: {node: '>=20'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2130,6 +2956,10 @@ packages: resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==} hasBin: true + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -2142,6 +2972,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2164,10 +2997,18 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2191,6 +3032,14 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2212,6 +3061,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + temp-dir@3.0.0: resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} engines: {node: '>=14.16'} @@ -2220,6 +3073,10 @@ packages: resolution: {integrity: sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==} engines: {node: '>=14.18'} + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -2235,6 +3092,9 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2254,6 +3114,33 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-jest@29.4.10: + resolution: {integrity: sha512-vMTlTTtvz5aKZgzOoc7DQ5TzAL2fCzl8JnG1+ZpwjQa/g0xLlwE44yQ+1Cao9ZP1xVv9y5g34IFXEiqGOGFBUA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <7' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2261,10 +3148,22 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@4.31.0: resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} engines: {node: '>=16'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript-auto-import-cache@0.3.5: resolution: {integrity: sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==} @@ -2325,6 +3224,9 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + update-browserslist-db@1.1.1: resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true @@ -2340,6 +3242,10 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -2500,6 +3406,9 @@ packages: typescript: optional: true + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + which-typed-array@1.1.18: resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} @@ -2528,10 +3437,21 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2543,6 +3463,14 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2565,6 +3493,8 @@ snapshots: '@babel/compat-data@7.26.3': {} + '@babel/compat-data@7.29.3': {} + '@babel/core@7.25.2': dependencies: '@ampproject/remapping': 2.3.0 @@ -2585,6 +3515,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.2 @@ -2601,6 +3551,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.3 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.24.3 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-globals@7.28.0': {} '@babel/helper-module-imports@7.28.6': @@ -2619,6 +3577,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + '@babel/helper-plugin-utils@7.28.6': {} '@babel/helper-string-parser@7.27.1': {} @@ -2627,18 +3594,105 @@ snapshots: '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helpers@7.26.0': dependencies: '@babel/template': 7.28.6 '@babel/types': 7.29.0 + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.25.2)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/standalone@7.28.2': {} @@ -2666,6 +3720,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@0.2.3': {} + '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.0.0)': dependencies: '@types/semver': 7.5.8 @@ -2697,12 +3753,23 @@ snapshots: '@emmetio/stream-reader@2.2.0': {} + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 @@ -2787,16 +3854,209 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@johnsoncodehk/pug-beautify@0.2.2': {} - - '@jridgewell/gen-mapping@0.3.12': + '@istanbuljs/load-nyc-config@1.1.0': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.29 - - '@jridgewell/resolve-uri@3.1.2': {} + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 - '@jridgewell/sourcemap-codec@1.5.5': {} + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@30.4.1': + dependencies: + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + chalk: 4.1.2 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + slash: 3.0.0 + + '@jest/core@30.4.2': + dependencies: + '@jest/console': 30.4.1 + '@jest/pattern': 30.4.0 + '@jest/reporters': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.4.0 + exit-x: 0.2.2 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-changed-files: 30.4.1 + jest-config: 30.4.2(@types/node@24.2.0) + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-resolve-dependencies: 30.4.2 + jest-runner: 30.4.2 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 + jest-watcher: 30.4.1 + pretty-format: 30.4.1 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.4.0': {} + + '@jest/environment@30.4.1': + dependencies: + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + jest-mock: 30.4.1 + + '@jest/expect-utils@30.4.1': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.4.1': + dependencies: + expect: 30.4.1 + jest-snapshot: 30.4.1 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.4.1': + dependencies: + '@jest/types': 30.4.1 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 24.2.0 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + + '@jest/get-type@30.1.0': {} + + '@jest/globals@30.4.1': + dependencies: + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/types': 30.4.1 + jest-mock: 30.4.1 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.4.0': + dependencies: + '@types/node': 24.2.0 + jest-regex-util: 30.4.0 + + '@jest/reporters@30.4.1': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@jridgewell/trace-mapping': 0.3.29 + '@types/node': 24.2.0 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + jest-worker: 30.4.1 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.4.1': + dependencies: + '@sinclair/typebox': 0.34.49 + + '@jest/snapshot-utils@30.4.1': + dependencies: + '@jest/types': 30.4.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.4.1': + dependencies: + '@jest/console': 30.4.1 + '@jest/types': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@30.4.1': + dependencies: + '@jest/test-result': 30.4.1 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + slash: 3.0.0 + + '@jest/transform@30.4.1': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 30.4.1 + '@jridgewell/trace-mapping': 0.3.29 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.4.1': + dependencies: + '@jest/pattern': 30.4.0 + '@jest/schemas': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 24.2.0 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@johnsoncodehk/pug-beautify@0.2.2': {} + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.29': dependencies: @@ -2845,6 +4105,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2862,6 +4129,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.2.9': {} + '@quansync/fs@1.0.0': dependencies: quansync: 1.0.0 @@ -3074,6 +4343,16 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.34.49': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@15.4.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -3123,6 +4402,21 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@30.0.0': + dependencies: + expect: 30.4.1 + pretty-format: 30.4.1 + '@types/json-schema@7.0.15': {} '@types/mdast@4.0.4': @@ -3137,12 +4431,20 @@ snapshots: '@types/semver@7.5.8': {} + '@types/stack-utils@2.0.3': {} + '@types/tern@0.23.9': dependencies: '@types/estree': 1.0.8 '@types/unist@3.0.3': {} + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.6.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3238,6 +4540,78 @@ snapshots: '@ungap/structured-clone@1.2.1': {} + '@ungap/structured-clone@1.3.1': {} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + '@vitejs/plugin-vue@6.0.1(vite@8.0.8(@types/node@24.2.0)(jiti@2.6.1)(yaml@2.8.3))(vue@3.5.18(typescript@5.9.2))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.29 @@ -3285,26 +4659,26 @@ snapshots: '@vue/babel-helper-vue-transform-on@2.0.1': {} - '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.25.2)': + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0)': dependencies: '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.25.2) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 '@vue/babel-helper-vue-transform-on': 2.0.1 - '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.25.2) + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0) '@vue/shared': 3.5.32 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.29.0 transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.25.2)': + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.0)': dependencies: '@babel/code-frame': 7.29.0 - '@babel/core': 7.25.2 + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/parser': 7.29.2 @@ -3500,6 +4874,10 @@ snapshots: alien-signals@2.0.6: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-escapes@7.0.0: dependencies: environment: 1.1.0 @@ -3512,10 +4890,17 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} any-promise@1.3.0: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -3538,6 +4923,58 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + babel-jest@30.4.1(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 30.4.1 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.4.0(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.4.0: + dependencies: + '@types/babel__core': 7.20.5 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.4.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 30.4.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + balanced-match@1.0.2: {} boolbase@1.0.0: {} @@ -3562,6 +4999,16 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.1(browserslist@4.24.3) + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + bumpp@11.0.1: dependencies: args-tokenizer: 0.3.0 @@ -3595,6 +5042,10 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + caniuse-lite@1.0.30001690: {} ccount@2.0.1: {} @@ -3606,6 +5057,8 @@ snapshots: chalk@5.4.1: {} + char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -3614,6 +5067,10 @@ snapshots: dependencies: is-regex: 1.2.1 + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.0: {} + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -3623,8 +5080,18 @@ snapshots: slice-ansi: 5.0.0 string-width: 7.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + co@4.6.0: {} + codemirror@5.65.18: {} + collect-v8-coverage@1.0.3: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3750,8 +5217,12 @@ snapshots: dependencies: ms: 2.1.3 + dedent@1.7.2: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -3770,6 +5241,8 @@ snapshots: detect-libc@2.1.2: {} + detect-newline@3.1.0: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -3788,6 +5261,8 @@ snapshots: electron-to-chromium@1.5.76: {} + emittery@0.13.1: {} + emmet@2.4.11: dependencies: '@emmetio/abbreviation': 2.3.3 @@ -3805,6 +5280,10 @@ snapshots: environment@1.1.0: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -3815,6 +5294,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.6.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.6.1))(vue-eslint-parser@9.4.3(eslint@9.32.0(jiti@2.6.1))): @@ -3898,6 +5379,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -3914,6 +5397,29 @@ snapshots: eventemitter3@5.0.1: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@30.4.1: + dependencies: + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + exsolve@1.0.7: {} fast-deep-equal@3.1.3: {} @@ -3934,6 +5440,10 @@ snapshots: dependencies: reusify: 1.0.4 + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3950,6 +5460,11 @@ snapshots: find-up-simple@1.0.0: {} + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3977,6 +5492,8 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true @@ -3984,6 +5501,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-east-asian-width@1.3.0: {} get-intrinsic@1.2.6: @@ -3999,6 +5518,10 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-package-type@0.1.0: {} + + get-stream@6.0.1: {} + git-raw-commits@5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.0.0): dependencies: '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.0.0) @@ -4032,6 +5555,24 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@10.5.0: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@14.0.0: {} gopd@1.2.0: {} @@ -4049,6 +5590,15 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -4091,8 +5641,12 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-escaper@2.0.2: {} + html-void-elements@3.0.0: {} + human-signals@2.1.0: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4104,10 +5658,20 @@ snapshots: import-lazy@4.0.0: {} + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + imurmurhash@0.1.4: {} index-to-position@0.1.2: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} is-arguments@1.2.0: @@ -4115,6 +5679,8 @@ snapshots: call-bound: 1.0.3 has-tostringtag: 1.0.2 + is-arrayish@0.2.1: {} + is-callable@1.2.7: {} is-core-module@2.16.1: @@ -4136,6 +5702,8 @@ snapshots: dependencies: get-east-asian-width: 1.3.0 + is-generator-fn@2.1.0: {} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 @@ -4160,24 +5728,373 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-stream@2.0.1: {} + is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.18 isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.25.2 + '@babel/parser': 7.29.2 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jest-changed-files@30.4.1: + dependencies: + execa: 5.1.1 + jest-util: 30.4.1 + p-limit: 3.1.0 + + jest-circus@30.4.2: + dependencies: + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.0 + jest-each: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + p-limit: 3.1.0 + pretty-format: 30.4.1 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.4.2(@types/node@24.2.0): + dependencies: + '@jest/core': 30.4.2 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.4.2(@types/node@24.2.0) + jest-util: 30.4.1 + jest-validate: 30.4.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.4.2(@types/node@24.2.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.4.0 + '@jest/test-sequencer': 30.4.1 + '@jest/types': 30.4.1 + babel-jest: 30.4.1(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.4.2 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-runner: 30.4.2 + jest-util: 30.4.1 + jest-validate: 30.4.1 + parse-json: 5.2.0 + pretty-format: 30.4.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.2.0 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + + jest-docblock@30.4.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.4.1 + chalk: 4.1.2 + jest-util: 30.4.1 + pretty-format: 30.4.1 + + jest-environment-node@30.4.1: + dependencies: + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + jest-mock: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 + + jest-haste-map@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + jest-worker: 30.4.1 + picomatch: 4.0.4 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + pretty-format: 30.4.1 + + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + + jest-message-util@30.4.1: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 30.4.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.4.1 + picomatch: 4.0.4 + pretty-format: 30.4.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + jest-util: 30.4.1 + + jest-pnp-resolver@1.2.3(jest-resolve@30.4.1): + optionalDependencies: + jest-resolve: 30.4.1 + + jest-regex-util@30.4.0: {} + + jest-resolve-dependencies@30.4.2: + dependencies: + jest-regex-util: 30.4.0 + jest-snapshot: 30.4.1 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.4.1: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-pnp-resolver: 1.2.3(jest-resolve@30.4.1) + jest-util: 30.4.1 + jest-validate: 30.4.1 + slash: 3.0.0 + unrs-resolver: 1.12.2 + + jest-runner@30.4.2: + dependencies: + '@jest/console': 30.4.1 + '@jest/environment': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-haste-map: 30.4.1 + jest-leak-detector: 30.4.1 + jest-message-util: 30.4.1 + jest-resolve: 30.4.1 + jest-runtime: 30.4.2 + jest-util: 30.4.1 + jest-watcher: 30.4.1 + jest-worker: 30.4.1 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.4.2: + dependencies: + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/globals': 30.4.1 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + chalk: 4.1.2 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.4.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 30.4.1 + graceful-fs: 4.2.11 + jest-diff: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + pretty-format: 30.4.1 + semver: 7.7.4 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color + + jest-util@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jest-validate@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.4.1 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.4.1 + + jest-watcher@30.4.1: + dependencies: + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 24.2.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.4.1 + string-length: 4.0.2 + + jest-worker@30.4.1: + dependencies: + '@types/node': 24.2.0 + '@ungap/structured-clone': 1.3.1 + jest-util: 30.4.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.4.2(@types/node@24.2.0): + dependencies: + '@jest/core': 30.4.2 + '@jest/types': 30.4.1 + import-local: 3.2.0 + jest-cli: 30.4.2(@types/node@24.2.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + jiti@2.6.1: {} jju@1.4.0: {} js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -4186,6 +6103,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -4212,6 +6131,8 @@ snapshots: laplacenoma@0.0.3: {} + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4300,10 +6221,16 @@ snapshots: pkg-types: 2.2.0 quansync: 0.2.10 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} lodash@4.17.21: {} @@ -4330,6 +6257,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + math-intrinsics@1.1.0: {} mdast-util-to-hast@13.2.0: @@ -4346,6 +6283,8 @@ snapshots: meow@13.2.0: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromark-util-character@2.1.1: @@ -4370,6 +6309,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} minimatch@10.0.3: @@ -4419,10 +6360,14 @@ snapshots: nanoid@3.3.11: {} + napi-postinstall@0.3.4: {} + natural-compare@1.4.0: {} neo-async@2.6.2: {} + node-int64@0.4.0: {} + node-releases@2.0.19: {} normalize-package-data@6.0.2: @@ -4431,6 +6376,12 @@ snapshots: semver: 7.7.4 validate-npm-package-license: 3.0.4 + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -4453,6 +6404,14 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -4474,14 +6433,24 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} package-manager-detector@1.6.0: {} @@ -4490,6 +6459,13 @@ snapshots: dependencies: callsites: 3.1.0 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse-json@8.1.0: dependencies: '@babel/code-frame': 7.29.0 @@ -4500,6 +6476,8 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -4523,6 +6501,12 @@ snapshots: pirates@4.0.6: {} + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + pkg-types@1.3.0: dependencies: confbox: 0.1.8 @@ -4552,6 +6536,13 @@ snapshots: prettier@3.6.2: {} + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.6 + property-information@7.1.0: {} pug-error@2.1.0: {} @@ -4569,12 +6560,18 @@ snapshots: punycode@2.3.1: {} + pure-rand@7.0.1: {} + quansync@0.2.10: {} quansync@1.0.0: {} queue-microtask@1.2.3: {} + react-is@18.3.1: {} + + react-is@19.2.6: {} + read-package-up@11.0.0: dependencies: find-up-simple: 1.0.0 @@ -4599,10 +6596,18 @@ snapshots: dependencies: regex-utilities: 2.3.0 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -4678,6 +6683,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4704,10 +6711,14 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} simple-git-hooks@2.13.1: {} + slash@3.0.0: {} + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -4720,6 +6731,11 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + source-map@0.6.1: {} space-separated-tokens@2.0.2: {} @@ -4740,8 +6756,17 @@ snapshots: sprintf-js@1.0.3: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + string-argv@0.3.2: {} + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4773,6 +6798,10 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + strip-json-comments@3.1.1: {} sucrase@3.35.0: @@ -4795,12 +6824,22 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + temp-dir@3.0.0: {} tempfile@5.0.0: dependencies: temp-dir: 3.0.0 + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 3.1.2 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -4816,6 +6855,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tmpl@1.0.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -4830,6 +6871,26 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-jest@29.4.10(@babel/core@7.29.0)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.0))(jest-util@30.4.1)(jest@30.4.2(@types/node@24.2.0))(typescript@5.9.2): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 30.4.2(@types/node@24.2.0) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.8.0 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + babel-jest: 30.4.1(@babel/core@7.29.0) + jest-util: 30.4.1 + tslib@2.8.1: optional: true @@ -4837,8 +6898,14 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + type-fest@4.31.0: {} + type-fest@4.41.0: {} + typescript-auto-import-cache@0.3.5: dependencies: semver: 7.7.4 @@ -4905,6 +6972,33 @@ snapshots: universalify@2.0.1: {} + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + update-browserslist-db@1.1.1(browserslist@4.24.3): dependencies: browserslist: 4.24.3 @@ -4925,6 +7019,12 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.18 + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -5097,6 +7197,10 @@ snapshots: optionalDependencies: typescript: 5.9.2 + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 @@ -5132,14 +7236,35 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + xml-name-validator@4.0.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} yaml@2.8.3: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} zwitch@2.0.4: {} diff --git a/src/codemirror/codemirror.test.ts b/src/codemirror/codemirror.test.ts new file mode 100644 index 00000000..036d69ad --- /dev/null +++ b/src/codemirror/codemirror.test.ts @@ -0,0 +1,426 @@ +jest.mock('codemirror', () => { + const mockCodeMirror = jest.fn() as any + mockCodeMirror.version = '5.65.18' + mockCodeMirror.modes = { + javascript: {}, + css: {}, + htmlmixed: {}, + } + mockCodeMirror.mimeModes = { + 'text/javascript': {}, + 'text/css': {}, + 'text/html': {}, + } + mockCodeMirror.commands = {} + mockCodeMirror.keyMap = { + sublime: {}, + default: {}, + } + mockCodeMirror.defineExtension = jest.fn() + mockCodeMirror.defineDocExtension = jest.fn() + mockCodeMirror.defineOption = jest.fn() + mockCodeMirror.defineMode = jest.fn() + mockCodeMirror.defineMIME = jest.fn() + mockCodeMirror.getMode = jest.fn() + mockCodeMirror.fromTextArea = jest.fn() + mockCodeMirror.Pos = jest.fn() + mockCodeMirror.changeEnd = jest.fn() + mockCodeMirror.isWordChar = jest.fn() + mockCodeMirror.normalizeKeyMap = jest.fn() + mockCodeMirror.signal = jest.fn() + mockCodeMirror.on = jest.fn() + mockCodeMirror.off = jest.fn() + mockCodeMirror.addClass = jest.fn() + mockCodeMirror.rmClass = jest.fn() + mockCodeMirror.e_preventDefault = jest.fn() + mockCodeMirror.e_stop = jest.fn() + mockCodeMirror.e_stopPropagation = jest.fn() + mockCodeMirror.e_target = jest.fn() + mockCodeMirror.contains = jest.fn() + mockCodeMirror.lookupKey = jest.fn() + mockCodeMirror.isModifierKey = jest.fn() + mockCodeMirror.keyName = jest.fn() + mockCodeMirror.restartMode = jest.fn() + mockCodeMirror.innerMode = jest.fn() + mockCodeMirror.StringStream = jest.fn() + mockCodeMirror.TextMarker = jest.fn() + mockCodeMirror.LineWidget = jest.fn() + mockCodeMirror.Doc = jest.fn() + mockCodeMirror.extensionHooks = {} + mockCodeMirror.optionHandlers = {} + mockCodeMirror.initHooks = [] + mockCodeMirror.copyState = jest.fn() + mockCodeMirror.startState = jest.fn() + mockCodeMirror.getModeHelper = jest.fn() + mockCodeMirror.colorize = jest.fn() + mockCodeMirror.resolveMode = jest.fn() + mockCodeMirror.clearMode = jest.fn() + mockCodeMirror.modeExtensions = {} + mockCodeMirror.extensions = {} + mockCodeMirror.GlobalState = jest.fn() + mockCodeMirror.contentClass = 'CodeMirror-code' + mockCodeMirror.sizerClass = 'CodeMirror-sizer' + mockCodeMirror.gutterClass = 'CodeMirror-gutters' + mockCodeMirror.lineClass = 'CodeMirror-line' + mockCodeMirror.wrapperClass = 'CodeMirror' + mockCodeMirror.inputClass = 'CodeMirror-input' + mockCodeMirror.scrollbarClass = 'CodeMirror-scrollbar' + mockCodeMirror.lineSpaceClass = 'CodeMirror-lines' + mockCodeMirror.measureClass = 'CodeMirror-measure' + mockCodeMirror.gutterBGClass = 'CodeMirror-gutter-background' + mockCodeMirror.codeClass = 'cm-s-default' + mockCodeMirror.lineSeparator = '\n' + mockCodeMirror.defaults = { + tabSize: 4, + indentUnit: 2, + lineNumbers: false, + } + mockCodeMirror.create = jest.fn() + mockCodeMirror.defineModeSpec = jest.fn() + mockCodeMirror.defineMIMESpec = jest.fn() + return mockCodeMirror +}) + +jest.mock('codemirror/addon/dialog/dialog.css', () => ({})) +jest.mock('./codemirror.css', () => ({})) +jest.mock('codemirror/mode/javascript/javascript.js', () => ({})) +jest.mock('codemirror/mode/css/css.js', () => ({})) +jest.mock('codemirror/mode/htmlmixed/htmlmixed.js', () => ({})) +jest.mock('codemirror/addon/edit/closebrackets.js', () => ({})) +jest.mock('codemirror/addon/edit/closetag.js', () => ({})) +jest.mock('codemirror/addon/comment/comment.js', () => ({})) +jest.mock('codemirror/addon/fold/foldcode.js', () => ({})) +jest.mock('codemirror/addon/fold/foldgutter.js', () => ({})) +jest.mock('codemirror/addon/fold/brace-fold.js', () => ({})) +jest.mock('codemirror/addon/fold/indent-fold.js', () => ({})) +jest.mock('codemirror/addon/fold/comment-fold.js', () => ({})) +jest.mock('codemirror/addon/search/search.js', () => ({})) +jest.mock('codemirror/addon/search/searchcursor.js', () => ({})) +jest.mock('codemirror/addon/dialog/dialog.js', () => ({})) +jest.mock('codemirror/keymap/sublime.js', () => ({})) + +import CodeMirror from './codemirror' + +describe('codemirror', () => { + it('should export CodeMirror', () => { + expect(CodeMirror).toBeDefined() + }) + + it('should export CodeMirror as a function', () => { + expect(typeof CodeMirror).toBe('function') + }) + + it('should have CodeMirror constructor available', () => { + expect(typeof CodeMirror).toBe('function') + }) + + it('should have version property', () => { + expect(CodeMirror.version).toBeDefined() + expect(typeof CodeMirror.version).toBe('string') + }) + + it('should have modes registered', () => { + expect(CodeMirror.modes).toBeDefined() + expect(typeof CodeMirror.modes).toBe('object') + }) + + it('should have javascript mode loaded', () => { + expect(CodeMirror.modes.javascript).toBeDefined() + }) + + it('should have css mode loaded', () => { + expect(CodeMirror.modes.css).toBeDefined() + }) + + it('should have htmlmixed mode loaded', () => { + expect(CodeMirror.modes.htmlmixed).toBeDefined() + }) + + it('should have mimeModes registered', () => { + expect(CodeMirror.mimeModes).toBeDefined() + expect(typeof CodeMirror.mimeModes).toBe('object') + }) + + it('should have text/javascript mime mode', () => { + expect(CodeMirror.mimeModes['text/javascript']).toBeDefined() + }) + + it('should have text/css mime mode', () => { + expect(CodeMirror.mimeModes['text/css']).toBeDefined() + }) + + it('should have text/html mime mode', () => { + expect(CodeMirror.mimeModes['text/html']).toBeDefined() + }) + + it('should have commands registered', () => { + expect(CodeMirror.commands).toBeDefined() + expect(typeof CodeMirror.commands).toBe('object') + }) + + it('should have keyMap registered', () => { + expect(CodeMirror.keyMap).toBeDefined() + expect(typeof CodeMirror.keyMap).toBe('object') + }) + + it('should have sublime keymap loaded', () => { + expect(CodeMirror.keyMap.sublime).toBeDefined() + }) + + it('should have default keymap', () => { + expect(CodeMirror.keyMap.default).toBeDefined() + }) + + it('should have defineExtension method', () => { + expect(typeof CodeMirror.defineExtension).toBe('function') + }) + + it('should have defineDocExtension method', () => { + expect(typeof CodeMirror.defineDocExtension).toBe('function') + }) + + it('should have defineOption method', () => { + expect(typeof CodeMirror.defineOption).toBe('function') + }) + + it('should have defineMode method', () => { + expect(typeof CodeMirror.defineMode).toBe('function') + }) + + it('should have defineMIME method', () => { + expect(typeof CodeMirror.defineMIME).toBe('function') + }) + + it('should have getMode method', () => { + expect(typeof CodeMirror.getMode).toBe('function') + }) + + it('should have fromTextArea static method', () => { + expect(typeof CodeMirror.fromTextArea).toBe('function') + }) + + it('should have Pos constructor', () => { + expect(CodeMirror.Pos).toBeDefined() + expect(typeof CodeMirror.Pos).toBe('function') + }) + + it('should have changeEnd static method', () => { + expect(typeof CodeMirror.changeEnd).toBe('function') + }) + + it('should have isWordChar static method', () => { + expect(typeof CodeMirror.isWordChar).toBe('function') + }) + + it('should have normalizeKeyMap method', () => { + expect(typeof CodeMirror.normalizeKeyMap).toBe('function') + }) + + it('should have signal method', () => { + expect(typeof CodeMirror.signal).toBe('function') + }) + + it('should have on method', () => { + expect(typeof CodeMirror.on).toBe('function') + }) + + it('should have off method', () => { + expect(typeof CodeMirror.off).toBe('function') + }) + + it('should have addClass method', () => { + expect(typeof CodeMirror.addClass).toBe('function') + }) + + it('should have rmClass method', () => { + expect(typeof CodeMirror.rmClass).toBe('function') + }) + + it('should have e_preventDefault method', () => { + expect(typeof CodeMirror.e_preventDefault).toBe('function') + }) + + it('should have e_stop method', () => { + expect(typeof CodeMirror.e_stop).toBe('function') + }) + + it('should have e_stopPropagation method', () => { + expect(typeof CodeMirror.e_stopPropagation).toBe('function') + }) + + it('should have e_target method', () => { + expect(typeof CodeMirror.e_target).toBe('function') + }) + + it('should have contains method', () => { + expect(typeof CodeMirror.contains).toBe('function') + }) + + it('should have lookupKey method', () => { + expect(typeof CodeMirror.lookupKey).toBe('function') + }) + + it('should have isModifierKey method', () => { + expect(typeof CodeMirror.isModifierKey).toBe('function') + }) + + it('should have keyName method', () => { + expect(typeof CodeMirror.keyName).toBe('function') + }) + + it('should have restartMode method', () => { + expect(typeof CodeMirror.restartMode).toBe('function') + }) + + it('should have innerMode method', () => { + expect(typeof CodeMirror.innerMode).toBe('function') + }) + + it('should have StringStream constructor', () => { + expect(CodeMirror.StringStream).toBeDefined() + expect(typeof CodeMirror.StringStream).toBe('function') + }) + + it('should have TextMarker class', () => { + expect(CodeMirror.TextMarker).toBeDefined() + }) + + it('should have LineWidget class', () => { + expect(CodeMirror.LineWidget).toBeDefined() + }) + + it('should have Doc constructor', () => { + expect(CodeMirror.Doc).toBeDefined() + expect(typeof CodeMirror.Doc).toBe('function') + }) + + it('should have extensionHooks registered', () => { + expect(CodeMirror.extensionHooks).toBeDefined() + expect(typeof CodeMirror.extensionHooks).toBe('object') + }) + + it('should have optionHandlers registered', () => { + expect(CodeMirror.optionHandlers).toBeDefined() + expect(typeof CodeMirror.optionHandlers).toBe('object') + }) + + it('should have initHooks array', () => { + expect(CodeMirror.initHooks).toBeDefined() + expect(Array.isArray(CodeMirror.initHooks)).toBe(true) + }) + + it('should have copyState method', () => { + expect(typeof CodeMirror.copyState).toBe('function') + }) + + it('should have startState method', () => { + expect(typeof CodeMirror.startState).toBe('function') + }) + + it('should have getModeHelper method', () => { + expect(typeof CodeMirror.getModeHelper).toBe('function') + }) + + it('should have colorize method', () => { + expect(typeof CodeMirror.colorize).toBe('function') + }) + + it('should have resolveMode method', () => { + expect(typeof CodeMirror.resolveMode).toBe('function') + }) + + it('should have clearMode method', () => { + expect(typeof CodeMirror.clearMode).toBe('function') + }) + + it('should have modeExtensions object', () => { + expect(CodeMirror.modeExtensions).toBeDefined() + expect(typeof CodeMirror.modeExtensions).toBe('object') + }) + + it('should have extensions object', () => { + expect(CodeMirror.extensions).toBeDefined() + expect(typeof CodeMirror.extensions).toBe('object') + }) + + it('should have GlobalState class', () => { + expect(CodeMirror.GlobalState).toBeDefined() + }) + + it('should have contentClass constant', () => { + expect(CodeMirror.contentClass).toBeDefined() + }) + + it('should have sizerClass constant', () => { + expect(CodeMirror.sizerClass).toBeDefined() + }) + + it('should have gutterClass constant', () => { + expect(CodeMirror.gutterClass).toBeDefined() + }) + + it('should have lineClass constant', () => { + expect(CodeMirror.lineClass).toBeDefined() + }) + + it('should have wrapperClass constant', () => { + expect(CodeMirror.wrapperClass).toBeDefined() + }) + + it('should have inputClass constant', () => { + expect(CodeMirror.inputClass).toBeDefined() + }) + + it('should have scrollbarClass constant', () => { + expect(CodeMirror.scrollbarClass).toBeDefined() + }) + + it('should have lineSpaceClass constant', () => { + expect(CodeMirror.lineSpaceClass).toBeDefined() + }) + + it('should have measureClass constant', () => { + expect(CodeMirror.measureClass).toBeDefined() + }) + + it('should have gutterBGClass constant', () => { + expect(CodeMirror.gutterBGClass).toBeDefined() + }) + + it('should have codeClass constant', () => { + expect(CodeMirror.codeClass).toBeDefined() + }) + + it('should have lineSeparator constant', () => { + expect(CodeMirror.lineSeparator).toBeDefined() + }) + + it('should have defaults object with default options', () => { + expect(CodeMirror.defaults).toBeDefined() + expect(typeof CodeMirror.defaults).toBe('object') + }) + + it('should have defaults.tabSize', () => { + expect(CodeMirror.defaults.tabSize).toBeDefined() + }) + + it('should have defaults.indentUnit', () => { + expect(CodeMirror.defaults.indentUnit).toBeDefined() + }) + + it('should have defaults.lineNumbers property', () => { + expect(CodeMirror.defaults.lineNumbers).toBeDefined() + }) + + it('should have create defined', () => { + expect(typeof CodeMirror.create).toBe('function') + }) + + it('should have defineModeSpec method', () => { + expect(typeof CodeMirror.defineModeSpec).toBe('function') + }) + + it('should have defineMIMESpec method', () => { + expect(typeof CodeMirror.defineMIMESpec).toBe('function') + }) +}) diff --git a/src/core.test.ts b/src/core.test.ts new file mode 100644 index 00000000..b81b8e77 --- /dev/null +++ b/src/core.test.ts @@ -0,0 +1,297 @@ +import { + useStore, + File, + useVueImportMap, + mergeImportMap, + compileFile, + languageToolsVersion, +} from './core' +import type { ImportMap } from './core' +import { ref } from 'vue' + +describe('core exports', () => { + it('should export useStore function', () => { + expect(useStore).toBeDefined() + expect(typeof useStore).toBe('function') + }) + + it('should export File class', () => { + expect(File).toBeDefined() + expect(typeof File).toBe('function') + }) + + it('should export useVueImportMap function', () => { + expect(useVueImportMap).toBeDefined() + expect(typeof useVueImportMap).toBe('function') + }) + + it('should export mergeImportMap function', () => { + expect(mergeImportMap).toBeDefined() + expect(typeof mergeImportMap).toBe('function') + }) + + it('should export compileFile function', () => { + expect(compileFile).toBeDefined() + expect(typeof compileFile).toBe('function') + }) + + it('should export languageToolsVersion', () => { + expect(languageToolsVersion).toBeDefined() + expect(typeof languageToolsVersion).toBe('string') + }) +}) + +describe('File class', () => { + it('should create a File instance with filename', () => { + const file = new File('test.vue') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should create a File instance with filename and code', () => { + const file = new File('test.vue', '') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should create a File instance with all parameters', () => { + const file = new File('test.vue', '', true) + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(true) + }) + + it('should have compiled property with default values', () => { + const file = new File('test.vue') + expect(file.compiled).toEqual({ + js: '', + css: '', + ssr: '', + clientMap: '', + ssrMap: '', + }) + }) + + it('should have editorViewState property initialized to null', () => { + const file = new File('test.vue') + expect(file.editorViewState).toBeNull() + }) + + it('should return correct language for .vue files', () => { + const file = new File('test.vue') + expect(file.language).toBe('vue') + }) + + it('should return correct language for .html files', () => { + const file = new File('test.html') + expect(file.language).toBe('html') + }) + + it('should return correct language for .css files', () => { + const file = new File('test.css') + expect(file.language).toBe('css') + }) + + it('should return correct language for .ts files', () => { + const file = new File('test.ts') + expect(file.language).toBe('typescript') + }) + + it('should return correct language for .js files', () => { + const file = new File('test.js') + expect(file.language).toBe('javascript') + }) +}) + +describe('useVueImportMap', () => { + it('should return an object with expected properties', () => { + const result = useVueImportMap() + expect(result).toHaveProperty('productionMode') + expect(result).toHaveProperty('importMap') + expect(result).toHaveProperty('vueVersion') + expect(result).toHaveProperty('defaultVersion') + }) + + it('should create importMap with vue and server-renderer imports', () => { + const result = useVueImportMap() + const importMap = result.importMap.value + expect(importMap.imports).toBeDefined() + expect(importMap.imports?.vue).toBeDefined() + expect(importMap.imports?.['vue/server-renderer']).toBeDefined() + }) + + it('should accept vueVersion option', () => { + const result = useVueImportMap({ vueVersion: '3.4.0' }) + expect(result.vueVersion.value).toBe('3.4.0') + }) +}) + +describe('mergeImportMap', () => { + it('should merge two import maps', () => { + const a: ImportMap = { + imports: { vue: 'https://example.com/vue' }, + scopes: { scope1: { lib: 'https://example.com/lib' } }, + } + const b: ImportMap = { + imports: { 'vue/server-renderer': 'https://example.com/sr' }, + scopes: { scope2: { lib2: 'https://example.com/lib2' } }, + } + const result = mergeImportMap(a, b) + expect(result.imports?.vue).toBe('https://example.com/vue') + expect(result.imports?.['vue/server-renderer']).toBe( + 'https://example.com/sr', + ) + expect(result.scopes?.scope1?.lib).toBe('https://example.com/lib') + expect(result.scopes?.scope2?.lib2).toBe('https://example.com/lib2') + }) + + it('should handle empty import maps', () => { + const result = mergeImportMap({}, {}) + expect(result).toEqual({ imports: {}, scopes: {} }) + }) + + it('should handle import maps with only imports', () => { + const a: ImportMap = { imports: { vue: 'https://example.com/vue' } } + const b: ImportMap = { + imports: { 'vue/server-renderer': 'https://example.com/sr' }, + } + const result = mergeImportMap(a, b) + expect(result.imports?.vue).toBe('https://example.com/vue') + expect(result.imports?.['vue/server-renderer']).toBe( + 'https://example.com/sr', + ) + }) + + it('should handle import maps with only scopes', () => { + const a: ImportMap = { + scopes: { scope1: { lib: 'https://example.com/lib' } }, + } + const b: ImportMap = { + scopes: { scope2: { lib2: 'https://example.com/lib2' } }, + } + const result = mergeImportMap(a, b) + expect(result.scopes?.scope1?.lib).toBe('https://example.com/lib') + expect(result.scopes?.scope2?.lib2).toBe('https://example.com/lib2') + }) +}) + +describe('useStore', () => { + it('should create a store with default options', () => { + const store = useStore() + expect(store).toBeDefined() + expect(store.files).toBeDefined() + expect(store.errors).toBeDefined() + expect(store.showOutput).toBe(false) + }) + + it('should create a store with custom options', () => { + const store = useStore({ + mainFile: ref('src/Custom.vue'), + showOutput: ref(true), + }) + expect(store.mainFile).toBe('src/Custom.vue') + expect(store.showOutput).toBe(true) + }) + + it('should have init method', () => { + const store = useStore() + expect(typeof store.init).toBe('function') + }) + + it('should have setActive method', () => { + const store = useStore() + expect(typeof store.setActive).toBe('function') + }) + + it('should have addFile method', () => { + const store = useStore() + expect(typeof store.addFile).toBe('function') + }) + + it('should have deleteFile method', () => { + const store = useStore() + expect(typeof store.deleteFile).toBe('function') + }) + + it('should have renameFile method', () => { + const store = useStore() + expect(typeof store.renameFile).toBe('function') + }) + + it('should have getImportMap method', () => { + const store = useStore() + expect(typeof store.getImportMap).toBe('function') + }) + + it('should have setImportMap method', () => { + const store = useStore() + expect(typeof store.setImportMap).toBe('function') + }) + + it('should have getTsConfig method', () => { + const store = useStore() + expect(typeof store.getTsConfig).toBe('function') + }) + + it('should have serialize method', () => { + const store = useStore() + expect(typeof store.serialize).toBe('function') + }) + + it('should have deserialize method', () => { + const store = useStore() + expect(typeof store.deserialize).toBe('function') + }) + + it('should have getFiles method', () => { + const store = useStore() + expect(typeof store.getFiles).toBe('function') + }) + + it('should have setFiles method', () => { + const store = useStore() + expect(typeof store.setFiles).toBe('function') + }) +}) + +describe('compileFile', () => { + it('should return empty array for empty code', async () => { + const store = useStore() + const file = new File('test.vue', '') + const result = await compileFile(store, file) + expect(result).toEqual([]) + }) + + it('should compile CSS files', async () => { + const store = useStore() + const file = new File('test.css', '.test { color: red; }') + const result = await compileFile(store, file) + expect(result).toEqual([]) + expect(file.compiled.css).toBe('.test { color: red; }') + }) + + it('should return empty array for unknown file types', async () => { + const store = useStore() + const file = new File('test.txt', 'some text') + const result = await compileFile(store, file) + expect(result).toEqual([]) + }) + + it('should compile JSON files', async () => { + const store = useStore() + const file = new File('test.json', '{"key": "value"}') + const result = await compileFile(store, file) + expect(result).toEqual([]) + expect(file.compiled.js).toBe('export default {"key":"value"}') + expect(file.compiled.ssr).toBe('export default {"key":"value"}') + }) + + it('should return error for invalid JSON', async () => { + const store = useStore() + const file = new File('test.json', '{invalid json}') + const result = await compileFile(store, file) + expect(result.length).toBeGreaterThan(0) + }) +}) diff --git a/src/import-map.test.ts b/src/import-map.test.ts new file mode 100644 index 00000000..8af41b87 --- /dev/null +++ b/src/import-map.test.ts @@ -0,0 +1,231 @@ +import { + getVersions, + isVaporSupported, + useVueImportMap, + mergeImportMap, +} from './import-map' +import { version as currentVersion } from 'vue' + +describe('getVersions', () => { + it('should parse version string into number array', () => { + expect(getVersions('3.4.0')).toEqual([3, 4, 0]) + }) + + it('should handle two-part version', () => { + expect(getVersions('3.6')).toEqual([3, 6]) + }) + + it('should handle single version', () => { + expect(getVersions('3')).toEqual([3]) + }) + + it('should handle version with extra parts', () => { + expect(getVersions('3.4.5.6')).toEqual([3, 4, 5, 6]) + }) +}) + +describe('isVaporSupported', () => { + it('should return true for version 3.6', () => { + expect(isVaporSupported('3.6.0')).toBe(true) + }) + + it('should return true for version greater than 3.6', () => { + expect(isVaporSupported('3.7.0')).toBe(true) + expect(isVaporSupported('4.0.0')).toBe(true) + }) + + it('should return false for version less than 3.6', () => { + expect(isVaporSupported('3.5.0')).toBe(false) + expect(isVaporSupported('3.0.0')).toBe(false) + expect(isVaporSupported('2.7.0')).toBe(false) + }) +}) + +describe('useVueImportMap', () => { + it('should return default structure with current version', () => { + const { productionMode, importMap, vueVersion, defaultVersion } = + useVueImportMap() + + expect(productionMode.value).toBe(false) + expect(vueVersion.value).toBe(null) + expect(defaultVersion).toBe(currentVersion) + expect(importMap.value.imports).toBeDefined() + expect(importMap.value.imports?.vue).toBeDefined() + expect(importMap.value.imports?.['vue/server-renderer']).toBeDefined() + }) + + it('should use runtimeDev default when provided and no vueVersion', () => { + const { importMap } = useVueImportMap({ + runtimeDev: 'https://custom.dev/vue.js', + }) + + expect(importMap.value.imports?.vue).toBe('https://custom.dev/vue.js') + }) + + it('should use runtimeProd default when provided and productionMode is true', () => { + const { productionMode, importMap } = useVueImportMap({ + runtimeProd: 'https://custom.prod/vue.js', + }) + + productionMode.value = true + + expect(importMap.value.imports?.vue).toBe('https://custom.prod/vue.js') + }) + + it('should use serverRenderer default when provided and no vueVersion', () => { + const { importMap } = useVueImportMap({ + serverRenderer: 'https://custom.server/renderer.js', + }) + + expect(importMap.value.imports?.['vue/server-renderer']).toBe( + 'https://custom.server/renderer.js', + ) + }) + + it('should use vueVersion from defaults', () => { + const { vueVersion, importMap } = useVueImportMap({ + vueVersion: '3.5.0', + }) + + expect(vueVersion.value).toBe('3.5.0') + expect(importMap.value.imports?.vue).toContain('3.5.0') + }) + + it('should generate CDN URL for version < 3.6', () => { + const { vueVersion, importMap } = useVueImportMap({ + vueVersion: '3.4.0', + }) + + expect(vueVersion.value).toBe('3.4.0') + expect(importMap.value.imports?.vue).toContain('@vue/runtime-dom') + expect(importMap.value.imports?.vue).toContain('3.4.0') + expect(importMap.value.imports?.vue).not.toContain('.prod') + }) + + it('should generate CDN URL with vapor for version >= 3.6', () => { + const { vueVersion, importMap } = useVueImportMap({ + vueVersion: '3.6.0', + }) + + expect(vueVersion.value).toBe('3.6.0') + expect(importMap.value.imports?.vue).toContain('vue.runtime-with-vapor') + expect(importMap.value.imports?.vue).toContain('3.6.0') + }) + + it('should generate production URL when productionMode is true', () => { + const { productionMode, importMap } = useVueImportMap({ + vueVersion: '3.6.0', + }) + + productionMode.value = true + + expect(importMap.value.imports?.vue).toContain('.prod.js') + }) + + it('should update importMap when vueVersion changes', () => { + const { vueVersion, importMap } = useVueImportMap() + + vueVersion.value = '3.3.0' + expect(importMap.value.imports?.vue).toContain('3.3.0') + + vueVersion.value = '3.7.0' + expect(importMap.value.imports?.vue).toContain('3.7.0') + }) + + it('should use function defaults for runtimeDev', () => { + const { importMap } = useVueImportMap({ + runtimeDev: () => 'https://dynamic.dev/vue.js', + }) + + expect(importMap.value.imports?.vue).toBe('https://dynamic.dev/vue.js') + }) + + it('should use function defaults for runtimeProd', () => { + const { productionMode, importMap } = useVueImportMap({ + runtimeProd: () => 'https://dynamic.prod/vue.js', + }) + + productionMode.value = true + + expect(importMap.value.imports?.vue).toBe('https://dynamic.prod/vue.js') + }) + + it('should use function defaults for serverRenderer', () => { + const { importMap } = useVueImportMap({ + serverRenderer: () => 'https://dynamic.server/renderer.js', + }) + + expect(importMap.value.imports?.['vue/server-renderer']).toBe( + 'https://dynamic.server/renderer.js', + ) + }) +}) + +describe('mergeImportMap', () => { + it('should merge two import maps with imports', () => { + const a: ImportMap = { + imports: { + vue: 'https://a/vue.js', + }, + } + const b: ImportMap = { + imports: { + 'vue/server-renderer': 'https://b/renderer.js', + }, + } + + const result = mergeImportMap(a, b) + + expect(result.imports?.vue).toBe('https://a/vue.js') + expect(result.imports?.['vue/server-renderer']).toBe( + 'https://b/renderer.js', + ) + }) + + it('should merge two import maps with scopes', () => { + const a: ImportMap = { + scopes: { + 'https://a.com': { vue: 'https://a/vue.js' }, + }, + } + const b: ImportMap = { + scopes: { + 'https://b.com': { vue: 'https://b/vue.js' }, + }, + } + + const result = mergeImportMap(a, b) + + expect(result.scopes?.['https://a.com']?.vue).toBe('https://a/vue.js') + expect(result.scopes?.['https://b.com']?.vue).toBe('https://b/vue.js') + }) + + it('should handle empty import maps', () => { + const result = mergeImportMap({}, {}) + + expect(result.imports).toEqual({}) + expect(result.scopes).toEqual({}) + }) + + it('should override imports from first map with second map', () => { + const a: ImportMap = { + imports: { + vue: 'https://a/vue.js', + }, + } + const b: ImportMap = { + imports: { + vue: 'https://b/vue.js', + }, + } + + const result = mergeImportMap(a, b) + + expect(result.imports?.vue).toBe('https://b/vue.js') + }) +}) + +interface ImportMap { + imports?: Record + scopes?: Record> +} diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 00000000..2acd757b --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,238 @@ +import * as indexExports from './index' +import { + useStore, + File, + useVueImportMap, + mergeImportMap, + compileFile, + languageToolsVersion, +} from './core' +import Repl from './Repl.vue' +import Preview from './output/Preview.vue' +import Sandbox from './output/Sandbox.vue' + +describe('index.ts exports', () => { + it('should export Repl component', () => { + expect(indexExports.Repl).toBeDefined() + expect(indexExports.Repl).toBe(Repl) + }) + + it('should export Preview component', () => { + expect(indexExports.Preview).toBeDefined() + expect(indexExports.Preview).toBe(Preview) + }) + + it('should export Sandbox component', () => { + expect(indexExports.Sandbox).toBeDefined() + expect(indexExports.Sandbox).toBe(Sandbox) + }) + + it('should export useStore from core', () => { + expect(indexExports.useStore).toBeDefined() + expect(indexExports.useStore).toBe(useStore) + }) + + it('should export File from core', () => { + expect(indexExports.File).toBeDefined() + expect(indexExports.File).toBe(File) + }) + + it('should export useVueImportMap from core', () => { + expect(indexExports.useVueImportMap).toBeDefined() + expect(indexExports.useVueImportMap).toBe(useVueImportMap) + }) + + it('should export mergeImportMap from core', () => { + expect(indexExports.mergeImportMap).toBeDefined() + expect(indexExports.mergeImportMap).toBe(mergeImportMap) + }) + + it('should export compileFile from core', () => { + expect(indexExports.compileFile).toBeDefined() + expect(indexExports.compileFile).toBe(compileFile) + }) + + it('should export languageToolsVersion from core', () => { + expect(indexExports.languageToolsVersion).toBeDefined() + expect(indexExports.languageToolsVersion).toBe(languageToolsVersion) + }) + + it('should export all core exports', () => { + expect(indexExports.useStore).toBeDefined() + expect(indexExports.File).toBeDefined() + expect(indexExports.useVueImportMap).toBeDefined() + expect(indexExports.mergeImportMap).toBeDefined() + expect(indexExports.compileFile).toBeDefined() + expect(indexExports.languageToolsVersion).toBeDefined() + }) +}) + +describe('Repl component', () => { + it('should be exported', () => { + expect(indexExports.Repl).toBeDefined() + }) +}) + +describe('Preview component', () => { + it('should be exported', () => { + expect(indexExports.Preview).toBeDefined() + }) +}) + +describe('Sandbox component', () => { + it('should be exported', () => { + expect(indexExports.Sandbox).toBeDefined() + }) +}) + +describe('File class via index', () => { + it('should create a File instance with filename', () => { + const file = new indexExports.File('test.vue') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should create a File instance with filename and code', () => { + const file = new indexExports.File('test.vue', '') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should return correct language for .vue files', () => { + const file = new indexExports.File('test.vue') + expect(file.language).toBe('vue') + }) + + it('should return correct language for .ts files', () => { + const file = new indexExports.File('test.ts') + expect(file.language).toBe('typescript') + }) + + it('should return correct language for .js files', () => { + const file = new indexExports.File('test.js') + expect(file.language).toBe('javascript') + }) + + it('should return correct language for .css files', () => { + const file = new indexExports.File('test.css') + expect(file.language).toBe('css') + }) + + it('should return correct language for .html files', () => { + const file = new indexExports.File('test.html') + expect(file.language).toBe('html') + }) +}) + +describe('useStore via index', () => { + it('should create a store with default options', () => { + const store = indexExports.useStore() + expect(store).toBeDefined() + expect(store.files).toBeDefined() + expect(store.errors).toBeDefined() + }) + + it('should have all expected methods', () => { + const store = indexExports.useStore() + expect(typeof store.init).toBe('function') + expect(typeof store.setActive).toBe('function') + expect(typeof store.addFile).toBe('function') + expect(typeof store.deleteFile).toBe('function') + expect(typeof store.renameFile).toBe('function') + expect(typeof store.getImportMap).toBe('function') + expect(typeof store.setImportMap).toBe('function') + expect(typeof store.getTsConfig).toBe('function') + expect(typeof store.serialize).toBe('function') + expect(typeof store.deserialize).toBe('function') + expect(typeof store.getFiles).toBe('function') + expect(typeof store.setFiles).toBe('function') + }) +}) + +describe('useVueImportMap via index', () => { + it('should return an object with expected properties', () => { + const result = indexExports.useVueImportMap() + expect(result).toHaveProperty('productionMode') + expect(result).toHaveProperty('importMap') + expect(result).toHaveProperty('vueVersion') + expect(result).toHaveProperty('defaultVersion') + }) + + it('should create importMap with vue and server-renderer imports', () => { + const result = indexExports.useVueImportMap() + const importMap = result.importMap.value + expect(importMap.imports).toBeDefined() + expect(importMap.imports?.vue).toBeDefined() + expect(importMap.imports?.['vue/server-renderer']).toBeDefined() + }) +}) + +describe('mergeImportMap via index', () => { + it('should merge two import maps', () => { + const a = { + imports: { vue: 'https://example.com/vue' }, + scopes: { scope1: { lib: 'https://example.com/lib' } }, + } + const b = { + imports: { 'vue/server-renderer': 'https://example.com/sr' }, + scopes: { scope2: { lib2: 'https://example.com/lib2' } }, + } + const result = indexExports.mergeImportMap(a, b) + expect(result.imports?.vue).toBe('https://example.com/vue') + expect(result.imports?.['vue/server-renderer']).toBe( + 'https://example.com/sr', + ) + expect(result.scopes?.scope1?.lib).toBe('https://example.com/lib') + expect(result.scopes?.scope2?.lib2).toBe('https://example.com/lib2') + }) + + it('should handle empty import maps', () => { + const result = indexExports.mergeImportMap({}, {}) + expect(result).toEqual({ imports: {}, scopes: {} }) + }) +}) + +describe('compileFile via index', () => { + it('should return empty array for empty code', async () => { + const store = indexExports.useStore() + const file = new indexExports.File('test.vue', '') + const result = await indexExports.compileFile(store, file) + expect(result).toEqual([]) + }) + + it('should compile CSS files', async () => { + const store = indexExports.useStore() + const file = new indexExports.File('test.css', '.test { color: red; }') + const result = await indexExports.compileFile(store, file) + expect(result).toEqual([]) + expect(file.compiled.css).toBe('.test { color: red; }') + }) + + it('should compile JSON files', async () => { + const store = indexExports.useStore() + const file = new indexExports.File('test.json', '{"key": "value"}') + const result = await indexExports.compileFile(store, file) + expect(result).toEqual([]) + expect(file.compiled.js).toBe('export default {"key":"value"}') + expect(file.compiled.ssr).toBe('export default {"key":"value"}') + }) + + it('should return error for invalid JSON', async () => { + const store = indexExports.useStore() + const file = new indexExports.File('test.json', '{invalid json}') + const result = await indexExports.compileFile(store, file) + expect(result.length).toBeGreaterThan(0) + }) +}) + +describe('languageToolsVersion via index', () => { + it('should be a string', () => { + expect(typeof indexExports.languageToolsVersion).toBe('string') + }) + + it('should be a non-empty string', () => { + expect(indexExports.languageToolsVersion.length).toBeGreaterThan(0) + }) +}) diff --git a/src/monaco/env.test.ts b/src/monaco/env.test.ts new file mode 100644 index 00000000..bcaaecac --- /dev/null +++ b/src/monaco/env.test.ts @@ -0,0 +1,368 @@ +import { describe, it, expect, beforeEach, jest } from '@jest/globals' + +// Set up browser globals for the test environment +beforeAll(() => { + // @ts-ignore - mock browser global + global.self = global + // @ts-ignore - mock browser global + global.MonacoEnvironment = undefined +}) + +// Mock external dependencies before importing the module +jest.mock('@volar/monaco', () => ({ + activateMarkers: jest.fn(() => ({ dispose: jest.fn() })), + activateAutoInsertion: jest.fn(() => ({ dispose: jest.fn() })), + registerProviders: jest.fn(() => Promise.resolve({ dispose: jest.fn() })), +})) + +jest.mock('monaco-editor-core/esm/vs/editor/editor.worker?worker', () => ({ + default: jest.fn(), +})) + +jest.mock('./vue.worker?worker', () => ({ + default: jest.fn(() => ({ + addEventListener: jest.fn((event, callback) => { + if (event === 'message') { + setTimeout(() => callback({ data: 'inited' }), 0) + } + }), + postMessage: jest.fn(), + })), +})) + +jest.mock('vue', () => ({ + watchEffect: jest.fn((fn) => fn()), +})) + +jest.mock('./utils', () => ({ + getOrCreateModel: jest.fn(), +})) + +jest.mock('./language-configs', () => ({ + vue: {}, + js: {}, + ts: {}, + css: {}, +})) + +jest.mock('../utils', () => ({ + debounce: jest.fn((fn) => fn), +})) + +describe('monaco/env', () => { + let initMonaco: typeof import('./env').initMonaco + let WorkerHost: typeof import('./env').WorkerHost + let reloadLanguageTools: typeof import('./env').reloadLanguageTools + let loadMonacoEnv: typeof import('./env').loadMonacoEnv + let editor: typeof import('monaco-editor-core').editor + let languages: typeof import('monaco-editor-core').languages + let Uri: typeof import('monaco-editor-core').Uri + let __mockModels: Map + let getOrCreateModel: jest.Mock + + beforeEach(async () => { + // Reset modules to get fresh state for each test + jest.resetModules() + + // Import after mocks + const env = await import('./env') + const monacoCore = await import('monaco-editor-core') + const utils = await import('./utils') + + initMonaco = env.initMonaco + WorkerHost = env.WorkerHost + reloadLanguageTools = env.reloadLanguageTools + loadMonacoEnv = env.loadMonacoEnv + editor = monacoCore.editor + languages = monacoCore.languages + Uri = monacoCore.Uri + __mockModels = (monacoCore as any).__mockModels + getOrCreateModel = utils.getOrCreateModel + + // Clear mock state + __mockModels.clear() + jest.clearAllMocks() + }) + + describe('initMonaco', () => { + const createMockStore = (overrides?: Partial) => ({ + files: {}, + dependencyVersion: {}, + vueVersion: null, + typescriptVersion: null, + locale: 'en', + getTsConfig: jest.fn(), + setActive: jest.fn(), + setActiveFile: { filename: 'test.vue' }, + reloadLanguageTools: jest.fn(), + ...overrides, + }) + + it('should call getModels to check existing models', () => { + const store = createMockStore() + initMonaco(store) + expect(editor.getModels).toHaveBeenCalled() + }) + + it('should not reinitialize if already initted', () => { + const store = createMockStore() + initMonaco(store) + const callCount = editor.getModels.mock.calls.length + initMonaco(store) + expect(editor.getModels.mock.calls.length).toBe(callCount) + }) + + it('should create models for files in store that do not exist', () => { + const store = createMockStore({ + files: { + 'test.vue': { language: 'vue', code: 'test code' }, + }, + }) + initMonaco(store) + expect(getOrCreateModel).toHaveBeenCalled() + }) + + it('should dispose models not in store (excluding node_modules and inmemory)', () => { + const mockModel = { + uri: { toString: () => 'file:///other.vue' }, + dispose: jest.fn(), + } + __mockModels.set('file:///other.vue', mockModel) + + const store = createMockStore({ files: {} }) + initMonaco(store) + + expect(mockModel.dispose).toHaveBeenCalled() + }) + + it('should not dispose node_modules models', () => { + const mockModel = { + uri: { toString: () => 'file:///node_modules/test.vue' }, + dispose: jest.fn(), + } + __mockModels.set('file:///node_modules/test.vue', mockModel) + + const store = createMockStore({ files: {} }) + initMonaco(store) + + expect(mockModel.dispose).not.toHaveBeenCalled() + }) + + it('should not dispose inmemory models', () => { + const mockModel = { + uri: { toString: () => 'inmemory://test' }, + dispose: jest.fn(), + } + __mockModels.set('inmemory://test', mockModel) + + const store = createMockStore({ files: {} }) + initMonaco(store) + + expect(mockModel.dispose).not.toHaveBeenCalled() + }) + }) + + describe('WorkerHost', () => { + it('should call getOrCreateModel with parsed uri', () => { + const host = new WorkerHost() + const uri = 'file:///test.vue' + const text = 'test content' + + host.onFetchCdnFile(uri, text) + + expect(getOrCreateModel).toHaveBeenCalledWith( + expect.any(Object), + undefined, + text, + ) + }) + }) + + describe('reloadLanguageTools', () => { + const createMockStore = (overrides?: Partial) => ({ + files: { + 'test.vue': { language: 'vue', code: '' }, + }, + dependencyVersion: {}, + vueVersion: null, + typescriptVersion: null, + locale: 'en', + getTsConfig: jest.fn(() => ({})), + setActive: jest.fn(), + setActiveFile: { filename: 'test.vue' }, + reloadLanguageTools: jest.fn(), + ...overrides, + }) + + it('should create web worker with correct config', async () => { + const mockWorker = { + addEventListener: jest.fn(), + postMessage: jest.fn(), + } + editor.createWebWorker.mockReturnValue(mockWorker as any) + + const store = createMockStore({ + dependencyVersion: { 'some-dep': '1.0.0' }, + vueVersion: '3.3.0', + typescriptVersion: '5.0.0', + }) + + await reloadLanguageTools(store) + + expect(editor.createWebWorker).toHaveBeenCalledWith({ + moduleId: 'vs/language/vue/vueWorker', + label: 'vue', + host: expect.any(WorkerHost), + createData: { + tsconfig: {}, + dependencies: { + 'some-dep': '1.0.0', + vue: '3.3.0', + '@vue/compiler-core': '3.3.0', + '@vue/compiler-dom': '3.3.0', + '@vue/compiler-sfc': '3.3.0', + '@vue/compiler-ssr': '3.3.0', + '@vue/reactivity': '3.3.0', + '@vue/runtime-core': '3.3.0', + '@vue/runtime-dom': '3.3.0', + '@vue/shared': '3.3.0', + typescript: '5.0.0', + }, + }, + }) + }) + + it('should handle store without vueVersion', async () => { + const mockWorker = { + addEventListener: jest.fn(), + postMessage: jest.fn(), + } + editor.createWebWorker.mockReturnValue(mockWorker as any) + + const store = createMockStore({ + vueVersion: null, + typescriptVersion: '5.0.0', + }) + + await reloadLanguageTools(store) + + expect(editor.createWebWorker).toHaveBeenCalledWith( + expect.objectContaining({ + createData: expect.objectContaining({ + dependencies: { + typescript: '5.0.0', + }, + }), + }), + ) + }) + + it('should handle store without typescriptVersion', async () => { + const mockWorker = { + addEventListener: jest.fn(), + postMessage: jest.fn(), + } + editor.createWebWorker.mockReturnValue(mockWorker as any) + + const store = createMockStore({ + vueVersion: '3.3.0', + typescriptVersion: null, + }) + + await reloadLanguageTools(store) + + expect(editor.createWebWorker).toHaveBeenCalledWith( + expect.objectContaining({ + createData: expect.objectContaining({ + dependencies: expect.objectContaining({ + vue: '3.3.0', + }), + }), + }), + ) + }) + + it('should call volar functions with correct parameters', async () => { + const mockWorker = { + addEventListener: jest.fn(), + postMessage: jest.fn(), + } + editor.createWebWorker.mockReturnValue(mockWorker as any) + + const store = createMockStore() + + await reloadLanguageTools(store) + + expect((await import('@volar/monaco')).activateMarkers).toHaveBeenCalled() + expect( + (await import('@volar/monaco')).activateAutoInsertion, + ).toHaveBeenCalled() + expect( + (await import('@volar/monaco')).registerProviders, + ).toHaveBeenCalled() + }) + }) + + describe('loadMonacoEnv', () => { + const createMockStore = (overrides?: Partial) => ({ + files: {}, + dependencyVersion: {}, + vueVersion: null, + typescriptVersion: null, + locale: 'en', + getTsConfig: jest.fn(), + setActive: jest.fn(), + setActiveFile: { filename: 'test.vue' }, + reloadLanguageTools: undefined, + ...overrides, + }) + + it('should set up MonacoEnvironment with getWorker function', () => { + const store = createMockStore({ typescriptVersion: '5.0.0' }) + loadMonacoEnv(store) + + expect((globalThis as any).MonacoEnvironment).toBeDefined() + expect(typeof (globalThis as any).MonacoEnvironment.getWorker).toBe( + 'function', + ) + }) + + it('should register languages', () => { + const store = createMockStore() + loadMonacoEnv(store) + + expect(languages.register).toHaveBeenCalledTimes(4) + }) + + it('should set language configurations', () => { + const store = createMockStore() + loadMonacoEnv(store) + + expect(languages.setLanguageConfiguration).toHaveBeenCalledTimes(4) + }) + + it('should register onLanguage handler for vue', () => { + const store = createMockStore() + loadMonacoEnv(store) + + expect(languages.onLanguage).toHaveBeenCalledWith( + 'vue', + expect.any(Function), + ) + }) + + it('should register editor opener for go to definition', () => { + const store = createMockStore() + loadMonacoEnv(store) + + expect(editor.registerEditorOpener).toHaveBeenCalled() + }) + + it('should set up debounced reloadLanguageTools on store', () => { + const store = createMockStore() + loadMonacoEnv(store) + + expect(store.reloadLanguageTools).toBeDefined() + }) + }) +}) diff --git a/src/monaco/highlight.test.ts b/src/monaco/highlight.test.ts new file mode 100644 index 00000000..f05270f4 --- /dev/null +++ b/src/monaco/highlight.test.ts @@ -0,0 +1,51 @@ +// Mock shiki modules before importing the module under test +jest.mock('shiki/core', () => ({ + createHighlighterCoreSync: jest.fn(() => ({})), +})) + +jest.mock('shiki/engine-javascript.mjs', () => ({ + createJavaScriptRegexEngine: jest.fn(() => ({})), +})) + +jest.mock('shiki/langs/vue.mjs', () => ({})) +jest.mock('shiki/langs/tsx.mjs', () => ({})) +jest.mock('shiki/langs/jsx.mjs', () => ({})) +jest.mock('shiki/themes/dark-plus.mjs', () => ({ name: 'dark-plus' })) +jest.mock('shiki/themes/light-plus.mjs', () => ({ name: 'light-plus' })) + +jest.mock('@shikijs/monaco', () => ({ + shikiToMonaco: jest.fn(), +})) + +import { registerHighlighter } from './highlight' + +describe('registerHighlighter', () => { + beforeEach(() => { + // Reset the registered state before each test + jest.resetModules() + }) + + it('should register monaco languages and return theme names', () => { + const result = registerHighlighter() + + expect(result).toEqual({ + light: 'light-plus', + dark: 'dark-plus', + }) + }) + + it('should return the same theme names on subsequent calls', () => { + const firstCall = registerHighlighter() + const secondCall = registerHighlighter() + + expect(firstCall).toEqual(secondCall) + }) + + it('should not throw when called multiple times', () => { + expect(() => { + registerHighlighter() + registerHighlighter() + registerHighlighter() + }).not.toThrow() + }) +}) diff --git a/src/monaco/language-configs.test.ts b/src/monaco/language-configs.test.ts new file mode 100644 index 00000000..b2ac43df --- /dev/null +++ b/src/monaco/language-configs.test.ts @@ -0,0 +1,410 @@ +import { describe, it, expect, jest, beforeAll } from '@jest/globals' + +// Mock monaco-editor-core before importing language-configs +jest.mock('monaco-editor-core', () => ({ + languages: { + IndentAction: { + None: 0, + Indent: 1, + IndentOutdent: 2, + Outdent: 3, + }, + }, +})) + +import { css, vue, js, ts } from './language-configs' + +describe('language-configs', () => { + describe('css', () => { + it('should have correct comments configuration', () => { + expect(css.comments).toEqual({ + blockComment: ['/*', '*/'], + }) + }) + + it('should have correct brackets configuration', () => { + expect(css.brackets).toEqual([ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ]) + }) + + it('should have correct autoClosingPairs configuration', () => { + expect(css.autoClosingPairs).toEqual([ + { open: '{', close: '}', notIn: ['string', 'comment'] }, + { open: '[', close: ']', notIn: ['string', 'comment'] }, + { open: '(', close: ')', notIn: ['string', 'comment'] }, + { open: '"', close: '"', notIn: ['string', 'comment'] }, + { open: "'", close: "'", notIn: ['string', 'comment'] }, + ]) + }) + + it('should have correct surroundingPairs configuration', () => { + expect(css.surroundingPairs).toEqual([ + { open: "'", close: "'" }, + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + ]) + }) + + it('should have correct folding configuration', () => { + expect(css.folding).toEqual({ + markers: { + start: expect.any(RegExp), + end: expect.any(RegExp), + }, + }) + expect(css.folding!.markers.start.source).toContain('region') + expect(css.folding!.markers.end.source).toContain('endregion') + }) + + it('should have correct indentationRules configuration', () => { + expect(css.indentationRules).toEqual({ + increaseIndentPattern: expect.any(RegExp), + decreaseIndentPattern: expect.any(RegExp), + }) + }) + + it('should have correct wordPattern configuration', () => { + expect(css.wordPattern).toBeInstanceOf(RegExp) + }) + + it('should not have lineComment configuration', () => { + expect((css.comments as any).lineComment).toBeUndefined() + }) + }) + + describe('vue', () => { + it('should have correct comments configuration', () => { + expect(vue.comments).toEqual({ + blockComment: [''], + }) + }) + + it('should have correct brackets configuration', () => { + expect(vue.brackets).toEqual([ + [''], + ['<', '>'], + ['{', '}'], + ['(', ')'], + ]) + }) + + it('should have correct autoClosingPairs configuration', () => { + expect(vue.autoClosingPairs).toEqual([ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: "'", close: "'" }, + { open: '"', close: '"' }, + { open: '', notIn: ['comment', 'string'] }, + { open: '`', close: '`', notIn: ['string', 'comment'] }, + { open: '/**', close: ' */', notIn: ['string'] }, + ]) + }) + + it('should have correct autoCloseBefore configuration', () => { + expect(vue.autoCloseBefore).toBe(';:.,=}])><`\'" \n\t') + }) + + it('should have correct surroundingPairs configuration', () => { + expect(vue.surroundingPairs).toEqual([ + { open: "'", close: "'" }, + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '<', close: '>' }, + { open: '`', close: '`' }, + ]) + }) + + it('should have correct colorizedBracketPairs configuration', () => { + expect(vue.colorizedBracketPairs).toEqual([]) + }) + + it('should have correct folding configuration', () => { + expect(vue.folding).toEqual({ + markers: { + start: expect.any(RegExp), + end: expect.any(RegExp), + }, + }) + }) + + it('should have correct wordPattern configuration', () => { + expect(vue.wordPattern).toBeInstanceOf(RegExp) + }) + + it('should have correct onEnterRules configuration', () => { + expect(vue.onEnterRules).toHaveLength(2) + expect(vue.onEnterRules![0].action.indentAction).toBeDefined() + expect(vue.onEnterRules![1].action.indentAction).toBeDefined() + }) + + it('should have correct indentationRules configuration', () => { + expect(vue.indentationRules).toEqual({ + increaseIndentPattern: expect.any(RegExp), + decreaseIndentPattern: expect.any(RegExp), + }) + }) + + it('should not have lineComment configuration', () => { + expect((vue.comments as any).lineComment).toBeUndefined() + }) + }) + + describe('js', () => { + it('should have correct comments configuration', () => { + expect(js.comments).toEqual({ + lineComment: '//', + blockComment: ['/*', '*/'], + }) + }) + + it('should have correct brackets configuration', () => { + expect(js.brackets).toEqual([ + ['${', '}'], + ['{', '}'], + ['[', ']'], + ['(', ')'], + ]) + }) + + it('should have correct autoClosingPairs configuration', () => { + expect(js.autoClosingPairs).toEqual([ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: "'", close: "'", notIn: ['string', 'comment'] }, + { open: '"', close: '"', notIn: ['string'] }, + { open: '`', close: '`', notIn: ['string', 'comment'] }, + { open: '/**', close: ' */', notIn: ['string'] }, + ]) + }) + + it('should have correct surroundingPairs configuration', () => { + expect(js.surroundingPairs).toEqual([ + { open: "'", close: "'" }, + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '<', close: '>' }, + { open: '`', close: '`' }, + ]) + }) + + it('should have correct autoCloseBefore configuration', () => { + expect(js.autoCloseBefore).toBe(';:.,=}])>` \n\t') + }) + + it('should have correct folding configuration', () => { + expect(js.folding).toEqual({ + markers: { + start: expect.any(RegExp), + end: expect.any(RegExp), + }, + }) + }) + + it('should have correct wordPattern configuration', () => { + expect(js.wordPattern).toBeInstanceOf(RegExp) + }) + + it('should have correct indentationRules configuration', () => { + expect(js.indentationRules).toEqual({ + decreaseIndentPattern: expect.any(RegExp), + increaseIndentPattern: expect.any(RegExp), + unIndentedLinePattern: expect.any(RegExp), + }) + }) + + it('should have correct onEnterRules configuration', () => { + expect(js.onEnterRules).toHaveLength(7) + expect(js.onEnterRules![0].action.indentAction).toBeDefined() + expect(js.onEnterRules![0].action.appendText).toBe(' * ') + }) + + it('should not have colorizedBracketPairs configuration', () => { + expect((js as any).colorizedBracketPairs).toBeUndefined() + }) + }) + + describe('ts', () => { + it('should have correct comments configuration', () => { + expect(ts.comments).toEqual({ + lineComment: '//', + blockComment: ['/*', '*/'], + }) + }) + + it('should have correct brackets configuration', () => { + expect(ts.brackets).toEqual([ + ['${', '}'], + ['{', '}'], + ['[', ']'], + ['(', ')'], + ]) + }) + + it('should have correct autoClosingPairs configuration', () => { + expect(ts.autoClosingPairs).toEqual([ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: "'", close: "'", notIn: ['string', 'comment'] }, + { open: '"', close: '"', notIn: ['string'] }, + { open: '`', close: '`', notIn: ['string', 'comment'] }, + { open: '/**', close: ' */', notIn: ['string'] }, + ]) + }) + + it('should have correct surroundingPairs configuration', () => { + expect(ts.surroundingPairs).toEqual([ + { open: "'", close: "'" }, + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '<', close: '>' }, + { open: '`', close: '`' }, + ]) + }) + + it('should have correct colorizedBracketPairs configuration', () => { + expect(ts.colorizedBracketPairs).toEqual([ + ['(', ')'], + ['[', ']'], + ['{', '}'], + ['<', '>'], + ]) + }) + + it('should have correct autoCloseBefore configuration', () => { + expect(ts.autoCloseBefore).toBe(';:.,=}])>` \n\t') + }) + + it('should have correct folding configuration', () => { + expect(ts.folding).toEqual({ + markers: { + start: expect.any(RegExp), + end: expect.any(RegExp), + }, + }) + }) + + it('should have correct wordPattern configuration', () => { + expect(ts.wordPattern).toBeInstanceOf(RegExp) + }) + + it('should have correct indentationRules configuration', () => { + expect(ts.indentationRules).toEqual({ + decreaseIndentPattern: expect.any(RegExp), + increaseIndentPattern: expect.any(RegExp), + unIndentedLinePattern: expect.any(RegExp), + }) + }) + + it('should have correct onEnterRules configuration', () => { + expect(ts.onEnterRules).toHaveLength(7) + expect(ts.onEnterRules![0].action.indentAction).toBeDefined() + expect(ts.onEnterRules![0].action.appendText).toBe(' * ') + }) + + it('should have same indentationRules as js', () => { + expect(ts.indentationRules).toEqual(js.indentationRules) + }) + + it('should have same onEnterRules as js', () => { + expect(ts.onEnterRules).toEqual(js.onEnterRules) + }) + }) + + describe('cross-language comparisons', () => { + it('should have different comments for css vs vue', () => { + expect(css.comments).not.toEqual(vue.comments) + }) + + it('should have same comments for js and ts', () => { + expect(js.comments).toEqual(ts.comments) + }) + + it('should have same brackets for js and ts', () => { + expect(js.brackets).toEqual(ts.brackets) + }) + + it('should have same autoClosingPairs for js and ts', () => { + expect(js.autoClosingPairs).toEqual(ts.autoClosingPairs) + }) + + it('should have same surroundingPairs for js and ts', () => { + expect(js.surroundingPairs).toEqual(ts.surroundingPairs) + }) + + it('should have same folding for js and ts', () => { + expect(js.folding).toEqual(ts.folding) + }) + + it('should have same wordPattern for js and ts', () => { + expect(js.wordPattern.toString()).toBe(ts.wordPattern.toString()) + }) + + it('should have different brackets for css vs js', () => { + expect(css.brackets).not.toEqual(js.brackets) + }) + }) + + describe('configuration structure', () => { + it('css should have all required properties', () => { + expect(css).toHaveProperty('comments') + expect(css).toHaveProperty('brackets') + expect(css).toHaveProperty('autoClosingPairs') + expect(css).toHaveProperty('surroundingPairs') + expect(css).toHaveProperty('folding') + expect(css).toHaveProperty('indentationRules') + expect(css).toHaveProperty('wordPattern') + }) + + it('vue should have all required properties', () => { + expect(vue).toHaveProperty('comments') + expect(vue).toHaveProperty('brackets') + expect(vue).toHaveProperty('autoClosingPairs') + expect(vue).toHaveProperty('surroundingPairs') + expect(vue).toHaveProperty('colorizedBracketPairs') + expect(vue).toHaveProperty('autoCloseBefore') + expect(vue).toHaveProperty('folding') + expect(vue).toHaveProperty('wordPattern') + expect(vue).toHaveProperty('onEnterRules') + expect(vue).toHaveProperty('indentationRules') + }) + + it('js should have all required properties', () => { + expect(js).toHaveProperty('comments') + expect(js).toHaveProperty('brackets') + expect(js).toHaveProperty('autoClosingPairs') + expect(js).toHaveProperty('surroundingPairs') + expect(js).toHaveProperty('autoCloseBefore') + expect(js).toHaveProperty('folding') + expect(js).toHaveProperty('wordPattern') + expect(js).toHaveProperty('indentationRules') + expect(js).toHaveProperty('onEnterRules') + }) + + it('ts should have all required properties', () => { + expect(ts).toHaveProperty('comments') + expect(ts).toHaveProperty('brackets') + expect(ts).toHaveProperty('autoClosingPairs') + expect(ts).toHaveProperty('surroundingPairs') + expect(ts).toHaveProperty('colorizedBracketPairs') + expect(ts).toHaveProperty('autoCloseBefore') + expect(ts).toHaveProperty('folding') + expect(ts).toHaveProperty('wordPattern') + expect(ts).toHaveProperty('indentationRules') + expect(ts).toHaveProperty('onEnterRules') + }) + }) +}) diff --git a/src/monaco/utils.test.ts b/src/monaco/utils.test.ts new file mode 100644 index 00000000..b8267709 --- /dev/null +++ b/src/monaco/utils.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect, beforeEach, jest } from '@jest/globals' + +// Define mock types +interface MockUri { + toString: () => string + path: string + scheme: string +} + +interface MockModel { + uri: MockUri + language: string | undefined + getValue: () => string + setValue: (newValue: string) => void +} + +// Mock storage for models +const mockModels = new Map() + +// Mock Uri factory +const createMockUri = (path: string): MockUri => ({ + toString: () => path, + path, + scheme: 'file', +}) + +// Mock editor factory +const createMockEditor = () => { + const editor = { + getModel: jest.fn((uri: MockUri) => mockModels.get(uri.toString())), + createModel: jest.fn( + (value: string, lang: string | undefined, uri: MockUri) => { + let currentValue = value + const model: MockModel = { + uri, + language: lang, + getValue: () => currentValue, + setValue: (newValue: string) => { + currentValue = newValue + mockModels.set(uri.toString(), model) + }, + } + mockModels.set(uri.toString(), model) + return model + }, + ), + } + return editor +} + +// Test the getOrCreateModel logic directly +const testGetOrCreateModel = ( + editor: { + getModel: jest.Mock + createModel: jest.Mock + }, + uri: MockUri, + lang: string | undefined, + value: string, +) => { + const model = editor.getModel(uri) + if (model) { + model.setValue(value) + return model + } + return editor.createModel(value, lang, uri) +} + +describe('utils', () => { + let mockEditor: ReturnType + + beforeEach(() => { + mockModels.clear() + mockEditor = createMockEditor() + }) + + describe('getOrCreateModel', () => { + it('should create a new model when one does not exist', () => { + const uri = createMockUri('file:///test/file.ts') + const lang = 'typescript' + const value = 'const x = 1' + + const model = testGetOrCreateModel(mockEditor, uri, lang, value) + + expect(mockEditor.getModel).toHaveBeenCalledWith(uri) + expect(mockEditor.createModel).toHaveBeenCalledWith(value, lang, uri) + expect(model).toBeDefined() + expect(model.language).toBe(lang) + }) + + it('should get existing model and update its value when it exists', () => { + const uri = createMockUri('file:///test/file.ts') + const lang = 'typescript' + const initialValue = 'const x = 1' + const newValue = 'const y = 2' + + // First call creates the model + testGetOrCreateModel(mockEditor, uri, lang, initialValue) + + // Reset call history to test second call behavior + mockEditor.getModel.mockClear() + mockEditor.createModel.mockClear() + + // Second call should get the existing model + const model = testGetOrCreateModel(mockEditor, uri, lang, newValue) + + expect(mockEditor.getModel).toHaveBeenCalledTimes(1) + expect(mockEditor.createModel).not.toHaveBeenCalled() + expect(model).toBeDefined() + expect(model.getValue()).toBe(newValue) + }) + + it('should handle undefined language when creating model', () => { + const uri = createMockUri('file:///test/file') + const lang = undefined + const value = 'some content' + + const model = testGetOrCreateModel(mockEditor, uri, lang, value) + + expect(mockEditor.createModel).toHaveBeenCalledWith(value, lang, uri) + expect(model.language).toBeUndefined() + }) + + it('should update value of existing model regardless of language parameter', () => { + const uri = createMockUri('file:///test/file.ts') + const initialValue = 'const x = 1' + const newValue = 'const y = 2' + const newLang = 'javascript' + + // Create initial model + testGetOrCreateModel(mockEditor, uri, 'typescript', initialValue) + + // Reset call history + mockEditor.getModel.mockClear() + mockEditor.createModel.mockClear() + + // Get and update with different language parameter + const model = testGetOrCreateModel(mockEditor, uri, newLang, newValue) + + expect(mockEditor.getModel).toHaveBeenCalledTimes(1) + expect(mockEditor.createModel).not.toHaveBeenCalled() + expect(model.getValue()).toBe(newValue) + }) + + it('should handle empty string value', () => { + const uri = createMockUri('file:///test/file.ts') + const lang = 'typescript' + const value = '' + + const model = testGetOrCreateModel(mockEditor, uri, lang, value) + + expect(mockEditor.createModel).toHaveBeenCalledWith(value, lang, uri) + expect(model.getValue()).toBe('') + }) + + it('should handle multiple different uris', () => { + const uri1 = createMockUri('file:///test/file1.ts') + const uri2 = createMockUri('file:///test/file2.ts') + const lang = 'typescript' + + const model1 = testGetOrCreateModel(mockEditor, uri1, lang, 'content1') + const model2 = testGetOrCreateModel(mockEditor, uri2, lang, 'content2') + + expect(mockEditor.createModel).toHaveBeenCalledTimes(2) + expect(model1).not.toBe(model2) + expect(model1.getValue()).toBe('content1') + expect(model2.getValue()).toBe('content2') + }) + }) +}) diff --git a/src/monaco/vue.worker.test.ts b/src/monaco/vue.worker.test.ts new file mode 100644 index 00000000..9a144314 --- /dev/null +++ b/src/monaco/vue.worker.test.ts @@ -0,0 +1,632 @@ +import type { CreateData } from './vue.worker' + +// Mock the env module to avoid ESM module issues with @volar/monaco +jest.mock('./env', () => { + return { + WorkerHost: class WorkerHost { + onFetchCdnFile(_uri: string, _text: string) { + // Mock implementation + } + }, + } +}) + +describe('vue.worker', () => { + describe('CreateData interface', () => { + it('should accept valid CreateData configuration', () => { + const createData: CreateData = { + tsconfig: { + compilerOptions: { + target: 99, // ESNext + module: 99, // ESNext + strict: true, + esModuleInterop: true, + }, + vueCompilerOptions: { + target: 3.3, + lib: 'vue', + }, + }, + dependencies: { + vue: '3.5.18', + typescript: '5.9.2', + }, + } + + expect(createData.tsconfig.compilerOptions?.strict).toBe(true) + expect(createData.dependencies.vue).toBe('3.5.18') + }) + + it('should accept empty CreateData configuration', () => { + const createData: CreateData = { + tsconfig: {}, + dependencies: {}, + } + + expect(createData.tsconfig.compilerOptions).toBeUndefined() + expect(createData.tsconfig.vueCompilerOptions).toBeUndefined() + expect(Object.keys(createData.dependencies)).toHaveLength(0) + }) + + it('should accept partial CreateData configuration', () => { + const createData: CreateData = { + tsconfig: { + compilerOptions: { + strict: true, + }, + }, + dependencies: { + vue: '3.5.18', + }, + } + + expect(createData.tsconfig.compilerOptions?.strict).toBe(true) + expect(createData.tsconfig.vueCompilerOptions).toBeUndefined() + expect(createData.dependencies.vue).toBe('3.5.18') + }) + }) + + describe('URI conversion functions', () => { + it('asFileName should extract path from URI', async () => { + const { URI } = await import('vscode-uri') + const uri = URI.file('/test/file.vue') + expect(uri.path).toBe('/test/file.vue') + }) + + it('asUri should create URI from file name', async () => { + const { URI } = await import('vscode-uri') + const fileName = '/test/file.vue' + const uri = URI.file(fileName) + expect(uri.toString()).toContain('file:///') + expect(uri.path).toBe('/test/file.vue') + }) + }) + + describe('TypeScript version handling', () => { + it('should construct correct CDN URL for TypeScript', () => { + const tsVersion = '5.9.2' + const expectedUrl = `https://cdn.jsdelivr.net/npm/typescript@${tsVersion}/lib/typescript.js` + expect(expectedUrl).toBe( + 'https://cdn.jsdelivr.net/npm/typescript@5.9.2/lib/typescript.js', + ) + }) + + it('should handle different TypeScript versions', () => { + const versions = ['5.0.0', '5.5.0', '5.9.2', '6.0.0'] + versions.forEach((version) => { + const url = `https://cdn.jsdelivr.net/npm/typescript@${version}/lib/typescript.js` + expect(url).toContain(`typescript@${version}`) + }) + }) + }) + + describe('Vue compiler options', () => { + it('should handle default Vue compiler options', async () => { + const { getDefaultCompilerOptions } = await import('@vue/language-core') + const defaultOptions = getDefaultCompilerOptions() + + expect(defaultOptions).toBeDefined() + expect(typeof defaultOptions).toBe('object') + }) + + it('should handle global types file name', async () => { + const { getGlobalTypesFileName, getDefaultCompilerOptions } = + await import('@vue/language-core') + const options = getDefaultCompilerOptions() + const fileName = getGlobalTypesFileName(options) + + expect(fileName).toBeDefined() + expect(typeof fileName).toBe('string') + expect(fileName).toContain('.d.ts') + }) + }) + + describe('language service environment', () => { + it('should create environment with workspace folders', async () => { + const { URI } = await import('vscode-uri') + const workspaceFolders = [URI.file('/')] + + expect(workspaceFolders).toHaveLength(1) + expect(workspaceFolders[0].path).toBe('/') + }) + + it('should handle npm file system paths', async () => { + const { createNpmFileSystem } = await import('@volar/jsdelivr') + + const fs = createNpmFileSystem( + () => '', + () => '', + () => {}, + ) + + expect(fs).toBeDefined() + expect(typeof fs.stat).toBe('function') + expect(typeof fs.readFile).toBe('function') + }) + }) + + describe('worker context', () => { + it('should handle worker host methods', async () => { + const { WorkerHost } = await import('./env') + const host = new WorkerHost() + + expect(host).toBeDefined() + expect(typeof host.onFetchCdnFile).toBe('function') + }) + + it('should call onFetchCdnFile with uri and text', async () => { + const { WorkerHost } = await import('./env') + const host = new WorkerHost() + + // The method should exist and be callable + expect(host.onFetchCdnFile).toBeDefined() + + // Call the method (it won't do anything without the mocked utils) + host.onFetchCdnFile('file:///node_modules/test.js', 'console.log("test")') + }) + }) + + describe('TypeScript compiler options conversion', () => { + it('should handle empty compiler options', async () => { + const ts = await import('typescript') + + const { options, errors } = ts.convertCompilerOptionsFromJson({}, '') + + expect(errors).toHaveLength(0) + expect(options).toBeDefined() + }) + + it('should convert valid compiler options', async () => { + const ts = await import('typescript') + + const { options, errors } = ts.convertCompilerOptionsFromJson( + { + strict: true, + target: 'ESNext', + module: 'ESNext', + }, + '', + ) + + expect(errors).toHaveLength(0) + expect(options.strict).toBe(true) + }) + }) + + describe('global types setup', () => { + it('should generate global types', async () => { + const { generateGlobalTypes, getDefaultCompilerOptions } = await import( + '@vue/language-core' + ) + + const options = getDefaultCompilerOptions() + const globalTypes = generateGlobalTypes(options) + + expect(globalTypes).toBeDefined() + expect(typeof globalTypes).toBe('string') + expect(globalTypes.length).toBeGreaterThan(0) + }) + + it('should create stat result for global types', () => { + const ctime = Date.now() + const globalTypesLength = 1000 + + const statResult = { + type: 1, + ctime: ctime, + mtime: ctime, + size: globalTypesLength, + } + + expect(statResult.type).toBe(1) + expect(statResult.size).toBe(globalTypesLength) + expect(statResult.ctime).toBe(ctime) + expect(statResult.mtime).toBe(ctime) + }) + }) + + describe('language plugins', () => { + it('should create Vue language plugin', async () => { + const ts = await import('typescript') + const { createVueLanguagePlugin, getDefaultCompilerOptions } = + await import('@vue/language-core') + + const compilerOptions = getDefaultCompilerOptions() + const vueCompilerOptions = getDefaultCompilerOptions() + + const plugin = createVueLanguagePlugin( + ts, + compilerOptions, + vueCompilerOptions, + (uri) => uri.toString(), + ) + + expect(plugin).toBeDefined() + }) + }) + + describe('service plugins', () => { + it('should create Vue language service plugins', async () => { + const ts = await import('typescript') + const { createVueLanguageServicePlugins } = await import( + '@vue/language-service' + ) + + const plugins = createVueLanguageServicePlugins(ts, { + getComponentDirectives: () => [], + getComponentEvents: () => [], + getComponentNames: () => [], + getComponentProps: () => [], + getComponentSlots: () => [], + getElementAttrs: () => [], + getElementNames: () => [], + isRefAtPosition: () => ({ isRef: false }), + getQuickInfoAtPosition: () => Promise.resolve(''), + collectExtractProps: () => Promise.resolve([]), + getImportPathForFile: () => '', + getDocumentHighlights: () => Promise.resolve([]), + getEncodedSemanticClassifications: () => + Promise.resolve({ spans: [], endOfLineState: 0 }), + getReactiveReferences: () => Promise.resolve([]), + }) + + expect(plugins).toBeDefined() + expect(Array.isArray(plugins)).toBe(true) + }) + + it('should filter out ignored Vue service plugins', () => { + const ignoreVueServicePlugins = new Set([ + 'vue-extract-file', + 'vue-document-drop', + 'vue-document-highlights', + 'typescript-semantic-tokens', + ]) + + const allPlugins = [ + { name: 'vue-extract-file' }, + { name: 'vue-document-drop' }, + { name: 'vue-document-highlights' }, + { name: 'typescript-semantic-tokens' }, + { name: 'vue-basic-languages' }, + { name: 'css-basic-languages' }, + ] + + const filtered = allPlugins.filter( + (plugin) => !ignoreVueServicePlugins.has(plugin.name!), + ) + + expect(filtered).toHaveLength(2) + expect(filtered.map((p) => p.name)).toEqual([ + 'vue-basic-languages', + 'css-basic-languages', + ]) + }) + }) + + describe('TypeScript directive comment plugin', () => { + it('should create TypeScript directive comment plugin', async () => { + const { create: createDirectiveCommentPlugin } = await import( + 'volar-service-typescript/lib/plugins/directiveComment' + ) + + const plugin = createDirectiveCommentPlugin() + + expect(plugin).toBeDefined() + expect(typeof plugin.create).toBe('function') + }) + }) + + describe('TypeScript semantic plugin', () => { + it('should create TypeScript semantic plugin', async () => { + const { create: createSemanticPlugin } = await import( + 'volar-service-typescript/lib/plugins/semantic' + ) + const ts = await import('typescript') + + const plugin = createSemanticPlugin(ts) + + expect(plugin).toBeDefined() + expect(typeof plugin.create).toBe('function') + }) + }) + + describe('Vue TypeScript plugin requests', () => { + it('should import getComponentDirectives', async () => { + const { getComponentDirectives } = await import( + '@vue/typescript-plugin/lib/requests/getComponentDirectives' + ) + expect(typeof getComponentDirectives).toBe('function') + }) + + it('should import getComponentEvents', async () => { + const { getComponentEvents } = await import( + '@vue/typescript-plugin/lib/requests/getComponentEvents' + ) + expect(typeof getComponentEvents).toBe('function') + }) + + it('should import getComponentNames', async () => { + const { getComponentNames } = await import( + '@vue/typescript-plugin/lib/requests/getComponentNames' + ) + expect(typeof getComponentNames).toBe('function') + }) + + it('should import getComponentProps', async () => { + const { getComponentProps } = await import( + '@vue/typescript-plugin/lib/requests/getComponentProps' + ) + expect(typeof getComponentProps).toBe('function') + }) + + it('should import getComponentSlots', async () => { + const { getComponentSlots } = await import( + '@vue/typescript-plugin/lib/requests/getComponentSlots' + ) + expect(typeof getComponentSlots).toBe('function') + }) + + it('should import getElementAttrs', async () => { + const { getElementAttrs } = await import( + '@vue/typescript-plugin/lib/requests/getElementAttrs' + ) + expect(typeof getElementAttrs).toBe('function') + }) + + it('should import getElementNames', async () => { + const { getElementNames } = await import( + '@vue/typescript-plugin/lib/requests/getElementNames' + ) + expect(typeof getElementNames).toBe('function') + }) + + it('should import isRefAtPosition', async () => { + const { isRefAtPosition } = await import( + '@vue/typescript-plugin/lib/requests/isRefAtPosition' + ) + expect(typeof isRefAtPosition).toBe('function') + }) + }) + + describe('createVueLanguageServiceProxy', () => { + it('should import createVueLanguageServiceProxy', async () => { + const { createVueLanguageServiceProxy } = await import( + '@vue/typescript-plugin/lib/common' + ) + expect(typeof createVueLanguageServiceProxy).toBe('function') + }) + }) + + describe('file system operations', () => { + it('should handle file URI scheme check', async () => { + const { URI } = await import('vscode-uri') + + const fileUri = URI.file('/test/file.vue') + const httpUri = URI.parse('https://example.com/file.vue') + + expect(fileUri.scheme).toBe('file') + expect(httpUri.scheme).toBe('https') + }) + + it('should handle node_modules path extraction', () => { + const testCases = [ + { input: '/node_modules', expected: '' }, + { input: '/node_modules/vue', expected: 'vue' }, + { + input: '/node_modules/@vue/runtime-core', + expected: '@vue/runtime-core', + }, + { + input: '/node_modules/typescript/lib/typescript.js', + expected: 'typescript/lib/typescript.js', + }, + ] + + testCases.forEach(({ input, expected }) => { + const path = input.startsWith('/node_modules/') + ? input.slice('/node_modules/'.length) + : input === '/node_modules' + ? '' + : input + expect(path).toBe(expected) + }) + }) + }) + + describe('virtual code handling', () => { + it('should import VueVirtualCode', async () => { + const { VueVirtualCode } = await import('@vue/language-core') + expect(VueVirtualCode).toBeDefined() + }) + + it('should check instance of VueVirtualCode', async () => { + const { VueVirtualCode } = await import('@vue/language-core') + + // VueVirtualCode is a class, we can check instanceof + expect(typeof VueVirtualCode).toBe('function') + }) + }) + + describe('error handling', () => { + it('should handle missing source script error message', () => { + const fileName = '/test/file.vue' + const errorMessage = `No source script found for file: ${fileName}` + + expect(errorMessage).toContain('No source script found') + expect(errorMessage).toContain(fileName) + }) + + it('should handle missing virtual code error message', () => { + const fileName = '/test/file.vue' + const errorMessage = `No virtual code found for file: ${fileName}` + + expect(errorMessage).toContain('No virtual code found') + expect(errorMessage).toContain(fileName) + }) + + it('should handle not implemented errors', () => { + const notImplementedFunctions = [ + 'collectExtractProps', + 'getImportPathForFile', + 'getDocumentHighlights', + 'getEncodedSemanticClassifications', + 'getReactiveReferences', + ] + + notImplementedFunctions.forEach((fn) => { + const error = new Error('Not implemented') + expect(error.message).toBe('Not implemented') + }) + }) + }) + + describe('hover text processing', () => { + it('should process hover content from string', () => { + const hover = { + contents: 'interface Test {\n value: string\n}', + } + + let text = '' + if (typeof hover?.contents === 'string') { + text = hover.contents + } + + expect(text).toBe('interface Test {\n value: string\n}') + }) + + it('should process hover content from array', () => { + const hover = { + contents: [ + { language: 'typescript', value: 'interface Test' }, + 'Additional info', + ], + } + + let text = '' + if (Array.isArray(hover?.contents)) { + text = hover.contents + .map((c) => (typeof c === 'string' ? c : c.value)) + .join('\n') + } + + expect(text).toBe('interface Test\nAdditional info') + }) + + it('should process hover content from markdown', () => { + const hover = { + contents: { + kind: 'markdown', + value: '```typescript\ninterface Test\n```', + }, + } + + let text = '' + if ( + hover && + typeof hover.contents !== 'string' && + !Array.isArray(hover.contents) + ) { + text = hover.contents.value + } + + expect(text).toBe('```typescript\ninterface Test\n```') + }) + + it('should clean code fences from hover text', () => { + let text = '```typescript\ninterface Test {\n value: string\n}\n```' + + text = text.replace(/```typescript/g, '') + text = text.replace(/```/g, '') + text = text.replace(/---/g, '') + text = text.trim() + + expect(text).toBe('interface Test {\n value: string\n}') + }) + + it('should collapse multiple newlines to single newline', () => { + let text = 'line1\n\nline2\n\n\nline3' + + while (true) { + const newText = text.replace(/\n\n/g, '\n') + if (newText === text) { + break + } + text = newText + } + + expect(text).toBe('line1\nline2\nline3') + }) + + it('should replace newlines with pipe separator', () => { + let text = 'interface Test | value: string' + + // Already in pipe format + const result = text.replace(/\n/g, ' | ') + + expect(result).toBe('interface Test | value: string') + }) + }) + + describe('worker initialization', () => { + it('should have correct CreateData type structure', () => { + const createData: CreateData = { + tsconfig: { + compilerOptions: { + target: 99, + module: 99, + jsx: 1, + strict: true, + moduleResolution: 99, + }, + vueCompilerOptions: { + target: 3.3, + lib: 'vue', + plugins: [], + }, + }, + dependencies: { + vue: '3.5.18', + '@vue/runtime-dom': '3.5.18', + typescript: '5.9.2', + }, + } + + expect(createData.tsconfig.compilerOptions).toBeDefined() + expect(createData.tsconfig.vueCompilerOptions).toBeDefined() + expect(createData.dependencies).toBeDefined() + }) + }) + + describe('worker message handling', () => { + it('should handle init event with tsLocale', () => { + const message: { + data: { event: string; tsVersion: string; tsLocale?: string } + } = { + data: { + event: 'init', + tsVersion: '5.9.2', + tsLocale: 'zh-CN', + }, + } + + expect(message.data.event).toBe('init') + expect(message.data.tsVersion).toBe('5.9.2') + expect(message.data.tsLocale).toBe('zh-CN') + }) + + it('should handle init event without tsLocale', () => { + const message: { + data: { event: string; tsVersion: string; tsLocale?: string } + } = { + data: { + event: 'init', + tsVersion: '5.9.2', + }, + } + + expect(message.data.event).toBe('init') + expect(message.data.tsVersion).toBe('5.9.2') + expect(message.data.tsLocale).toBeUndefined() + }) + }) +}) diff --git a/src/output/PreviewProxy.test.ts b/src/output/PreviewProxy.test.ts new file mode 100644 index 00000000..eb3fa7e1 --- /dev/null +++ b/src/output/PreviewProxy.test.ts @@ -0,0 +1,416 @@ +import { PreviewProxy } from './PreviewProxy' + +// Mock window and related APIs for node environment +const mockAddEventListener = jest.fn() +const mockRemoveEventListener = jest.fn() +const mockPostMessage = jest.fn() + +// Create a mock for the iframe's contentWindow +const mockIframeContentWindow = { + postMessage: mockPostMessage, +} as unknown as Window + +// Create a mock window object +const mockWindow = { + addEventListener: mockAddEventListener, + removeEventListener: mockRemoveEventListener, + postMessage: mockPostMessage, +} as unknown as Window & typeof globalThis + +// Mock the global window +Object.defineProperty(global, 'window', { + value: mockWindow, + writable: true, + configurable: true, +}) + +// Create a mock iframe element +const createMockIframe = () => + ({ + contentWindow: mockIframeContentWindow, + }) as unknown as HTMLIFrameElement + +describe('PreviewProxy', () => { + let iframe: HTMLIFrameElement + let handlers: Record + let proxy: PreviewProxy + + beforeEach(() => { + // Clear all mocks before each test + jest.clearAllMocks() + + // Create a fresh mock iframe for each test + iframe = createMockIframe() + + handlers = { + on_fetch_progress: jest.fn(), + on_error: jest.fn(), + on_unhandled_rejection: jest.fn(), + on_console: jest.fn(), + on_console_group: jest.fn(), + on_console_group_collapsed: jest.fn(), + on_console_group_end: jest.fn(), + } + + proxy = new PreviewProxy(iframe, handlers) + }) + + afterEach(() => { + proxy.destroy() + }) + + describe('constructor', () => { + it('should initialize with iframe and handlers', () => { + expect(proxy.iframe).toBe(iframe) + expect(proxy.handlers).toBe(handlers) + expect(proxy.pending_cmds).toBeInstanceOf(Map) + }) + + it('should add message event listener', () => { + expect(mockAddEventListener).toHaveBeenCalledWith( + 'message', + expect.any(Function), + false, + ) + }) + }) + + describe('destroy', () => { + it('should remove message event listener', () => { + proxy.destroy() + expect(mockRemoveEventListener).toHaveBeenCalledWith( + 'message', + expect.any(Function), + ) + }) + }) + + describe('iframe_command', () => { + it('should post message to iframe content window', () => { + proxy.iframe_command('test_action', { foo: 'bar' }) + + expect(mockPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'test_action', + cmd_id: expect.any(Number), + args: { foo: 'bar' }, + }), + '*', + ) + }) + + it('should return a promise that resolves on cmd_ok', async () => { + const promise = proxy.iframe_command('test_action', {}) + + // Get the cmd_id from the posted message + const postedMessage = mockPostMessage.mock.calls[0][0] + const cmd_id = postedMessage.cmd_id + + // Simulate response from iframe + const mockEvent = { + source: mockIframeContentWindow, + data: { action: 'cmd_ok', cmd_id, args: { result: 'success' } }, + } + + // Get the event handler that was registered + const registeredHandler = mockAddEventListener.mock + .calls[0][1] as Function + registeredHandler(mockEvent) + + const result = await promise + expect(result).toEqual({ result: 'success' }) + }) + + it('should return a promise that rejects on cmd_error', async () => { + const promise = proxy.iframe_command('test_action', {}) + + // Get the cmd_id from the posted message + const postedMessage = mockPostMessage.mock.calls[0][0] + const cmd_id = postedMessage.cmd_id + + // Simulate error response from iframe + const errorMessage = 'Test error' + const errorStack = 'Error stack trace' + const mockEvent = { + source: mockIframeContentWindow, + data: { + action: 'cmd_error', + cmd_id, + message: errorMessage, + stack: errorStack, + }, + } + + const registeredHandler = mockAddEventListener.mock + .calls[0][1] as Function + registeredHandler(mockEvent) + + await expect(promise).rejects.toThrow(errorMessage) + }) + }) + + describe('handle_command_message', () => { + it('should resolve pending command on cmd_ok', async () => { + const promise = proxy.iframe_command('test_action', {}) + + // Access the pending_cmds map to get the cmd_id + const cmd_id = Array.from(proxy['pending_cmds'].keys())[0] + + proxy.handle_command_message({ + action: 'cmd_ok', + cmd_id, + args: { data: 'test' }, + }) + + await expect(promise).resolves.toEqual({ data: 'test' }) + }) + + it('should reject pending command on cmd_error', async () => { + const promise = proxy.iframe_command('test_action', {}) + + const cmd_id = Array.from(proxy['pending_cmds'].keys())[0] + + proxy.handle_command_message({ + action: 'cmd_error', + cmd_id, + message: 'Error occurred', + stack: 'Error stack trace', + }) + + await expect(promise).rejects.toThrow('Error occurred') + }) + + it('should remove command from pending_cmds after handling', () => { + proxy.iframe_command('test_action', {}) + const cmd_id = Array.from(proxy['pending_cmds'].keys())[0] + + expect(proxy['pending_cmds'].has(cmd_id)).toBe(true) + + proxy.handle_command_message({ + action: 'cmd_ok', + cmd_id, + args: {}, + }) + + expect(proxy['pending_cmds'].has(cmd_id)).toBe(false) + }) + + it('should log error for unknown command id with non-command action', () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation() + + proxy.handle_command_message({ + action: 'unknown_action', + cmd_id: 999, + }) + + expect(consoleSpy).toHaveBeenCalledWith( + 'command not found', + 999, + expect.any(Object), + expect.any(Array), + ) + + consoleSpy.mockRestore() + }) + + it('should not log error for cmd_error with unknown id', () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation() + + proxy.handle_command_message({ + action: 'cmd_error', + cmd_id: 999, + message: 'error', + }) + + expect(consoleSpy).not.toHaveBeenCalled() + + consoleSpy.mockRestore() + }) + + it('should not log error for cmd_ok with unknown id', () => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation() + + proxy.handle_command_message({ + action: 'cmd_ok', + cmd_id: 999, + args: {}, + }) + + expect(consoleSpy).not.toHaveBeenCalled() + + consoleSpy.mockRestore() + }) + }) + + describe('handle_repl_message', () => { + it('should ignore messages from other sources', () => { + const mockEvent = { + source: {} as Window, + data: { action: 'cmd_ok', cmd_id: 1, args: {} }, + } + + proxy.handle_repl_message(mockEvent) + + // Should not process the message + expect(handlers.on_fetch_progress).not.toHaveBeenCalled() + }) + + it('should handle cmd_ok action', async () => { + const promise = proxy.iframe_command('test_action', {}) + const cmd_id = mockPostMessage.mock.calls[0][0].cmd_id + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'cmd_ok', cmd_id, args: { result: 'ok' } }, + }) + + await expect(promise).resolves.toEqual({ result: 'ok' }) + }) + + it('should handle cmd_error action', async () => { + const promise = proxy.iframe_command('test_action', {}) + const cmd_id = mockPostMessage.mock.calls[0][0].cmd_id + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { + action: 'cmd_error', + cmd_id, + message: 'Command failed', + stack: 'stack trace', + }, + }) + + await expect(promise).rejects.toThrow('Command failed') + }) + + it('should call on_fetch_progress handler', () => { + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'fetch_progress', args: { remaining: 5 } }, + }) + + expect(handlers.on_fetch_progress).toHaveBeenCalledWith(5) + }) + + it('should call on_error handler', () => { + const errorData = { message: 'Runtime error', line: 10 } + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'error', ...errorData }, + }) + + expect(handlers.on_error).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'error', + message: 'Runtime error', + line: 10, + }), + ) + }) + + it('should call on_unhandled_rejection handler', () => { + const rejectionData = { reason: 'Promise rejected' } + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'unhandledrejection', ...rejectionData }, + }) + + expect(handlers.on_unhandled_rejection).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'unhandledrejection', + reason: 'Promise rejected', + }), + ) + }) + + it('should call on_console handler', () => { + const consoleData = { level: 'log', args: ['hello'] } + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'console', ...consoleData }, + }) + + expect(handlers.on_console).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'console', + level: 'log', + args: ['hello'], + }), + ) + }) + + it('should call on_console_group handler', () => { + const groupData = { label: 'My Group' } + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'console_group', ...groupData }, + }) + + expect(handlers.on_console_group).toHaveBeenCalledWith( + expect.objectContaining({ action: 'console_group', label: 'My Group' }), + ) + }) + + it('should call on_console_group_collapsed handler', () => { + const groupData = { label: 'Collapsed Group' } + + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'console_group_collapsed', ...groupData }, + }) + + expect(handlers.on_console_group_collapsed).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'console_group_collapsed', + label: 'Collapsed Group', + }), + ) + }) + + it('should call on_console_group_end handler', () => { + proxy.handle_repl_message({ + source: mockIframeContentWindow, + data: { action: 'console_group_end' }, + }) + + expect(handlers.on_console_group_end).toHaveBeenCalledWith( + expect.objectContaining({ action: 'console_group_end' }), + ) + }) + }) + + describe('eval', () => { + it('should call iframe_command with eval action and script', () => { + const iframeCommandSpy = jest.spyOn(proxy, 'iframe_command') + const script = 'console.log("test")' + + proxy.eval(script) + + expect(iframeCommandSpy).toHaveBeenCalledWith('eval', { script }) + }) + + it('should handle array of scripts', () => { + const iframeCommandSpy = jest.spyOn(proxy, 'iframe_command') + const scripts = ['const a = 1', 'const b = 2'] + + proxy.eval(scripts) + + expect(iframeCommandSpy).toHaveBeenCalledWith('eval', { script: scripts }) + }) + }) + + describe('handle_links', () => { + it('should call iframe_command with catch_clicks action', () => { + const iframeCommandSpy = jest.spyOn(proxy, 'iframe_command') + + proxy.handle_links() + + expect(iframeCommandSpy).toHaveBeenCalledWith('catch_clicks', {}) + }) + }) +}) diff --git a/src/output/moduleCompiler.test.ts b/src/output/moduleCompiler.test.ts new file mode 100644 index 00000000..141d007b --- /dev/null +++ b/src/output/moduleCompiler.test.ts @@ -0,0 +1,574 @@ +import { describe, it, expect, beforeEach } from '@jest/globals' +import { compileModulesForPreview } from './moduleCompiler' +import { useStore, File } from '../store' + +describe('compileModulesForPreview', () => { + let store: ReturnType + + beforeEach(async () => { + store = useStore() + await store.setFiles({ + 'src/App.vue': '', + }) + }) + + describe('basic functionality', () => { + it('should compile modules for preview', async () => { + await store.setFiles({ + 'src/App.vue': '', + }) + const result = compileModulesForPreview(store) + expect(Array.isArray(result)).toBe(true) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle a simple JS module', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(Array.isArray(result)).toBe(true) + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('SSR mode', () => { + it('should compile for SSR when isSSR is true', async () => { + await store.setFiles({ + 'src/App.vue': '', + }) + const result = compileModulesForPreview(store, true) + expect(Array.isArray(result)).toBe(true) + }) + + it('should not include CSS in SSR mode', async () => { + await store.setFiles({ + 'src/App.vue': + '\n', + }) + const result = compileModulesForPreview(store, true) + const hasCssPush = result.some((code) => + code.includes('window.__css__.push'), + ) + expect(hasCssPush).toBe(false) + }) + + it('should include CSS in non-SSR mode', async () => { + await store.setFiles({ + 'src/App.vue': + '\n', + }) + const result = compileModulesForPreview(store, false) + // CSS is included either via compiled css or as unimported css file + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('CSS file handling', () => { + it('should add unimported CSS files in non-SSR mode', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + 'src/style.css': '.test { color: blue; }', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store, false) + const hasCssPush = result.some((code) => + code.includes('window.__css__.push'), + ) + expect(hasCssPush).toBe(true) + }) + + it('should not add unimported CSS files in SSR mode', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + 'src/style.css': '.test { color: blue; }', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store, true) + const hasCssPush = result.some((code) => + code.includes('window.__css__.push'), + ) + expect(hasCssPush).toBe(false) + }) + }) + + describe('HTML file handling', () => { + it('should process HTML files in non-SSR mode', async () => { + await store.setFiles( + { + 'src/index.html': + '
', + }, + 'src/index.html', + ) + const result = compileModulesForPreview(store, false) + // HTML files should produce output with innerHTML assignment + expect(result.some((code) => code.includes('innerHTML'))).toBe(true) + }) + + it('should handle HTML with script tags', async () => { + await store.setFiles( + { + 'src/index.html': ` + + + + +`, + }, + 'src/index.html', + ) + const result = compileModulesForPreview(store, false) + expect(result.some((code) => code.includes('innerHTML'))).toBe(true) + expect(result.some((code) => code.includes("console.log('test')"))).toBe( + true, + ) + }) + + it('should handle HTML with script type="module" tags', async () => { + await store.setFiles( + { + 'src/index.html': ` + + + + +`, + 'src/foo.js': 'export const foo = "bar"', + }, + 'src/index.html', + ) + const result = compileModulesForPreview(store, false) + expect(result.some((code) => code.includes('innerHTML'))).toBe(true) + }) + }) + + describe('module compilation', () => { + it('should handle ES module imports', async () => { + await store.setFiles({ + 'src/App.vue': ` +`, + 'src/foo.js': 'export const foo = "bar"', + }) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle default exports', async () => { + await store.setFiles( + { + 'src/main.js': `import App from './App.vue' +export default App`, + 'src/App.vue': '', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle named exports', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + // Check for module instantiation (Symbol.toStringTag indicates module processing) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle export * from syntax', async () => { + await store.setFiles( + { + 'src/main.js': `export * from './module'`, + 'src/module.js': 'export const foo = "bar"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle export { foo, bar } from syntax', async () => { + await store.setFiles( + { + 'src/main.js': `export { foo } from './module'`, + 'src/module.js': 'export const foo = "bar"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle re-exports with aliases', async () => { + await store.setFiles( + { + 'src/main.js': `export { foo as bar } from './module'`, + 'src/module.js': 'export const foo = "bar"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('dynamic imports', () => { + it('should handle dynamic imports', async () => { + await store.setFiles( + { + 'src/main.js': `const module = import('./lazy-module') +export default module`, + 'src/lazy-module.js': 'export const lazy = true', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should process all files when dynamic import is present', async () => { + await store.setFiles( + { + 'src/main.js': `const module = import('./lazy') +export default module`, + 'src/lazy.js': 'export const lazy = true', + 'src/other.js': 'export const other = "value"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + // With dynamic imports, all files should be processed + expect(result.length).toBeGreaterThanOrEqual(2) + }) + }) + + describe('import variations', () => { + it('should handle namespace imports', async () => { + await store.setFiles( + { + 'src/main.js': `import * as utils from './utils' +console.log(utils)`, + 'src/utils.js': 'export const foo = "bar"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle default imports', async () => { + await store.setFiles( + { + 'src/main.js': `import def from './module' +console.log(def)`, + 'src/module.js': 'export default "default export"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle mixed imports', async () => { + await store.setFiles( + { + 'src/main.js': `import def, { named } from './module' +console.log(def, named)`, + 'src/module.js': `export default "default" +export const named = "named"`, + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('export variations', () => { + it('should handle function exports', async () => { + await store.setFiles( + { + 'src/main.js': 'export function foo() { return "bar" }', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle class exports', async () => { + await store.setFiles( + { + 'src/main.js': 'export class MyClass { constructor() {} }', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle variable exports', async () => { + await store.setFiles( + { + 'src/main.js': 'export const a = 1, b = 2', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle default function export', async () => { + await store.setFiles( + { + 'src/main.js': 'export default function foo() {}', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle default class export', async () => { + await store.setFiles( + { + 'src/main.js': 'export default class A {}', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should handle anonymous default export', async () => { + await store.setFiles( + { + 'src/main.js': 'export default { value: 42 }', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + // Anonymous default exports use module.default = syntax + expect(result.some((code) => code.includes('.default'))).toBe(true) + }) + }) + + describe('file resolution', () => { + it('should resolve .ts extension automatically', async () => { + await store.setFiles( + { + 'src/main.js': `import { foo } from './module' +export default foo`, + 'src/module.ts': 'export const foo = "typescript"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should resolve .js extension automatically', async () => { + await store.setFiles( + { + 'src/main.js': `import { foo } from './module' +export default foo`, + 'src/module.js': 'export const foo = "javascript"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should throw error for non-existent file', async () => { + await store.setFiles( + { + 'src/main.js': `import { foo } from './nonexistent' +export default foo`, + }, + 'src/main.js', + ) + expect(() => compileModulesForPreview(store)).toThrow('does not exist') + }) + }) + + describe('module instantiation', () => { + it('should create module with Symbol.toStringTag', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('Symbol.toStringTag'))).toBe( + true, + ) + }) + + it('should register module in __modules__', async () => { + await store.setFiles( + { + 'src/main.js': 'export const value = 42', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.some((code) => code.includes('__modules__'))).toBe(true) + }) + }) + + describe('binding conversion', () => { + it('should convert import bindings in expressions', async () => { + await store.setFiles( + { + 'src/main.js': `import { value } from './module' +const result = value + 1 +export default result`, + 'src/module.js': 'export const value = 42', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle shorthand property syntax', async () => { + await store.setFiles( + { + 'src/main.js': `import { foo } from './module' +const obj = { foo } +export default obj`, + 'src/module.js': 'export const foo = "bar"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle class extends with import', async () => { + await store.setFiles( + { + 'src/main.js': `import { Base } from './base' +const createDerived = () => class Derived extends Base {} +export default createDerived`, + 'src/base.js': 'export class Base {}', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('circular dependencies', () => { + it('should handle circular imports without infinite loop', async () => { + await store.setFiles( + { + 'src/a.js': `import { b } from './b' +export const a = "a" + b`, + 'src/b.js': `import { a } from './a' +export const b = "b" + a`, + }, + 'src/a.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + }) + + describe('multiple files', () => { + it('should process multiple imported files', async () => { + await store.setFiles( + { + 'src/main.js': `import { a } from './a' +import { b } from './b' +export default a + b`, + 'src/a.js': 'export const a = 1', + 'src/b.js': 'export const b = 2', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThanOrEqual(3) + }) + + it('should maintain file order in output', async () => { + await store.setFiles( + { + 'src/main.js': `import { helper } from './helper' +export const main = helper()`, + 'src/helper.js': 'export const helper = () => "help"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + // Helper should be processed before main + expect(result.length).toBeGreaterThanOrEqual(2) + }) + }) + + describe('edge cases', () => { + it('should handle empty module', async () => { + await store.setFiles( + { + 'src/empty.js': '', + }, + 'src/empty.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle module with only comments', async () => { + await store.setFiles( + { + 'src/comments.js': '// just a comment', + }, + 'src/comments.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + + it('should handle deeply nested imports', async () => { + await store.setFiles( + { + 'src/main.js': `import { deep } from './a/b/c/module' +export default deep`, + 'src/a/b/c/module.js': 'export const deep = "deep value"', + }, + 'src/main.js', + ) + const result = compileModulesForPreview(store) + expect(result.length).toBeGreaterThan(0) + }) + }) +}) diff --git a/src/sourcemap.test.ts b/src/sourcemap.test.ts new file mode 100644 index 00000000..52395af2 --- /dev/null +++ b/src/sourcemap.test.ts @@ -0,0 +1,266 @@ +import { trimAnalyzedBindings, getSourceMap, toVisualizer } from './sourcemap' + +describe('sourcemap', () => { + describe('trimAnalyzedBindings', () => { + it('should remove analyzed bindings comment from script code', () => { + const input = `/* Analyzed bindings: { foo: "bar" } */ +const x = 1` + const expected = 'const x = 1' + expect(trimAnalyzedBindings(input)).toBe(expected) + }) + + it('should handle multi-line analyzed bindings comment', () => { + const input = `/* Analyzed bindings: { + foo: "bar", + baz: "qux" +} */ +const x = 1` + const expected = 'const x = 1' + expect(trimAnalyzedBindings(input)).toBe(expected) + }) + + it('should return trimmed code when no comment present', () => { + const input = ` const x = 1 ` + const expected = 'const x = 1' + expect(trimAnalyzedBindings(input)).toBe(expected) + }) + + it('should return empty string for empty input', () => { + expect(trimAnalyzedBindings('')).toBe('') + }) + + it('should return empty string for comment-only input', () => { + const input = `/* Analyzed bindings: { foo: "bar" } */ +` + expect(trimAnalyzedBindings(input)).toBe('') + }) + }) + + describe('getSourceMap', () => { + it('should return scriptMap when templateMap is not provided', () => { + const scriptMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['script content'], + names: [], + mappings: 'AAAA', + } + + const result = getSourceMap( + 'test.vue', + 'script code', + scriptMap, + null as any, + ) + expect(result).toEqual(scriptMap) + }) + + it('should return scriptMap when templateMap is undefined', () => { + const scriptMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['script content'], + names: [], + mappings: 'AAAA', + } + + const result = getSourceMap( + 'test.vue', + 'script code', + scriptMap, + undefined as any, + ) + expect(result).toEqual(scriptMap) + }) + + it('should merge scriptMap and templateMap when both are provided', () => { + const scriptMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['script content'], + names: ['foo'], + mappings: 'AAAA', + } + + const templateMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['template content'], + names: ['bar'], + mappings: 'AACA', + } + + const scriptCode = 'const x = 1\nconst y = 2\n' + + const result = getSourceMap( + 'test.vue', + scriptCode, + scriptMap, + templateMap, + ) + expect(result).toBeDefined() + expect(result.sourcesContent).toEqual(templateMap.sourcesContent) + }) + + it('should handle empty scriptMap when templateMap is provided', () => { + const templateMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['template content'], + names: ['bar'], + mappings: 'AACA', + } + + const scriptCode = 'const x = 1\n' + + const result = getSourceMap( + 'test.vue', + scriptCode, + null as any, + templateMap, + ) + expect(result).toBeDefined() + expect(result.sourcesContent).toEqual(templateMap.sourcesContent) + }) + + it('should offset template mappings by script line count', () => { + const scriptMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['script content'], + names: [], + mappings: 'AAAA', + } + + const templateMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['template content'], + names: [], + mappings: 'AAAA', + } + + // Script with 3 newlines should offset template mappings by 3 + const scriptCode = 'const x = 1\nconst y = 2\nconst z = 3\n' + + const result = getSourceMap( + 'test.vue', + scriptCode, + scriptMap, + templateMap, + ) + expect(result).toBeDefined() + }) + }) + + describe('toVisualizer', () => { + it('should generate a valid source-map-visualization URL', () => { + const code = 'const x = 1' + const sourceMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['const x = 1'], + names: [], + mappings: 'AAAA', + } + + const result = toVisualizer(code, sourceMap) + expect(result).toMatch( + /^https:\/\/evanw\.github\.io\/source-map-visualization#/, + ) + }) + + it('should generate different URLs for different code', () => { + const sourceMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['const x = 1'], + names: [], + mappings: 'AAAA', + } + + const result1 = toVisualizer('const x = 1', sourceMap) + const result2 = toVisualizer('const y = 2', sourceMap) + expect(result1).not.toBe(result2) + }) + + it('should generate different URLs for different sourceMaps', () => { + const code = 'const x = 1' + const sourceMap1 = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['const x = 1'], + names: [], + mappings: 'AAAA', + } + const sourceMap2 = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['const x = 1'], + names: [], + mappings: 'AACA', + } + + const result1 = toVisualizer(code, sourceMap1) + const result2 = toVisualizer(code, sourceMap2) + expect(result1).not.toBe(result2) + }) + + it('should handle empty code', () => { + const sourceMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: [''], + names: [], + mappings: '', + } + + const result = toVisualizer('', sourceMap) + expect(result).toMatch( + /^https:\/\/evanw\.github\.io\/source-map-visualization#/, + ) + }) + + it('should handle empty mappings', () => { + const code = 'const x = 1' + const sourceMap = { + file: 'test.vue', + sourceRoot: '', + version: '3' as const, + sources: ['test.vue'], + sourcesContent: ['const x = 1'], + names: [], + mappings: '', + } + + const result = toVisualizer(code, sourceMap) + expect(result).toMatch( + /^https:\/\/evanw\.github\.io\/source-map-visualization#/, + ) + }) + }) +}) diff --git a/src/store.test.ts b/src/store.test.ts new file mode 100644 index 00000000..3b63d17a --- /dev/null +++ b/src/store.test.ts @@ -0,0 +1,608 @@ +import { + describe, + it, + expect, + beforeEach, + afterEach, + jest, +} from '@jest/globals' +import { ref } from 'vue' + +// Mock global browser functions for Node.js environment +const mockConfirm = jest.fn() +const mockAlert = jest.fn() + +// Set up global mocks before any imports +Object.assign(global, { + confirm: mockConfirm, + alert: mockAlert, +}) + +// Mock dependencies before imports +jest.mock('./transform', () => ({ + compileFile: jest.fn().mockResolvedValue([]), +})) + +jest.mock('./utils', () => ({ + atou: jest.fn((str: string) => { + try { + // Remove the # prefix if present and decode base64 + const cleanStr = str.startsWith('#') ? str.slice(1) : str + return Buffer.from(cleanStr, 'base64').toString('utf-8') + } catch { + return str + } + }), + utoa: jest.fn((str: string) => { + try { + return Buffer.from(str, 'utf-8').toString('base64') + } catch { + return str + } + }), +})) + +jest.mock('./import-map', () => ({ + useVueImportMap: jest.fn(() => { + const importMapRef = ref({ imports: {} }) + const vueVersionRef = ref(null) + return { + importMap: importMapRef, + vueVersion: vueVersionRef, + } + }), + mergeImportMap: jest.fn((a: any, b: any) => { + const result: any = { ...a, ...b } + if (a?.imports || b?.imports) { + result.imports = { ...a?.imports, ...b?.imports } + } + return result + }), +})) + +import { + useStore, + File, + stripSrcPrefix, + importMapFile, + tsconfigFile, +} from './store' + +describe('File class', () => { + describe('constructor', () => { + it('should create a file with filename and code', () => { + const file = new File('test.vue', '') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should create a file with default empty code', () => { + const file = new File('test.vue') + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('') + expect(file.hidden).toBe(false) + }) + + it('should create a file with hidden option', () => { + const file = new File('test.vue', 'code', true) + expect(file.filename).toBe('test.vue') + expect(file.code).toBe('code') + expect(file.hidden).toBe(true) + }) + }) + + describe('compiled property', () => { + it('should have default compiled values', () => { + const file = new File('test.vue') + expect(file.compiled).toEqual({ + js: '', + css: '', + ssr: '', + clientMap: '', + ssrMap: '', + }) + }) + }) + + describe('editorViewState property', () => { + it('should have null editorViewState by default', () => { + const file = new File('test.vue') + expect(file.editorViewState).toBeNull() + }) + }) + + describe('language getter', () => { + it('should return "vue" for .vue files', () => { + const file = new File('test.vue') + expect(file.language).toBe('vue') + }) + + it('should return "html" for .html files', () => { + const file = new File('test.html') + expect(file.language).toBe('html') + }) + + it('should return "css" for .css files', () => { + const file = new File('test.css') + expect(file.language).toBe('css') + }) + + it('should return "typescript" for .ts files', () => { + const file = new File('test.ts') + expect(file.language).toBe('typescript') + }) + + it('should return "javascript" for .js files', () => { + const file = new File('test.js') + expect(file.language).toBe('javascript') + }) + + it('should return "javascript" for unknown extensions', () => { + const file = new File('test.json') + expect(file.language).toBe('javascript') + }) + }) +}) + +describe('stripSrcPrefix', () => { + it('should remove src/ prefix from filename', () => { + expect(stripSrcPrefix('src/App.vue')).toBe('App.vue') + }) + + it('should return filename unchanged if no src/ prefix', () => { + expect(stripSrcPrefix('App.vue')).toBe('App.vue') + }) + + it('should handle nested src/ paths', () => { + expect(stripSrcPrefix('src/components/Test.vue')).toBe( + 'components/Test.vue', + ) + }) +}) + +describe('importMapFile constant', () => { + it('should be "import-map.json"', () => { + expect(importMapFile).toBe('import-map.json') + }) +}) + +describe('tsconfigFile constant', () => { + it('should be "tsconfig.json"', () => { + expect(tsconfigFile).toBe('tsconfig.json') + }) +}) + +describe('useStore', () => { + beforeEach(() => { + jest.clearAllMocks() + mockConfirm.mockReturnValue(true) + mockAlert.mockImplementation(() => {}) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + describe('initialization', () => { + it('should create a store with default values', () => { + const store = useStore() + expect(store.files).toBeDefined() + expect(store.activeFile).toBeDefined() + expect(store.showOutput).toBe(false) + expect(store.outputMode).toBe('preview') + expect(store.vueVersion).toBeNull() + expect(store.loading).toBe(false) + }) + + it('should have empty errors when import-map.json exists', () => { + const store = useStore() + // The import-map.json file should be created by setImportMap during initialization + // Clear any errors from the initial getImportMap call + store.errors = [] + expect(store.errors).toEqual([]) + }) + + it('should create a store with custom files', () => { + const customFiles = ref({ + 'src/App.vue': new File( + 'src/App.vue', + '', + ), + }) + const store = useStore({ files: customFiles }) + expect(store.files['src/App.vue']).toBeDefined() + }) + + it('should set activeFilename to mainFile by default', () => { + const store = useStore() + expect(store.activeFilename).toBe('src/App.vue') + }) + + it('should use custom mainFile when provided', () => { + const store = useStore({ + mainFile: ref('src/main.ts'), + files: ref({ + 'src/main.ts': new File('src/main.ts', 'console.log("hello")'), + }), + }) + expect(store.mainFile).toBe('src/main.ts') + expect(store.activeFilename).toBe('src/main.ts') + }) + }) + + describe('setActive', () => { + it('should set the active filename', () => { + const store = useStore() + store.setActive('src/Other.vue') + expect(store.activeFilename).toBe('src/Other.vue') + }) + }) + + describe('addFile', () => { + it('should add a new file by filename string', () => { + const store = useStore() + store.addFile('src/Test.vue') + expect(store.files['src/Test.vue']).toBeDefined() + expect(store.activeFilename).toBe('src/Test.vue') + }) + + it('should add a new File object', () => { + const store = useStore() + const file = new File( + 'src/Custom.vue', + '', + ) + store.addFile(file) + expect(store.files['src/Custom.vue'].filename).toBe('src/Custom.vue') + expect(store.files['src/Custom.vue'].code).toBe( + '', + ) + }) + + it('should not set active file for hidden files', () => { + const store = useStore() + const initialActive = store.activeFilename + const hiddenFile = new File('src/Hidden.vue', '', true) + store.addFile(hiddenFile) + expect(store.files['src/Hidden.vue']).toBeDefined() + expect(store.activeFilename).toBe(initialActive) + }) + }) + + describe('deleteFile', () => { + beforeEach(() => { + mockConfirm.mockReturnValue(true) + }) + + it('should delete a file', () => { + const store = useStore() + store.addFile('src/Test.vue') + expect(store.files['src/Test.vue']).toBeDefined() + store.deleteFile('src/Test.vue') + expect(store.files['src/Test.vue']).toBeUndefined() + }) + + it('should switch to mainFile when deleting active file', () => { + const store = useStore() + store.addFile('src/Test.vue') + expect(store.activeFilename).toBe('src/Test.vue') + store.deleteFile('src/Test.vue') + expect(store.activeFilename).toBe('src/App.vue') + }) + + it('should not delete file when confirm returns false', () => { + mockConfirm.mockReturnValue(false) + const store = useStore() + store.addFile('src/Test.vue') + store.deleteFile('src/Test.vue') + expect(store.files['src/Test.vue']).toBeDefined() + }) + }) + + describe('renameFile', () => { + it('should rename a file', () => { + const store = useStore() + store.addFile('src/Old.vue') + store.renameFile('src/Old.vue', 'src/New.vue') + expect(store.files['src/Old.vue']).toBeUndefined() + expect(store.files['src/New.vue']).toBeDefined() + expect(store.files['src/New.vue']?.filename).toBe('src/New.vue') + }) + + it('should update mainFile when renaming it', () => { + const store = useStore() + store.renameFile('src/App.vue', 'src/RenamedApp.vue') + expect(store.mainFile).toBe('src/RenamedApp.vue') + }) + + it('should update activeFilename when renaming active file', () => { + const store = useStore() + store.renameFile('src/App.vue', 'src/RenamedApp.vue') + expect(store.activeFilename).toBe('src/RenamedApp.vue') + }) + + it('should set error when file not found', () => { + const store = useStore() + store.renameFile('src/NonExistent.vue', 'src/New.vue') + expect(store.errors).toContain( + 'Could not rename "src/NonExistent.vue", file not found', + ) + }) + + it('should set error when new filename is empty', () => { + const store = useStore() + store.renameFile('src/App.vue', '') + expect(store.errors).toContain('Cannot rename "src/App.vue" to ""') + }) + + it('should set error when renaming to same name', () => { + const store = useStore() + store.renameFile('src/App.vue', 'src/App.vue') + expect(store.errors).toContain( + 'Cannot rename "src/App.vue" to "src/App.vue"', + ) + }) + }) + + describe('getImportMap', () => { + it('should return parsed import map from file', () => { + const store = useStore() + const importMap = { imports: { vue: 'https://vue.js' } } + store.files[importMapFile] = new File( + importMapFile, + JSON.stringify(importMap), + ) + expect(store.getImportMap()).toEqual(importMap) + }) + + it('should return empty object when file does not exist', () => { + const store = useStore() + delete store.files[importMapFile] + expect(store.getImportMap()).toEqual({}) + }) + + it('should set error and return empty object for invalid JSON', () => { + const store = useStore() + store.files[importMapFile] = new File(importMapFile, 'invalid json') + expect(store.getImportMap()).toEqual({}) + expect(store.errors.length).toBeGreaterThan(0) + }) + }) + + describe('setImportMap', () => { + it('should set import map file content', () => { + const store = useStore() + const importMap = { imports: { vue: 'https://vue.js' } } + store.setImportMap(importMap) + expect(store.files[importMapFile]).toBeDefined() + expect(JSON.parse(store.files[importMapFile].code)).toEqual(importMap) + }) + + it('should create import map file if not exists', () => { + const store = useStore() + delete store.files[importMapFile] + const importMap = { imports: { vue: 'https://vue.js' } } + store.setImportMap(importMap) + expect(store.files[importMapFile]).toBeDefined() + }) + + it('should fix URL from sfc.vuejs to play.vuejs', () => { + const store = useStore() + const importMap = { imports: { vue: 'https://sfc.vuejs.org' } } + store.setImportMap(importMap) + const result = JSON.parse(store.files[importMapFile].code) + expect(result.imports.vue).toBe('https://play.vuejs.org') + }) + }) + + describe('getTsConfig', () => { + it('should return parsed tsconfig from file', () => { + const store = useStore() + const tsconfig = { compilerOptions: { target: 'ESNext' } } + store.files[tsconfigFile] = new File( + tsconfigFile, + JSON.stringify(tsconfig), + ) + expect(store.getTsConfig()).toEqual(tsconfig) + }) + + it('should return empty object for invalid JSON', () => { + const store = useStore() + store.files[tsconfigFile] = new File(tsconfigFile, 'invalid json') + expect(store.getTsConfig()).toEqual({}) + }) + }) + + describe('serialize', () => { + it('should serialize files to a string', () => { + const store = useStore() + const serialized = store.serialize() + expect(typeof serialized).toBe('string') + expect(serialized).toMatch(/^#/) + }) + + it('should include vue version when set', () => { + const store = useStore() + store.vueVersion = '3.4.0' + const serialized = store.serialize() + expect(serialized).toBeDefined() + }) + }) + + describe('deserialize', () => { + it('should deserialize files from a string', () => { + const store = useStore() + const files = { 'src/Test.vue': '' } + const serialized = '#' + btoa(JSON.stringify(files)) + store.deserialize(serialized) + expect(store.files['src/Test.vue']).toBeDefined() + }) + + it('should set vue version from serialized state', () => { + const store = useStore() + const files = { 'src/Test.vue': 'code', _version: '3.4.0' } + const serialized = '#' + btoa(JSON.stringify(files)) + store.deserialize(serialized) + expect(store.vueVersion).toBe('3.4.0') + }) + + it('should set typescript version from serialized state', () => { + const store = useStore() + const files = { 'src/Test.vue': 'code', _tsVersion: '5.0.0' } + const serialized = '#' + btoa(JSON.stringify(files)) + store.deserialize(serialized) + expect(store.typescriptVersion).toBe('5.0.0') + }) + + it('should set default file on invalid serialized state', () => { + const store = useStore() + const consoleSpy = jest + .spyOn(console, 'error') + .mockImplementation(() => {}) + store.deserialize('#invalid-base64!') + expect(consoleSpy).toHaveBeenCalled() + expect(mockAlert).toHaveBeenCalledWith('Failed to load code from URL.') + consoleSpy.mockRestore() + }) + }) + + describe('getFiles', () => { + it('should return all files with code', () => { + const store = useStore() + const files = store.getFiles() + expect(files['App.vue']).toBeDefined() + }) + + it('should strip src/ prefix from filenames', () => { + const store = useStore() + const files = store.getFiles() + expect(files['App.vue']).toBeDefined() + expect(files['src/App.vue']).toBeUndefined() + }) + }) + + describe('setFiles', () => { + it('should set new files', async () => { + const store = useStore() + const newFiles = { + 'src/Test.vue': '', + } + await store.setFiles(newFiles) + expect(store.files['src/Test.vue']).toBeDefined() + }) + + it('should set main file', async () => { + const store = useStore() + const newFiles = { + 'src/Main.vue': '', + } + await store.setFiles(newFiles, 'src/Main.vue') + expect(store.mainFile).toBe('src/Main.vue') + expect(store.activeFilename).toBe('src/Main.vue') + }) + + it('should use welcome template when main file is missing', async () => { + const store = useStore() + const newFiles: Record = {} + await store.setFiles(newFiles) + expect(store.files['src/App.vue']).toBeDefined() + }) + + it('should add src/ prefix to filenames without it', async () => { + const store = useStore() + const newFiles = { 'Test.vue': '' } + await store.setFiles(newFiles) + expect(store.files['src/Test.vue']).toBeDefined() + }) + + it('should not add src/ prefix to import-map.json', async () => { + const store = useStore() + const newFiles = { 'import-map.json': JSON.stringify({ imports: {} }) } + await store.setFiles(newFiles) + expect(store.files['import-map.json']).toBeDefined() + }) + + it('should not add src/ prefix to tsconfig.json', async () => { + const store = useStore() + const newFiles = { 'tsconfig.json': JSON.stringify({}) } + await store.setFiles(newFiles) + expect(store.files['tsconfig.json']).toBeDefined() + }) + }) + + describe('init', () => { + it('should initialize the store', () => { + const store = useStore() + expect(() => store.init()).not.toThrow() + }) + }) + + describe('reactive properties', () => { + it('should have errors array', () => { + const store = useStore() + expect(Array.isArray(store.errors)).toBe(true) + }) + + it('should have showOutput boolean', () => { + const store = useStore() + expect(typeof store.showOutput).toBe('boolean') + }) + + it('should have outputMode string', () => { + const store = useStore() + expect(typeof store.outputMode).toBe('string') + }) + + it('should have sfcOptions object', () => { + const store = useStore() + expect(typeof store.sfcOptions).toBe('object') + }) + + it('should have ssrOutput object', () => { + const store = useStore() + expect(store.ssrOutput).toEqual({ html: '', context: '' }) + }) + + it('should have locale property', () => { + const store = useStore() + expect(store.locale).toBeUndefined() + }) + + it('should have typescriptVersion property', () => { + const store = useStore() + expect(store.typescriptVersion).toBe('latest') + }) + + it('should have dependencyVersion object', () => { + const store = useStore() + expect(store.dependencyVersion).toEqual({}) + }) + }) + + describe('template property', () => { + it('should have welcomeSFC template', () => { + const store = useStore() + expect(store.template.welcomeSFC).toBeDefined() + }) + + it('should have newSFC template', () => { + const store = useStore() + expect(store.template.newSFC).toBeDefined() + }) + }) + + describe('compiler property', () => { + it('should have a compiler', () => { + const store = useStore() + expect(store.compiler).toBeDefined() + }) + }) + + describe('builtinImportMap property', () => { + it('should have builtinImportMap', () => { + const store = useStore() + expect(store.builtinImportMap).toBeDefined() + }) + }) +}) diff --git a/src/transform.test.ts b/src/transform.test.ts new file mode 100644 index 00000000..8f2f46ac --- /dev/null +++ b/src/transform.test.ts @@ -0,0 +1,365 @@ +import { compileFile, COMP_IDENTIFIER } from './transform' +import type { Store, File } from './store' +import * as defaultCompiler from 'vue/compiler-sfc' + +function createMockStore(): Store { + return { + files: {}, + activeFile: null as any, + mainFile: 'src/App.vue', + errors: [], + showOutput: false, + outputMode: 'preview', + sfcOptions: {}, + ssrOutput: { html: '', context: '' }, + compiler: defaultCompiler, + vueVersion: null, + locale: undefined, + typescriptVersion: 'latest', + dependencyVersion: {}, + init: () => {}, + setActive: () => {}, + addFile: () => {}, + deleteFile: () => {}, + renameFile: () => {}, + getImportMap: () => ({}), + getTsConfig: () => ({}), + } +} + +function createMockFile(filename: string, code: string): File { + return { + filename, + code, + hidden: false, + compiled: { + js: '', + css: '', + ssr: '', + clientMap: '', + ssrMap: '', + }, + editorViewState: null, + get language() { + if (this.filename.endsWith('.vue')) return 'vue' + if (this.filename.endsWith('.html')) return 'html' + if (this.filename.endsWith('.css')) return 'css' + if (this.filename.endsWith('.ts')) return 'typescript' + return 'javascript' + }, + } +} + +describe('compileFile', () => { + let store: Store + + beforeEach(() => { + store = createMockStore() + }) + + describe('empty files', () => { + it('should return empty array for empty code', async () => { + const file = createMockFile('src/test.vue', '') + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + }) + + it('should return empty array for whitespace-only code', async () => { + const file = createMockFile('src/test.vue', ' \n\t ') + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + }) + }) + + describe('CSS files', () => { + it('should compile CSS files', async () => { + const cssCode = '.test { color: red; }' + const file = createMockFile('src/test.css', cssCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.css).toBe(cssCode) + }) + }) + + describe('JavaScript files', () => { + it('should compile JS files', async () => { + const jsCode = 'export const foo = 42' + const file = createMockFile('src/test.js', jsCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toBe(jsCode) + expect(file.compiled.ssr).toBe(jsCode) + }) + + it('should compile TS files', async () => { + const tsCode = 'export const foo: number = 42' + const file = createMockFile('src/test.ts', tsCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toContain('export const foo = 42') + expect(file.compiled.ssr).toContain('export const foo = 42') + }) + }) + + describe('JSON files', () => { + it('should compile valid JSON files', async () => { + const jsonCode = '{"name": "test", "value": 42}' + const file = createMockFile('src/test.json', jsonCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toBe('export default {"name":"test","value":42}') + expect(file.compiled.ssr).toBe( + 'export default {"name":"test","value":42}', + ) + }) + + it('should return error for invalid JSON', async () => { + const invalidJson = '{invalid json}' + const file = createMockFile('src/test.json', invalidJson) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + }) + }) + + describe('Vue files', () => { + it('should compile basic Vue SFC', async () => { + const vueCode = ` + + + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toBeDefined() + expect(file.compiled.ssr).toBeDefined() + expect(file.compiled.css).toBeDefined() + }) + + it('should compile Vue SFC with script setup', async () => { + const vueCode = ` + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toBeDefined() + expect(file.compiled.ssr).toBeDefined() + }) + + it('should handle scoped styles', async () => { + const vueCode = ` + + + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toContain('__scopeId') + }) + + it('should return error for Vue parse errors', async () => { + const invalidVue = `` + const file = createMockFile('src/test.vue', invalidVue) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + }) + + it('should return error for unsupported template lang', async () => { + const vueCode = `` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + expect(errors[0]).toContain('lang="pug"') + }) + + it('should return error for unsupported style lang', async () => { + const vueCode = ` + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + expect(errors[0]).toContain('lang="scss"') + }) + + it('should return error for unsupported script lang', async () => { + const vueCode = ` + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + expect(errors[0]).toContain('Unsupported lang "py"') + }) + + it('should handle CSS vars in SFC', async () => { + const vueCode = ` + + + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors).toEqual([]) + }) + + it('should handle custom element mode', async () => { + const storeWithCE = createMockStore() + storeWithCE.sfcOptions = { + script: { + customElement: /\.ce\.vue$/, + }, + } + const vueCode = ` + + + +` + const file = createMockFile('src/test.ce.vue', vueCode) + const errors = await compileFile(storeWithCE, file) + expect(errors).toEqual([]) + expect(file.compiled.js).toContain('.styles') + }) + + it('should handle module styles error', async () => { + const vueCode = ` + +` + const file = createMockFile('src/test.vue', vueCode) + const errors = await compileFile(store, file) + expect(errors.length).toBeGreaterThan(0) + expect(errors[0]).toContain('