From 42e08bf439323ff13c72d680ffb2695af4abe312 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:42:45 +0000 Subject: [PATCH 01/10] Initial plan From c868d8697efe1065dc4655106d01ef34a309608f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:46:40 +0000 Subject: [PATCH 02/10] Initial plan for BaseSimpleTable refactoring to wrap RenderTable Co-authored-by: JonnyTran <4750391+JonnyTran@users.noreply.github.com> --- extralit-frontend/package-lock.json | 630 +++++++++++++++++++++++++++- 1 file changed, 627 insertions(+), 3 deletions(-) diff --git a/extralit-frontend/package-lock.json b/extralit-frontend/package-lock.json index 8f7f5be3c..dcbfd6729 100644 --- a/extralit-frontend/package-lock.json +++ b/extralit-frontend/package-lock.json @@ -3468,6 +3468,128 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@interactjs/types": { "version": "1.10.27", "resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz", @@ -6507,6 +6629,28 @@ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "dev": true }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, "node_modules/@open-draft/until": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", @@ -7563,6 +7707,15 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", @@ -15961,10 +16114,11 @@ "dev": true }, "node_modules/graphql": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", - "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -26225,6 +26379,15 @@ "node": ">= 4" } }, + "node_modules/rettime": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", + "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -27883,6 +28046,21 @@ "integrity": "sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==", "license": "MIT" }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -28317,6 +28495,30 @@ "@popperjs/core": "^2.9.0" } }, + "node_modules/tldts": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tldts-core": "^7.0.19" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -29127,6 +29329,18 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, "node_modules/upath": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", @@ -30279,6 +30493,15 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/vite-node/node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", @@ -30314,6 +30537,40 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vite-node/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/vite-node/node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/vite-node/node_modules/vite": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", @@ -30389,6 +30646,21 @@ } } }, + "node_modules/vite-node/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -30475,6 +30747,35 @@ "vitest": ">=2.0.0" } }, + "node_modules/vitest/node_modules/@mswjs/interceptors": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz", + "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/vitest/node_modules/@vitest/mocker": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", @@ -30502,6 +30803,90 @@ } } }, + "node_modules/vitest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vitest/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vitest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/vitest/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/vitest/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/vitest/node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -30527,6 +30912,62 @@ } } }, + "node_modules/vitest/node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/vitest/node_modules/msw": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.3.tgz", + "integrity": "sha512-/5rpGC0eK8LlFqsHaBmL19/PVKxu/CCt8pO1vzp9X6SDLsRDh/Ccudkf3Ur5lyaKxJz9ndAx+LaThdv0ySqB6A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.40.0", + "@open-draft/deferred-promise": "^2.2.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.0.2", + "graphql": "^16.12.0", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.7.0", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^5.2.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vitest/node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -30547,6 +30988,94 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vitest/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/vitest/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vitest/node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/vitest/node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vitest/node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/vitest/node_modules/type-fest": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.2.0.tgz", + "integrity": "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "peer": true, + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/vitest/node_modules/vite": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", @@ -30622,6 +31151,86 @@ } } }, + "node_modules/vitest/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/vitest/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/vitest/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -33224,6 +33833,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-js-esm": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/zip-js-esm/-/zip-js-esm-1.1.1.tgz", From 7543c89ac0ae171baef4f4e0357d3dac9181e400 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:52:20 +0000 Subject: [PATCH 03/10] Refactor BaseSimpleTable to wrap RenderTable for optional editing and validation Co-authored-by: JonnyTran <4750391+JonnyTran@users.noreply.github.com> --- .../base-simple-table/BaseSimpleTable.test.ts | 463 +++++++++++++----- .../base-simple-table/BaseSimpleTable.vue | 338 +++++++++++-- 2 files changed, 639 insertions(+), 162 deletions(-) diff --git a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.test.ts b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.test.ts index 42ac85af3..490eb6b04 100644 --- a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.test.ts +++ b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.test.ts @@ -1,4 +1,4 @@ -import { mount } from "@vue/test-utils"; +import { mount, shallowMount } from "@vue/test-utils"; import BaseSimpleTable from "./BaseSimpleTable.vue"; // Mock Tabulator @@ -31,9 +31,19 @@ jest.mock("tabulator-tables", () => ({ toggleColumn: jest.fn(), blockRedraw: jest.fn(), restoreRedraw: jest.fn(), + validate: jest.fn(() => true), + on: jest.fn(), + extendModule: jest.fn(), })), })); +// Mock RenderTable component +jest.mock("~/components/base/base-render-table/RenderTable.vue", () => ({ + name: "RenderTable", + template: '
', + props: ["tableJSON", "editable", "hasValidValues", "questions"], +})); + describe("BaseSimpleTable", () => { const mockColumns = [ { @@ -60,147 +70,372 @@ describe("BaseSimpleTable", () => { { name: "Jane Smith", age: 25, email: "jane@example.com" }, ]; - it("renders without crashing", () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - }, + const mockTableJSON = { + schema: { + fields: [ + { name: "name", type: "string" }, + { name: "age", type: "integer" }, + ], + schemaName: "test-schema", + primaryKey: [], + }, + data: mockData, + }; + + describe("read-only mode (default)", () => { + it("renders without crashing", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); + + expect(wrapper.exists()).toBe(true); + expect(wrapper.find(".tabulator-container").exists()).toBe(true); }); - expect(wrapper.exists()).toBe(true); - expect(wrapper.find(".tabulator-container").exists()).toBe(true); - }); + it("accepts columns and data props", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); - it("accepts columns and data props", () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - }, + expect(wrapper.props("columns")).toEqual(mockColumns); + expect(wrapper.props("data")).toEqual(mockData); }); - expect(wrapper.props("columns")).toEqual(mockColumns); - expect(wrapper.props("data")).toEqual(mockData); - }); + it("accepts options prop", () => { + const options = { + height: 400, + pagination: true, + paginationSize: 10, + }; - it("accepts options prop", () => { - const options = { - height: 400, - pagination: true, - paginationSize: 10, - }; + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + options, + }, + }); - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - options, - }, + expect(wrapper.props("options")).toEqual(options); }); - expect(wrapper.props("options")).toEqual(options); - }); + it("accepts loading prop", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + loading: true, + }, + }); + + expect(wrapper.props("loading")).toBe(true); + }); + + it("processes columns correctly", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); + + const processedColumns = (wrapper.vm as any).processedColumns; + + expect(processedColumns).toHaveLength(3); + expect(processedColumns[0]).toMatchObject({ + field: "name", + title: "Name", + headerSort: true, + headerFilter: "input", + }); + expect(processedColumns[1]).toMatchObject({ + field: "age", + title: "Age", + width: 100, + headerSort: true, + }); + expect(processedColumns[2]).toMatchObject({ + field: "email", + title: "Email", + headerFilter: "input", + }); + }); + + it("provides public API methods", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); + + const vm = wrapper.vm as any; - it("accepts loading prop", () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - loading: true, - }, + // Test that all public methods exist + expect(typeof vm.getData).toBe("function"); + expect(typeof vm.getSelectedData).toBe("function"); + expect(typeof vm.getSelectedRows).toBe("function"); + expect(typeof vm.selectRow).toBe("function"); + expect(typeof vm.deselectRow).toBe("function"); + expect(typeof vm.addRow).toBe("function"); + expect(typeof vm.updateRow).toBe("function"); + expect(typeof vm.deleteRow).toBe("function"); + expect(typeof vm.clearData).toBe("function"); + expect(typeof vm.setData).toBe("function"); + expect(typeof vm.setFilter).toBe("function"); + expect(typeof vm.clearFilter).toBe("function"); + expect(typeof vm.setSort).toBe("function"); + expect(typeof vm.clearSort).toBe("function"); + expect(typeof vm.redraw).toBe("function"); + expect(typeof vm.scrollToRow).toBe("function"); + expect(typeof vm.scrollToColumn).toBe("function"); + expect(typeof vm.download).toBe("function"); + expect(typeof vm.getRowCount).toBe("function"); + expect(typeof vm.getColumns).toBe("function"); + expect(typeof vm.hideColumn).toBe("function"); + expect(typeof vm.showColumn).toBe("function"); + expect(typeof vm.toggleColumn).toBe("function"); }); - expect(wrapper.props("loading")).toBe(true); + it("emits events correctly", async () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); + + // Simulate table built event + const vm = wrapper.vm as any; + vm.isInitialized = true; + + // Test that events can be emitted + wrapper.vm.$emit("table-built"); + wrapper.vm.$emit("data-loaded", mockData); + wrapper.vm.$emit("data-changed", mockData); + + await wrapper.vm.$nextTick(); + + expect(wrapper.emitted("table-built")).toBeTruthy(); + expect(wrapper.emitted("data-loaded")).toBeTruthy(); + expect(wrapper.emitted("data-changed")).toBeTruthy(); + }); + + it("does not use RenderTable when editable is false", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: false, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.useRenderTable).toBe(false); + }); }); - it("processes columns correctly", () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - }, + describe("new props", () => { + it("accepts editable prop with default false", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); + + expect(wrapper.props("editable")).toBe(false); }); - const processedColumns = (wrapper.vm as any).processedColumns; + it("accepts editable prop set to true", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: true, + }, + }); - expect(processedColumns).toHaveLength(3); - expect(processedColumns[0]).toMatchObject({ - field: "name", - title: "Name", - headerSort: true, - headerFilter: "input", + expect(wrapper.props("editable")).toBe(true); }); - expect(processedColumns[1]).toMatchObject({ - field: "age", - title: "Age", - width: 100, - headerSort: true, + + it("accepts validation prop", () => { + const validation = { columns: {}, index: [], checks: {} }; + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + validation, + }, + }); + + expect(wrapper.props("validation")).toEqual(validation); }); - expect(processedColumns[2]).toMatchObject({ - field: "email", - title: "Email", - headerFilter: "input", + + it("accepts tableJSON prop", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + tableJSON: mockTableJSON, + }, + }); + + expect(wrapper.props("tableJSON")).toEqual(mockTableJSON); + }); + + it("accepts hasValidValues prop", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + hasValidValues: true, + }, + }); + + expect(wrapper.props("hasValidValues")).toBe(true); + }); + + it("accepts questions prop", () => { + const questions = [{ id: "q1", name: "Question 1" }]; + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + questions, + }, + }); + + expect(wrapper.props("questions")).toEqual(questions); }); }); - it("provides public API methods", () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - }, - }); - - const vm = wrapper.vm as any; - - // Test that all public methods exist - expect(typeof vm.getData).toBe("function"); - expect(typeof vm.getSelectedData).toBe("function"); - expect(typeof vm.getSelectedRows).toBe("function"); - expect(typeof vm.selectRow).toBe("function"); - expect(typeof vm.deselectRow).toBe("function"); - expect(typeof vm.addRow).toBe("function"); - expect(typeof vm.updateRow).toBe("function"); - expect(typeof vm.deleteRow).toBe("function"); - expect(typeof vm.clearData).toBe("function"); - expect(typeof vm.setData).toBe("function"); - expect(typeof vm.setFilter).toBe("function"); - expect(typeof vm.clearFilter).toBe("function"); - expect(typeof vm.setSort).toBe("function"); - expect(typeof vm.clearSort).toBe("function"); - expect(typeof vm.redraw).toBe("function"); - expect(typeof vm.scrollToRow).toBe("function"); - expect(typeof vm.scrollToColumn).toBe("function"); - expect(typeof vm.download).toBe("function"); - expect(typeof vm.getRowCount).toBe("function"); - expect(typeof vm.getColumns).toBe("function"); - expect(typeof vm.hideColumn).toBe("function"); - expect(typeof vm.showColumn).toBe("function"); - expect(typeof vm.toggleColumn).toBe("function"); + describe("useRenderTable computed", () => { + it("returns false when editable is false and tableJSON is null", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: false, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.useRenderTable).toBe(false); + }); + + it("returns true when editable is true", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: true, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.useRenderTable).toBe(true); + }); + + it("returns true when tableJSON is provided", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + tableJSON: mockTableJSON, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.useRenderTable).toBe(true); + }); }); - it("emits events correctly", async () => { - const wrapper = mount(BaseSimpleTable, { - propsData: { - columns: mockColumns, - data: mockData, - }, + describe("computedTableJSON", () => { + it("returns tableJSON directly when provided", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + tableJSON: mockTableJSON, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.computedTableJSON).toEqual(mockTableJSON); + }); + + it("converts data/columns to TableData format when editable", () => { + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: true, + }, + }); + + const vm = wrapper.vm as any; + const computed = vm.computedTableJSON; + + expect(computed.schema).toBeDefined(); + expect(computed.schema.fields).toHaveLength(3); + expect(computed.data).toEqual(mockData); + }); + + it("returns null when not using RenderTable", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + editable: false, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.computedTableJSON).toBeNull(); + }); + + it("merges validation into tableJSON when both provided", () => { + const validation = { columns: { name: { dtype: "str" } }, index: [], checks: {} }; + const wrapper = shallowMount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + tableJSON: mockTableJSON, + validation, + }, + }); + + const vm = wrapper.vm as any; + expect(vm.computedTableJSON.validation).toEqual(validation); }); + }); - // Simulate table built event - const vm = wrapper.vm as any; - vm.isInitialized = true; + describe("validateTable method", () => { + it("provides validateTable method", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); - // Test that events can be emitted - wrapper.vm.$emit("table-built"); - wrapper.vm.$emit("data-loaded", mockData); - wrapper.vm.$emit("data-changed", mockData); + const vm = wrapper.vm as any; + expect(typeof vm.validateTable).toBe("function"); + }); - await wrapper.vm.$nextTick(); + it("returns true in simple mode", () => { + const wrapper = mount(BaseSimpleTable, { + propsData: { + columns: mockColumns, + data: mockData, + }, + }); - expect(wrapper.emitted("table-built")).toBeTruthy(); - expect(wrapper.emitted("data-loaded")).toBeTruthy(); - expect(wrapper.emitted("data-changed")).toBeTruthy(); + const vm = wrapper.vm as any; + expect(vm.validateTable()).toBe(true); + }); }); }); diff --git a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue index 18ea00d2a..9a5f33f5f 100644 --- a/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue +++ b/extralit-frontend/components/base/base-simple-table/BaseSimpleTable.vue @@ -1,14 +1,49 @@ - + +