From 3763e8ad74765e06b9627758f1558dec90a5295f Mon Sep 17 00:00:00 2001 From: Artem Tsyrulnikov Date: Wed, 25 Mar 2026 13:54:53 +0300 Subject: [PATCH] =?UTF-8?q?fix=20=D1=84=D0=BE=D1=80=D0=BC=D1=83=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lock | 155 ++++- package.json | 7 +- src/feature/excelEngine/excelEngine.test.ts | 681 ++++++++++++++++++++ src/feature/excelEngine/excelEngine.ts | 14 +- src/feature/excelEngine/functions.test.ts | 354 ++++++++++ 5 files changed, 1199 insertions(+), 12 deletions(-) create mode 100644 src/feature/excelEngine/excelEngine.test.ts create mode 100644 src/feature/excelEngine/functions.test.ts diff --git a/bun.lock b/bun.lock index 24d410c..5d9f878 100644 --- a/bun.lock +++ b/bun.lock @@ -76,6 +76,7 @@ "ts-prune": "^0.10.3", "typescript": "^4.4.4", "vite": "^4.3.9", + "vitest": "^4.1.1", }, }, }, @@ -178,7 +179,7 @@ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], @@ -190,6 +191,8 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], @@ -276,12 +279,44 @@ "@remix-run/router": ["@remix-run/router@1.23.0", "", {}, "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.11", "", { "os": "android", "cpu": "arm64" }, "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm" }, "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.11", "", { "os": "none", "cpu": "arm64" }, "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.11", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.11", "", { "os": "win32", "cpu": "x64" }, "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@swc/core": ["@swc/core@1.13.3", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.3", "@swc/core-darwin-x64": "1.13.3", "@swc/core-linux-arm-gnueabihf": "1.13.3", "@swc/core-linux-arm64-gnu": "1.13.3", "@swc/core-linux-arm64-musl": "1.13.3", "@swc/core-linux-x64-gnu": "1.13.3", "@swc/core-linux-x64-musl": "1.13.3", "@swc/core-win32-arm64-msvc": "1.13.3", "@swc/core-win32-ia32-msvc": "1.13.3", "@swc/core-win32-x64-msvc": "1.13.3" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-ZaDETVWnm6FE0fc+c2UE8MHYVS3Fe91o5vkmGfgwGXFbxYvAjKSqxM/j4cRc9T7VZNSJjriXq58XkfCp3Y6f+w=="], "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ux0Ws4pSpBTqbDS9GlVP354MekB1DwYlbxXU3VhnDr4GBcCOimpocx62x7cFJkSpEBF8bmX8+/TTCGKh4PbyXw=="], @@ -316,8 +351,12 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -420,6 +459,20 @@ "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.11.0", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.27", "@swc/core": "^1.12.11" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w=="], + "@vitest/expect": ["@vitest/expect@4.1.1", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.1", "", { "dependencies": { "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.1", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ=="], + + "@vitest/runner": ["@vitest/runner@4.1.1", "", { "dependencies": { "@vitest/utils": "4.1.1", "pathe": "^2.0.3" } }, "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg=="], + + "@vitest/spy": ["@vitest/spy@4.1.1", "", {}, "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA=="], + + "@vitest/utils": ["@vitest/utils@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -454,6 +507,8 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], @@ -486,6 +541,8 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], @@ -518,6 +575,8 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -552,6 +611,8 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -582,6 +643,8 @@ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], @@ -626,8 +689,12 @@ "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -644,7 +711,7 @@ "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], @@ -850,6 +917,30 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], @@ -874,6 +965,8 @@ "lucide-react": ["lucide-react@0.525.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -1018,6 +1111,8 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -1054,9 +1149,11 @@ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], @@ -1174,6 +1271,8 @@ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + "rolldown": ["rolldown@1.0.0-rc.11", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.11" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-x64": "1.0.0-rc.11", "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw=="], + "rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -1206,6 +1305,8 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], @@ -1216,6 +1317,10 @@ "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -1260,7 +1365,13 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -1342,6 +1453,8 @@ "vite": ["vite@4.5.14", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g=="], + "vitest": ["vitest@4.1.1", "", { "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", "@vitest/pretty-format": "4.1.1", "@vitest/runner": "4.1.1", "@vitest/snapshot": "4.1.1", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.1", "@vitest/browser-preview": "4.1.1", "@vitest/browser-webdriverio": "4.1.1", "@vitest/ui": "4.1.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA=="], + "vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], @@ -1356,6 +1469,8 @@ "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -1372,16 +1487,26 @@ "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "@jridgewell/gen-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@jridgewell/trace-mapping/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + "@reduxjs/toolkit/immer": ["immer@9.0.21", "", {}, "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + "@typescript-eslint/utils/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cosmiconfig/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + "eslint-import-resolver-typescript/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -1404,6 +1529,8 @@ "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "prettier-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="], @@ -1416,20 +1543,24 @@ "react-rnd/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="], + "string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], "tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "vitest/vite": ["vite@8.0.2", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.3", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.11", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA=="], + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], @@ -1438,8 +1569,16 @@ "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@typescript-eslint/utils/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + "eslint-import-resolver-typescript/tinyglobby/fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "loglevel-colored-level-prefix/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], @@ -1460,10 +1599,14 @@ "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "vitest/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + "loglevel-colored-level-prefix/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], "prettier-eslint/@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="], diff --git a/package.json b/package.json index 4256b13..92b5a4b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "build": "tsc && vite build", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", - "format": "prettier --write ." + "format": "prettier --write .", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -81,6 +83,7 @@ "tailwindcss": "^3.3.2", "ts-prune": "^0.10.3", "typescript": "^4.4.4", - "vite": "^4.3.9" + "vite": "^4.3.9", + "vitest": "^4.1.1" } } \ No newline at end of file diff --git a/src/feature/excelEngine/excelEngine.test.ts b/src/feature/excelEngine/excelEngine.test.ts new file mode 100644 index 0000000..3c222f7 --- /dev/null +++ b/src/feature/excelEngine/excelEngine.test.ts @@ -0,0 +1,681 @@ +import { describe, it, expect } from 'vitest' +import { Workbook, CellUtils } from './excelEngine' + +function createWorkbookWithSheet(name = 'Sheet1') { + const wb = new Workbook() + const sheet = wb.addSheet(name) + return { wb, sheet } +} + +describe('Workbook — формулы', () => { + // ==================== Базовые значения ==================== + + describe('Базовые значения', () => { + it('устанавливает и получает число', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 42) + expect(wb.getValue('Sheet1!A1')).toBe(42) + }) + + it('устанавливает и получает строку', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 'hello') + expect(wb.getValue('Sheet1!A1')).toBe('hello') + }) + + it('пустая ячейка возвращает null', () => { + const { wb } = createWorkbookWithSheet() + expect(wb.getValue('Sheet1!A1')).toBe(null) + }) + }) + + // ==================== Простые формулы ==================== + + describe('Простые формулы', () => { + it('формула с числом', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', '=1+2') + expect(wb.getValue('Sheet1!A1')).toBe(3) + }) + + it('формула со ссылкой на ячейку', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 10) + sheet.setCell('A2', '=A1+5') + expect(wb.getValue('Sheet1!A2')).toBe(15) + }) + + it('формула с несколькими ссылками', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 10) + sheet.setCell('A2', 20) + sheet.setCell('A3', '=A1+A2') + expect(wb.getValue('Sheet1!A3')).toBe(30) + }) + }) + + // ==================== Функции в формулах ==================== + + describe('СУММ в формуле', () => { + it('суммирует диапазон', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 1) + sheet.setCell('A2', 2) + sheet.setCell('A3', 3) + sheet.setCell('B1', '=СУММ(A1:A3)') + expect(wb.getValue('Sheet1!B1')).toBe(6) + }) + }) + + describe('СРЗНАЧ в формуле', () => { + it('считает среднее диапазона', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 2) + sheet.setCell('A2', 4) + sheet.setCell('A3', 6) + sheet.setCell('B1', '=СРЗНАЧ(A1:A3)') + expect(wb.getValue('Sheet1!B1')).toBe(4) + }) + }) + + describe('МАКС в формуле', () => { + it('возвращает максимум двух ячеек', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 3) + sheet.setCell('A2', 7) + sheet.setCell('B1', '=МАКС(A1, A2)') + expect(wb.getValue('Sheet1!B1')).toBe(7) + }) + }) + + describe('СТЕПЕНЬ в формуле', () => { + it('возводит значение ячейки в степень', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 2) + sheet.setCell('B1', '=СТЕПЕНЬ(A1, 10)') + expect(wb.getValue('Sheet1!B1')).toBe(1024) + }) + }) + + describe('КОРЕНЬ в формуле', () => { + it('извлекает корень из ячейки', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 25) + sheet.setCell('B1', '=КОРЕНЬ(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(5) + }) + }) + + describe('ABS в формуле', () => { + it('возвращает модуль ячейки', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', -15) + sheet.setCell('B1', '=ABS(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(15) + }) + }) + + // ==================== БОЛЬШЕ / МЕНЬШЕ ==================== + + describe('БОЛЬШЕ в формуле', () => { + it('сравнивает два числа', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 10) + sheet.setCell('A2', 5) + sheet.setCell('B1', '=БОЛЬШЕ(A1, A2)') + expect(wb.getValue('Sheet1!B1')).toBe(true) + }) + + it('возвращает false при меньшем значении', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 3) + sheet.setCell('A2', 5) + sheet.setCell('B1', '=БОЛЬШЕ(A1, A2)') + expect(wb.getValue('Sheet1!B1')).toBe(false) + }) + }) + + describe('МЕНЬШЕ в формуле', () => { + it('сравнивает два числа', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 3) + sheet.setCell('A2', 10) + sheet.setCell('B1', '=МЕНЬШЕ(A1, A2)') + expect(wb.getValue('Sheet1!B1')).toBe(true) + }) + }) + + // ==================== ЕСЛИ + БОЛЬШЕ/МЕНЬШЕ ==================== + + describe('ЕСЛИ + БОЛЬШЕ/МЕНЬШЕ', () => { + it('ЕСЛИ(БОЛЬШЕ(...)) возвращает правильную ветку', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 100) + sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 50), "Много", "Мало")') + expect(wb.getValue('Sheet1!B1')).toBe('Много') + }) + + it('ЕСЛИ(МЕНЬШЕ(...)) возвращает ветку лжи', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 100) + sheet.setCell('B1', '=ЕСЛИ(МЕНЬШЕ(A1, 50), "Мало", "Много")') + expect(wb.getValue('Sheet1!B1')).toBe('Много') + }) + + it('ЕСЛИ с И и БОЛЬШЕ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 50) + sheet.setCell('B1', '=ЕСЛИ(И(БОЛЬШЕ(A1, 10), МЕНЬШЕ(A1, 100)), "В диапазоне", "Вне")') + expect(wb.getValue('Sheet1!B1')).toBe('В диапазоне') + }) + + it('ЕСЛИ с ИЛИ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 5) + sheet.setCell('B1', '=ЕСЛИ(ИЛИ(БОЛЬШЕ(A1, 100), МЕНЬШЕ(A1, 10)), "Крайнее", "Норм")') + expect(wb.getValue('Sheet1!B1')).toBe('Крайнее') + }) + + it('вложенный ЕСЛИ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 50) + sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 100), "Много", ЕСЛИ(МЕНЬШЕ(A1, 10), "Мало", "Норм"))') + expect(wb.getValue('Sheet1!B1')).toBe('Норм') + }) + }) + + // ==================== ЕПУСТО ==================== + + describe('ЕПУСТО в формуле', () => { + it('true для пустой ячейки', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('B1', '=ЕПУСТО(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(true) + }) + + it('false для заполненной ячейки', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 42) + sheet.setCell('B1', '=ЕПУСТО(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(false) + }) + + it('false для ячейки с нулём', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 0) + sheet.setCell('B1', '=ЕПУСТО(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(false) + }) + + it('ЕСЛИ(ЕПУСТО(...)) работает', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('B1', '=ЕСЛИ(ЕПУСТО(A1), "Пусто", "Заполнено")') + expect(wb.getValue('Sheet1!B1')).toBe('Пусто') + }) + }) + + // ==================== Строковые функции в формулах ==================== + + describe('Строковые функции в формулах', () => { + it('СЦЕПИТЬ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 'Привет') + sheet.setCell('A2', ' мир') + sheet.setCell('B1', '=СЦЕПИТЬ(A1, A2)') + expect(wb.getValue('Sheet1!B1')).toBe('Привет мир') + }) + + it('ПРОПИСН', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 'hello') + sheet.setCell('B1', '=ПРОПИСН(A1)') + expect(wb.getValue('Sheet1!B1')).toBe('HELLO') + }) + + it('СТРОЧН', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 'HELLO') + sheet.setCell('B1', '=СТРОЧН(A1)') + expect(wb.getValue('Sheet1!B1')).toBe('hello') + }) + }) + + // ==================== Округление в формулах ==================== + + describe('Округление в формулах', () => { + it('ОКРУГЛ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 3.456) + sheet.setCell('B1', '=ОКРУГЛ(A1, 2)') + expect(wb.getValue('Sheet1!B1')).toBe(3.46) + }) + + it('ЦЕЛОЕ', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 3.9) + sheet.setCell('B1', '=ЦЕЛОЕ(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(3) + }) + }) + + // ==================== Межлистовые ссылки ==================== + + describe('Межлистовые ссылки', () => { + it('ссылка на другой лист', () => { + const wb = new Workbook() + const sheetL = wb.addSheet('L') + const sheetR = wb.addSheet('R') + sheetL.setCell('A1', 100) + sheetR.setCell('A1', '=L!A1+50') + expect(wb.getValue('R!A1')).toBe(150) + }) + }) + + // ==================== Зависимости и пересчёт ==================== + + describe('Зависимости и пересчёт', () => { + it('пересчёт при изменении зависимой ячейки', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 10) + sheet.setCell('B1', '=A1*2') + expect(wb.getValue('Sheet1!B1')).toBe(20) + + sheet.setCell('A1', 20) + expect(wb.getValue('Sheet1!B1')).toBe(40) + }) + + it('цепочка зависимостей', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 5) + sheet.setCell('A2', '=A1+1') + sheet.setCell('A3', '=A2+1') + expect(wb.getValue('Sheet1!A3')).toBe(7) + + sheet.setCell('A1', 10) + expect(wb.getValue('Sheet1!A3')).toBe(12) + }) + }) + + // ==================== Замена функций (регрессия) ==================== + + describe('Регрессия: замена функций не ломает кириллические имена', () => { + it('ЕСЛИ не должен быть разбит заменой И', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 50) + sheet.setCell('B1', '=ЕСЛИ(БОЛЬШЕ(A1, 100), "Много", "Мало")') + expect(wb.getValue('Sheet1!B1')).toBe('Мало') + }) + + it('И внутри ЕСЛИ(И(...)) работает корректно', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 50) + sheet.setCell('B1', '=ЕСЛИ(И(БОЛЬШЕ(A1, 10), МЕНЬШЕ(A1, 100)), "Да", "Нет")') + expect(wb.getValue('Sheet1!B1')).toBe('Да') + }) + + it('НЕ не ломает другие функции', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 50) + sheet.setCell('B1', '=ЕСЛИ(НЕ(БОЛЬШЕ(A1, 100)), "Не больше", "Больше")') + expect(wb.getValue('Sheet1!B1')).toBe('Не больше') + }) + }) + + // ==================== Case-insensitive функции ==================== + + describe('Функции в нижнем регистре', () => { + it('случдр в нижнем регистре', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', '=случдр(1, 10, 2)') + const val = wb.getValue('Sheet1!A1') as number + expect(val).toBeGreaterThanOrEqual(1) + expect(val).toBeLessThanOrEqual(10) + }) + + it('округл в нижнем регистре', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', '=округл(3.456, 2)') + expect(wb.getValue('Sheet1!A1')).toBe(3.46) + }) + + it('сумм в нижнем регистре', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 1) + sheet.setCell('A2', 2) + sheet.setCell('A3', 3) + sheet.setCell('B1', '=сумм(A1:A3)') + expect(wb.getValue('Sheet1!B1')).toBe(6) + }) + + it('если в нижнем регистре с больше', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 100) + sheet.setCell('B1', '=если(больше(A1, 50), "Много", "Мало")') + expect(wb.getValue('Sheet1!B1')).toBe('Много') + }) + + it('смешанный регистр', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 5) + sheet.setCell('B1', '=Если(Больше(A1, 3), "да", "нет")') + expect(wb.getValue('Sheet1!B1')).toBe('да') + }) + + it('епусто в нижнем регистре — пустая ячейка', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('B1', '=епусто(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(true) + }) + + it('епусто в нижнем регистре — заполненная ячейка', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('A1', 42) + sheet.setCell('B1', '=епусто(A1)') + expect(wb.getValue('Sheet1!B1')).toBe(false) + }) + + it('если(епусто(...)) в нижнем регистре', () => { + const { wb, sheet } = createWorkbookWithSheet() + sheet.setCell('B1', '=если(епусто(A1), "Пусто", "Есть")') + expect(wb.getValue('Sheet1!B1')).toBe('Пусто') + }) + }) +}) + +// ==================== Сценарий протокола ==================== + +describe('Сценарий протокола: шаблон → ввод данных → пересчёт → выгрузка', () => { + /** + * Имитирует реальный flow: + * 1. Загружается шаблон с формулами (fromJSON) + * 2. Пользователь заполняет форму → значения пишутся в ячейки (setCell) + * 3. Движок пересчитывает формулы (recalc) + * 4. getAllModifiedCells / toJSON отдают данные для бэкенда + */ + + function createProtocolWorkbook() { + const wb = new Workbook() + wb.addSheet('L') // лист протокола + wb.addSheet('R') // лист настроек/расчётов + return wb + } + + it('загрузка шаблона и пересчёт формул', () => { + const wb = createProtocolWorkbook() + + // Шаблон: R!A1 — имя файла, R!A2 — область печати + // L!A1 — заголовок, L!B1 — формула суммы + wb.fromJSON({ + L: { + A1: 'Протокол испытаний', + A2: 10, + A3: 20, + A4: '=СУММ(A2:A3)', + }, + R: { + A1: 'protocol_output.xlsx', + A2: 'A1:D20', + }, + }) + + expect(wb.getValue('L!A1')).toBe('Протокол испытаний') + expect(wb.getValue('L!A4')).toBe(30) + expect(wb.getValue('R!A1')).toBe('protocol_output.xlsx') + expect(wb.getValue('R!A2')).toBe('A1:D20') + }) + + it('ввод данных из формы и пересчёт зависимых ячеек', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: '', // ячейка для ввода (пустая в шаблоне) + B1: '=ОКРУГЛ(A1 * 1.2, 2)', // формула зависит от A1 + C1: '=ЕСЛИ(БОЛЬШЕ(B1, 100), "Не соответствует", "Соответствует")', + }, + }) + + // Первый recalc после загрузки (как в useMultiSheetEngine.safeRecalc) + wb.recalc() + + // Пользователь вводит значение через форму → setCellValue + wb.setCell('L!A1', 50) + expect(wb.getValue('L!B1')).toBe(60) + expect(wb.getValue('L!C1')).toBe('Соответствует') + + // Пользователь меняет значение + wb.setCell('L!A1', 100) + expect(wb.getValue('L!B1')).toBe(120) + expect(wb.getValue('L!C1')).toBe('Не соответствует') + }) + + it('формулы со ссылками между листами L и R', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + R: { + A5: 0.15, // коэффициент из настроек + C1: '=случдр(10, 20, 2)', // случайное значение + }, + L: { + A1: 100, + B1: '=ОКРУГЛ(A1 * R!A5, 2)', // 100 * 0.15 = 15 + }, + }) + + expect(wb.getValue('L!B1')).toBe(15) + + // recalc чтобы зарегистрировать зависимости + wb.recalc() + + // Меняем входное значение + wb.setCell('L!A1', 200) + expect(wb.getValue('L!B1')).toBe(30) + }) + + it('ЕСЛИ + БОЛЬШЕ + МЕНЬШЕ в контексте протокола', () => { + const wb = createProtocolWorkbook() + + // Типичный сценарий: проверка допусков + wb.fromJSON({ + L: { + A1: 'Параметр', + A2: 50, // измеренное значение + B2: 10, // нижний допуск + C2: 100, // верхний допуск + D2: '=ЕСЛИ(И(БОЛЬШЕ(A2, B2), МЕНЬШЕ(A2, C2)), "Годен", "Брак")', + }, + }) + + expect(wb.getValue('L!D2')).toBe('Годен') + + // Значение вне допуска + wb.setCell('L!A2', 5) + expect(wb.getValue('L!D2')).toBe('Брак') + + // Значение на верхней границе — БОЛЬШЕ возвращает false при равенстве + wb.setCell('L!A2', 100) + expect(wb.getValue('L!D2')).toBe('Брак') + }) + + it('ЕПУСТО в контексте протокола — пропуск пустых строк', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: 42, + B1: '=ЕСЛИ(ЕПУСТО(A1), "", ОКРУГЛ(A1 * 1.1, 1))', + A2: '', // пустая + B2: '=ЕСЛИ(ЕПУСТО(A2), "", ОКРУГЛ(A2 * 1.1, 1))', + }, + }) + + expect(wb.getValue('L!B1')).toBe(46.2) + expect(wb.getValue('L!B2')).toBe('') + }) + + it('СТЕПЕНЬ в формулах протокола', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: 3, + B1: '=СТЕПЕНЬ(A1, 2)', // 9 + C1: '=КОРЕНЬ(B1)', // 3 + }, + }) + + expect(wb.getValue('L!B1')).toBe(9) + expect(wb.getValue('L!C1')).toBe(3) + }) + + it('цепочка пересчёта: A → B → C → D', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: 10, + B1: '=A1 * 2', // 20 + C1: '=B1 + 5', // 25 + D1: '=ОКРУГЛ(C1 / 3, 2)', // 8.33 + }, + }) + + expect(wb.getValue('L!D1')).toBe(8.33) + + wb.setCell('L!A1', 100) + // 100 → 200 → 205 → 68.33 + expect(wb.getValue('L!B1')).toBe(200) + expect(wb.getValue('L!C1')).toBe(205) + expect(wb.getValue('L!D1')).toBe(68.33) + }) + + it('toJSON возвращает raw-значения (формулы) для бэкенда', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: 10, + B1: '=A1 * 2', + }, + }) + + // getValue возвращает вычисленное + expect(wb.getValue('L!B1')).toBe(20) + + // toJSON возвращает raw (формулу) — это идёт в cells_to_update + const json = wb.toJSON() + expect(json['L']['A1']).toBe(10) + expect(json['L']['B1']).toBe('=A1 * 2') + }) + + it('getAllModifiedCells: baseline vs текущее состояние', () => { + const wb = createProtocolWorkbook() + + // Загружаем шаблон + const templateData = { + L: { + A1: '', + B1: '=A1 * 2', + }, + } + + wb.fromJSON(templateData) + + // Сохраняем baseline (как делает setTemplateData в хуке) + const baseline = JSON.parse(JSON.stringify(templateData)) + + // Пользователь вводит данные + wb.setCell('L!A1', 42) + + // Сравниваем текущее с baseline + const currentData = wb.toJSON() + const modified: Record = {} + + for (const cellAddr of Object.keys(currentData['L'] || {})) { + const current = String(currentData['L'][cellAddr] ?? '') + const original = String(baseline['L']?.[cellAddr] ?? '') + if (current !== original) { + modified[cellAddr] = currentData['L'][cellAddr] + } + } + + // A1 изменилась (была '', стала 42) + expect(modified['A1']).toBe(42) + // B1 не изменилась (формула та же) + expect(modified['B1']).toBeUndefined() + }) + + it('множественные элементы формы → разные ячейки', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + L: { + A1: '', // ячейка для ввода + B1: '', // ячейка для ввода + A2: '', // ячейка для ввода + B2: '', // ячейка для ввода + // Формулы зависят от ячеек, куда пишутся значения формы + C1: '=СЦЕПИТЬ(A1, " ", B1)', + C2: '=A2 + B2', + }, + }) + + // Первый recalc после загрузки (как в useMultiSheetEngine) + wb.recalc() + + // Элемент формы 1 → A1 (текст) + wb.setCell('L!A1', 'Иванов') + // Элемент формы 2 → B1 (текст) + wb.setCell('L!B1', 'И.И.') + + // Элемент формы 3 → A2 (число) + wb.setCell('L!A2', 10) + // Элемент формы 4 → B2 (число) + wb.setCell('L!B2', 20) + + expect(wb.getValue('L!C1')).toBe('Иванов И.И.') + expect(wb.getValue('L!C2')).toBe(30) + }) + + it('формулы в нижнем регистре в шаблоне (как в реальных данных)', () => { + const wb = createProtocolWorkbook() + + wb.fromJSON({ + R: { + C7: '=случдр(16, 17.6, 3)', + D7: '=если(больше(A1, 100), "Много", "Мало")', + }, + L: { + A1: 50, + }, + }) + + // случдр должна вернуть число в диапазоне + const c7 = wb.getValue('R!C7') as number + expect(c7).toBeGreaterThanOrEqual(16) + expect(c7).toBeLessThanOrEqual(17.6) + + // если + больше в нижнем регистре + expect(wb.getValue('R!D7')).toBe('Мало') + }) +}) + +// ==================== CellUtils ==================== + +describe('CellUtils', () => { + it('indexToColumn', () => { + expect(CellUtils.indexToColumn(0)).toBe('A') + expect(CellUtils.indexToColumn(25)).toBe('Z') + expect(CellUtils.indexToColumn(26)).toBe('AA') + }) + + it('createCellName с номером колонки', () => { + expect(CellUtils.createCellName(0, 1)).toBe('A1') + expect(CellUtils.createCellName(2, 5)).toBe('C5') + }) + + it('createCellName со строкой колонки', () => { + expect(CellUtils.createCellName('B', 3)).toBe('B3') + }) +}) diff --git a/src/feature/excelEngine/excelEngine.ts b/src/feature/excelEngine/excelEngine.ts index 8bc32a2..b65b745 100644 --- a/src/feature/excelEngine/excelEngine.ts +++ b/src/feature/excelEngine/excelEngine.ts @@ -123,7 +123,7 @@ export class Cell { // Обрабатываем ЕПУСТО до замены ссылок на значения, // чтобы проверить raw-значение ячейки напрямую code = code.replace( - /ЕПУСТО\s*\(\s*(?:([A-Za-z0-9_]+)!)?([A-Z](?:[1-9]|[1-8][0-9]|150))\s*\)/g, + /ЕПУСТО\s*\(\s*(?:([A-Za-z0-9_]+)!)?([A-Za-z](?:[1-9]|[1-8][0-9]|150))\s*\)/gi, (_match, sheetName, cellRef) => { const qualifiedRef = sheetName ? `${sheetName}!${cellRef}` @@ -204,12 +204,18 @@ export class Cell { // Заменяем функции Excel const functions = this.sheet.workbook.getFunctions() - for (const [funcName] of Object.entries(functions)) { + // Сортируем функции по длине имени (от длинных к коротким), + // чтобы ЕСЛИ заменилось раньше И, и не было частичных замен + const sortedFuncNames = Object.keys(functions).sort( + (a, b) => b.length - a.length + ) + + for (const funcName of sortedFuncNames) { // Экранируем специальные символы в названии функции для регулярного выражения const escapedFuncName = funcName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - // Для кириллических символов \b может не работать, используем более универсальное решение + // Используем юникодные границы: перед функцией не должно быть буквы/цифры (включая кириллицу) const funcRegex = new RegExp( - `(?:^|[^\\w])(${escapedFuncName})\\s*\\(`, + `(?:^|[^\\wА-Яа-яЁё])(${escapedFuncName})\\s*\\(`, 'gi' ) const before = code diff --git a/src/feature/excelEngine/functions.test.ts b/src/feature/excelEngine/functions.test.ts new file mode 100644 index 0000000..ff39656 --- /dev/null +++ b/src/feature/excelEngine/functions.test.ts @@ -0,0 +1,354 @@ +import { describe, it, expect } from 'vitest' +import { DEFAULT_FUNCTIONS } from './functions' + +describe('DEFAULT_FUNCTIONS', () => { + // ==================== Математические ==================== + + describe('СУММ', () => { + it('суммирует числа', () => { + expect(DEFAULT_FUNCTIONS.СУММ(1, 2, 3)).toBe(6) + }) + it('суммирует массив', () => { + expect(DEFAULT_FUNCTIONS.СУММ([1, 2, 3])).toBe(6) + }) + it('игнорирует не-числа', () => { + expect(DEFAULT_FUNCTIONS.СУММ(1, 'abc', 3)).toBe(4) + }) + it('возвращает 0 без аргументов', () => { + expect(DEFAULT_FUNCTIONS.СУММ()).toBe(0) + }) + }) + + describe('СРЗНАЧ', () => { + it('считает среднее', () => { + expect(DEFAULT_FUNCTIONS.СРЗНАЧ(2, 4, 6)).toBe(4) + }) + it('игнорирует не-числа', () => { + expect(DEFAULT_FUNCTIONS.СРЗНАЧ(2, 'abc', 4)).toBe(3) + }) + it('возвращает 0 если нет чисел', () => { + expect(DEFAULT_FUNCTIONS.СРЗНАЧ('a', 'b')).toBe(0) + }) + }) + + describe('СЧЁТ', () => { + it('считает количество чисел', () => { + expect(DEFAULT_FUNCTIONS['СЧЁТ'](1, 'a', 2, null, 3)).toBe(3) + }) + it('возвращает 0 без чисел', () => { + expect(DEFAULT_FUNCTIONS['СЧЁТ']('a', 'b')).toBe(0) + }) + }) + + describe('МАКС', () => { + it('возвращает максимум', () => { + expect(DEFAULT_FUNCTIONS.МАКС(3, 7)).toBe(7) + }) + it('ошибка при не-числах', () => { + expect(DEFAULT_FUNCTIONS.МАКС('a' as any, 1)).toBe('#ЗНАЧ!') + }) + it('ошибка при Infinity', () => { + expect(DEFAULT_FUNCTIONS.МАКС(Infinity, 1)).toBe('#ЧИСЛО!') + }) + }) + + describe('МИН', () => { + it('возвращает минимум', () => { + expect(DEFAULT_FUNCTIONS.МИН(3, 7, 1)).toBe(1) + }) + it('работает с массивом', () => { + expect(DEFAULT_FUNCTIONS.МИН([3, 7, 1])).toBe(1) + }) + it('возвращает 0 без чисел', () => { + expect(DEFAULT_FUNCTIONS.МИН('a', 'b')).toBe(0) + }) + }) + + describe('ABS', () => { + it('возвращает модуль', () => { + expect(DEFAULT_FUNCTIONS.ABS(-5)).toBe(5) + expect(DEFAULT_FUNCTIONS.ABS(5)).toBe(5) + expect(DEFAULT_FUNCTIONS.ABS(0)).toBe(0) + }) + it('ошибка при не-числе', () => { + expect(DEFAULT_FUNCTIONS.ABS('abc' as any)).toBe('#ЗНАЧ!') + }) + it('ошибка при Infinity', () => { + expect(DEFAULT_FUNCTIONS.ABS(Infinity)).toBe('#ЧИСЛО!') + }) + }) + + describe('КОРЕНЬ', () => { + it('возвращает корень', () => { + expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](9)).toBe(3) + expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](0)).toBe(0) + }) + it('ошибка при отрицательном числе', () => { + expect(DEFAULT_FUNCTIONS['КОРЕНЬ'](-4)).toBe('#ЧИСЛО!') + }) + it('ошибка при не-числе', () => { + expect(DEFAULT_FUNCTIONS['КОРЕНЬ']('abc' as any)).toBe('#ЗНАЧ!') + }) + }) + + describe('СТЕПЕНЬ', () => { + it('возводит в степень', () => { + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](2, 3)).toBe(8) + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](5, 0)).toBe(1) + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](9, 0.5)).toBe(3) + }) + it('ошибка при не-числе', () => { + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ']('a' as any, 2)).toBe('#ЗНАЧ!') + }) + it('ошибка при Infinity', () => { + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](Infinity, 2)).toBe('#ЧИСЛО!') + }) + it('ошибка при переполнении', () => { + expect(DEFAULT_FUNCTIONS['СТЕПЕНЬ'](10, 1000)).toBe('#ЧИСЛО!') + }) + }) + + // ==================== Сравнение ==================== + + describe('БОЛЬШЕ', () => { + it('возвращает true когда первое больше', () => { + expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](5, 3)).toBe(true) + }) + it('возвращает false когда первое меньше', () => { + expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](3, 5)).toBe(false) + }) + it('возвращает false при равенстве', () => { + expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](5, 5)).toBe(false) + }) + it('ошибка при не-числах', () => { + expect(DEFAULT_FUNCTIONS['БОЛЬШЕ']('a' as any, 1)).toBe('#ЗНАЧ!') + }) + it('ошибка при Infinity', () => { + expect(DEFAULT_FUNCTIONS['БОЛЬШЕ'](Infinity, 1)).toBe('#ЧИСЛО!') + }) + }) + + describe('МЕНЬШЕ', () => { + it('возвращает true когда первое меньше', () => { + expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](3, 5)).toBe(true) + }) + it('возвращает false когда первое больше', () => { + expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](5, 3)).toBe(false) + }) + it('возвращает false при равенстве', () => { + expect(DEFAULT_FUNCTIONS['МЕНЬШЕ'](5, 5)).toBe(false) + }) + it('ошибка при не-числах', () => { + expect(DEFAULT_FUNCTIONS['МЕНЬШЕ']('a' as any, 1)).toBe('#ЗНАЧ!') + }) + }) + + describe('ЕПУСТО', () => { + it('true для null', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО'](null)).toBe(true) + }) + it('true для undefined', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО'](undefined)).toBe(true) + }) + it('true для пустой строки', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО']('')).toBe(true) + }) + it('false для числа', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО'](0)).toBe(false) + }) + it('false для строки', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО']('текст')).toBe(false) + }) + it('false для false', () => { + expect(DEFAULT_FUNCTIONS['ЕПУСТО'](false)).toBe(false) + }) + }) + + // ==================== Логические ==================== + + describe('ЕСЛИ', () => { + it('возвращает значение при истине', () => { + expect(DEFAULT_FUNCTIONS['ЕСЛИ'](true, 'да', 'нет')).toBe('да') + }) + it('возвращает значение при лжи', () => { + expect(DEFAULT_FUNCTIONS['ЕСЛИ'](false, 'да', 'нет')).toBe('нет') + }) + }) + + describe('И', () => { + it('true когда все true', () => { + expect(DEFAULT_FUNCTIONS['И'](true, true, true)).toBe(true) + }) + it('false когда хоть один false', () => { + expect(DEFAULT_FUNCTIONS['И'](true, false, true)).toBe(false) + }) + }) + + describe('ИЛИ', () => { + it('true когда хоть один true', () => { + expect(DEFAULT_FUNCTIONS['ИЛИ'](false, true, false)).toBe(true) + }) + it('false когда все false', () => { + expect(DEFAULT_FUNCTIONS['ИЛИ'](false, false, false)).toBe(false) + }) + }) + + describe('НЕ', () => { + it('инвертирует true', () => { + expect(DEFAULT_FUNCTIONS['НЕ'](true)).toBe(false) + }) + it('инвертирует false', () => { + expect(DEFAULT_FUNCTIONS['НЕ'](false)).toBe(true) + }) + }) + + // ==================== Строковые ==================== + + describe('СЦЕПИТЬ', () => { + it('объединяет строки', () => { + expect(DEFAULT_FUNCTIONS['СЦЕПИТЬ']('Привет', ' ', 'мир')).toBe( + 'Привет мир' + ) + }) + }) + + describe('ДЛСТР', () => { + it('возвращает длину строки', () => { + expect(DEFAULT_FUNCTIONS['ДЛСТР']('hello')).toBe(5) + }) + it('возвращает 0 для пустого', () => { + expect(DEFAULT_FUNCTIONS['ДЛСТР']('')).toBe(0) + }) + }) + + describe('ПРОПИСН', () => { + it('переводит в верхний регистр', () => { + expect(DEFAULT_FUNCTIONS['ПРОПИСН']('hello')).toBe('HELLO') + }) + }) + + describe('СТРОЧН', () => { + it('переводит в нижний регистр', () => { + expect(DEFAULT_FUNCTIONS['СТРОЧН']('HELLO')).toBe('hello') + }) + }) + + describe('СЖПРОБЕЛЫ', () => { + it('удаляет пробелы по краям', () => { + expect(DEFAULT_FUNCTIONS['СЖПРОБЕЛЫ'](' hello ')).toBe('hello') + }) + }) + + // ==================== Округление ==================== + + describe('ОКРУГЛ', () => { + it('округляет до целого', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.7)).toBe(4) + expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.2)).toBe(3) + }) + it('округляет до разрядов', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛ'](3.456, 2)).toBe(3.46) + }) + }) + + describe('ОКРУГЛВВЕРХ', () => { + it('округляет вверх', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛВВЕРХ'](3.1)).toBe(4) + }) + it('округляет вверх с разрядами', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛВВЕРХ'](3.411, 1)).toBe(3.5) + }) + }) + + describe('ОКРУГЛВНИЗ', () => { + it('округляет вниз', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛВНИЗ'](3.9)).toBe(3) + }) + }) + + describe('ОКРВВЕРХ', () => { + it('округляет вверх до кратного', () => { + expect(DEFAULT_FUNCTIONS['ОКРВВЕРХ'](7, 5)).toBe(10) + }) + it('возвращает 0 при кратном 0', () => { + expect(DEFAULT_FUNCTIONS['ОКРВВЕРХ'](7, 0)).toBe(0) + }) + }) + + describe('ОКРВНИЗ', () => { + it('округляет вниз до кратного', () => { + expect(DEFAULT_FUNCTIONS['ОКРВНИЗ'](7, 5)).toBe(5) + }) + }) + + describe('ЦЕЛОЕ', () => { + it('отбрасывает дробную часть вниз', () => { + expect(DEFAULT_FUNCTIONS['ЦЕЛОЕ'](3.9)).toBe(3) + expect(DEFAULT_FUNCTIONS['ЦЕЛОЕ'](-3.1)).toBe(-4) + }) + }) + + describe('ОТБР', () => { + it('отбрасывает дробную часть', () => { + expect(DEFAULT_FUNCTIONS['ОТБР'](3.9)).toBe(3) + expect(DEFAULT_FUNCTIONS['ОТБР'](-3.9)).toBe(-3) + }) + it('с разрядами', () => { + expect(DEFAULT_FUNCTIONS['ОТБР'](3.456, 2)).toBe(3.45) + }) + }) + + describe('ОКРУГЛТ', () => { + it('округляет до кратного', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](7, 5)).toBe(5) + expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](8, 5)).toBe(10) + }) + it('возвращает 0 при кратном 0', () => { + expect(DEFAULT_FUNCTIONS['ОКРУГЛТ'](7, 0)).toBe(0) + }) + }) + + // ==================== Случайные ==================== + + describe('СЛУЧ', () => { + it('возвращает число от 0 до 1', () => { + const result = DEFAULT_FUNCTIONS['СЛУЧ']() + expect(result).toBeGreaterThanOrEqual(0) + expect(result).toBeLessThan(1) + }) + }) + + describe('СЛУЧМЕЖДУ', () => { + it('возвращает целое число в диапазоне', () => { + for (let i = 0; i < 20; i++) { + const result = DEFAULT_FUNCTIONS['СЛУЧМЕЖДУ'](1, 10) as number + expect(result).toBeGreaterThanOrEqual(1) + expect(result).toBeLessThanOrEqual(10) + expect(Number.isInteger(result)).toBe(true) + } + }) + }) + + describe('СЛУЧДР', () => { + it('возвращает число в диапазоне', () => { + for (let i = 0; i < 20; i++) { + const result = DEFAULT_FUNCTIONS['СЛУЧДР'](1, 10, 2) as number + expect(result).toBeGreaterThanOrEqual(1) + expect(result).toBeLessThanOrEqual(10) + } + }) + it('ошибка при не-числах', () => { + expect(DEFAULT_FUNCTIONS['СЛУЧДР']('a' as any, 10, 2)).toBe('#ЗНАЧ!') + }) + it('ошибка при Infinity', () => { + expect(DEFAULT_FUNCTIONS['СЛУЧДР'](Infinity, 10, 2)).toBe('#ЧИСЛО!') + }) + it('меняет местами если низ > верх', () => { + for (let i = 0; i < 20; i++) { + const result = DEFAULT_FUNCTIONS['СЛУЧДР'](10, 1, 0) as number + expect(result).toBeGreaterThanOrEqual(1) + expect(result).toBeLessThanOrEqual(10) + } + }) + }) +})