diff --git a/Frontend-Learner/.nuxtrc b/Frontend-Learner/.nuxtrc new file mode 100644 index 00000000..1e1fe833 --- /dev/null +++ b/Frontend-Learner/.nuxtrc @@ -0,0 +1 @@ +setups.@nuxt/test-utils="4.0.0" \ No newline at end of file diff --git a/Frontend-Learner/package-lock.json b/Frontend-Learner/package-lock.json index 311492c7..4736d625 100644 --- a/Frontend-Learner/package-lock.json +++ b/Frontend-Learner/package-lock.json @@ -17,12 +17,24 @@ }, "devDependencies": { "@nuxt/eslint-config": "^1.12.1", + "@nuxt/test-utils": "^4.0.0", "@nuxtjs/i18n": "^10.2.1", + "@playwright/test": "^1.58.2", "@types/node": "^22.19.8", + "@vue/test-utils": "^2.4.6", "eslint": "^9.39.2", - "typescript": "^5.4.5" + "jsdom": "^28.1.0", + "typescript": "^5.4.5", + "vitest": "^4.0.18" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -49,6 +61,64 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -339,12 +409,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -435,9 +505,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -472,6 +542,19 @@ } } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@clack/core": { "version": "1.0.0-alpha.7", "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0-alpha.7.tgz", @@ -517,6 +600,140 @@ "node": ">=10.0.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.28.tgz", + "integrity": "sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@csstools/selector-resolve-nested": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", @@ -1380,6 +1597,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", + "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@heroicons/vue": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.2.0.tgz", @@ -2806,6 +3041,233 @@ "url": "https://dotenvx.com" } }, + "node_modules/@nuxt/test-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-4.0.0.tgz", + "integrity": "sha512-QJfyCiqYxflUKA5xlEGuXdDApTBhJxoPXxYePIDtA90hkmKbhYs/mrMM+Bi9LiUrI/cCJOPRyIx9jOzhMvTIgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/prompts": "1.0.0", + "@nuxt/devtools-kit": "^2.7.0", + "@nuxt/kit": "^3.21.0", + "c12": "^3.3.3", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "estree-walker": "^3.0.3", + "exsolve": "^1.0.8", + "fake-indexeddb": "^6.2.5", + "get-port-please": "^3.2.0", + "h3": "^1.15.5", + "h3-next": "npm:h3@2.0.1-rc.11", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "node-fetch-native": "^1.6.7", + "node-mock-http": "^1.0.4", + "nypm": "^0.6.4", + "ofetch": "^1.5.1", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "radix3": "^1.1.2", + "scule": "^1.3.0", + "std-env": "^3.10.0", + "tinyexec": "^1.0.2", + "ufo": "^1.6.3", + "unplugin": "^3.0.0", + "vitest-environment-nuxt": "^1.0.1", + "vue": "^3.5.27" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@cucumber/cucumber": ">=11.0.0", + "@jest/globals": ">=30.0.0", + "@playwright/test": "^1.43.1", + "@testing-library/vue": "^8.0.1", + "@vue/test-utils": "^2.4.2", + "happy-dom": ">=20.0.11", + "jsdom": ">=27.4.0", + "playwright-core": "^1.43.1", + "vitest": "^4.0.2" + }, + "peerDependenciesMeta": { + "@cucumber/cucumber": { + "optional": true + }, + "@jest/globals": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "@testing-library/vue": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "@vue/test-utils": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright-core": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/@clack/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.0.0.tgz", + "integrity": "sha512-Orf9Ltr5NeiEuVJS8Rk2XTw3IxNC2Bic3ash7GgYeA8LJ/zmSNpSQ/m5UAhe03lA6KFgklzZ5KTHs4OAMA/SAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@nuxt/test-utils/node_modules/@clack/prompts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.0.0.tgz", + "integrity": "sha512-rWPXg9UaCFqErJVQ+MecOaWsozjaxol4yjnmYcGNipAWzdaWa2x+VJmKfGq7L0APwBohQOYdHC+9RO4qRXej+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.0.0", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/devtools-kit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.7.0.tgz", + "integrity": "sha512-MIJdah6CF6YOW2GhfKnb8Sivu6HpcQheqdjOlZqShBr+1DyjtKQbAKSCAyKPaoIzZP4QOo2SmTFV6aN8jBeEIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.19.3", + "execa": "^8.0.1" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.1.tgz", + "integrity": "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.3.3", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.8", + "ignore": "^7.0.5", + "jiti": "^2.6.1", + "klona": "^2.0.6", + "knitwork": "^1.3.0", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^3.0.0", + "scule": "^1.3.0", + "semver": "^7.7.4", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.3", + "unctx": "^2.5.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/h3-next": { + "name": "h3", + "version": "2.0.1-rc.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.11.tgz", + "integrity": "sha512-2myzjCqy32c1As9TjZW9fNZXtLqNedjFSrdFy2AjFBQQ3LzrnGoDdFDYfC0tV2e4vcyfJ2Sfo/F6NQhO2Ly/Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rou3": "^0.7.12", + "srvx": "^0.10.1" + }, + "engines": { + "node": ">=20.11.1" + }, + "peerDependencies": { + "crossws": "^0.4.1" + }, + "peerDependenciesMeta": { + "crossws": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/rc9": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.0.tgz", + "integrity": "sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.5" + } + }, + "node_modules/@nuxt/test-utils/node_modules/srvx": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.10.1.tgz", + "integrity": "sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "srvx": "bin/srvx.mjs" + }, + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/unplugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@nuxt/vite-builder": { "version": "3.20.2", "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.20.2.tgz", @@ -3604,6 +4066,13 @@ "unctx": "^2.4.1" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, "node_modules/@oxc-minify/binding-android-arm64": { "version": "0.102.0", "resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.102.0.tgz", @@ -4698,6 +5167,23 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -5294,6 +5780,13 @@ "integrity": "sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==", "license": "CC0-1.0" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@stylistic/eslint-plugin": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.7.0.tgz", @@ -5326,6 +5819,24 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -5962,6 +6473,127 @@ "integrity": "sha512-qWhDs6yFGR5xDfdrwiSa3CWGIHxD597uGE/A9xGqytBjANvh4rLCTTkq7szhMV4+Ygh+PMS90KVJ8xWG/TkX4w==", "license": "MIT" }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.27", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", @@ -6071,40 +6703,40 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", - "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.26", - "entities": "^7.0.0", + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", - "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.26", - "@vue/shared": "3.5.26" + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", - "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", "license": "MIT", "peer": true, "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.26", - "@vue/compiler-dom": "3.5.26", - "@vue/compiler-ssr": "3.5.26", - "@vue/shared": "3.5.26", + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -6112,13 +6744,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", - "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.26", - "@vue/shared": "3.5.26" + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" } }, "node_modules/@vue/devtools-api": { @@ -6184,55 +6816,67 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz", - "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.26" + "@vue/shared": "3.5.29" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz", - "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.26", - "@vue/shared": "3.5.26" + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz", - "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.26", - "@vue/runtime-core": "3.5.26", - "@vue/shared": "3.5.26", + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz", - "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.26", - "@vue/shared": "3.5.26" + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" }, "peerDependencies": { - "vue": "3.5.26" + "vue": "3.5.29" } }, "node_modules/@vue/shared": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", - "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -6490,6 +7134,16 @@ "devOptional": true, "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-kit": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", @@ -6642,6 +7296,16 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -6952,6 +7616,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7242,6 +7916,24 @@ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", @@ -7592,12 +8284,90 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, + "node_modules/cssstyle": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.1.0.tgz", + "integrity": "sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/db0": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.4.tgz", @@ -7649,6 +8419,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -7913,6 +8690,51 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -7954,9 +8776,9 @@ } }, "node_modules/entities": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", - "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -8721,6 +9543,16 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", @@ -8745,6 +9577,16 @@ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "license": "MIT" }, + "node_modules/fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9241,9 +10083,9 @@ } }, "node_modules/h3": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", - "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", @@ -9251,9 +10093,9 @@ "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.2", + "node-mock-http": "^1.0.4", "radix3": "^1.1.2", - "ufo": "^1.6.1", + "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, @@ -9311,6 +10153,19 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -9395,6 +10250,20 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-shutdown": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz", @@ -9770,6 +10639,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -9900,6 +10776,64 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9929,6 +10863,96 @@ "node": ">=20.0.0" } }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -11696,24 +12720,28 @@ "license": "MIT" }, "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", + "citty": "^0.2.0", "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" + "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12109,6 +13137,32 @@ "node": ">=14.13.0" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -12205,9 +13259,9 @@ "license": "MIT" }, "node_modules/perfect-debounce": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz", - "integrity": "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, "node_modules/picocolors": { @@ -12257,6 +13311,54 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -12957,6 +14059,13 @@ "node": ">= 6" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/protocols": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", @@ -13257,6 +14366,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reserved-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", @@ -13455,6 +14574,13 @@ } } }, + "node_modules/rou3": { + "version": "0.7.12", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", + "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "dev": true, + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -13539,6 +14665,19 @@ "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "license": "BlueOak-1.0.0" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scslre": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", @@ -13561,9 +14700,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13683,6 +14822,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -13841,6 +14987,13 @@ "node": ">=12.0.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -14157,6 +15310,13 @@ "node": ">=16" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/system-architecture": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-0.1.0.tgz", @@ -14500,6 +15660,13 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", @@ -14525,6 +15692,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.23" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14581,6 +15778,19 @@ "node": ">=6" } }, + "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", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -14706,9 +15916,9 @@ } }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, "node_modules/ultrahtml": { @@ -14744,6 +15954,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -15552,6 +16772,95 @@ "@types/estree": "^1.0.0" } }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest-environment-nuxt": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz", + "integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/test-utils": ">=3.13.1" + } + }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", @@ -15559,17 +16868,17 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", - "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", "license": "MIT", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.5.26", - "@vue/compiler-sfc": "3.5.26", - "@vue/runtime-dom": "3.5.26", - "@vue/server-renderer": "3.5.26", - "@vue/shared": "3.5.26" + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" @@ -15589,6 +16898,13 @@ "ufo": "^1.6.1" } }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", + "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", + "dev": true, + "license": "MIT" + }, "node_modules/vue-devtools-stub": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/vue-devtools-stub/-/vue-devtools-stub-0.1.0.tgz", @@ -15689,6 +17005,29 @@ "vue": "^3.5.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -15701,6 +17040,16 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -15726,6 +17075,23 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -15876,6 +17242,13 @@ "node": ">=12" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/Frontend-Learner/package.json b/Frontend-Learner/package.json index 84040469..ea5dc734 100644 --- a/Frontend-Learner/package.json +++ b/Frontend-Learner/package.json @@ -19,9 +19,14 @@ }, "devDependencies": { "@nuxt/eslint-config": "^1.12.1", + "@nuxt/test-utils": "^4.0.0", "@nuxtjs/i18n": "^10.2.1", + "@playwright/test": "^1.58.2", "@types/node": "^22.19.8", + "@vue/test-utils": "^2.4.6", "eslint": "^9.39.2", - "typescript": "^5.4.5" + "jsdom": "^28.1.0", + "typescript": "^5.4.5", + "vitest": "^4.0.18" } } diff --git a/Frontend-Learner/playwright-report/data/103c3642d4d1edd7c1730b14ee41ded575054308.md b/Frontend-Learner/playwright-report/data/103c3642d4d1edd7c1730b14ee41ded575054308.md new file mode 100644 index 00000000..117488a0 --- /dev/null +++ b/Frontend-Learner/playwright-report/data/103c3642d4d1edd7c1730b14ee41ded575054308.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "47" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/20601da3571c83d89e2a0096b1545ed4ec97ad72.png b/Frontend-Learner/playwright-report/data/20601da3571c83d89e2a0096b1545ed4ec97ad72.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/playwright-report/data/20601da3571c83d89e2a0096b1545ed4ec97ad72.png differ diff --git a/Frontend-Learner/playwright-report/data/3217f3003eb290f2a9d76783c0fbacd8faaf9d1b.md b/Frontend-Learner/playwright-report/data/3217f3003eb290f2a9d76783c0fbacd8faaf9d1b.md new file mode 100644 index 00000000..97190eee --- /dev/null +++ b/Frontend-Learner/playwright-report/data/3217f3003eb290f2a9d76783c0fbacd8faaf9d1b.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "43" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/59524cbca819897bf05a6d44e6b2440e09e6b0dd.md b/Frontend-Learner/playwright-report/data/59524cbca819897bf05a6d44e6b2440e09e6b0dd.md new file mode 100644 index 00000000..480f9f79 --- /dev/null +++ b/Frontend-Learner/playwright-report/data/59524cbca819897bf05a6d44e6b2440e09e6b0dd.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "41" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/8bd432871766dfea1ea0a2fa595b0b6da8270587.md b/Frontend-Learner/playwright-report/data/8bd432871766dfea1ea0a2fa595b0b6da8270587.md new file mode 100644 index 00000000..6f19988b --- /dev/null +++ b/Frontend-Learner/playwright-report/data/8bd432871766dfea1ea0a2fa595b0b6da8270587.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "21" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/c1fb5eac6712ad7de52a9dde1e2cf4fcee906540.md b/Frontend-Learner/playwright-report/data/c1fb5eac6712ad7de52a9dde1e2cf4fcee906540.md new file mode 100644 index 00000000..b366c539 --- /dev/null +++ b/Frontend-Learner/playwright-report/data/c1fb5eac6712ad7de52a9dde1e2cf4fcee906540.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "52" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/cd58fe922527196f990eb4a780eaf2a045712652.md b/Frontend-Learner/playwright-report/data/cd58fe922527196f990eb4a780eaf2a045712652.md new file mode 100644 index 00000000..bd88a7e5 --- /dev/null +++ b/Frontend-Learner/playwright-report/data/cd58fe922527196f990eb4a780eaf2a045712652.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "27" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/data/e33ee5fb9cfb38f085380e44b3173639f8f6d3eb.md b/Frontend-Learner/playwright-report/data/e33ee5fb9cfb38f085380e44b3173639f8f6d3eb.md new file mode 100644 index 00000000..725985f8 --- /dev/null +++ b/Frontend-Learner/playwright-report/data/e33ee5fb9cfb38f085380e44b3173639f8f6d3eb.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "29" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/playwright-report/index.html b/Frontend-Learner/playwright-report/index.html new file mode 100644 index 00000000..e5916086 --- /dev/null +++ b/Frontend-Learner/playwright-report/index.html @@ -0,0 +1,85 @@ + + + + + + + + + Playwright Test Report + + + + +
+ + + \ No newline at end of file diff --git a/Frontend-Learner/playwright.config.ts b/Frontend-Learner/playwright.config.ts new file mode 100644 index 00000000..13bb89f4 --- /dev/null +++ b/Frontend-Learner/playwright.config.ts @@ -0,0 +1,57 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * @file playwright.config.ts + * @description ไฟล์ตั้งค่าสำหรับการทำ Automated E2E Testing ด้วย Playwright + */ +export default defineConfig({ + // โฟลเดอร์ที่เก็บไฟล์เทส (ชี้ไปที่โฟลเดอร์ปลายทางที่เราสร้าง) + testDir: './tests/e2e', + + // รันเทสแบบขนาน (พร้อมๆ กันหลายไฟล์) เพื่อให้เสร็จเร็วขึ้น + fullyParallel: true, + + // หากการรันเทสบน CI/CD ล้มเหลว ให้ลองรันซ้ำ 2 ครั้ง + retries: process.env.CI ? 2 : 0, + + // จำนวน Worker ที่ใช้รันเทส + workers: process.env.CI ? 1 : undefined, + + // รูปแบบการแสดงผลลัพธ์ (Reporter) + reporter: 'html', + + use: { + // กำหนดล่วงหน้าว่าเว็บที่เรากำลังจะพุ่งไปหาคือ URL อะไร (พอร์ต 3000 ของ Nuxt) + baseURL: 'http://localhost:3000', + + // ตั้งค่าให้เก็บประวัติแบบติดตามผล (Trace) ถ้าระบบพัง จะได้กลับมาดูได้ + trace: 'on-first-retry', + + // ตั้งค่าเก็บรูปภาพหน้าจอเมื่อพัง (Screenshot on failure) + screenshot: 'only-on-failure' + }, + + // ตั้งค่าอุปกรณ์ที่ใช้ทดสอบ (Browsers) + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + // หากต้องการเทสบน Firefox หรือ Safari สามารถเปิดคอมเมนต์บรรทัดถัดไปได้เลย + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + ], + + // (Optional) หากต้องการให้เปิด Local Server อัตโนมัติก่อนรันเทส สามารถเอาคอมเมนต์ออกได้ + // webServer: { + // command: 'npm run dev', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/error-context.md b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/error-context.md new file mode 100644 index 00000000..b366c539 --- /dev/null +++ b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "52" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/test-failed-1.png b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-1559c-ลัก-Classroom-Basic-Layout--chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/error-context.md b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/error-context.md new file mode 100644 index 00000000..480f9f79 --- /dev/null +++ b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "41" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/test-failed-1.png b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-5f28a-าถึงเนื้อหา-Access-Control--chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/error-context.md b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/error-context.md new file mode 100644 index 00000000..117488a0 --- /dev/null +++ b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "47" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/test-failed-1.png b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/classroom-ระบบห้องเรียนออน-e14d4--หรือ-พื้นที่ทำข้อสอบ-Quiz--chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/error-context.md b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/error-context.md new file mode 100644 index 00000000..97190eee --- /dev/null +++ b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "43" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/test-failed-1.png b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-3f9f9-์ส-Search-Input-ไม่พบข้อมูล-chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/error-context.md b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/error-context.md new file mode 100644 index 00000000..6f19988b --- /dev/null +++ b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "21" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/test-failed-1.png b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-555b9-tegory-Filter-ใน-My-Courses-chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/error-context.md b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/error-context.md new file mode 100644 index 00000000..bd88a7e5 --- /dev/null +++ b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "27" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/test-failed-1.png b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-8d952-น้า-คอร์สของฉัน-My-Courses--chromium/test-failed-1.png differ diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/error-context.md b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/error-context.md new file mode 100644 index 00000000..725985f8 --- /dev/null +++ b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/error-context.md @@ -0,0 +1,65 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e5]: + - generic [ref=e6]: + - generic [ref=e8]: E + - heading "เข้าสู่ระบบ" [level=1] [ref=e9] + - paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ + - generic [ref=e11]: + - generic [ref=e12]: + - generic [ref=e13]: + - generic [ref=e14]: อีเมล + - generic [ref=e15]: + - generic: + - generic: email + - textbox [ref=e16] + - generic [ref=e17]: + - generic [ref=e18]: รหัสผ่าน + - generic [ref=e19]: + - generic: + - generic: lock + - textbox [ref=e20] + - button "visibility" [ref=e21] [cursor=pointer]: + - generic [ref=e22]: visibility + - generic [ref=e23]: + - generic [ref=e24] [cursor=pointer]: + - checkbox "จดจำฉัน" [ref=e26] + - generic [ref=e28]: จดจำฉัน + - link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]: + - /url: /auth/forgot-password + - button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]: + - generic [ref=e31]: เข้าสู่ระบบ + - generic [ref=e32]: + - generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account) + - generic [ref=e34]: + - generic [ref=e35]: studentedtest@example.com + - generic [ref=e36]: + - generic [ref=e37]: "Password:" + - generic [ref=e38]: admin123 + - paragraph [ref=e40]: + - text: ยังไม่มีบัญชีสมาชิก? + - link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]: + - /url: /auth/register + - link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]: + - /url: / + - generic [ref=e44]: ← + - text: กลับไปหน้าแรก + - generic: + - img + - generic: + - generic: + - generic: + - button "Go to parent" [disabled] + - button "Open in editor" + - button "Close" + - generic [ref=e45]: + - button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]: + - img [ref=e47] + - generic "Page load time" [ref=e50]: + - generic [ref=e51]: "29" + - generic [ref=e52]: ms + - button "Toggle Component Inspector" [ref=e54] [cursor=pointer]: + - img [ref=e55] +``` \ No newline at end of file diff --git a/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/test-failed-1.png b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/test-failed-1.png new file mode 100644 index 00000000..065cb49e Binary files /dev/null and b/Frontend-Learner/test-results/dashboard-ระบบหน้าแดชบอร์ด-c6dae-รกของ-Dashboard-โหลดได้ปกติ-chromium/test-failed-1.png differ diff --git a/Frontend-Learner/tests/e2e/browse.spec.ts b/Frontend-Learner/tests/e2e/browse.spec.ts new file mode 100644 index 00000000..4eafffde --- /dev/null +++ b/Frontend-Learner/tests/e2e/browse.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from '@playwright/test'; + +test.describe('หมวดหน้าค้นหาคอร์สและผลลัพธ์ (Discovery & Browse)', () => { + + test('2.1 หน้าแรก (Landing Page) โหลดได้ปกติ', async ({ page }) => { + await page.goto('/'); + + // ยืนยันว่าหน้าเว็บขึ้นจริงๆ ด้วยการจับ Element พื้นฐานอย่าง Logo หรือ Footer หรือแบนเนอร์ + // ตัวอย่างเช่นหาคำว่า "เริ่มเรียนฟรี" หรือหัวข้อแคมเปญที่คุณวางไว้หน้า Landing + const heroTitle = page.locator('h1, h2, .hero-title').first(); + await expect(heroTitle).toBeVisible(); + + // เช็คว่าเมนูด้านบน (Navbar) หน้าหลักแสดงผล โดยเช็คจากโลโก้หรือส่วนหัว + const navBar = page.getByRole('link', { name: 'EduLearn Platform' }).first(); + await expect(navBar).toBeVisible({ timeout: 10000 }); + }); + + test('2.2 ค้นหาหลักสูตร (Search Course)', async ({ page }) => { + // ไปที่หน้ารวมคอร์ส + await page.goto('/browse'); + + // หาช่องค้นหา ด้วย placeholder + const searchInput = page.locator('input[placeholder="ค้นหาคอร์ส..."]').first(); + + // พิมพ์คำค้นหา + await searchInput.fill('การเขียนโปรแกรม'); + await searchInput.press('Enter'); + + // 1) รอให้เว็บโหลดผลการค้นหา และตรวจสอบว่ามีการ์ดคอร์สขึ้น (Course Card) + const searchResults = page.locator('a[href^="/course/"]').first(); + await expect(searchResults).toBeVisible(); + }); + + test('2.3 ตัวกรองหมวดหมู่คอร์ส (Category Filter)', async ({ page }) => { + await page.goto('/browse'); + + // คลิกลองเลือกระบุหมวดหมู่ เช่น การออกแบบ + const categoryButton = page.locator('button').filter({ hasText: 'การออกแบบ' }).first(); + + if (await categoryButton.isVisible()) { + await categoryButton.click(); + + // ดูผลลัพธ์ว่าหน้าเว็บยังแสดงผลการ์ดอยู่ (อาจไม่ได้เปลี่ยน URL) + // ลบเช็ค toHaveURL ออกเพราะระบบอาจจะ filter ด้วย Client-Side แทน + + const courseCard = page.locator('a[href^="/course/"]').first(); + await expect(courseCard).toBeVisible(); + } + }); + +}); diff --git a/Frontend-Learner/tests/e2e/classroom.spec.ts b/Frontend-Learner/tests/e2e/classroom.spec.ts new file mode 100644 index 00000000..136ad904 --- /dev/null +++ b/Frontend-Learner/tests/e2e/classroom.spec.ts @@ -0,0 +1,95 @@ +import { test, expect } from '@playwright/test'; + +const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000'; + +async function waitAppSettled(page: any) { + await page.waitForLoadState('domcontentloaded'); + await page.waitForLoadState('networkidle').catch(() => {}); + await page.waitForTimeout(200); +} + +// ฟังก์ชันจำลองล็อกอิน +async function setupLogin(page: any) { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + await page.locator('input[type="email"]').or(page.getByRole('textbox', { name: /อีเมล|email/i })).first().fill('studentedtest@example.com'); + await page.locator('input[type="password"]').or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i })).first().fill('admin123'); + await page.getByRole('button', { name: /เข้าสู่ระบบ|login/i }).or(page.locator('button[type="submit"]')).first().click(); + + await page.waitForURL('**/dashboard', { timeout: 15_000 }).catch(() => {}); + await waitAppSettled(page); +} + +test.describe('ระบบห้องเรียนออนไลน์ (Classroom & Learning)', () => { + + test.beforeEach(async ({ page }) => { + await setupLogin(page); + }); + + test('6.1 เข้าห้องเรียนหลัก (Classroom Basic Layout)', async ({ page }) => { + // สมมติว่ามี Course ID: 1 ทดสอบแบบเปิดหน้าตรงๆ + await page.goto(`${BASE_URL}/classroom/learning?course_id=1`); + + // 1. โครงร่างของหน้า (Top Bar) ควรมีปุ่มกลับ กับไอคอนแผงด้านข้าง + const backBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'arrow_back' }) }).first(); + await expect(backBtn).toBeVisible({ timeout: 15_000 }); + + const menuCurriculumBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'menu_open' }) }).first(); + await expect(menuCurriculumBtn).toBeVisible({ timeout: 15_000 }); + + // 2. เช็คว่ามีพื้นที่ Sidebar หลักสูตร (CurriculumSidebar Component) โผล่ขึ้นมาหรือมีอยู่ใน DOM + const sidebar = page.locator('.q-drawer').first(); + if (!await sidebar.isVisible()) { + await menuCurriculumBtn.click(); + } + await expect(sidebar).toBeVisible(); + }); + + test('6.2 เช็คสถานะการเข้าถึงเนื้อหา (Access Control)', async ({ page }) => { + // ลองสุ่ม Course ID สูงๆ ที่อาจจะไม่อนุญาตให้เรียน (ไม่มีสิทธิ์) ควรรองรับกล่องแจ้งเตือนด้วย Alert ของระบบ + // ใน learning.vue จะมีการสั่ง `alert(msg)` แต่อาจจะต้องพึ่งกลไก Intercepter + + page.on('dialog', async dialog => { + // หน้าต่าง Alert ถ้ามีสิทธิ์ไม่อนุญาตมันจะเด้งอันนี้ + expect(dialog.message()).toBeTruthy(); + await dialog.accept(); + }); + + await page.goto(`${BASE_URL}/classroom/learning?course_id=99999`); + + // รอดู Loading หายไป + const loadingMask = page.locator('.animate-pulse, .q-spinner'); + await loadingMask.first().waitFor({ state: 'hidden', timeout: 20_000 }).catch(() => {}); + }); + + test('6.3 การแสดงผลช่องวิดีโอ (Video Player) หรือ พื้นที่ทำข้อสอบ (Quiz)', async ({ page }) => { + // เข้าหน้าห้องเรียน Course id: 1 + await page.goto(`${BASE_URL}/classroom/learning?course_id=1`); + + // กรณีที่ 1: อาจแสดง Video ถ้าเป็นบทเรียนวิดีโอ + const videoLocator = page.locator('video').first(); + + // กรณีที่ 2: ถ้าบทแรกเป็น Quiz จะแสดงไอคอนแบบทดสอบ + const quizLocator = page.getByText(/เริ่มทำแบบทดสอบ|แบบทดสอบ/i).first(); + + // กรณีที่ 3: ไม่มีบทเรียนเนื้อหาใดๆ เลยให้แสดง + const errorLocator = page.getByText(/ไม่สามารถเข้าถึง/i).first(); + + try { + await Promise.race([ + videoLocator.waitFor({ state: 'visible', timeout: 20_000 }), + quizLocator.waitFor({ state: 'visible', timeout: 20_000 }), + errorLocator.waitFor({ state: 'visible', timeout: 20_000 }) + ]); + + const isOkay = (await videoLocator.isVisible()) || (await quizLocator.isVisible()) || (await errorLocator.isVisible()); + expect(isOkay).toBeTruthy(); + } catch { + // ถ้าไม่มีเลยใน 20 วิ ถือว่าหน้าอาจจะล้มเหลว หรือเป็น Content เปล่า + // ให้ลอง Capture เพื่อเก็บข้อมูลไปใช้งาน + await page.screenshot({ path: 'tests/e2e/screenshots/classroom-blank-state.png', fullPage: true }); + } + }); + +}); diff --git a/Frontend-Learner/tests/e2e/dashboard.spec.ts b/Frontend-Learner/tests/e2e/dashboard.spec.ts new file mode 100644 index 00000000..ed1b83ce --- /dev/null +++ b/Frontend-Learner/tests/e2e/dashboard.spec.ts @@ -0,0 +1,92 @@ +import { test, expect } from '@playwright/test'; + +const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000'; + +async function waitAppSettled(page: any) { + await page.waitForLoadState('domcontentloaded'); + await page.waitForLoadState('networkidle').catch(() => {}); + await page.waitForTimeout(200); +} + +// ฟังก์ชันจำลองล็อกอิน (เพื่อที่จะเข้า Dashboard ได้) +async function setupLogin(page: any) { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + await page.locator('input[type="email"]').or(page.getByRole('textbox', { name: /อีเมล|email/i })).first().fill('studentedtest@example.com'); + await page.locator('input[type="password"]').or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i })).first().fill('admin123'); + await page.getByRole('button', { name: /เข้าสู่ระบบ|login/i }).or(page.locator('button[type="submit"]')).first().click(); + + await page.waitForURL('**/dashboard', { timeout: 15_000 }).catch(() => {}); + await waitAppSettled(page); +} + +test.describe('ระบบหน้าแดชบอร์ดนักเรียน (Dashboard & My Courses)', () => { + + // บังคับให้ Test ทุกข้อในกลุ่มนี้ ล็อกอินก่อนเสมอ + test.beforeEach(async ({ page }) => { + await setupLogin(page); + }); + + test('5.1 หน้าแรกของ Dashboard โหลดได้ปกติ', async ({ page }) => { + await page.goto(`${BASE_URL}/dashboard`); + await page.waitForTimeout(1000); + + // ตรวจสอบว่ามี UI เบื้องต้น เช่น คำต้อนรับ หรือสรุปสถิติ (ถ้ามี) + const welcomeText = page.getByText(/ยินดีต้อนรับกลับ/i, { exact: false }); + const profileSummary = page.locator('.q-avatar, img[alt*="Profile"], img[src*="avatar"]').first(); + + await expect(welcomeText.first().or(profileSummary)).toBeVisible({ timeout: 10_000 }); + }); + + test('5.2 โหลดหน้า คอร์สของฉัน (My Courses)', async ({ page }) => { + // ลองกดเมนูด้านซ้ายเพื่อนำทาง + const myCoursesMenu = page.getByRole('link', { name: /คอร์สของฉัน|My Courses/i }).first() + .or(page.locator('a[href="/dashboard/my-courses"]').first()); + + await myCoursesMenu.click(); + await page.waitForURL('**/dashboard/my-courses', { timeout: 10_000 }); + + // ตรวจสอบโครงสร้างว่าโหลดเรียบร้อย + const heading = page.locator('h2').filter({ hasText: /คอร์สของฉัน|My Courses/i }).first(); + await expect(heading).toBeVisible(); + + // เช็คว่ามีช่องค้นหาคอร์ส + const searchInput = page.locator('input[placeholder*="ค้นหา"]').first(); + await expect(searchInput).toBeVisible(); + + // เช็คปุ่มเปลี่ยนมุมมอง Grid/List (ดูจากไอคอน) + await expect(page.locator('i.q-icon').filter({ hasText: 'grid_view' }).first()).toBeVisible(); + await expect(page.locator('i.q-icon').filter({ hasText: 'view_list' }).first()).toBeVisible(); + }); + + test('5.3 กรองคอร์สด้วยเมนูหมวดหมู่ (Category Filter) ใน My Courses', async ({ page }) => { + await page.goto(`${BASE_URL}/dashboard/my-courses`); + await page.waitForTimeout(1500); // รอ API หรือ UI Mount นิดนึง + + // มองหาปุ่มตัวกรองทั้งหมด + const filterAllBtn = page.getByRole('button').filter({ hasText: /ทั้งหมด|All/i }).first(); + + if (await filterAllBtn.isVisible()) { + await filterAllBtn.click(); + // เช็คว่าข้อมูลโหลด (หาองค์ประกอบ Loading spinner หรือรอ Layout นิ่ง) + await page.locator('.q-spinner').waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {}); + } + }); + + test('5.4 ลองค้นหาคอร์ส (Search Input) ไม่พบข้อมูล', async ({ page }) => { + await page.goto(`${BASE_URL}/dashboard/my-courses`); + + const searchInput = page.locator('input[placeholder*="ค้นหา"]').first(); + await expect(searchInput).toBeVisible(); + + // ลองพิมพ์ชื่อมั่วๆ + await searchInput.fill('คอร์สที่ไม่มีอยู่จริงแน่นอน1234'); + + // ตรวจสอบว่ามีกล่อง Empty State ขึ้นบอกอธิบาย + const emptyState = page.locator('h3').filter({ hasText: /ไม่พบ|ไม่เจอ|No result/i }).first() + .or(page.locator('i.q-icon').filter({ hasText: 'search_off' })); + + await expect(emptyState.first()).toBeVisible({ timeout: 10_000 }); + }); +}); diff --git a/Frontend-Learner/tests/e2e/forgot-password.spec.ts b/Frontend-Learner/tests/e2e/forgot-password.spec.ts new file mode 100644 index 00000000..24260d3e --- /dev/null +++ b/Frontend-Learner/tests/e2e/forgot-password.spec.ts @@ -0,0 +1,102 @@ +import { test, expect, type Page } from '@playwright/test'; + +const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000'; + +// ✅ หน้าจริงคือ /auth/forgot-password (อ้างอิงจากรูป) +const FORGOT_URL = `${BASE_URL}/auth/forgot-password`; + +async function waitAppSettled(page: Page) { + await page.waitForLoadState('domcontentloaded'); + await page.waitForLoadState('networkidle').catch(() => {}); + await page.waitForTimeout(200); +} + +function emailInput(page: Page) { + // เผื่อบางที input ไม่ได้ type=email แต่เป็น textbox ธรรมดา + return page.locator('input[type="email"]').or(page.getByRole('textbox')).first(); +} + +function submitBtn(page: Page) { + // ปุ่มในรูปเป็น “ส่งลิงก์รีเซ็ต” + return page.getByRole('button', { name: /ส่งลิงก์รีเซ็ต/i }).first(); +} + +function backToLoginLink(page: Page) { + // ในรูปเป็นลิงก์ “กลับไปหน้าเข้าสู่ระบบ” + return page.getByRole('link', { name: /กลับไปหน้าเข้าสู่ระบบ/i }).first(); +} + +test.describe('หน้าลืมรหัสผ่าน (Forgot Password)', () => { + test.beforeEach(async ({ page }) => { + await page.goto(FORGOT_URL, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + }); + + test('3.1 โหลดหน้าลืมรหัสผ่านได้ครบถ้วน (Smoke Test)', async ({ page }) => { + await expect(page.getByRole('heading', { name: /ลืมรหัสผ่าน/i })).toBeVisible(); + await expect(emailInput(page)).toBeVisible(); + await expect(submitBtn(page)).toBeVisible(); + await expect(backToLoginLink(page)).toBeVisible(); + + await page.screenshot({ path: 'tests/e2e/screenshots/forgot-01-smoke.png', fullPage: true }); + }); + + test('3.2 Validation: ใส่อีเมลภาษาไทยแล้วขึ้น Error', async ({ page }) => { + await emailInput(page).fill('ฟฟฟฟ'); + + // trigger blur + await page.getByRole('heading', { name: /ลืมรหัสผ่าน/i }).click(); + + // ข้อความจริงในระบบ “ห้ามใส่ภาษาไทย” + const err = page.getByText(/ห้ามใส่ภาษาไทย/i).first(); + await expect(err).toBeVisible({ timeout: 10_000 }); + + await page.screenshot({ path: 'tests/e2e/screenshots/forgot-02-thai-email.png', fullPage: true }); + }); + + test('3.3 กดลิงก์กลับไปหน้า Login ได้', async ({ page }) => { + await backToLoginLink(page).click(); + await page.waitForURL('**/auth/login', { timeout: 10_000 }); + await expect(page).toHaveURL(/\/auth\/login/i); + + await page.screenshot({ path: 'tests/e2e/screenshots/forgot-03-back-login.png', fullPage: true }); + }); + + test('3.4 ทดลองส่งลิงก์รีเซ็ตรหัสผ่าน (API Mock)', async ({ page }) => { + // ✅ ดัก request แบบกว้างขึ้น: POST ที่ URL มี forgot/reset + await page.route('**/*', async (route) => { + const req = route.request(); + const url = req.url(); + const method = req.method(); + + const looksLikeForgotApi = + method === 'POST' && + /forgot|reset/i.test(url) && + // กันไม่ให้ไป intercept asset + !/\.(png|jpg|jpeg|webp|svg|css|js|map)$/i.test(url); + + if (looksLikeForgotApi) { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ success: true, data: { message: 'Reset link sent' } }), + }); + return; + } + + await route.continue(); + }); + + await emailInput(page).fill('test@gmail.com'); + await submitBtn(page).click(); + + // ✅ ตรวจหน้าสำเร็จตามที่คุณคาดหวัง + await expect(page.getByText(/ส่งลิงก์เรียบร้อยแล้ว/i, { exact: false })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(/กรุณาตรวจสอบกล่องจดหมาย/i, { exact: false })).toBeVisible(); + + // ปุ่ม “ส่งอีกครั้ง” (ถ้ามี) + await expect(page.getByRole('button', { name: /ส่งอีกครั้ง/i })).toBeVisible({ timeout: 10_000 }).catch(() => {}); + + await page.screenshot({ path: 'tests/e2e/screenshots/forgot-04-mock-success.png', fullPage: true }); + }); +}); diff --git a/Frontend-Learner/tests/e2e/login.spec.ts b/Frontend-Learner/tests/e2e/login.spec.ts new file mode 100644 index 00000000..7a033655 --- /dev/null +++ b/Frontend-Learner/tests/e2e/login.spec.ts @@ -0,0 +1,122 @@ +import { test, expect, type Page, type Locator } from '@playwright/test'; + +const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000'; + +// ใช้ account ตามที่คุณให้มา +const EMAIL = 'studentedtest@example.com'; +const PASSWORD = 'admin123'; + +async function waitAppSettled(page: Page) { + await page.waitForLoadState('domcontentloaded'); + await page.waitForLoadState('networkidle').catch(() => {}); + await page.waitForTimeout(200); +} + +function emailLocator(page: Page): Locator { + return page + .locator('input[type="email"]') + .or(page.getByRole('textbox', { name: /อีเมล|email/i })) + .first(); +} + +function passwordLocator(page: Page): Locator { + return page + .locator('input[type="password"]') + .or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i })) + .first(); +} + +function loginButtonLocator(page: Page): Locator { + return page + .getByRole('button', { name: /เข้าสู่ระบบ|login/i }) + .or(page.locator('button[type="submit"]')) + .first(); +} + +async function expectAnyVisible(page: Page, locators: Locator[], timeout = 20_000) { + const start = Date.now(); + while (Date.now() - start < timeout) { + for (const loc of locators) { + try { + if (await loc.isVisible()) return; + } catch {} + } + await page.waitForTimeout(200); + } + throw new Error('None of the expected dashboard locators became visible.'); +} + +test.describe('Login -> Dashboard', () => { + test('Success Login แล้วเข้า /dashboard ได้', async ({ page }) => { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + await emailLocator(page).fill(EMAIL); + await passwordLocator(page).fill(PASSWORD); + await loginButtonLocator(page).click(); + + await page.waitForURL('**/dashboard', { timeout: 25_000 }); + await waitAppSettled(page); + + // ✅ ใช้ Locator ที่พบเจอแน่นอนใน Layout/Page โดยไม่ยึดติดกับภาษาปัจจุบัน (I18n) + const dashboardEvidence = [ + // มองหา Layout container ฝั่ง Dashboard + page.locator('.q-page-container').first(), + page.locator('.q-drawer').first(), + // มองหารูปโปรไฟล์ (UserAvatar) + page.locator('img[src*="avataaars"]').first(), + page.locator('img[alt],[alt="User Avatar"]').first() + ]; + + await expectAnyVisible(page, dashboardEvidence, 20_000); + + await page.screenshot({ path: 'tests/e2e/screenshots/login-to-dashboard.png', fullPage: true }); + }); + + test('Invalid Email - Thai characters (พิมพ์ภาษาไทย)', async ({ page }) => { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + await emailLocator(page).fill('ทดสอบภาษาไทย'); + await passwordLocator(page).fill(PASSWORD); + + const errorHint = page.getByText('ห้ามใส่ภาษาไทย'); + + await expect(errorHint.first()).toBeVisible({ timeout: 12_000 }); + await page.screenshot({ path: 'tests/e2e/screenshots/login-thai-email.png', fullPage: true }); + }); + + test('Invalid Email Format (อีเมลผิดรูปแบบ)', async ({ page }) => { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + // *สำคัญ*: HTML5 จะดักจับ invalid-email-format ตั้งแต่กด Submit (native validation) + // ทำให้ Vue Form ไม่เริ่มทำงาน + // ดังนั้นเพื่อให้ทดสอบเจอ Error จาก useFormValidation จริงๆ เราใช้ 'test@domain' + // ซึ่ง HTML5 ปล่อยผ่าน แต่ /regex/ ของระบบตรวจเจอว่าไม่มี .com + await emailLocator(page).fill('test@domain'); + await passwordLocator(page).fill(PASSWORD); + await loginButtonLocator(page).click(); + await waitAppSettled(page); + + const errorHint = page.getByText('กรุณากรอกอีเมลให้ถูกต้อง (you@example.com)'); + + await expect(errorHint.first()).toBeVisible({ timeout: 12_000 }); + await page.screenshot({ path: 'tests/e2e/screenshots/login-invalid-email.png', fullPage: true }); + }); + + test('Wrong Password (รหัสผ่านผิด หรืออีเมลไม่ถูกต้องในระบบ)', async ({ page }) => { + await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' }); + await waitAppSettled(page); + + await emailLocator(page).fill(EMAIL); + await passwordLocator(page).fill('wrong-password-123'); + await loginButtonLocator(page).click(); + await waitAppSettled(page); + + const errorHint = page.getByText('กรุณาเช็ค Email หรือ รหัสผ่านใหม่อีกครั้ง'); + + await expect(errorHint.first()).toBeVisible({ timeout: 12_000 }); + await page.screenshot({ path: 'tests/e2e/screenshots/login-wrong-password.png', fullPage: true }); + }); +}); diff --git a/Frontend-Learner/tests/e2e/register.spec.ts b/Frontend-Learner/tests/e2e/register.spec.ts new file mode 100644 index 00000000..5d3c6903 --- /dev/null +++ b/Frontend-Learner/tests/e2e/register.spec.ts @@ -0,0 +1,241 @@ +import { test, expect, type Page, type Locator } from '@playwright/test'; + +const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000'; + +async function waitAppSettled(page: Page) { + await page.waitForLoadState('domcontentloaded'); + await page.waitForLoadState('networkidle').catch(() => {}); + await page.waitForTimeout(250); +} + +// ===== Anchors / Scope ===== +function headingRegister(page: Page) { + return page.getByRole('heading', { name: 'สร้างบัญชีผู้ใช้งาน' }); +} + +// ===== Inputs (ตาม snapshot ที่คุณส่งมา) ===== +function usernameInput(page: Page): Locator { + // snapshot: textbox "username" + return page.getByRole('textbox', { name: 'username' }).first(); +} + +function emailInput(page: Page): Locator { + // snapshot: textbox "student@example.com" + return page.getByRole('textbox', { name: 'student@example.com' }).first(); +} + +function prefixCombobox(page: Page): Locator { + // snapshot: combobox มี option นาย/นาง/นางสาว + return page.getByRole('combobox').first(); +} + +function firstNameInput(page: Page): Locator { + // snapshot: label "ชื่อ *" + textbox + return page.getByText(/^ชื่อ\s*\*$/).locator('..').getByRole('textbox').first(); +} + +function lastNameInput(page: Page): Locator { + return page.getByText(/^นามสกุล\s*\*$/).locator('..').getByRole('textbox').first(); +} + +function phoneInput(page: Page): Locator { + return page.getByText(/^เบอร์โทรศัพท์\s*\*$/).locator('..').getByRole('textbox').first(); +} + +function passwordInput(page: Page): Locator { + // snapshot: label "รหัสผ่าน *" + textbox (มีปุ่ม visibility อยู่ข้างๆ) + return page.getByText(/^รหัสผ่าน\s*\*$/).locator('..').getByRole('textbox').first(); +} + +function confirmPasswordInput(page: Page): Locator { + return page.getByText(/^ยืนยันรหัสผ่าน\s*\*$/).locator('..').getByRole('textbox').first(); +} + +function submitButton(page: Page): Locator { + return page.getByRole('button', { name: 'สร้างบัญชี' }); +} + +function loginLink(page: Page): Locator { + return page.getByRole('link', { name: 'เข้าสู่ระบบ' }); +} + +function errorBox(page: Page): Locator { + // ทั้ง field message และ notification/toast/alert + return page.locator( + ['.q-field__messages', '.q-field__bottom', '.text-negative', '.q-notification', '.q-banner', '[role="alert"]'].join( + ', ' + ) + ); +} + +async function pickPrefix(page: Page, value: 'นาย' | 'นาง' | 'นางสาว' = 'นาย') { + const combo = prefixCombobox(page); + + // ถ้าเป็น