diff --git a/package-lock.json b/package-lock.json index cb922690..98b1de86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "preact": "^10.11.3" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@types/enzyme": "^3.10.12", "@types/jest": "^29.2.4", "@types/jest-axe": "^3.5.5", @@ -94,6 +95,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -2642,6 +2644,22 @@ "node": ">=10" } }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rollup/plugin-alias": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz", @@ -2836,6 +2854,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", "dev": true, + "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -2896,7 +2915,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2907,7 +2925,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -3091,6 +3108,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.0.tgz", "integrity": "sha512-AHZtlXAMGkDmyLuLZsRpH3p4G/1iARIwc/T0vIem2YB+xW6pZaXYXzCBnZSF/5fdM97R9QqZWZ+h3iW10XgevQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.47.0", "@typescript-eslint/type-utils": "5.47.0", @@ -3124,6 +3142,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.47.0", "@typescript-eslint/types": "5.47.0", @@ -3278,7 +3297,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -3288,29 +3306,25 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3321,15 +3335,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3342,7 +3354,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -3352,7 +3363,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3361,15 +3371,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3386,7 +3394,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -3400,7 +3407,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3413,7 +3419,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3428,7 +3433,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -3438,15 +3442,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/abab": { "version": "2.0.6", @@ -3459,6 +3461,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3493,7 +3496,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true, - "peer": true, "peerDependencies": { "acorn": "^8" } @@ -3560,6 +3562,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3576,7 +3579,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "peer": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -4127,6 +4129,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -4474,7 +4477,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, - "peer": true, "engines": { "node": ">=6.0" } @@ -5400,6 +5402,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, + "peer": true, "dependencies": { "ansi-colors": "^4.1.1" }, @@ -5424,6 +5427,7 @@ "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", "dev": true, + "peer": true, "dependencies": { "array.prototype.flat": "^1.2.3", "cheerio": "^1.0.0-rc.3", @@ -5539,8 +5543,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/es-shim-unscopables": { "version": "1.0.0", @@ -5676,6 +5679,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.4.0", "@humanwhocodes/config-array": "^0.11.8", @@ -5962,7 +5966,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.8.x" } @@ -6563,8 +6566,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/global-dirs": { "version": "3.0.1", @@ -7596,6 +7598,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.3.1", "@jest/types": "^29.3.1", @@ -8784,7 +8787,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, - "peer": true, "engines": { "node": ">=6.11.5" } @@ -9555,8 +9557,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/nice-try": { "version": "1.0.5", @@ -10321,6 +10322,38 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pngjs": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", @@ -10345,6 +10378,7 @@ "url": "https://tidelift.com/funding/github/npm/postcss" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -11052,6 +11086,7 @@ "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -11636,6 +11671,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "dev": true, + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -12037,7 +12073,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, - "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -12843,7 +12878,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.14", "jest-worker": "^27.4.5", @@ -12878,7 +12912,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -12893,7 +12926,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, - "peer": true, "dependencies": { "randombytes": "^2.1.0" } @@ -13201,6 +13233,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13481,7 +13514,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -13552,7 +13584,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, - "peer": true, "engines": { "node": ">=10.13.0" } @@ -13561,8 +13592,7 @@ "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/whatwg-encoding": { "version": "2.0.0", @@ -13815,6 +13845,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz", "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -15670,6 +15701,15 @@ "rimraf": "^3.0.2" } }, + "@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "requires": { + "playwright": "1.57.0" + } + }, "@rollup/plugin-alias": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz", @@ -15820,6 +15860,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", "dev": true, + "peer": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -15880,7 +15921,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", "dev": true, - "peer": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -15891,7 +15931,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, - "peer": true, "requires": { "@types/eslint": "*", "@types/estree": "*" @@ -16074,6 +16113,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.0.tgz", "integrity": "sha512-AHZtlXAMGkDmyLuLZsRpH3p4G/1iARIwc/T0vIem2YB+xW6pZaXYXzCBnZSF/5fdM97R9QqZWZ+h3iW10XgevQ==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.47.0", "@typescript-eslint/type-utils": "5.47.0", @@ -16091,6 +16131,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.47.0", "@typescript-eslint/types": "5.47.0", @@ -16172,7 +16213,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -16182,29 +16222,25 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true + "dev": true }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true + "dev": true }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true + "dev": true }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -16215,15 +16251,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true + "dev": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16236,7 +16270,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", "dev": true, - "peer": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -16246,7 +16279,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", "dev": true, - "peer": true, "requires": { "@xtuc/long": "4.2.2" } @@ -16255,15 +16287,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true + "dev": true }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16280,7 +16310,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -16294,7 +16323,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -16307,7 +16335,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -16322,7 +16349,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", "dev": true, - "peer": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -16332,15 +16358,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true + "dev": true }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true + "dev": true }, "abab": { "version": "2.0.6", @@ -16352,7 +16376,8 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true + "dev": true, + "peer": true }, "acorn-globals": { "version": "6.0.0", @@ -16377,7 +16402,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true, - "peer": true, "requires": {} }, "acorn-jsx": { @@ -16428,6 +16452,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -16440,7 +16465,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "peer": true, "requires": {} }, "ansi-colors": { @@ -16844,6 +16868,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -17100,8 +17125,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true + "dev": true }, "ci-info": { "version": "3.7.0", @@ -17826,6 +17850,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, + "peer": true, "requires": { "ansi-colors": "^4.1.1" } @@ -17841,6 +17866,7 @@ "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", "dev": true, + "peer": true, "requires": { "array.prototype.flat": "^1.2.3", "cheerio": "^1.0.0-rc.3", @@ -17941,8 +17967,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true + "dev": true }, "es-shim-unscopables": { "version": "1.0.0", @@ -18041,6 +18066,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.4.0", "@humanwhocodes/config-array": "^0.11.8", @@ -18250,8 +18276,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true + "dev": true }, "execa": { "version": "4.1.0", @@ -18699,8 +18724,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true + "dev": true }, "global-dirs": { "version": "3.0.1", @@ -19462,6 +19486,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", "dev": true, + "peer": true, "requires": { "@jest/core": "^29.3.1", "@jest/types": "^29.3.1", @@ -20365,8 +20390,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "peer": true + "dev": true }, "loader-utils": { "version": "3.2.1", @@ -20985,8 +21009,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true + "dev": true }, "nice-try": { "version": "1.0.5", @@ -21551,6 +21574,22 @@ } } }, + "playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.57.0" + } + }, + "playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true + }, "pngjs": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", @@ -21562,6 +21601,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz", "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -21994,7 +22034,8 @@ "preact": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "peer": true }, "prelude-ls": { "version": "1.2.1", @@ -22431,6 +22472,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "dev": true, + "peer": true, "requires": { "fsevents": "~2.3.2" } @@ -22728,7 +22770,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", "dev": true, - "peer": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -23351,7 +23392,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", "dev": true, - "peer": true, "requires": { "@jridgewell/trace-mapping": "^0.3.14", "jest-worker": "^27.4.5", @@ -23365,7 +23405,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "peer": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -23377,7 +23416,6 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, - "peer": true, "requires": { "randombytes": "^2.1.0" } @@ -23596,7 +23634,8 @@ "version": "4.9.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "dev": true + "dev": true, + "peer": true }, "unbox-primitive": { "version": "1.0.2", @@ -23818,7 +23857,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, - "peer": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -23867,8 +23905,7 @@ "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true + "dev": true } } }, @@ -23876,8 +23913,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "peer": true + "dev": true }, "whatwg-encoding": { "version": "2.0.0", diff --git a/package.json b/package.json index 931b1a1c..ede98dff 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "umd:main": "dist/gridjs.umd.js", "source": "index.ts", "devDependencies": { + "@playwright/test": "^1.57.0", "@types/enzyme": "^3.10.12", "@types/jest": "^29.2.4", "@types/jest-axe": "^3.5.5", diff --git a/tests/playwright/Blog/blog.spec.js b/tests/playwright/Blog/blog.spec.js new file mode 100644 index 00000000..9477afef --- /dev/null +++ b/tests/playwright/Blog/blog.spec.js @@ -0,0 +1,440 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/blog"; + +/* +Try to Grab the title from the post Hello, World! +Expected title: Hello, World! +*/ +test("Hello, World!", async ({ page }) => { + await page.goto(url); + + /* + 1) Hello, World! aka getByLabel('Blog recent posts navigation').getByRole('link', { name: 'Hello, World!' }) + 2) aka getByRole('main').getByRole('link', { name: 'Hello, World!' }) + */ + const HelloWorldLink = page + .getByRole("link", { + name: "Hello, World!", + }) + .nth(1); + + await expect(HelloWorldLink).toBeVisible(); + + await HelloWorldLink.click(); + await page.waitForURL(/.*\/blog\/hello-world/); + + const HelloWorldTitle = page.getByRole("heading", { + name: "Hello, World!", + level: 1, + }); + await expect(HelloWorldTitle).toBeVisible(); + + const title = await HelloWorldTitle.textContent(); + expect(title).toBe("Hello, World!"); +}); + +/* +Try to Grab the title from the post Grid.js v3 +Expected title: Grid.js v3 +*/ +test.describe("Blog post: Grid.js v3", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + + /* + 1) Grid.js v3 aka getByLabel('Blog recent posts navigation').getByRole('link', { name: 'Grid.js v3' }) + 2) aka getByRole('main').getByRole('link', { name: 'Grid.js v3' }) + */ + const GridjsLink = page + .getByRole("link", { + name: "Grid.js v3", + }) + .nth(1); + + await expect(GridjsLink).toBeVisible(); + + await GridjsLink.click(); + await page.waitForURL(/.*\/blog\/gridjs-v3/); + }); + + // Grad the h1 title "Grid.js v3" + test("1. Grab the h1 title: Grid.js v3", async ({ page }) => { + const GridjsTitle = page.getByRole("heading", { + name: "Grid.js v3", + level: 1, + }); + await expect(GridjsTitle).toBeVisible(); + + await expect(GridjsTitle).toHaveText("Grid.js v3"); + }); + + // Grad the h2 title "Selection plugin" + test("2. Grab the h2 title: Selection plugin", async ({ page }) => { + const SelectionPluginTitle = page.getByRole("heading", { + name: "Selection plugin", + level: 2, + }); + await expect(SelectionPluginTitle).toBeVisible(); + + await expect(SelectionPluginTitle).toHaveText("Selection plugin"); + }); + + // Grad the h2 title "Lerna" + test("3. Grab the h2 title: Lerna", async ({ page }) => { + const LernaTitle = page.getByRole("heading", { + name: "Lerna", + level: 2, + }); + await expect(LernaTitle).toBeVisible(); + + await expect(LernaTitle).toHaveText("Lerna"); + }); + + test("4. Grab the h2 title: Table width algorithm", async ({ page }) => { + const TableWidthAlgorithmTitle = page.getByRole("heading", { + name: "Table width algorithm", + level: 2, + }); + await expect(TableWidthAlgorithmTitle).toBeVisible(); + + await expect(TableWidthAlgorithmTitle).toHaveText( + "Table width algorithm", + ); + }); +}); + +test.describe("Clicks all links on the blog post page: Grid.js v3", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + + const GridjsLink = page + .getByRole("link", { + name: "Grid.js v3", + }) + .nth(1); + + await expect(GridjsLink).toBeVisible(); + + await GridjsLink.click(); + await page.waitForURL(/.*\/blog\/gridjs-v3/); + }); + + test("1. The Github links of the author: Afshin Mehrabani", async ({ + page, + }) => { + const Authorlink = page + .getByRole("link", { + name: "Afshin Mehrabani", + }) + .nth(1); + await expect(Authorlink).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + Authorlink.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/afshinm"); + }); + + test("2. selection plugin here", async ({ page }) => { + const link = page.getByRole("link", { name: "selection plugin here" }); + await expect(link).toBeVisible(); + + await link.click(); + + // Page Not Found + await expect(page).toHaveURL( + "http://localhost:3000/docs/plugins/selection/index", + ); + + const h1title = page.getByRole("heading", { + name: "Page Not Found", + level: 1, + }); + await expect(h1title).toBeVisible(); + await expect(h1title).toHaveText("Page Not Found"); + }); + + test("3. Lerna", async ({ page }) => { + const link = page.getByRole("link", { name: "Lerna" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://lerna.js.org/"); + }); + + test("4. Older post Hello, World!", async ({ page }) => { + const link = page.getByRole("link", { + name: "Older Post Hello, World! ยป", + }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog/hello-world"); + }); + + test("5. Hello, World! on left side", async ({ page }) => { + const link = page.getByRole("link", { name: "Hello, World!" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog/hello-world"); + }); + + test("6. Selection plugin on right side", async ({ page }) => { + const link = page.getByRole("link", { + name: "Selection plugin", + exact: true, + }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/blog/gridjs-v3#selection-plugin", + ); + }); + + test("7. Lerna on right side", async ({ page }) => { + const link = page.getByRole("link", { name: "Lerna" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/blog/gridjs-v3#lerna", + ); + }); + + test("6. Table width algorithm on right side", async ({ page }) => { + const link = page.getByRole("link", { name: "Table width algorithm" }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/blog/gridjs-v3#table-width-algorithm", + ); + }); + + test("7. announcements", async ({ page }) => { + const link = page.getByRole("link", { name: "announcements" }).first(); + await expect(link).toBeVisible(); + await link.click(); + + await expect(page).toHaveURL( + "http://localhost:3000/blog/tags/announcements", + ); + + // Grab the title: " 2 posts tagged with "announcements" " + const title = page.getByRole("heading", { + name: '2 posts tagged with "announcements"', + level: 1, + }); + await expect(title).toHaveText('2 posts tagged with "announcements"'); + + // test for more: "View All Tags" + const tagsLink = page.getByRole("link", { name: "View All Tags" }); + await expect(tagsLink).toBeVisible(); + await tagsLink.click(); + await expect(page).toHaveURL("http://localhost:3000/blog/tags"); + + // Grab the h1 title "Tags" + const tagsTitle = page.getByRole("heading", { name: "Tags", level: 1 }); + await expect(tagsTitle).toBeVisible(); + await expect(tagsTitle).toHaveText("Tags"); + + // Grab the h2 title "A" + const h2Title = page.getByRole("heading", { name: "A", level: 2 }); + await expect(h2Title).toBeVisible(); + await expect(h2Title).toHaveText("A"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/blog"); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(1); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); + +// test for the color theme switch function +test.describe("Test for the color theme switch function", async () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/blog"); + + // force the website to be light mode initially + await page.emulateMedia({ colorScheme: "light" }); + }); + + //TODO: Implement the test for the color theme switch function + test("Ensure that initialized color scheme is light", async ({ page }) => { + const htmlTag = page.locator("html"); + await expect(htmlTag).toHaveAttribute("data-theme", "light"); + }); + + test("Switch to dark mode", async ({ page }) => { + const htmlTag = page.locator("html"); + await expect(htmlTag).toHaveAttribute("data-theme", "light"); + + // Regex expression ot match the button aria-label after switched to dark mode + const toggleButton = page.getByRole("button", { + name: /Switch between dark and light mode/i, + }); + await expect(toggleButton).toBeVisible(); + await expect(toggleButton).toHaveAttribute( + "aria-label", + "Switch between dark and light mode (currently light mode)", + ); + + await toggleButton.click(); + + await expect(htmlTag).toHaveAttribute("data-theme", "dark"); + await expect(toggleButton).toHaveAttribute( + "aria-label", + "Switch between dark and light mode (currently dark mode)", + ); + }); + + test("Switch back to light mode", async ({ page }) => { + const htmlTag = page.locator("html"); + await expect(htmlTag).toHaveAttribute("data-theme", "light"); + + // First, switch color scheme to dark mode + const toggleButton = page.getByRole("button", { + name: /Switch between dark and light mode/i, + }); + await expect(toggleButton).toBeVisible(); + await expect(toggleButton).toHaveAttribute( + "aria-label", + "Switch between dark and light mode (currently light mode)", + ); + + await toggleButton.click(); + + await expect(htmlTag).toHaveAttribute("data-theme", "dark"); + await expect(toggleButton).toHaveAttribute( + "aria-label", + "Switch between dark and light mode (currently dark mode)", + ); + + // Click again to switch back to light mode + await toggleButton.click(); + + await expect(htmlTag).toHaveAttribute("data-theme", "light"); + await expect(toggleButton).toHaveAttribute( + "aria-label", + "Switch between dark and light mode (currently light mode)", + ); + }); +}); diff --git a/tests/playwright/Dashboard/Config/00_Footer.spec.js b/tests/playwright/Dashboard/Config/00_Footer.spec.js new file mode 100644 index 00000000..ddd35742 --- /dev/null +++ b/tests/playwright/Dashboard/Config/00_Footer.spec.js @@ -0,0 +1,44 @@ +// tests/Footer/footer-mismatch.spec.js +import { test, expect } from "@playwright/test"; + +const HOME = (process.env.BASE_URL || "https://gridjs.io/").replace(/\/+$/, "/"); +const DOCS = "https://gridjs.io/docs"; + +test.describe("Footer regression", () => { + test("should catch footer mismatch between Home and Docs", async ({ page }) => { + const homeFooterLinks = await getFooterLinkTexts(page, HOME, "div.footer_SBgd"); + const docsFooterLinks = await getFooterLinkTexts(page, DOCS, "footer.footer.footer--dark"); + + const homeNorm = normalizeList(homeFooterLinks); + const docsNorm = normalizeList(docsFooterLinks); + + expect( + homeNorm, + `BUG: Footer differs between Home and Docs\n\nHOME:\n- ${homeNorm.join("\n- ")}\n\nDOCS:\n- ${docsNorm.join("\n- ")}\n`, + ).toEqual(docsNorm); + }); +}); + +async function getFooterLinkTexts(page, url, footerSelector) { + await page.goto(url, { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(800); + + const footer = page.locator(footerSelector).first(); + await expect(footer, `Footer not found: ${footerSelector}`).toBeVisible({ timeout: 20_000 }); + + const links = footer.locator("a"); + const count = await links.count(); + + const texts = []; + for (let i = 0; i < count; i++) { + const t = ((await links.nth(i).innerText().catch(() => "")) || "").trim(); + if (t) texts.push(t); + } + return texts; +} + +function normalizeList(arr) { + return arr + .map((s) => s.replace(/\s+/g, " ").trim().toLowerCase()) + .filter(Boolean); +} diff --git a/tests/playwright/Dashboard/Config/00_Navigation.spec.js b/tests/playwright/Dashboard/Config/00_Navigation.spec.js new file mode 100644 index 00000000..7fd6da54 --- /dev/null +++ b/tests/playwright/Dashboard/Config/00_Navigation.spec.js @@ -0,0 +1,122 @@ +// tests/Dashboard/03_Config/00_Navigation.spec.js +import { test, expect } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "https://gridjs.io/docs"; + +test.describe("Grid.js docs: nav + external + theme + search regression", () => { + test.setTimeout(90_000); // biar nggak mati di 30s kalau internet lambat + + test("should navigate, toggle theme, and search should return results for 'Introduction'", async ({ page }, testInfo) => { + const logs = []; + page.on("console", (m) => logs.push(`[console:${m.type()}] ${m.text()}`)); + page.on("pageerror", (e) => logs.push(`[pageerror] ${e.message}`)); + + // kalau ada request yang gagal (misalnya Algolia DSN / CORS), ini bantu banget buat bukti + page.on("requestfailed", (req) => { + logs.push(`[requestfailed] ${req.method()} ${req.url()} :: ${req.failure()?.errorText || ""}`); + }); + + await page.goto(BASE_URL, { waitUntil: "domcontentloaded" }); + + // --- NAVBAR INTERNAL LINKS --- + const internalNav = [ + { name: "Docs", href: "/docs" }, + { name: "Examples", href: "/docs/examples/hello-world" }, + { name: "Support Grid.js", href: "/docs/sponsors" }, + { name: "Community", href: "/docs/community" }, + { name: "Blog", href: "/blog" }, + ]; + + for (const item of internalNav) { + const link = page.locator(`a.navbar__item.navbar__link:has-text("${item.name}")`).first(); + await expect(link).toBeVisible({ timeout: 10_000 }); + await link.click(); + await expect(page).toHaveURL(new RegExp(escapeRegex(item.href), "i"), { timeout: 10_000 }); + } + + await page.goto(BASE_URL, { waitUntil: "domcontentloaded" }); + + // --- EXTERNAL LINKS (NEW TAB) - robust handling --- + const externalNav = [ + { name: "NPM", expectedUrlContains: "npmjs.com/package/gridjs" }, + { name: "GitHub", expectedUrlContains: "github.com/grid-js/gridjs" }, + ]; + + for (const item of externalNav) { + const link = page.locator(`a.navbar__item.navbar__link:has-text("${item.name}")`).first(); + await expect(link).toBeVisible({ timeout: 10_000 }); + + // kadang popup diblokir/lemot => kasih timeout lebih longgar + fallback + let popup = null; + try { + [popup] = await Promise.all( + [ + page.waitForEvent("popup", { timeout: 15_000 }), + link.click(), + ] + ); + } catch (e) { + // fallback: kalau popup nggak kebuka, minimal verifikasi href-nya benar + const href = await link.getAttribute("href"); + logs.push(`[popup-missed] ${item.name} href=${href}`); + expect(href || "").toContain(item.expectedUrlContains.split("/")[0]); // minimal domain check + continue; + } + + await popup.waitForLoadState("domcontentloaded", { timeout: 20_000 }); + const url = popup.url().toLowerCase(); + expect(url).toContain(item.expectedUrlContains.toLowerCase()); + await popup.close(); + await page.bringToFront(); + await expect(page).toHaveURL(/gridjs\.io\/docs/i, { timeout: 10_000 }); + } + + // --- THEME TOGGLE --- + const html = page.locator("html"); + const beforeTheme = await html.getAttribute("data-theme"); + const themeBtn = page.locator('button[aria-label^="Switch between dark and light mode"]').first(); + + await expect(themeBtn).toBeVisible({ timeout: 10_000 }); + await themeBtn.click(); + + await expect.poll(async () => await html.getAttribute("data-theme"), { timeout: 10_000 }) + .not.toBe(beforeTheme); + + // --- SEARCH (DOCSEARCH) --- + const searchBtn = page.locator('button.DocSearch.DocSearch-Button[aria-label="Search"]').first(); + await expect(searchBtn).toBeVisible({ timeout: 10_000 }); + await searchBtn.click(); + + const input = page.locator("input.DocSearch-Input").first(); + await expect(input).toBeVisible({ timeout: 10_000 }); + + await input.fill("Introduction"); + + // โŒ Jangan pakai waitForTimeout -> ganti wait sampai salah satu kondisi muncul: + // - ada results + // - atau "No results for ..." + const noResults = page.locator('text=/No results for\\s+"Introduction"/i'); + const hits = page.locator(".DocSearch-Hits, .DocSearch-Hit-source, .DocSearch-Hit").first(); + + try { + await Promise.race([ + noResults.waitFor({ state: "visible", timeout: 15_000 }), + hits.waitFor({ state: "visible", timeout: 15_000 }), + ]); + + // Ini inti "nangkep bug": kalau muncul noResults, test FAIL. + await expect(noResults, 'BUG: Search shows "No results for \\"Introduction\\""').toHaveCount(0); + } catch (err) { + await page.screenshot({ path: testInfo.outputPath("search-timeout-or-failure.png"), fullPage: true }); + testInfo.attach("console-log.txt", { + body: logs.join("\n") || "(no logs)", + contentType: "text/plain", + }); + throw err; + } + }); +}); + +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} diff --git a/tests/playwright/Dashboard/Config/01_Data.spec.js b/tests/playwright/Dashboard/Config/01_Data.spec.js new file mode 100644 index 00000000..0d8e9195 --- /dev/null +++ b/tests/playwright/Dashboard/Config/01_Data.spec.js @@ -0,0 +1,83 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Data page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + + // Step 4: Click "data" submenu + const dataSubmenu = page.locator('a.menu__link[href="/docs/config/data"]'); + await expect(dataSubmenu).toBeVisible(); + await dataSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 6: Click "Documentation" again + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Repeat "๐Ÿ›  Config" โ†’ "data" + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(dataSubmenu).toBeVisible(); + await dataSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click all example links + const exampleLinks = [ + '/docs/examples/hello-world', + '/docs/examples/import-json', + '/docs/examples/import-async', + '/docs/examples/import-function' + ]; + for (const href of exampleLinks) { + const link = page.locator(`a[href="${href}"]`).first(); + await expect(link).toBeVisible(); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 9: Copy buttons + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + await expect(copyButtons.nth(0)).toBeVisible(); + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(0).click(); + await copyButtons.nth(1).click(); + + // Step 10: Click edit page + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + + // Step 11: Pagination + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.goBack(); + await expect(next).toBeVisible(); + await next.click(); + await page.goBack(); +}); diff --git a/tests/playwright/Dashboard/Config/02_From.spec.js b/tests/playwright/Dashboard/Config/02_From.spec.js new file mode 100644 index 00000000..a4ced91d --- /dev/null +++ b/tests/playwright/Dashboard/Config/02_From.spec.js @@ -0,0 +1,68 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” /config/from page interactions', async ({ page }) => { + // Step 1: Visit homepage + await page.goto('https://gridjs.io'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" + const configLink = page.locator('a.menu__link:has-text("Config")'); + await expect(configLink).toBeVisible(); + await configLink.click(); + + // Step 4: Click "from" submenu + const fromSubmenu = page.locator('a.menu__link[href="/docs/config/from"]'); + await expect(fromSubmenu).toBeVisible(); + await fromSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 6: Click "Documentation" lagi + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Click Config โ†’ From again + await expect(configLink).toBeVisible(); + await configLink.click(); + await expect(fromSubmenu).toBeVisible(); + await fromSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click blue link "From HTML table" + const fromHtml = page.locator('a:has-text("From HTML table")'); + await fromHtml.scrollIntoViewIfNeeded(); + await expect(fromHtml).toBeVisible({ timeout: 5000 }); + await fromHtml.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + + // Step 9: Edit this page + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + + // Step 10: Pagination + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.goBack(); + await expect(next).toBeVisible(); + await next.click(); + await page.goBack(); +}); diff --git a/tests/playwright/Dashboard/Config/03_Columns.spec.js b/tests/playwright/Dashboard/Config/03_Columns.spec.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/playwright/Dashboard/Config/03_Columns.spec.js @@ -0,0 +1,95 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Columns page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "columns" submenu + const columnsSubmenu = page.locator('a.menu__link[href="/docs/config/columns"]'); + await expect(columnsSubmenu).toBeVisible(); + await columnsSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 6: Click "Documentation" again + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Repeat "๐Ÿ›  Config" โ†’ "columns" + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(columnsSubmenu).toBeVisible(); + await columnsSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click all example links on the page (if any) + // We target links that start with /docs/examples/ + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + // remove target if it would open a new tab + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 9: Copy buttons in code blocks (there are code blocks on this page) + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + // Ensure at least the first copy button exists before interacting + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + // If there's a second copy button, click it too (like other pages) + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 10: Click "Edit this page" (remove target before clicking) + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + // Step 11: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/04_Server.spec.js b/tests/playwright/Dashboard/Config/04_Server.spec.js new file mode 100644 index 00000000..81625948 --- /dev/null +++ b/tests/playwright/Dashboard/Config/04_Server.spec.js @@ -0,0 +1,100 @@ +// tests/02_Dashboard/01_Navigation/01_Positive/03_Server.js +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” config/server, klik contoh & selalu back', async ({ page }) => { + // helper kembali ke /docs/config/server + const backToServer = async () => { + await page.goBack(); + await expect(page).toHaveURL(/\/docs\/config\/server\/?$/i); + await expect(page.getByRole('heading', { name: /^server$/i, level: 1 })).toBeVisible(); + }; + + // 1) Home + await page.goto('https://gridjs.io'); + await expect(page).toHaveTitle(/Grid\.js/i); + + // 2) Docs + await page.locator('a[href="/docs"]').first().click(); + await expect(page).toHaveURL(/\/docs\/?$/); + + // 3) Sidebar "Config" + const config = page.locator('a.menu__link:has-text("Config")'); + await expect(config).toBeVisible(); + await config.click(); + + // 4) Ke "server" + const serverMenu = page.locator('a.menu__link[href="/docs/config/server"]'); + await expect(serverMenu).toBeVisible(); + await serverMenu.click(); + await expect(page).toHaveURL(/\/docs\/config\/server\/?$/i); + await expect(page.getByRole('heading', { name: /^server$/i, level: 1 })).toBeVisible(); + + // 5) Example "Server" -> BACK + await page.locator('a[href="/docs/examples/server"]').first().click(); + await expect(page).toHaveURL(/\/docs\/examples\/server\/?$/i); + await expect(page.getByRole('heading', { name: /Import server-side data/i })).toBeVisible(); + await backToServer(); + + // 6) Example "Server-side search" -> BACK + await page.locator('a[href="/docs/examples/server-side-search"]').first().click(); + await expect(page).toHaveURL(/\/docs\/examples\/server-side-search\/?$/i); + await expect(page.getByRole('heading', { name: /Server Side Search/i })).toBeVisible(); + await backToServer(); + + // 7) Example "Server-side pagination" -> BACK + await page.locator('a[href="/docs/examples/server-side-pagination"]').first().click(); + await expect(page).toHaveURL(/\/docs\/examples\/server-side-pagination\/?$/i); + await expect(page.getByRole('heading', { name: /Server Side Pagination/i })).toBeVisible(); + await backToServer(); + + // 8) Copy code (klik yang ada saja) + const copyBtns = page.locator('span[class*="copyButtonIcons"]'); + const n = await copyBtns.count(); + if (n >= 1) { + await expect(copyBtns.first()).toBeVisible(); + await copyBtns.first().click(); + } + if (n >= 2) { + await expect(copyBtns.nth(1)).toBeVisible(); + await copyBtns.nth(1).click(); + } + + // 9) Edit this page -> handle 2 kemungkinan: langsung editor / halaman login + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); // buka di tab yang sama + await editLink.click(); + + const editRegex = /https:\/\/github\.com\/grid-js\/website\/edit\/master\/docs\/config\/server\.md/i; + const loginRegex = /https:\/\/github\.com\/login\?return_to=.+%2Fgrid-js%2Fwebsite%2Fedit%2Fmaster%2Fdocs%2Fconfig%2Fserver\.md/i; + + await page.waitForURL(u => editRegex.test(u.href) || loginRegex.test(u.href), { timeout: 15000 }); + + if (loginRegex.test(page.url())) { + // >>> FIX: jangan pakai .or() langsung, pilih salah satu selector yang ada <<< + const headingCount = await page.getByRole('heading', { name: /sign in to github/i }).count(); + if (headingCount > 0) { + await expect(page.getByRole('heading', { name: /sign in to github/i })).toBeVisible(); + } else { + await expect(page.locator('input[name="login"]')).toBeVisible(); + } + } else { + await expect(page).toHaveURL(editRegex); + // Pastikan editor (textarea/code mirror) tampil + await expect(page.getByRole('textbox')).toBeVisible(); + } + + // Kembali ke /docs/config/server + await backToServer(); + + // 10) Previous -> columns -> BACK + await page.locator('a.pagination-nav__link--prev').click(); + await expect(page).toHaveURL(/\/docs\/config\/columns\/?$/i); + await backToServer(); + + // 11) Next -> style -> BACK + await page.locator('a.pagination-nav__link--next').click(); + await expect(page).toHaveURL(/\/docs\/config\/style\/?$/i); + await backToServer(); +}); diff --git a/tests/playwright/Dashboard/Config/05_Style.spec.js b/tests/playwright/Dashboard/Config/05_Style.spec.js new file mode 100644 index 00000000..d462709a --- /dev/null +++ b/tests/playwright/Dashboard/Config/05_Style.spec.js @@ -0,0 +1,108 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Style page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "style" submenu + const styleSubmenu = page.locator('a.menu__link[href="/docs/config/style"]'); + await expect(styleSubmenu).toBeVisible(); + await styleSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "style" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible(); + await expect(pageTitle).toHaveText('style'); + + // Step 6: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Click "Documentation" again + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Repeat "๐Ÿ›  Config" โ†’ "style" + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(styleSubmenu).toBeVisible(); + await styleSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Verify properties table contains expected rows (container, table, td, th, header, footer) + const propsTable = page.locator('article table').first(); + await expect(propsTable).toBeVisible(); + await expect(propsTable).toContainText('container'); + await expect(propsTable).toContainText('table'); + await expect(propsTable).toContainText('td'); + await expect(propsTable).toContainText('th'); + await expect(propsTable).toContainText('header'); + await expect(propsTable).toContainText('footer'); + + // Step 10: Click all example links on the page (if any) + // Target links that start with /docs/examples/ + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + // remove target to stay in same tab + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 11: Copy buttons in code blocks + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 12: Click "Edit this page" (remove target before clicking) + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + // Step 13: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/06_ClassName.spec.js b/tests/playwright/Dashboard/Config/06_ClassName.spec.js new file mode 100644 index 00000000..8f2c8466 --- /dev/null +++ b/tests/playwright/Dashboard/Config/06_ClassName.spec.js @@ -0,0 +1,114 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” ClassName page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "className" submenu + const classNameSubmenu = page.locator('a.menu__link[href="/docs/config/className"]'); + await expect(classNameSubmenu).toBeVisible(); + await classNameSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "className" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible(); + await expect(pageTitle).toHaveText('className'); + + // Step 6: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Click "Documentation" again + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Repeat "๐Ÿ›  Config" โ†’ "className" + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(classNameSubmenu).toBeVisible(); + await classNameSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Verify properties table contains expected rows + // (check beberapa properti penting: container, table, td, th, header, footer, thead, tbody, paginationButtonPrev, notfound, error) + const propsTable = page.locator('article table').first(); + await expect(propsTable).toBeVisible(); + await expect(propsTable).toContainText('container'); + await expect(propsTable).toContainText('table'); + await expect(propsTable).toContainText('td'); + await expect(propsTable).toContainText('th'); + await expect(propsTable).toContainText('header'); + await expect(propsTable).toContainText('footer'); + await expect(propsTable).toContainText('thead'); + await expect(propsTable).toContainText('tbody'); + await expect(propsTable).toContainText('paginationButtonPrev'); + await expect(propsTable).toContainText('notfound'); + await expect(propsTable).toContainText('error'); + + // Step 10: Click all example links on the page (if any) + // Target links that start with /docs/examples/ + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + // remove target so it doesn't open a new tab + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 11: Copy buttons in code blocks + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 12: Click "Edit this page" (remove target before clicking) + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + // Step 13: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/07_Language.spec.js b/tests/playwright/Dashboard/Config/07_Language.spec.js new file mode 100644 index 00000000..259e9f8a --- /dev/null +++ b/tests/playwright/Dashboard/Config/07_Language.spec.js @@ -0,0 +1,213 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Language page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "language" submenu + const languageSubmenu = page.locator('a.menu__link[href="/docs/config/language"]'); + await expect(languageSubmenu).toBeVisible(); + await languageSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "language" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible(); + await expect(pageTitle).toHaveText('language'); + + // Step 6: Verify "Locales" link exists, click it and come back + const localesLink = page.locator('article a:has-text("Locales")').first(); + await expect(localesLink).toBeVisible(); + await localesLink.evaluate(el => el.removeAttribute('target')); + await localesLink.click(); + await page.waitForURL(u => /\/docs\/locales/i.test(u.href) || /locales/i.test(u.pathname), { timeout: 15000 }); + await expect(page.locator('article h1')).toBeVisible({ timeout: 15000 }); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toHaveText('language'); + + // Step 7: Verify "en_US" link in TIP exists, click it and come back + const enUsLink = page.locator('article a:has-text("en_US")').first(); + await expect(enUsLink).toBeVisible(); + await enUsLink.evaluate(el => el.removeAttribute('target')); + await enUsLink.click(); + + // Wait for likely destinations (docs, en_US page, GitHub blob/edit, or github.dev) + await page.waitForURL(u => + /en[_-]us/i.test(u.href) || + /en[_-]us/i.test(u.pathname) || + /\/src\/i18n\/en_US\.ts/i.test(u.href) || + /\/docs\/locales/i.test(u.href) || + /github\.com/i.test(u.href) || + /github\.dev/i.test(u.href), + { timeout: 15000 } + ); + + // Decide verification strategy based on current URL + const currentUrl = page.url(); + + if (/github\.dev/i.test(currentUrl)) { + // github.dev -> VSCode-like editor (Monaco): check editor exists + await expect(page.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(currentUrl)) { + // GitHub domain: could be blob, edit, raw, or login + // Try several selectors that indicate file content or edit UI + const fileBlob = page.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container, .repository-content, .js-file-line-container'); + const proposeOrCommit = page.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + const rawPre = page.locator('pre'); // raw file view + + // Wait for any of these to become visible (some might not exist depending on view) + let ok = false; + try { + await expect(fileBlob).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { + // ignore + } + if (!ok) { + try { + await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { + // ignore + } + } + if (!ok) { + try { + await expect(rawPre.first()).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { + // ignore + } + } + + // If nothing matched, at least assert the domain is GitHub and continue + if (!ok) { + // Last resort: check for any body text + await expect(page.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + // Not GitHub: assume docs page -> article should exist + await expect(page.locator('article')).toBeVisible({ timeout: 15000 }); + } + + // Back to language page and re-assert title + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText('language'); + + // Step 8: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Click "Documentation" again and re-open language submenu + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(languageSubmenu).toBeVisible(); + await languageSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 10: Click all example links on the page (if any) + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 11: Copy buttons in code blocks + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 12: Click "Edit this page" (remove target before clicking), then handle GitHub destinations and come back + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + + await page.waitForURL(url => { + const href = url.href; + return /github\.com\/login/i.test(href) || + /github\.dev/i.test(href) || + /\/edit\/(master|main)\//i.test(href) || + /github\.com\/grid-js\/gridjs\/blob\//i.test(href) || + /github\.com\/grid-js\/gridjs\/edit\//i.test(href); + }, { timeout: 15000 }); + + const currentUrl2 = page.url(); + + if (/github\.com\/login/i.test(currentUrl2)) { + await expect(page.locator('input[name="login"], input[id="login_field"]')).toBeVisible({ timeout: 10000 }); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } else if (/github\.dev/i.test(currentUrl2)) { + await expect(page.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } else { + const proposeOrCommit = page.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + const fileBlob = page.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container'); + let ok2 = false; + try { + await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); + ok2 = true; + } catch (e) {} + if (!ok2) { + try { + await expect(fileBlob).toBeVisible({ timeout: 7000 }); + ok2 = true; + } catch (e) {} + } + if (!ok2) { + await expect(page.locator('body')).toBeVisible({ timeout: 5000 }); + } + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 13: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/08_Width.spec.js b/tests/playwright/Dashboard/Config/08_Width.spec.js new file mode 100644 index 00000000..9bbef50a --- /dev/null +++ b/tests/playwright/Dashboard/Config/08_Width.spec.js @@ -0,0 +1,163 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Width page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "width" submenu + const widthSubmenu = page.locator('a.menu__link[href="/docs/config/width"]'); + await expect(widthSubmenu).toBeVisible(); + await widthSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "width" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible(); + await expect(pageTitle).toHaveText('width'); + + // Step 6: Verify page shows "Default" and "Type" info + const article = page.locator('article'); + await expect(article).toBeVisible(); + await expect(article).toContainText('Default'); + await expect(article).toContainText('100%'); + await expect(article).toContainText('Type'); + await expect(article).toContainText('string'); + + // Step 7: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click "Documentation" again and re-open width submenu + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(widthSubmenu).toBeVisible(); + await widthSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Click all example links on the page (if any) + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 10: Copy buttons (if any code blocks) + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // ------------------------- + // Step 11: Click "Edit this page" โ€” open in new tab, verify, close + // ------------------------- + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + + // ambil href (bisa null) dan cek + const editHref = await editLink.getAttribute('href'); + if (!editHref) { + throw new Error('Edit link href not found on the page'); + } + + // buka halaman edit di tab baru supaya history page utama tetap utuh + const newPage = await page.context().newPage(); + try { + const targetUrl = editHref.startsWith('http') + ? editHref + : new URL(editHref, 'https://gridjs.io').href; + + await newPage.goto(targetUrl, { waitUntil: 'networkidle' }); + + // verifikasi tujuan (github.dev / github.com login / github blob/edit / docs) + const newUrl = newPage.url(); + + if (/github\.dev/i.test(newUrl)) { + // gitHub web editor (VSCode) + await expect(newPage.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com\/login/i.test(newUrl)) { + // login + await expect(newPage.locator('input[name="login"], input#login_field')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(newUrl)) { + // classic GitHub (blob/edit) + const fileBlob = newPage.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container, .js-file-line-container'); + const proposeOrCommit = newPage.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + + let ok = false; + try { + await expect(fileBlob).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { /* ignore */ } + + if (!ok) { + try { + await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { /* ignore */ } + } + + if (!ok) { + // fallback minimal assertion + await expect(newPage.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + // halaman docs biasa + await expect(newPage.locator('article')).toBeVisible({ timeout: 15000 }); + } + } finally { + // pastikan selalu ditutup agar tidak mempengaruhi test selanjutnya + await newPage.close(); + } + + // re-assert main page masih di "width" + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText('width'); + + // Step 12: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText(/language/i); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText(/height/i); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/09_Height.spec.js b/tests/playwright/Dashboard/Config/09_Height.spec.js new file mode 100644 index 00000000..b755b773 --- /dev/null +++ b/tests/playwright/Dashboard/Config/09_Height.spec.js @@ -0,0 +1,173 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Height page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "height" submenu + const heightSubmenu = page.locator('a.menu__link[href="/docs/config/height"]'); + await expect(heightSubmenu).toBeVisible(); + await heightSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "height" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible({ timeout: 10000 }); + await expect(pageTitle).toHaveText('height'); + + // Step 6: Verify page shows "Default" and "Type" info and NOTE box + const article = page.locator('article'); + await expect(article).toBeVisible({ timeout: 10000 }); + await expect(article).toContainText('Default'); + await expect(article).toContainText('auto'); // Default: auto + await expect(article).toContainText('Type'); + await expect(article).toContainText('string'); // Type: string + + // Check NOTE text present using getByText (robust) + const noteText = page.getByText(/height sets the height of the table/i); + await expect(noteText).toBeVisible({ timeout: 10000 }); + + // Step 7: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click "Documentation" again and re-open height submenu + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(heightSubmenu).toBeVisible(); + await heightSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Click all example links on the page (if any) + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 10: Copy buttons (if any code blocks) + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + const copyCount = await copyButtons.count(); + if (copyCount >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if (copyCount >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // ------------------------- + // Step 11: Click "Edit this page" โ€” open in new tab, verify, close + // ------------------------- + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + + // ambil href (bisa null) dan cek + const editHref = await editLink.getAttribute('href'); + if (!editHref) { + throw new Error('Edit link href not found on the page'); + } + + // buka halaman edit di tab baru supaya history page utama tetap utuh + const newPage = await page.context().newPage(); + try { + const targetUrl = editHref.startsWith('http') ? editHref : new URL(editHref, 'https://gridjs.io').href; + + // Pergi ke target, tunggu DOMContentLoaded, lalu tunggu redirect akhir + await newPage.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + + await newPage.waitForURL(url => + /github\.dev/i.test(url.href) || + /github\.com\/login/i.test(url.href) || + /github\.com/i.test(url.href) || + /\/docs\//i.test(url.href), + { timeout: 15000 } + ); + + const newUrl = newPage.url(); + + if (/github\.dev/i.test(newUrl)) { + // github.dev web editor (VSCode) + await expect(newPage.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com\/login/i.test(newUrl)) { + // login screen + await expect(newPage.locator('input[name="login"], input#login_field')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(newUrl)) { + // GitHub file/edit view + const fileBlob = newPage.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container, .js-file-line-container'); + const proposeOrCommit = newPage.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + + let ok = false; + try { + await expect(fileBlob).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { /* ignore */ } + + if (!ok) { + try { + await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); + ok = true; + } catch (e) { /* ignore */ } + } + + if (!ok) { + // fallback minimal assertion + await expect(newPage.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + // fallback for docs pages + await expect(newPage.locator('article')).toBeVisible({ timeout: 15000 }); + } + } finally { + await newPage.close(); + } + + // re-assert main page masih di "height" + await expect(page.locator('article h1')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('article h1')).toHaveText('height'); + + // Step 12: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText(/width/i); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText(/autoWidth/i); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/10_AutoWidth.spec.js b/tests/playwright/Dashboard/Config/10_AutoWidth.spec.js new file mode 100644 index 00000000..51ceadaf --- /dev/null +++ b/tests/playwright/Dashboard/Config/10_AutoWidth.spec.js @@ -0,0 +1,137 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” AutoWidth page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "autoWidth" submenu + const autoWidthSubmenu = page.locator('a.menu__link[href="/docs/config/autoWidth"]'); + await expect(autoWidthSubmenu).toBeVisible(); + await autoWidthSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify page title (h1) is "autoWidth" + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible({ timeout: 10000 }); + await expect(pageTitle).toHaveText('autoWidth'); + + // Step 6: Verify Default and Type info + const article = page.locator('article'); + await expect(article).toBeVisible(); + await expect(article).toContainText('Default'); + await expect(article).toContainText('true'); // Default: true + await expect(article).toContainText('Type'); + await expect(article).toContainText('boolean'); // Type: boolean + + // Step 7: Home breadcrumb and return + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Re-open docs -> config -> autoWidth + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(autoWidthSubmenu).toBeVisible(); + await autoWidthSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 9: Click example links if present + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 10: Copy buttons + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + const copyCount = await copyButtons.count(); + if (copyCount >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + if (copyCount >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 11: Click "Edit this page" in new tab and verify then close + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + const editHref = await editLink.getAttribute('href'); + if (!editHref) throw new Error('Edit link href not found on autoWidth page'); + + const newPage = await page.context().newPage(); + try { + const targetUrl = editHref.startsWith('http') ? editHref : new URL(editHref, 'https://gridjs.io').href; + await newPage.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + + await newPage.waitForURL(url => + /github\.dev/i.test(url.href) || + /github\.com\/login/i.test(url.href) || + /github\.com/i.test(url.href) || + /\/docs\//i.test(url.href), + { timeout: 15000 } + ); + + const newUrl = newPage.url(); + if (/github\.dev/i.test(newUrl)) { + await expect(newPage.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com\/login/i.test(newUrl)) { + await expect(newPage.locator('input[name="login"], input#login_field')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(newUrl)) { + const fileBlob = newPage.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container, .js-file-line-container'); + const proposeOrCommit = newPage.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + let ok = false; + try { await expect(fileBlob).toBeVisible({ timeout: 7000 }); ok = true; } catch (e) {} + if (!ok) { + try { await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); ok = true; } catch (e) {} + } + if (!ok) await expect(newPage.locator('body')).toBeVisible({ timeout: 5000 }); + } else { + await expect(newPage.locator('article')).toBeVisible({ timeout: 15000 }); + } + } finally { + await newPage.close(); + } + + // Step 12: Pagination Prev / Next + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await expect(page.locator('article h1')).toBeVisible(); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/11_FixedHeader.spec.js b/tests/playwright/Dashboard/Config/11_FixedHeader.spec.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/playwright/Dashboard/Config/11_FixedHeader.spec.js @@ -0,0 +1,95 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Columns page interactions', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Click "columns" submenu + const columnsSubmenu = page.locator('a.menu__link[href="/docs/config/columns"]'); + await expect(columnsSubmenu).toBeVisible(); + await columnsSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Click "Home" breadcrumb + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); + + // Step 6: Click "Documentation" again + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 7: Repeat "๐Ÿ›  Config" โ†’ "columns" + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(columnsSubmenu).toBeVisible(); + await columnsSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 8: Click all example links on the page (if any) + // We target links that start with /docs/examples/ + const examplesLocator = page.locator('a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + await expect(link).toBeVisible(); + // remove target if it would open a new tab + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + + // Step 9: Copy buttons in code blocks (there are code blocks on this page) + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + // Ensure at least the first copy button exists before interacting + if ((await copyButtons.count()) >= 1) { + await expect(copyButtons.nth(0)).toBeVisible(); + await copyButtons.nth(0).click(); + } + // If there's a second copy button, click it too (like other pages) + if ((await copyButtons.count()) >= 2) { + await expect(copyButtons.nth(1)).toBeVisible(); + await copyButtons.nth(1).click(); + } + + // Step 10: Click "Edit this page" (remove target before clicking) + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + await editLink.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + // Step 11: Pagination (Previous / Next) + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +}); diff --git a/tests/playwright/Dashboard/Config/12_Search.spec.js b/tests/playwright/Dashboard/Config/12_Search.spec.js new file mode 100644 index 00000000..d8b5d121 --- /dev/null +++ b/tests/playwright/Dashboard/Config/12_Search.spec.js @@ -0,0 +1,290 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('Grid.js Docs Flow โ€” Search page interactions (refined)', async ({ page }) => { + // Step 1: Open homepage + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); + + // Step 2: Click "Documentation" + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible({ timeout: 10000 }); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + // Step 3: Click "๐Ÿ›  Config" in sidebar + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible({ timeout: 10000 }); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); + + // Step 4: Open "search" submenu + const searchSubmenu = page.locator('a.menu__link[href="/docs/config/search"]'); + await expect(searchSubmenu).toBeVisible({ timeout: 10000 }); + await searchSubmenu.click(); + await page.waitForLoadState('networkidle'); + + // Step 5: Verify we are on the search page + const pageTitle = page.locator('article h1'); + await expect(pageTitle).toBeVisible({ timeout: 10000 }); + await expect(pageTitle).toHaveText('search'); + + // Step 6: Verify properties table rows exist + const propsTable = page.locator('article table').first(); + await expect(propsTable).toBeVisible({ timeout: 10000 }); + await expect(propsTable).toContainText('keyword'); + await expect(propsTable).toContainText('server'); + await expect(propsTable).toContainText('debounceTimeout'); + await expect(propsTable).toContainText('selector'); + + // Step 7: Verify example code block contains "search: true" (robust) + const exactCode = page.locator('article pre').filter({ hasText: /search:\s*true/i }).first(); + if ((await exactCode.count()) > 0) { + await expect(exactCode).toBeVisible({ timeout: 10000 }); + } else { + // fallback: check any pre containing 'search' + const fuzzyCode = page.locator('article pre').filter({ hasText: /search/i }).first(); + await expect(fuzzyCode).toBeVisible({ timeout: 10000 }); + } + + // ------------------------- + // Step 8: Click Locales link in TIP (admonition) if present + // ------------------------- + const localesLink = page.locator( + 'article .theme-admonition a:has-text("Locales"), article .admonition a:has-text("Locales"), article a[href*="/localization/locales"]' + ).first(); + + if ((await localesLink.count()) > 0) { + try { + await expect(localesLink).toBeVisible({ timeout: 8000 }); + // ensure same tab + await localesLink.evaluate(el => el.removeAttribute('target')); + await Promise.all([ + page.waitForLoadState('networkidle').catch(() => {}), + localesLink.click() + ]); + // wait for docs/locales or github + await page.waitForURL(u => /\/docs\/localization\/locales/i.test(u.href) || /\/docs\/locales/i.test(u.href) || /github\.com/i.test(u.href), { timeout: 15000 }); + + if (/\/docs\/localization\/locales/i.test(page.url()) || /\/docs\/locales/i.test(page.url())) { + await expect(page.locator('article h1')).toBeVisible({ timeout: 10000 }); + const h = (await page.locator('article h1').innerText()).toLowerCase(); + expect(h).toMatch(/locales|locale/i); + } + } catch (err) { + console.warn('Locales link check failed:', err); + } finally { + // Go back to search page safely + try { + await page.goBack(); + await page.waitForLoadState('networkidle'); + } catch { + await page.goto('https://gridjs.io/docs/config/search'); + await page.waitForLoadState('networkidle'); + } + await expect(page.locator('article h1')).toHaveText('search'); + } + } else { + console.warn('Locales link not found in TIP โ€” skipping.'); + } + + // ------------------------- + // Step 9: Click links in the Example column of the table (Search and Server-side search) + // ------------------------- + const table = page.locator('article table').first(); + await expect(table).toBeVisible({ timeout: 10000 }); + + // find Example column index from header + let exampleIndex = 3; + const headers = table.locator('thead tr th'); + const headerCount = await headers.count(); + if (headerCount > 0) { + for (let i = 0; i < headerCount; i++) { + const txt = (await headers.nth(i).innerText()).toLowerCase(); + if (txt.includes('example')) { + exampleIndex = i; + break; + } + } + } + + const rows = table.locator('tbody tr'); + const rowsCount = await rows.count(); + for (let r = 0; r < rowsCount; r++) { + const exampleCell = rows.nth(r).locator('td').nth(exampleIndex); + const links = exampleCell.locator('a'); + const linkCount = await links.count(); + for (let l = 0; l < linkCount; l++) { + const link = links.nth(l); + if (!(await link.isVisible())) continue; + const text = (await link.innerText()).trim().toLowerCase(); + try { + await link.evaluate(el => el.removeAttribute('target')); + await Promise.all([ + page.waitForLoadState('networkidle').catch(()=>{}), + link.click() + ]); + // basic assertions based on url or link text + const url = page.url(); + if (/\/docs\/examples\/server-side-search/i.test(url) || text.includes('server')) { + const h = page.locator('article h1').first(); + await expect(h).toBeVisible({ timeout: 10000 }); + const heading = (await h.innerText()).toLowerCase(); + expect(heading).toMatch(/server/i); + } else if (/\/docs\/examples\/search/i.test(url) || text.includes('search')) { + const h = page.locator('article h1').first(); + await expect(h).toBeVisible({ timeout: 10000 }); + const heading = (await h.innerText()).toLowerCase(); + expect(heading).toMatch(/search/i); + } else if (/\/docs\//i.test(url)) { + await expect(page.locator('article')).toBeVisible({ timeout: 10000 }); + } else if (/github\.com/i.test(url)) { + // GitHub page or login + const login = page.locator('input[name="login"], input#login_field'); + if ((await login.count()) > 0) { + await expect(login).toBeVisible({ timeout: 10000 }); + } else { + await expect(page.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + await expect(page.locator('body')).toBeVisible(); + } + } catch (err) { + console.warn(`Clicking example link (row ${r}, link ${l}) failed:`, err); + } finally { + // Try to go back to search page + try { + await page.goBack(); + await page.waitForLoadState('networkidle'); + } catch { + await page.goto('https://gridjs.io/docs/config/search'); + await page.waitForLoadState('networkidle'); + } + await expect(page.locator('article h1')).toHaveText('search'); + } + } + } + + // Step 10: Click all general example links (href^="/docs/examples/") + const generalExamples = page.locator('article a[href^="/docs/examples/"]'); + const genCount = await generalExamples.count(); + for (let i = 0; i < genCount; i++) { + const g = generalExamples.nth(i); + try { + await expect(g).toBeVisible({ timeout: 8000 }); + await g.evaluate(el => el.removeAttribute('target')); + await Promise.all([ + page.waitForLoadState('networkidle').catch(()=>{}), + g.click() + ]); + } catch (err) { + console.warn('General example click failed:', err); + } finally { + try { + await page.goBack(); + await page.waitForLoadState('networkidle'); + } catch { + await page.goto('https://gridjs.io/docs/config/search'); + await page.waitForLoadState('networkidle'); + } + } + } + + // Step 11: Copy buttons + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + const copyCount = await copyButtons.count(); + if (copyCount > 0) { + for (let i = 0; i < Math.min(copyCount, 2); i++) { + try { + await expect(copyButtons.nth(i)).toBeVisible({ timeout: 5000 }); + await copyButtons.nth(i).click(); + } catch (err) { + console.warn('Copy button click failed:', err); + } + } + } else { + console.warn('No copy buttons found.'); + } + + // Step 12: Click "Edit this page" in new tab and verify + const editLink = page.locator('a.theme-edit-this-page').first(); + if ((await editLink.count()) > 0) { + await expect(editLink).toBeVisible({ timeout: 8000 }); + const editHref = await editLink.getAttribute('href'); + if (editHref) { + const newPage = await page.context().newPage(); + try { + const target = editHref.startsWith('http') ? editHref : new URL(editHref, 'https://gridjs.io').href; + await newPage.goto(target, { waitUntil: 'domcontentloaded' }); + await newPage.waitForURL(url => + /github\.dev/i.test(url.href) || + /github\.com\/login/i.test(url.href) || + /github\.com/i.test(url.href) || + /\/docs\//i.test(url.href), + { timeout: 15000 } + ); + const newUrl = newPage.url(); + if (/github\.dev/i.test(newUrl)) { + await expect(newPage.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com\/login/i.test(newUrl)) { + await expect(newPage.locator('input[name="login"], input#login_field')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(newUrl)) { + const fileBlob = newPage.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container'); + const propose = newPage.locator('button:has-text("Propose changes"), a:has-text("Create pull request")'); + if ((await fileBlob.count()) > 0) { + await expect(fileBlob.first()).toBeVisible({ timeout: 7000 }); + } else if ((await propose.count()) > 0) { + await expect(propose.first()).toBeVisible({ timeout: 7000 }); + } else { + await expect(newPage.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + await expect(newPage.locator('article')).toBeVisible({ timeout: 15000 }); + } + } catch (err) { + console.warn('Edit page verification failed:', err); + } finally { + await newPage.close(); + } + } else { + console.warn('Edit link has no href.'); + } + } else { + console.warn('Edit link not found.'); + } + + // Step 13: Pagination prev / next + const prev = page.locator('a.pagination-nav__link--prev').first(); + if ((await prev.count()) > 0) { + await expect(prev).toBeVisible({ timeout: 8000 }); + await prev.click(); + try { + await page.goBack(); + await page.waitForLoadState('networkidle'); + } catch { + await page.goto('https://gridjs.io/docs/config/search'); + await page.waitForLoadState('networkidle'); + } + } else { + console.warn('Prev not found.'); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if ((await next.count()) > 0) { + await expect(next).toBeVisible({ timeout: 8000 }); + await next.click(); + try { + await page.goBack(); + await page.waitForLoadState('networkidle'); + } catch { + await page.goto('https://gridjs.io/docs/config/search'); + await page.waitForLoadState('networkidle'); + } + } else { + console.warn('Next not found.'); + } + + // Final check: ensure still on search page + await expect(page.locator('article h1')).toBeVisible(); + await expect(page.locator('article h1')).toHaveText('search'); +}); diff --git a/tests/playwright/Dashboard/Config/13_Sort.spec.js b/tests/playwright/Dashboard/Config/13_Sort.spec.js new file mode 100644 index 00000000..8e2516c4 --- /dev/null +++ b/tests/playwright/Dashboard/Config/13_Sort.spec.js @@ -0,0 +1,293 @@ +// @ts-check +/** + * Reusable helpers for Grid.js docs Playwright tests. + * Put this file in tests/helpers/docsHelpers.js + */ + +import { expect } from '@playwright/test'; + +/** + * @typedef {import('@playwright/test').Page} Page + * @typedef {import('@playwright/test').Locator} Locator + */ + +/** + * Open homepage and wait for network idle. + * @param {Page} page + */ +export async function openHome(page) { + await page.goto('https://gridjs.io'); + await page.waitForLoadState('networkidle'); +} + +/** + * Click Documentation link + * @param {Page} page + * @returns {Promise} docsLink locator returned (for later use) + */ +export async function clickDocumentation(page) { + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await page.waitForLoadState('networkidle'); + return docsLink; +} + +/** + * Click Config in sidebar + * @param {Page} page + */ +export async function clickConfig(page) { + const configSidebar = page.locator('a.menu__link:has-text("Config")'); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await page.waitForLoadState('networkidle'); +} + +/** + * Open submenu by href (e.g. '/docs/config/columns') + * @param {Page} page + * @param {string} submenuHref + */ +export async function openSubmenu(page, submenuHref) { + const submenu = page.locator(`a.menu__link[href="${submenuHref}"]`); + await expect(submenu).toBeVisible(); + await submenu.click(); + await page.waitForLoadState('networkidle'); +} + +/** + * Click Home breadcrumb + * @param {Page} page + */ +export async function clickHomeBreadcrumb(page) { + const homeBreadcrumb = page.locator('a[aria-label="Home page"]'); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('networkidle'); +} + +/** + * Click all example links (href starts with /docs/examples/) and come back + * @param {Page} page + */ +export async function clickAllExampleLinks(page) { + const examplesLocator = page.locator('article a[href^="/docs/examples/"]'); + const examplesCount = await examplesLocator.count(); + for (let i = 0; i < examplesCount; i++) { + const link = examplesLocator.nth(i); + if (!(await link.isVisible())) continue; + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + } +} + +/** + * Click copy buttons (up to maxClicks) + * @param {Page} page + * @param {number} [maxClicks=2] + */ +export async function clickCopyButtons(page, maxClicks = 2) { + const copyButtons = page.locator('span[class*="copyButtonIcons"]'); + const count = await copyButtons.count(); + for (let i = 0; i < Math.min(count, maxClicks); i++) { + const btn = copyButtons.nth(i); + if (!(await btn.isVisible())) continue; + await btn.click(); + } +} + +/** + * Helper: remove target attribute and click a link, keeping navigation in same tab + * @param {Locator} link + */ +async function removeTargetAndClick(link) { + await link.evaluate(el => el.removeAttribute('target')); + await link.click(); +} + +/** + * Click "Edit this page" by opening it in a new tab and verify common targets. + * This opens a new page so history of the main page is preserved. + * + * @param {Page} page + */ +export async function clickEditAndHandleGitHub(page) { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + const editHref = await editLink.getAttribute('href'); + if (!editHref) throw new Error('Edit link href not found'); + + const newPage = await page.context().newPage(); + try { + const target = editHref.startsWith('http') ? editHref : new URL(editHref, 'https://gridjs.io').href; + await newPage.goto(target, { waitUntil: 'domcontentloaded' }); + + // wait for one of likely destinations + await newPage.waitForURL(url => + /github\.dev/i.test(url.href) || + /github\.com\/login/i.test(url.href) || + /github\.com/i.test(url.href) || + /\/docs\//i.test(url.href), + { timeout: 15000 } + ); + + const newUrl = newPage.url(); + if (/github\.dev/i.test(newUrl)) { + await expect(newPage.locator('.monaco-editor, [aria-label="Editor content"]')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com\/login/i.test(newUrl)) { + await expect(newPage.locator('input[name="login"], input#login_field')).toBeVisible({ timeout: 15000 }); + } else if (/github\.com/i.test(newUrl)) { + // check for file blob or propose commit UI + const fileBlob = newPage.locator('.blob-wrapper, .file .highlight, #repo-content-pjax-container, .js-file-line-container'); + const proposeOrCommit = newPage.locator('button:has-text("Propose changes"), button:has-text("Commit changes"), a:has-text("Create pull request")'); + let ok = false; + try { await expect(fileBlob).toBeVisible({ timeout: 7000 }); ok = true; } catch {} + if (!ok) { + try { await expect(proposeOrCommit).toBeVisible({ timeout: 7000 }); ok = true; } catch {} + } + if (!ok) { + await expect(newPage.locator('body')).toBeVisible({ timeout: 5000 }); + } + } else { + await expect(newPage.locator('article')).toBeVisible({ timeout: 15000 }); + } + } finally { + await newPage.close(); + } +} + +/** + * Check pagination prev/next (click and come back) + * @param {Page} page + */ +export async function checkPagination(page) { + const prev = page.locator('a.pagination-nav__link--prev'); + const next = page.locator('a.pagination-nav__link--next'); + + await expect(prev).toBeVisible(); + await prev.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); + + await expect(next).toBeVisible(); + await next.click(); + await page.waitForLoadState('networkidle'); + await page.goBack(); + await page.waitForLoadState('networkidle'); +} + +/** + * Click links in table Example column (automatically detect Example column index) + * @param {Page} page + * @param {string} [tableSelector='article table'] + */ +export async function clickLinksInTableExampleColumn(page, tableSelector = 'article table') { + const table = page.locator(tableSelector).first(); + await expect(table).toBeVisible(); + + // determine example column index + const headerCells = table.locator('thead tr th'); + let exampleIndex = 3; // default fallback to 4th column + const headerCount = await headerCells.count(); + if (headerCount > 0) { + for (let i = 0; i < headerCount; i++) { + const txt = (await headerCells.nth(i).innerText()).toLowerCase(); + if (txt.includes('example')) { + exampleIndex = i; + break; + } + } + } + + const rows = table.locator('tbody tr'); + const rowsCount = await rows.count(); + for (let r = 0; r < rowsCount; r++) { + const cell = rows.nth(r).locator('td').nth(exampleIndex); + const links = cell.locator('a'); + const linkCount = await links.count(); + for (let l = 0; l < linkCount; l++) { + const link = links.nth(l); + if (!(await link.isVisible())) continue; + await removeTargetAndClick(link); + await page.waitForLoadState('networkidle'); + + // sanity checks for destination + const url = page.url(); + if (/\/docs\//i.test(url)) { + await expect(page.locator('article')).toBeVisible({ timeout: 10000 }); + } else if (/github\.com/i.test(url)) { + await expect(page.locator('body')).toBeVisible({ timeout: 5000 }); + } + await page.goBack(); + await page.waitForLoadState('networkidle'); + } + } +} + +/** + * Click a TOC anchor or hash link with fallback strategies: + * - prefer right-side TOC link (not inside article), + * - if not found, click article's hash-link by id, + * - final fallback: any visible link with the text. + * + * @param {Page} page + * @param {string} text - visible text of the TOC anchor (e.g. 'Column specific sort config') + * @param {string} [fallbackId] - fallback hash id (without '#'), e.g. 'column-specific-sort-config' + * @returns {Promise} true when something was clicked + */ +export async function clickAnchorByText(page, text, fallbackId) { + const allLinks = page.locator(`a:has-text("${text}")`); + const total = await allLinks.count(); + + // prefer right-side TOC links (outside article) + for (let i = 0; i < total; i++) { + const cur = allLinks.nth(i); + if (!(await cur.isVisible())) continue; + const isInsideArticle = await cur.evaluate(el => !!el.closest('article')); + if (!isInsideArticle) { + try { + await cur.scrollIntoViewIfNeeded(); + await removeTargetAndClick(cur); + await page.waitForLoadState('networkidle'); + return true; + } catch (e) { + // try next candidate + } + } + } + + // fallback: click article hash-link with given id + if (fallbackId) { + const hashLink = page.locator(`article a.hash-link[href="#${fallbackId}"]`); + if (await hashLink.count() > 0) { + try { + await hashLink.first().scrollIntoViewIfNeeded(); + await hashLink.first().click(); + await page.waitForLoadState('networkidle'); + return true; + } catch (e) { + // ignore and try final fallback + } + } + } + + // final fallback: click first visible link with the text + for (let i = 0; i < total; i++) { + const cur = allLinks.nth(i); + if (!(await cur.isVisible())) continue; + try { + await removeTargetAndClick(cur); + await page.waitForLoadState('networkidle'); + return true; + } catch (e) { + // continue + } + } + + return false; +} diff --git a/tests/playwright/Dashboard/Config/14_Pagination.spec.js b/tests/playwright/Dashboard/Config/14_Pagination.spec.js new file mode 100644 index 00000000..42633063 --- /dev/null +++ b/tests/playwright/Dashboard/Config/14_Pagination.spec.js @@ -0,0 +1,138 @@ +import { test, expect } from '@playwright/test'; + +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +async function hoverAndClickAllCopyButtons(page) { + const article = page.locator('article'); + const codeBlocks = article.locator('div.theme-code-block, div[class*="codeBlock"], pre'); + + const blocksCount = await codeBlocks.count(); + for (let i = 0; i < blocksCount; i++) { + const block = codeBlocks.nth(i); + if (!(await block.isVisible().catch(() => false))) continue; + + await block.hover().catch(() => {}); + await page.waitForTimeout(150); + + const copyBtns = block.locator( + 'button[aria-label*="Copy"], button[class*="copyButton"], span[class*="copyButtonIcons"], button[class*="copy"], span[class*="copy"]' + ); + + const btnCount = await copyBtns.count(); + for (let j = 0; j < btnCount; j++) { + const btn = copyBtns.nth(j); + if (!(await btn.isVisible().catch(() => false))) continue; + await btn.click(); + } + } +} + +async function clickAllArticleLinksAndReturn(page) { + const article = page.locator('article'); + const links = article.locator('a[href]'); + + const total = await links.count(); + for (let i = 0; i < total; i++) { + const link = links.nth(i); + if (!(await link.isVisible().catch(() => false))) continue; + + const href = await link.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + const startUrl = page.url(); + await link.evaluate(el => el.removeAttribute('target')); + + await link.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(startUrl); + } +} + +test('Grid.js Docs Flow โ€” ClassName page interactions (FULL, based on prior work)', async ({ page }) => { + await page.goto('https://gridjs.io', { waitUntil: 'domcontentloaded' }); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await waitDocReady(page); + + const configSidebar = page.locator('a.menu__link:has-text("Config")').first(); + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await waitDocReady(page); + + const classNameSubmenu = page.locator('a.menu__link[href="/docs/config/className"]').first(); + await expect(classNameSubmenu).toBeVisible(); + await classNameSubmenu.click(); + await waitDocReady(page); + + const pageTitle = page.locator('article h1').first(); + await expect(pageTitle).toBeVisible(); + await expect(pageTitle).toHaveText('className'); + + const homeBreadcrumb = page.locator('a[aria-label="Home page"]').first(); + await expect(homeBreadcrumb).toBeVisible(); + await homeBreadcrumb.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await waitDocReady(page); + + await expect(configSidebar).toBeVisible(); + await configSidebar.click(); + await expect(classNameSubmenu).toBeVisible(); + await classNameSubmenu.click(); + await waitDocReady(page); + + const propsTable = page.locator('article table').first(); + await expect(propsTable).toBeVisible(); + await expect(propsTable).toContainText('container'); + await expect(propsTable).toContainText('table'); + await expect(propsTable).toContainText('td'); + await expect(propsTable).toContainText('th'); + await expect(propsTable).toContainText('header'); + await expect(propsTable).toContainText('footer'); + await expect(propsTable).toContainText('loading'); + await expect(propsTable).toContainText('notfound'); + await expect(propsTable).toContainText('error'); + + await clickAllArticleLinksAndReturn(page); + await hoverAndClickAllCopyButtons(page); + + const editLink = page.locator('a.theme-edit-this-page').first(); + await expect(editLink).toBeVisible(); + await editLink.evaluate(el => el.removeAttribute('target')); + const startUrl = page.url(); + await editLink.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(startUrl); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(startUrl); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + const next = page.locator('a.pagination-nav__link--next').first(); + + if (await prev.isVisible().catch(() => false)) { + await prev.click(); + await page.waitForLoadState('domcontentloaded'); + await page.goBack(); + await waitDocReady(page); + } + + if (await next.isVisible().catch(() => false)) { + await next.click(); + await page.waitForLoadState('domcontentloaded'); + await page.goBack(); + await waitDocReady(page); + } +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/fixed_header.spec.js b/tests/playwright/Dashboard/Examples/Basic/fixed_header.spec.js new file mode 100644 index 00000000..8ae60b9e --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/fixed_header.spec.js @@ -0,0 +1,262 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/fixed-header"; + +test.describe("Fixed Header example page", () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("has h1 title 'Fixed Header'", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Fixed Header", + level: 1, + }); + await expect(title).toBeVisible(); + await expect(title).toHaveText("Fixed Header"); + }); + + test("renders Grid.js table with expected columns", async ({ page }) => { + await page.waitForSelector(".gridjs-wrapper"); + const nameHeader = page.locator('th[data-column-id="name"]'); + const emailHeader = page.locator('th[data-column-id="email"]'); + const titleHeader = page.locator('th[data-column-id="title"]'); + await expect(nameHeader).toBeVisible(); + await expect(emailHeader).toBeVisible(); + await expect(titleHeader).toBeVisible(); + }); + + test("header stays visible when table content scrolls", async ({ + page, + }) => { + const container = page.locator(".gridjs-wrapper"); + await expect(container).toBeVisible(); + + const scrollable = await container.evaluate( + (el) => el.scrollHeight > el.clientHeight, + ); + expect(scrollable).toBeTruthy(); + + await container.evaluate((el) => { + el.scrollTop = 200; + }); + const headerName = page.locator('th[data-column-id="name"]'); + await expect(headerName).toBeVisible(); + }); + + test("pagination shows 10 rows per page", async ({ page }) => { + const rows = page.locator(".gridjs-container table tbody tr"); + await expect(rows).toHaveCount(10); + }); + + test("pagination navigates to page 2 and changes rows", async ({ + page, + }) => { + const firstRow = page + .locator(".gridjs-container table tbody tr") + .first(); + const before = (await firstRow.textContent())?.trim(); + + const pageTwoBtn = page + .locator(".gridjs-pages") + .getByRole("button", { name: "2" }); + await expect(pageTwoBtn).toBeVisible(); + await pageTwoBtn.click(); + + const afterRow = page + .locator(".gridjs-container table tbody tr") + .first(); + await expect(afterRow).not.toHaveText(before || ""); + }); + + test("sorting by Name toggles ascending/descending", async ({ page }) => { + const sortBtn = page.locator( + 'th[data-column-id="name"] button.gridjs-sort', + ); + await expect(sortBtn).toBeVisible(); + + const getNames = async () => { + return await page + .locator(".gridjs-wrapper table tbody tr td:nth-child(1)") + .allTextContents(); + }; + + await sortBtn.click(); + await page.waitForTimeout(200); + const asc = await getNames(); + const sortedAsc = [...asc].sort((a, b) => a.localeCompare(b)); + expect(asc).toEqual(sortedAsc); + + await sortBtn.click(); + await page.waitForTimeout(200); + const desc = await getNames(); + const sortedDesc = [...desc].sort((a, b) => b.localeCompare(a)); + expect(desc).toEqual(sortedDesc); + }); + + test("header position sticks to container top on scroll", async ({ + page, + }) => { + // ็ขบไฟๅ‰ๅพ€ๆญฃ็ขบ็š„้ ้ข (ๅฆ‚ๆžœๅŽŸๆœฌ็š„ beforeAll ๆœ‰ๅฏซๅฏไปฅ็œ็•ฅ) + // await page.goto('https://gridjs.io/docs/examples/fixed-header'); + + // 1. ้Ž–ๅฎšๅฎนๅ™จ + const container = page.locator(".gridjs-wrapper").first(); + + // 2. [ไฟฎๆญฃ้—œ้ต] ้Ž–ๅฎš 'th' ่€Œ้ž 'thead' + // Grid.js ็š„ sticky ๅฑฌๆ€งๆ˜ฏๅฏซๅœจ th ไธŠ็š„ + const headerCell = container.locator("th").first(); + + // ็ขบไฟๅ…ƒ็ด ๅทฒ่ผ‰ๅ…ฅ + await headerCell.waitFor(); + + // ๅ–ๅพ—ๅฎนๅ™จ็š„ Top ๅบงๆจ™ + const { top: cTop } = await container.evaluate((el) => + el.getBoundingClientRect(), + ); + + // ๅ–ๅพ— Header Cell ๆฒๅ‹•ๅ‰็š„ Top ๅบงๆจ™ + const { top: hTopBefore } = await headerCell.evaluate((el) => + el.getBoundingClientRect(), + ); + + // ๅŸท่กŒๆฒๅ‹• + await container.evaluate((el) => { + el.scrollTop = 250; + }); + + // [้ธๆ“‡ๆ€ง] ็ญ‰ๅพ…ไธ€ไธ‹็ขบไฟ็€่ฆฝๅ™จๅฎŒๆˆ layout update (้€šๅธธ evaluate ๆ˜ฏๅŒๆญฅ็š„๏ผŒไฝ†ไฟ้šช่ตท่ฆ‹) + // await page.waitForTimeout(100); + + // ๅ–ๅพ— Header Cell ๆฒๅ‹•ๅพŒ็š„ Top ๅบงๆจ™ + const { top: hTopAfter } = await headerCell.evaluate((el) => + el.getBoundingClientRect(), + ); + + // ้ฉ—่ญ‰้‚่ผฏ๏ผš + // 1. ๆฒๅ‹•ๅ‰๏ผŒHeader ๆ‡‰่ฉฒ่ฒผ้ฝŠ Container (่ชคๅทฎ < 3px) + expect(Math.abs(hTopBefore - cTop)).toBeLessThan(3); + + // 2. ๆฒๅ‹•ๅพŒ๏ผŒๅ› ็‚บ sticky ็š„้—œไฟ‚๏ผŒHeader ๆ‡‰่ฉฒ"่ฆ–่ฆบไธŠ"้‚„ๆ˜ฏ่ฒผ้ฝŠ Container + // ๅฆ‚ๆžœๆ˜ฏๆ™ฎ้€šๅ…ƒ็ด ๏ผŒ้€™่ฃก็š„ๅทฎๅ€ผๆœƒๆŽฅ่ฟ‘ 250 (ๅ› ็‚บๆฒ่ตฐไบ†) + // ไฝ†ๅ› ็‚บๅฎƒๆ˜ฏ sticky๏ผŒ้€™่ฃก็š„ๅทฎๅ€ผๆ‡‰่ฉฒ้‚„ๆ˜ฏๆŽฅ่ฟ‘ 0 + expect(Math.abs(hTopAfter - cTop)).toBeLessThan(3); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/from_html_table.spec.js b/tests/playwright/Dashboard/Examples/Basic/from_html_table.spec.js new file mode 100644 index 00000000..52fdd57f --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/from_html_table.spec.js @@ -0,0 +1,156 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/from"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: From HTML Table", async ({ page }) => { + const title = page.getByRole("heading", { + name: "From HTML Table", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("From HTML Table"); + }); + + test("Should import data correctly from existing HTML table", async ({ + page, + }) => { + const gridContainer = page.locator(".gridjs-container").first(); + const rows = gridContainer.locator("table.gridjs-table tbody tr"); + + // 1. ้ฉ—่ญ‰่ณ‡ๆ–™็ญ†ๆ•ธ (็ฏ„ไพ‹ไธญ้ ่จญๆœ‰ 2 ็ญ†๏ผšJohn ๅ’Œ Mike) + await expect(rows).toHaveCount(2); + + // 2. ้ฉ—่ญ‰็ฌฌไธ€็ญ†่ณ‡ๆ–™ (John) + await expect(rows.nth(0)).toContainText("John"); + await expect(rows.nth(0)).toContainText("john@example.com"); + + // 3. ้ฉ—่ญ‰็ฌฌไบŒ็ญ†่ณ‡ๆ–™ (Mike) + // ๆณจๆ„๏ผš็ฏ„ไพ‹ไธญ็š„ Mike Email ๅŒ…ๅซ ๆจ™็ฑค๏ผŒGrid.js ้ ่จญๅฏ่ƒฝๆœƒไฟ็•™ HTML ๆˆ–็ด”ๆ–‡ๅญ— + // ๆˆ‘ๅ€‘ๆชขๆŸฅๆ–‡ๅญ—ๅ…งๅฎนๆ˜ฏๅฆๅญ˜ๅœจๅณๅฏ + await expect(rows.nth(1)).toContainText("Mike"); + await expect(rows.nth(1)).toContainText("mike@example.com"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/hello_world.spec.js b/tests/playwright/Dashboard/Examples/Basic/hello_world.spec.js new file mode 100644 index 00000000..2a783ba1 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/hello_world.spec.js @@ -0,0 +1,179 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/hello-world"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + test("1. Grab the h1 title: Hello, World!", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Hello, World!", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Hello, World!"); + }); + + /** + * ๆธฌ่ฉฆๆƒ…ๅขƒ 1: ้ฉ—่ญ‰่กจๆ ผ็ตๆง‹่ˆ‡ๆจ™้ ญ + * ็›ฎๆจ™๏ผš็ขบไฟ่กจๆ ผๆฌ„ไฝๅ็จฑ (Columns) ๆญฃ็ขบ้กฏ็คบ + */ + test("Should render table headers correctly", async ({ page }) => { + const table = page.locator("table.gridjs-table").first(); + + // ้ฉ—่ญ‰่กจๆ ผๅฏ่ฆ‹ + await expect(table).toBeVisible(); + + // ้ฉ—่ญ‰ๆจ™้ ญๆ–‡ๅญ—่ˆ‡้ †ๅบ + // ๆ นๆ“šๅฎ˜ๆ–น็ฏ„ไพ‹๏ผŒๆจ™้ ญๆ‡‰็‚บ: Name, Email, Phone Number + const headers = table.locator("th"); + await expect(headers).toHaveText(["Name", "Email", "Phone Number"]); + }); + + /** + * ๆธฌ่ฉฆๆƒ…ๅขƒ 2: ้ฉ—่ญ‰่ณ‡ๆ–™ๅ…งๅฎน + * ็›ฎๆจ™๏ผšๆชขๆŸฅ่กจๆ ผๅ…ง็š„ๅฏฆ้š›่ณ‡ๆ–™ (Rows & Cells) ๆ˜ฏๅฆ่ˆ‡้ ๆœŸ็›ธ็ฌฆ + */ + test("Should display correct data in rows", async ({ page }) => { + const rows = page.locator("table.gridjs-table tbody tr"); + + // --- ้ฉ—่ญ‰็ฌฌไธ€็ญ†่ณ‡ๆ–™ (John) --- + const firstRowCells = rows.nth(0).locator("td"); + await expect(firstRowCells.nth(0)).toHaveText("John"); + await expect(firstRowCells.nth(1)).toHaveText("john@example.com"); + await expect(firstRowCells.nth(2)).toHaveText("(353) 01 222 3333"); + + // --- ้ฉ—่ญ‰็ฌฌไบŒ็ญ†่ณ‡ๆ–™ (Mark) --- + const secondRowCells = rows.nth(1).locator("td"); + await expect(secondRowCells.nth(0)).toHaveText("Mark"); + await expect(secondRowCells.nth(1)).toHaveText("mark@gmail.com"); + await expect(secondRowCells.nth(2)).toHaveText("(01) 22 888 4444"); + + // (ๅฏ้ธ) ้ฉ—่ญ‰็ธฝ็ญ†ๆ•ธ๏ผŒ็ขบไฟๆฒ’ๆœ‰ๅคš้ค˜ๆˆ–็ผบๅฐ‘็š„่ณ‡ๆ–™ + // Hello World ็ฏ„ไพ‹้€šๅธธๆœ‰ 2 ็ญ†ๆˆ–ๆ›ดๅคš๏ผŒ่ฆ–ๆ‚จ็š„็‰ˆๆœฌ่€Œๅฎš๏ผŒ้€™่ฃกๅ‡่จญๆชขๆŸฅๅ‰ๅ…ฉ็ญ† + await expect(rows.nth(0)).toBeVisible(); + await expect(rows.nth(1)).toBeVisible(); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/hidden_columns.spec.js b/tests/playwright/Dashboard/Examples/Basic/hidden_columns.spec.js new file mode 100644 index 00000000..e87e5ca7 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/hidden_columns.spec.js @@ -0,0 +1,171 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/hidden-columns"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Hidden Columns", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Hidden Columns", + level: 1, + }); + await expect(title).toBeVisible(); + await expect(title).toHaveText("Hidden Columns"); + }); + + test("Click the link in the Note section", async ({ page }) => { + const link = page.getByRole("link", { name: "search plugin" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/search", + ); + }); + + test("Click the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); + + test('Should hide "Name" column header', async ({ page }) => { + const headerCells = page.locator("table.gridjs-table thead th"); + + // 1. ้ฉ—่ญ‰ๆจ™้ ญๆ•ธ้‡ + // ๅ‡่จญๅŽŸๅง‹ๆœ‰ 3 ๆฌ„ (Name, Email, Phone)๏ผŒ้šฑ่— 1 ๆฌ„ๅพŒๆ‡‰ๅ‰ฉ 2 ๆฌ„ + await expect(headerCells).toHaveCount(2); + + // 2. ้ฉ—่ญ‰ๆจ™้ ญๆ–‡ๅญ—ๅ…งๅฎน + // ็ขบไฟ "Name" ไธๅœจๅ…ถไธญ๏ผŒไธ”้ †ๅบๆญฃ็ขบ (Email ่ฎŠๆˆ็ฌฌไธ€ๅ€‹) + await expect(headerCells).toHaveText(["Email", "Title"]); + + // 3. ้›™้‡็ขบ่ช "Name" ๆจ™้ ญ่™•ๆ–ผ้šฑ่—็‹€ๆ…‹ (ๆˆ–ๆ˜ฏๆ นๆœฌๆœชๆธฒๆŸ“) + const nameHeader = page.locator("th").filter({ hasText: "Name" }); + await expect(nameHeader).toBeHidden(); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/loading_state.spec.js b/tests/playwright/Dashboard/Examples/Basic/loading_state.spec.js new file mode 100644 index 00000000..667435bf --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/loading_state.spec.js @@ -0,0 +1,180 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/loading-state"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Loading State", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Loading State", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Loading State"); + }); + + test("Should show loading bar initially and then hide it", async ({ + page, + }) => { + // 1. [้—œ้ตไฟฎๆญฃ] ๆ”พๅฏฌ้ธๆ“‡ๅ™จ + // ็›ดๆŽฅๆ‰พ gridjs-loading-bar๏ผŒไธฆ้Ž–ๅฎš็ฌฌไธ€ๅ€‹ (้ฟๅ…ๆŠ“ๅˆฐๅ…ถไป–็ฏ„ไพ‹็š„) + const loadingBar = page.locator(".gridjs-loading-bar").first(); + + // 2. ้‡ๆ–ฐๆ•ด็†้ ้ขไปฅ่งธ็™ผ่ผ‰ๅ…ฅ + // ๆณจๆ„๏ผš้€™่ฃกๆˆ‘ๅ€‘ๅˆปๆ„ไธ็ญ‰ๅพ… 'networkidle'๏ผŒๅช็ญ‰ๅพ… DOMContentLoaded + // ้€™ๆจฃๅฏไปฅๅ„˜ๆ—ฉ้–‹ๅง‹ๆชขๆŸฅ Loading Bar + await page.reload({ waitUntil: "domcontentloaded" }); + + // 3. ้ฉ—่ญ‰ Loading Bar ๅ‡บ็พ + // ้€™่ฃกๅฏ่ƒฝๆœƒๅคฑๆ•—ๅฆ‚ๆžœ่ผ‰ๅ…ฅ็œŸ็š„ๅคชๅฟซ (<100ms) + // ไฝ†้€šๅธธ Loading State ็ฏ„ไพ‹้ƒฝๆœƒๅˆปๆ„ delay 1~2็ง’ + await expect(loadingBar).toBeVisible(); + + // 4. ้ฉ—่ญ‰ Loading Bar ๆถˆๅคฑ + // ้€™ไปฃ่กจ่ณ‡ๆ–™่ผ‰ๅ…ฅๅฎŒๆˆ + await expect(loadingBar).toBeHidden(); + }); + + /** + * ๆธฌ่ฉฆๆƒ…ๅขƒ 2: ้ฉ—่ญ‰่ณ‡ๆ–™่ผ‰ๅ…ฅๅพŒ็š„ๆญฃ็ขบๆ€ง + * ้‡้ปž๏ผš็ขบไฟ Loading ็ตๆŸๅพŒ๏ผŒ่กจๆ ผๅ…ง็œŸ็š„ๆœ‰่ณ‡ๆ–™ + */ + test("Should render data after loading completes", async ({ page }) => { + const wrapper = page.locator(".gridjs-wrapper").first(); + const loadingBar = wrapper.locator(".gridjs-loading-bar"); + const rows = wrapper.locator("table.gridjs-table tbody tr"); + + // 1. ็ญ‰ๅพ… Loading ็ตๆŸ + // ้€™ๆ˜ฏๆœ€็ฉฉๅฅ็š„ๅฏซๆณ•๏ผšๅ…ˆ็ขบ่ช Loading Bar ๆถˆๅคฑ๏ผŒๅ†ๆชขๆŸฅ่ณ‡ๆ–™ + await expect(loadingBar).toBeHidden(); + + // 2. ้ฉ—่ญ‰่กจๆ ผ่ณ‡ๆ–™ๆ˜ฏๅฆๅ‡บ็พ + // ๅ‡่จญ็ฏ„ไพ‹่ผ‰ๅ…ฅๅพŒๆœƒๆœ‰่ณ‡ๆ–™ (ๅฆ‚ John, Mark) + await expect(rows).not.toHaveCount(0); // ็ขบไฟไธ็‚บ็ฉบ + + // 3. ้ฉ—่ญ‰็‰นๅฎš่ณ‡ๆ–™ๅ…งๅฎน (ๆ นๆ“šๅฎ˜ๆ–น็ฏ„ไพ‹่ณ‡ๆ–™) + await expect(rows.first()).toContainText("John"); + await expect(rows.first()).toContainText("john@example.com"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/pagination.spec.js b/tests/playwright/Dashboard/Examples/Basic/pagination.spec.js new file mode 100644 index 00000000..39cfbdeb --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/pagination.spec.js @@ -0,0 +1,194 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/pagination"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Pagination", async ({ page }) => { + const title = page.getByRole("heading", { + name: "pagination", + level: 1, + }); + await expect(title).toBeVisible(title); + + await expect(title).toHaveText("Pagination"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); + + test("When pagination is set to true", async ({ page }) => { + // Previous and Next page button should be exist + const previous = page.getByRole("button", { name: "Previous" }).nth(1); + const next = page.getByRole("button", { name: "Next" }).nth(1); + + await expect(previous).toBeVisible(); + await expect(next).toBeVisible(); + }); + + test("When pagination is set to false", async ({ page }) => { + const codeEditor = page + .locator("textarea.npm__react-simple-code-editor__textarea") + .first(); + + // 2. ๆบ–ๅ‚™ๆ–ฐ็š„้…็ฝฎไปฃ็ขผ (ๆ˜Ž็ขบ่จญๅฎš pagination: false) + // ๆˆ‘ๅ€‘ไฝฟ็”จ "Fill + Type" ๆททๅˆ็ญ–็•ฅไพ†็ขบไฟ็ทจ่ผฏๅ™จ่งธ็™ผๆ›ดๆ–ฐ + const codeBody = ` + new Grid({ + columns: ['Name', 'Email', 'Phone Number'], + data: [ + ['John', 'john@example.com', '(353) 01 222 3333'], + ['Mark', 'mark@gmail.com', '(01) 22 888 4444'], + ['Eoin', 'eo3n@yahoo.com', '(05) 10 878 5554'], + ['Nisen', 'nis900@gmail.com', '313 333 1923'] + ], + pagination: false + })`.trim(); // ๆ•…ๆ„ไธๅŠ ๅˆ†่™Ÿ๏ผŒ็•™็ตฆ type ่ผธๅ…ฅ + + // 3. ๆ“ไฝœ็ทจ่ผฏๅ™จ๏ผšๆธ…็ฉบ่ˆŠไปฃ็ขผ + await codeEditor.click(); + await codeEditor.focus(); + const modifier = process.platform === "darwin" ? "Meta" : "Control"; + await page.keyboard.press(`${modifier}+A`); + await page.keyboard.press("Backspace"); + + // 4. ่ผธๅ…ฅๆ–ฐไปฃ็ขผ + // Step A: ๅฟซ้€Ÿๅกซๅ…ฅไธป้ซ” + await codeEditor.fill(codeBody); + // Step B: ๆ‰‹ๅ‹•่ผธๅ…ฅ็ตๅฐพๅˆ†่™Ÿ๏ผŒๅผทๅˆถ่งธ็™ผ Live Preview ้‡ๆ–ฐๆธฒๆŸ“ + await codeEditor.type(";", { delay: 100 }); + + // Previous and Next page button should not be exist + const previous = page.getByRole("button", { name: "Previous" }).nth(1); + const next = page.getByRole("button", { name: "Next" }).nth(1); + + await expect(previous).not.toBeVisible(); + await expect(next).not.toBeVisible(); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/resizable_columns.spec.js b/tests/playwright/Dashboard/Examples/Basic/resizable_columns.spec.js new file mode 100644 index 00000000..ce7c3d48 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/resizable_columns.spec.js @@ -0,0 +1,202 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/resizable"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Resizable columns", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Resizable columns", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Resizable columns"); + }); + + test("Should resize column width when dragged", async ({ page }) => { + // 1. ้Ž–ๅฎš็›ฎๆจ™ๆฌ„ไฝ (Email) + // ๆˆ‘ๅ€‘ไฝฟ็”จ first() ็ขบไฟๅชๆ“ไฝœ็ฌฌไธ€ๅ€‹่กจๆ ผ + const emailHeader = page + .locator("th") + .filter({ hasText: "Email" }) + .first(); + + // 2. ้Ž–ๅฎš่ฉฒๆฌ„ไฝๅ…ง้ƒจ็š„"่ชฟๆ•ดๆ‰‹ๆŸ„" + // Grid.js ็š„ๅฏฆไฝœไธญ๏ผŒ้€™ๆ˜ฏ th ๅ…ง้ƒจ็š„ไธ€ๅ€‹ div๏ผŒclass ้€šๅธธ็‚บ .gridjs-resizable + const resizerHandle = emailHeader.locator(".gridjs-resizable"); + + // ็ขบไฟๆ‰‹ๆŸ„ๅญ˜ๅœจ (ๆœ‰ไบ›ๆƒ…ๆณไธ‹ๅฆ‚ๆžœๆฒ’่จญๅฎš resizable: true ๅฐฑไธๆœƒๆœ‰้€™ๅ€‹ๅ…ƒ็ด ) + await expect(resizerHandle).toBeVisible(); + + // 3. ๅ–ๅพ—ๅˆๅง‹ๅฏฌๅบฆ (Initial Width) + // ไฝฟ็”จ boundingBox() ๅ–ๅพ—ๅ…ƒ็ด ็š„็ฒพ็ขบๅนพไฝ•่ณ‡่จŠ + const initialBox = await emailHeader.boundingBox(); + if (!initialBox) throw new Error("Cannot get initial bounding box"); + + // 4. ๅ–ๅพ—ๆ‰‹ๆŸ„็š„ๅบงๆจ™๏ผŒๆบ–ๅ‚™้€ฒ่กŒๆ‹–ๆ›ณ + const resizerBox = await resizerHandle.boundingBox(); + if (!resizerBox) throw new Error("Cannot get resizer bounding box"); + + // ่จˆ็ฎ—ๆ‹–ๆ›ณ็š„่ตท้ปž (Start Point): ๆ‰‹ๆŸ„็š„ไธญๅฟƒ้ปž + const startX = resizerBox.x + resizerBox.width / 2; + const startY = resizerBox.y + resizerBox.height / 2; + + // ๅฎš็พฉๆ‹–ๆ›ณ่ท้›ข (ๅ‘ๅณๆ‹– 100px) + const dragDistance = 100; + + // 5. ๅŸท่กŒๆป‘้ผ ๆ‹–ๆ›ณๆจกๆ“ฌ (Mouse Simulation) + // Playwright ็š„ dragTo ๆœ‰ๆ™‚ๅฐ้€™็จฎ็ดฐๅพฎ UI ๆ“ไฝœไธๅค ็ฒพ็ขบ๏ผŒๆˆ‘ๅ€‘ไฝฟ็”จ mouse API ๆ‰‹ๅ‹•ๆŽงๅˆถ + + // a. ็งปๅ‹•ๅˆฐๆ‰‹ๆŸ„ไฝ็ฝฎ + await page.mouse.move(startX, startY); + + // b. ๆŒ‰ไธ‹ๆป‘้ผ ๅทฆ้ต (Mouse Down) + await page.mouse.down(); + + // c. ็งปๅ‹•ๆป‘้ผ  (Mouse Move) - ๆจกๆ“ฌๆ‹–ๆ›ณ้Ž็จ‹ + // ๅปบ่ญฐๅˆ†ๆฎต็งปๅ‹•ๆˆ–็›ดๆŽฅ็งปๅ‹•ๅˆฐ็ต‚้ปž + await page.mouse.move(startX + dragDistance, startY, { steps: 5 }); // steps: 5 ่ฎ“็งปๅ‹•็จๅพฎๅนณๆป‘ไธ€้ปž + + // d. ๆ”พ้–‹ๆป‘้ผ  (Mouse Up) + await page.mouse.up(); + + // 6. ๅ–ๅพ—ๆœ€็ต‚ๅฏฌๅบฆ (Final Width) + // ็ญ‰ๅพ…ไธ€้ปž้ปžๆ™‚้–“่ฎ“ DOM ๅฎŒๆˆ้‡็นช (้›–็„ถ้€šๅธธๆ˜ฏๅŒๆญฅ็š„๏ผŒไฝ†ๅœจๆธฌ่ฉฆไธญๅŠ ไธŠ waitForTimeout ๆฏ”่ผƒ็ฉฉๅฅ) + // await page.waitForTimeout(100); + const finalBox = await emailHeader.boundingBox(); + if (!finalBox) throw new Error("Cannot get final bounding box"); + + // 7. ้ฉ—่ญ‰็ตๆžœ + // ๆœ€็ต‚ๅฏฌๅบฆๆ‡‰่ฉฒๅคงๆ–ผๅˆๅง‹ๅฏฌๅบฆ (่€ƒๆ…ฎๅˆฐไธ€้ปž้ปž่ชคๅทฎ๏ผŒๆˆ‘ๅ€‘้ ๆœŸๅฎƒ่‡ณๅฐ‘ๅขžๅŠ  50px ไปฅไธŠ) + console.log( + `Initial Width: ${initialBox.width}, Final Width: ${finalBox.width}`, + ); + + expect(finalBox.width).toBeGreaterThan(initialBox.width); + + // ๆ›ด็ฒพ็ขบ็š„ๆ–ท่จ€๏ผšๅฏฌๅบฆๅขžๅŠ ้‡ๆ‡‰่ฉฒๆŽฅ่ฟ‘ๆˆ‘ๅ€‘ๆ‹–ๆ›ณ็š„่ท้›ข + // (ๆณจๆ„๏ผš็€่ฆฝๅ™จๆธฒๆŸ“ๅฏ่ƒฝๆœ‰ sub-pixel ๅทฎ็•ฐ๏ผŒๆ‰€ไปฅไธๅปบ่ญฐ็”จ toBe(initial + 100)) + expect(finalBox.width).toBeGreaterThan(initialBox.width + 50); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/search.spec.js b/tests/playwright/Dashboard/Examples/Basic/search.spec.js new file mode 100644 index 00000000..90ecad9c --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/search.spec.js @@ -0,0 +1,245 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/search"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Search", async ({ page }) => { + const title = page.getByRole("heading", { name: "Search", level: 1 }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Search"); + }); + + test("Click the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); + + test("When search is set to true", async ({ page }) => { + const firstGridContainer = page.locator(".gridjs-container").first(); + const searchInput = firstGridContainer.locator( + "input.gridjs-search-input", + ); + const tableBody = firstGridContainer.locator( + "table.gridjs-table tbody", + ); + + // 1. ็ขบไฟๆœๅฐ‹ๆก†ๅฏ่ฆ‹ + await expect(searchInput).toBeVisible(); + + // 2. ่ผธๅ…ฅ 'john' (ๆจกๆ“ฌไฝฟ็”จ่€…็œŸๅฏฆๆ‰“ๅญ—่กŒ็‚บ) + await searchInput.fill("john"); + + // 3. ้ฉ—่ญ‰ๆญฃๅ‘็ตๆžœ๏ผšๆ‡‰่ฉฒๅชๅ‰ฉไธ‹ไธ€่กŒ๏ผŒไธ”ๅŒ…ๅซ 'John' + // ๆณจๆ„๏ผšGrid.js ๆœๅฐ‹ๅๆ‡‰ๅพˆๅฟซ๏ผŒPlaywright ็š„ expect ๆœƒ่‡ชๅ‹• retry ็ญ‰ๅพ… DOM ่ฎŠๆ›ด + const rows = tableBody.locator("tr"); + await expect(rows).toHaveCount(1); + await expect(rows.first()).toContainText("John"); + + // 4. ้ฉ—่ญ‰่ฒ ๅ‘็ตๆžœ๏ผš็ขบ่ชๅ…ถไป–ๅๅญ—ๅทฒ่ขซ้Žๆฟพๆމ (Filtered Out) + // ๆˆ‘ๅ€‘ๆชขๆŸฅ tbody ๅฎนๅ™จๅ…งๆ˜ฏๅฆ"ไธๅŒ…ๅซ"้€™ไบ›ๆ–‡ๅญ— + await expect(tableBody).not.toContainText("Mark"); + await expect(tableBody).not.toContainText("Eoin"); + await expect(tableBody).not.toContainText("Nisen"); + + // ๆ›ฟไปฃๅฏซๆณ• (้‡ๅฐ็‰นๅฎšๅ…ƒ็ด ็š„ๆ›ดๅšดๆ ผๆชขๆŸฅ)๏ผš + // ็ขบไฟๆ‰พไธๅˆฐๅซๆœ‰้€™ไบ›ๆ–‡ๅญ—็š„ๅ„ฒๅญ˜ๆ ผ + await expect( + firstGridContainer.getByRole("cell", { name: "Mark" }), + ).toBeHidden(); + await expect( + firstGridContainer.getByRole("cell", { name: "Eoin" }), + ).toBeHidden(); + await expect( + firstGridContainer.getByRole("cell", { name: "Nisen" }), + ).toBeHidden(); + }); + + test("Demo: Change search from true to false (JS Version)", async ({ + page, + }) => { + // 1. ๅฎšไฝ็ทจ่ผฏๅ™จ (็ขบไฟๆ˜ฏ็ฌฌไธ€ๅ€‹ๅฏ่ฆ‹็š„็ทจ่ผฏๅ™จ) + const codeEditor = page + .locator("textarea.npm__react-simple-code-editor__textarea") + .first(); + + // ็ขบไฟ็ทจ่ผฏๅ™จๅทฒ็ถ“่ผ‰ๅ…ฅ + await codeEditor.waitFor({ state: "visible" }); + + // 2. ๅ–ๅพ—็ทจ่ผฏๅ™จ็›ฎๅ‰็š„็จ‹ๅผ็ขผๅ…งๅฎน + const originalCode = await codeEditor.inputValue(); + + // 3. ๅฐ‹ๆ‰พ "search: true" ้—œ้ตๅญ—็š„็ตๆŸไฝ็ฝฎ + // ๆˆ‘ๅ€‘่ฆๆ‰พ็š„ๆ˜ฏ 'true' ้€™ๅ€‹ๅญ—็ตๆŸ็š„ๅœฐๆ–น๏ผŒ้€™ๆจฃๆธธๆจ™ๆ‰่ƒฝๆ”พๅœจๅฎƒ็š„ๅพŒ้ข + // ๆณจๆ„๏ผš้€™่ฃกไฝฟ็”จๆญฃๅ‰‡่กจ้”ๅผไพ†่™•็†ๅฏ่ƒฝๅญ˜ๅœจ็š„็ฉบ็™ฝ (search: true ๆˆ– search:true) + const match = originalCode.match(/search:\s*true/); + + if (!match) { + throw new Error( + "ๅœจ็ทจ่ผฏๅ™จไธญๆ‰พไธๅˆฐ 'search: true'๏ผŒ่ซ‹็ขบ่ช็ฏ„ไพ‹็จ‹ๅผ็ขผๆ˜ฏๅฆๆญฃ็ขบ", + ); + } + + // ่จˆ็ฎ—ๆธธๆจ™ๆ‡‰่ฉฒ่ฆๅœจ็š„ไฝ็ฝฎ๏ผš (ๅŒน้…ๅˆฐ็š„่ตทๅง‹ index) + (ๅŒน้…ๅˆฐ็š„ๅญ—ไธฒ้•ทๅบฆ) + // ไพ‹ๅฆ‚ "search: true" ้•ทๅบฆๆ˜ฏ 12๏ผŒๆธธๆจ™ๅฐฑๆœƒๅฎšๅœจ true ็š„ๅพŒ้ข + const cursorPosition = match.index + match[0].length; + + await codeEditor.evaluate((node, pos) => { + // node ๅฐฑๆ˜ฏ้‚ฃๅ€‹ textarea ๅ…ƒ็ด  + node.setSelectionRange(pos, pos); // ๅฐ‡ๆธธๆจ™่จญๅฎšๅˆฐๆŒ‡ๅฎšไฝ็ฝฎ + node.focus(); // ็ขบไฟ็ทจ่ผฏๅ™จ็ฒๅพ—็„ฆ้ปž + }, cursorPosition); + + // 5. ๆจกๆ“ฌ็œŸไบบๅ‹•ไฝœ๏ผšๅˆช้™ค "true" + // "true" ๆœ‰ 4 ๅ€‹ๅญ—ๅ…ƒ๏ผŒๆ‰€ไปฅๆŒ‰ 4 ๆฌก Backspace + // delay: 100 ่ฎ“ๅฝฑ็‰‡็œ‹่ตทไพ†ๅƒ็œŸไบบๅœจๅˆช้™ค + for (let i = 0; i < 4; i++) { + await page.keyboard.press("Backspace", { delay: 100 }); + } + + // 6. ๆจกๆ“ฌ็œŸไบบๅ‹•ไฝœ๏ผš่ผธๅ…ฅ "false" + await page.keyboard.type("false", { delay: 100 }); + + await page.waitForTimeout(10000); // ๅœ้ “ไธ€ไธ‹่ฎ“่ง€็œพ็œ‹ๅˆฐ false + await page.keyboard.press("Space"); + await page.keyboard.press("Backspace"); + + // 8. ้ฉ—่ญ‰็ตๆžœ + const firstGridContainer = page.locator(".gridjs-container").first(); + + // ๆ–ท่จ€๏ผšๆœๅฐ‹ๆก†ๆ‡‰่ฉฒๆถˆๅคฑ + await expect( + firstGridContainer.locator("input.gridjs-search-input"), + ).toBeHidden(); + + // ๆ–ท่จ€๏ผš่กจๆ ผๆ‡‰่ฉฒ้‚„ๆดป่‘— (ๆฒ’ๆœ‰ๅ› ็‚บๅ ฑ้Œฏ่€Œๆถˆๅคฑ) + await expect( + firstGridContainer.locator("table.gridjs-table"), + ).toBeVisible(); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/sorting.spec.js b/tests/playwright/Dashboard/Examples/Basic/sorting.spec.js new file mode 100644 index 00000000..36d827a8 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/sorting.spec.js @@ -0,0 +1,198 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/sorting"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Sorting", async ({ page }) => { + const title = page.getByRole("heading", { name: "Sorting", level: 1 }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Sorting"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); + + test("Should sort data correctly (Ascending and Descending)", async ({ + page, + }) => { + const container = page.locator(".gridjs-wrapper").first(); + const rows = container.locator("table.gridjs-table tbody tr"); + + // 1. ้Ž–ๅฎš "Name" ๆฌ„ไฝๆจ™้ ญ + const nameHeader = container.locator("th", { hasText: "Name" }); + + // 2. [ไฟฎๆญฃ้—œ้ต] ้Ž–ๅฎšๆจ™้ ญๅ…ง็š„ "ๆŽ’ๅบๆŒ‰้ˆ•" (ๆ นๆ“šๆ‚จ็š„ๆˆชๅœ–๏ผŒๅฎƒๆ˜ฏ button.gridjs-sort) + const sortButton = nameHeader.locator(".gridjs-sort"); + + // ็ขบไฟๆŒ‰้ˆ•ๅญ˜ๅœจ + await expect(sortButton).toBeVisible(); + + // --- Step 1: ๆธฌ่ฉฆๅ‡ๅ†ชๆŽ’ๅบ (Ascending) --- + + // ้ปžๆ“ŠๆŽ’ๅบๆŒ‰้ˆ• + await sortButton.click(); + + // [ไฟฎๆญฃ้—œ้ต] ้ฉ—่ญ‰ "ๆŒ‰้ˆ•" ็š„ class ๆ˜ฏๅฆ่ฎŠ็‚บ asc + // ๆ นๆ“šๆˆชๅœ–๏ผŒๆˆๅŠŸๆŽ’ๅบๅพŒ class ๆœƒๅŒ…ๅซ "gridjs-sort-asc" + await expect(sortButton).toHaveClass(/gridjs-sort-asc/); + + // ๆŠ“ๅ–่ณ‡ๆ–™ไธฆ้ฉ—่ญ‰ + const namesAsc = await rows + .locator("td") + .nth(0) + .evaluateAll((cells) => cells.map((cell) => cell.innerText)); + console.log("Ascending Result:", namesAsc); + + const expectedAsc = [...namesAsc].sort((a, b) => a.localeCompare(b)); + expect(namesAsc).toEqual(expectedAsc); + + // --- Step 2: ๆธฌ่ฉฆ้™ๅ†ชๆŽ’ๅบ (Descending) --- + + // ๅ†ๆฌก้ปžๆ“ŠๅŒไธ€ๅ€‹ๆŽ’ๅบๆŒ‰้ˆ• + await sortButton.click(); + + // [ไฟฎๆญฃ้—œ้ต] ้ฉ—่ญ‰ "ๆŒ‰้ˆ•" ็š„ class ๆ˜ฏๅฆ่ฎŠ็‚บ desc + // ้€šๅธธ Grid.js ็š„ๅ‘ฝๅ่ฆๅ‰‡ๆ˜ฏ gridjs-sort-desc + await expect(sortButton).toHaveClass(/gridjs-sort-desc/); + + // ๆŠ“ๅ–่ณ‡ๆ–™ไธฆ้ฉ—่ญ‰ + const namesDesc = await rows + .locator("td") + .nth(0) + .evaluateAll((cells) => cells.map((cell) => cell.innerText)); + console.log("Descending Result:", namesDesc); + + const expectedDesc = [...namesDesc] + .sort((a, b) => a.localeCompare(b)) + .reverse(); + expect(namesDesc).toEqual(expectedDesc); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/Basic/wide_table.spec.js b/tests/playwright/Dashboard/Examples/Basic/wide_table.spec.js new file mode 100644 index 00000000..e123b1fd --- /dev/null +++ b/tests/playwright/Dashboard/Examples/Basic/wide_table.spec.js @@ -0,0 +1,204 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/wide-table"; + +const expectedHeaders = [ + "Name", + "Email", + "Title", + "Company", + "Country", + "County", +]; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Wide Table", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Wide Table", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Wide Table"); + }); + + /** + * ๆธฌ่ฉฆๆƒ…ๅขƒ 1: ้ฉ—่ญ‰ๆ‰€ๆœ‰ๆฌ„ไฝๆจ™้ ญ (Headers) ๆ˜ฏๅฆๅญ˜ๅœจๆ–ผ DOM ไธญ + * ้‡้ปž๏ผš้ฉ—่ญ‰ๆฌ„ไฝๆ•ธ้‡่ˆ‡ๅ็จฑๅฎŒๅ…จ็ฌฆๅˆ้ ๆœŸ + */ + test("Should render all column headers correctly", async ({ page }) => { + // [ไฟฎๆญฃ้ปž 1] ๅ…ˆ้Ž–ๅฎš็ฌฌไธ€ๅ€‹ wrapper๏ผŒๅ†ๆ‰พ่ฃก้ข็š„ th + // ้€™ๆจฃๅฐฑไธๆœƒๆŠ“ๅˆฐ็ฌฌไบŒๅ€‹่กจๆ ผ็š„ๆจ™้ ญ + const headerCells = page + .locator(".gridjs-wrapper") + .first() + .locator("thead th"); + + // 1. ้ฉ—่ญ‰ๆฌ„ไฝ็ธฝๆ•ธ + // ็พๅœจ headerCells ๅชๆœƒๅŒ…ๅซ็ฌฌไธ€ๅ€‹่กจๆ ผ็š„ๆจ™้ ญ๏ผŒๆ•ธ้‡ๆ‡‰่ฉฒๆœƒๆญฃ็ขบ + await expect(headerCells).toHaveCount(expectedHeaders.length); + + // 2. ้ฉ—่ญ‰ๆ‰€ๆœ‰ๆฌ„ไฝๅ็จฑ + await expect(headerCells).toHaveText(expectedHeaders); + }); + + /** + * ๆธฌ่ฉฆๆƒ…ๅขƒ 2: ้ฉ—่ญ‰ๆฐดๅนณๆฒๅ‹•่กŒ็‚บ (Horizontal Scroll) + * ้‡้ปž๏ผšๅฏฌ่กจๆ ผๆ‡‰่ฉฒ่ฆๆœ‰ๆฒ่ปธ๏ผŒไธ”ๆœ€ๅพŒไธ€ๅ€‹ๆฌ„ไฝๅˆๅง‹็‹€ๆ…‹ๅฏ่ƒฝๅœจ่ฆ–็ช—ๅค– + */ + test("Should handle horizontal scrolling for wide columns", async ({ + page, + }) => { + // ้Ž–ๅฎš่กจๆ ผ็š„ๅค–ๅฑคๅฎนๅ™จ (Grid.js ้€šๅธธไฝฟ็”จ gridjs-wrapper ไพ†่™•็†ๆฒๅ‹•) + const tableWrapper = page.locator(".gridjs-wrapper").first(); + const lastColumnHeader = page + .locator("table.gridjs-table thead th") + .last(); + + // 1. ้ฉ—่ญ‰ๅฎนๅ™จๆ˜ฏๅฆ"้œ€่ฆ"ๆฒๅ‹• (ๅ…งๅฎนๅฏฌๅบฆ > ๅฎนๅ™จๅฏฌๅบฆ) + // ๆˆ‘ๅ€‘ไฝฟ็”จ evaluate ไพ†ๆชขๆŸฅ DOM ๅฑฌๆ€ง + const isScrollable = await tableWrapper.evaluate((el) => { + return el.scrollWidth > el.clientWidth; + }); + + // ๅฆ‚ๆžœๆ˜ฏๅฏฌ่กจๆ ผ๏ผŒ้€™่ฃกๅฟ…้ ˆ็‚บ true + expect(isScrollable).toBeTruthy(); + + // 2. ้ฉ—่ญ‰ๆœ€ๅพŒไธ€ๅ€‹ๆฌ„ไฝ (Country) ็š„ๅฏ่ฆ‹ๆ€ง + // ๅœจๆฒๅ‹•ไน‹ๅ‰๏ผŒๆœ€ๅพŒไธ€ๅ€‹ๆฌ„ไฝๅฏ่ƒฝไธๅœจ่ฆ–ๅฃๅ…ง (่ฆ–ๆ‚จ็š„่žขๅน•ๅฏฌๅบฆ่€Œๅฎš) + // ็‚บไบ†ๆธฌ่ฉฆ็ฉฉๅฅๆ€ง๏ผŒๆˆ‘ๅ€‘ๅ…ˆๅผทๅˆถๅฐ‡ๅฎนๅ™จๆฒๅ‹•ๅˆฐๆœ€ๅณ้‚Š + await tableWrapper.evaluate((el) => { + el.scrollLeft = el.scrollWidth; + }); + + // 3. ๆ–ท่จ€๏ผšๆฒๅ‹•ๅพŒ๏ผŒๆœ€ๅพŒไธ€ๅ€‹ๆฌ„ไฝๆ‡‰่ฉฒ่ฆๆ˜ฏๅฏ่ฆ‹็š„ (Visible) + // ๆณจๆ„๏ผšPlaywright ็š„ toBeVisible ๆœƒๆชขๆŸฅๅ…ƒ็ด ๆ˜ฏๅฆๅœจ viewport ๅ…ง + await expect(lastColumnHeader).toBeVisible(); + + // ๅ†ๆฌก็ขบ่ชๅฎƒๆ˜ฏๆˆ‘ๅ€‘้ ๆœŸ็š„ๆœ€ๅพŒไธ€ๅ€‹ๆฌ„ไฝ + await expect(lastColumnHeader).toHaveText( + expectedHeaders[expectedHeaders.length - 1], + ); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/custon_sort.spec.js b/tests/playwright/Dashboard/Examples/advanced/custon_sort.spec.js new file mode 100644 index 00000000..a846b657 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/custon_sort.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/custom-sort"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Custom sort", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Custom sort", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Custom sort"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/events.spec.js b/tests/playwright/Dashboard/Examples/advanced/events.spec.js new file mode 100644 index 00000000..58e94698 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/events.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/event-handler"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Events", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Events", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Events"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/forceRender.spec.js b/tests/playwright/Dashboard/Examples/advanced/forceRender.spec.js new file mode 100644 index 00000000..96623aa2 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/forceRender.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/force-render"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: forceRender", async ({ page }) => { + const title = page.getByRole("heading", { + name: "forceRender", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("forceRender"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/multi_column_sort.spec.js b/tests/playwright/Dashboard/Examples/advanced/multi_column_sort.spec.js new file mode 100644 index 00000000..ef2b1426 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/multi_column_sort.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/multi-sort"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Multi column sort", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Multi column sort", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Multi column sort"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/nested_header.spec.js b/tests/playwright/Dashboard/Examples/advanced/nested_header.spec.js new file mode 100644 index 00000000..0f8fb930 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/nested_header.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/nested-header"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Nested Header", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Nested Header", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Nested Header"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/stock_market.spec.js b/tests/playwright/Dashboard/Examples/advanced/stock_market.spec.js new file mode 100644 index 00000000..696bb65f --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/stock_market.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/stock-market"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Stock Market", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Stock Market", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Stock Market"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/advanced/virtual_DOM.spec.js b/tests/playwright/Dashboard/Examples/advanced/virtual_DOM.spec.js new file mode 100644 index 00000000..7ab43e05 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/advanced/virtual_DOM.spec.js @@ -0,0 +1,145 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/virtual-dom"; + +test.describe("UI testing", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Virtual DOM", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Virtual DOM", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Virtual DOM"); + }); + + test("Check the home page link", async ({ page }) => { + const link = page.getByRole("link", { name: "Home page" }); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL("http://localhost:3000"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/cell_attributes.spec.js b/tests/playwright/Dashboard/Examples/customizing/cell_attributes.spec.js new file mode 100644 index 00000000..e0a738fc --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/cell_attributes.spec.js @@ -0,0 +1,219 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/cell-attributes"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Cell Attributes", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Cell Attributes", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Cell Attributes"); + }); + + test("Demo: Perfect Formatting with insertText (The Fundamental Fix)", async ({ + page, + }) => { + const targetUrl = "http://localhost:3000/docs/examples/cell-attributes"; + await page.goto(targetUrl); + + const codeEditor = page + .locator("textarea.npm__react-simple-code-editor__textarea") + .first(); + await expect(codeEditor).toBeVisible(); + + // 1. ๅ–ๅพ—ๅŽŸๅง‹็จ‹ๅผ็ขผ & ่จˆ็ฎ—ๆ‰‹่ก“็ฏ„ๅœ (่ˆ‡ไน‹ๅ‰็›ธๅŒ๏ผŒไฟ็•™ .render) + const originalCode = await codeEditor.inputValue(); + + const startMatch = originalCode.match(/new Grid\(\{/); + if (!startMatch) throw new Error("ๆ‰พไธๅˆฐ new Grid({"); + const startPos = startMatch.index + startMatch[0].length; + + const endPos = originalCode.lastIndexOf("})"); + if (endPos === -1) throw new Error("ๆ‰พไธๅˆฐ็ตๅฐพ็š„ })"); + + // 2. ้ธๅ–ไธฆๅˆช้™ค่ˆŠๅ…งๅฎน + await codeEditor.evaluate( + (node, { start, end }) => { + node.setSelectionRange(start, end); + node.focus(); + }, + { start: startPos, end: endPos }, + ); + + await page.keyboard.press("Backspace"); + + // 3. โœจ ๅฎš็พฉๅฎŒ็พŽๆŽ’็‰ˆ็š„็จ‹ๅผ็ขผ โœจ + // ็›ดๆŽฅ็”จๅๅผ•่™Ÿๅณๅฏ๏ผŒinsertText ๆœƒๅฟ ๅฏฆๅ‘ˆ็พๆฏไธ€ๅ€‹็ฉบๆ ผ + // ๆณจๆ„๏ผš็ฌฌไธ€่กŒ้–‹้ ญๅŠ ไธŠ \n ็ขบไฟๅพžๆ–ฐ็š„ไธ€่กŒ้–‹ๅง‹ + const prettyConfig = ` + columns: [ + { + name: 'Name', + attributes: (cell) => { + if (cell === 'John') { + return { + 'style': 'color: red; font-weight: bold;', + 'data-test': 'john-cell' + }; + } + } + }, + 'Email' + ], + data: [ + ['John', 'john@example.com'], + ['Mark', 'mark@gmail.com'] + ]`; + + // 4. + // ๆˆ‘ๅ€‘ไธไฝฟ็”จ pressSequentially (ๅฎƒๆœƒ่งธ็™ผ Enter ๅฐŽ่‡ด็ธฎๆŽ’ไบ‚ๆމ) + // ไฝฟ็”จ insertText (็ด”็ฒนๆ’ๅ…ฅๆ–‡ๅญ—๏ผŒไธ่งธ็™ผ็ทจ่ผฏๅ™จ็š„่‡ชๅ‹•็ธฎๆŽ’) + const delay = 15; // ๆ‰“ๅญ—้€Ÿๅบฆ (ๆฏซ็ง’) + + for (const char of prettyConfig) { + await page.keyboard.insertText(char); + await page.waitForTimeout(delay); + } + + // 5. ่งธ็™ผๆ›ดๆ–ฐ (Space + Backspace) + // ็ขบไฟ React ๅตๆธฌๅˆฐ changes + await page.waitForTimeout(10000); + await page.keyboard.press("Space"); + await page.keyboard.press("Backspace"); + + // --- ้ฉ—่ญ‰้šŽๆฎต --- + const gridContainer = page.locator(".gridjs-container").first(); + const johnCell = gridContainer + .locator("td", { hasText: /^John$/ }) + .first(); + + // ้ฉ—่ญ‰ Mock Data ่ˆ‡ Style + await expect(johnCell).toBeVisible(); + await expect(johnCell).toHaveCSS("color", "rgb(255, 0, 0)"); + await expect(johnCell).toHaveCSS("font-weight", "700"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/cell_formatting.spec.js b/tests/playwright/Dashboard/Examples/customizing/cell_formatting.spec.js new file mode 100644 index 00000000..c236a108 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/cell_formatting.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/cell-formatting"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Cell formatting", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Cell formatting", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Cell formatting"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/html_in_cells.spec.js b/tests/playwright/Dashboard/Examples/customizing/html_in_cells.spec.js new file mode 100644 index 00000000..a3b51967 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/html_in_cells.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/html-cells"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: HTML in cells", async ({ page }) => { + const title = page.getByRole("heading", { + name: "HTML in cells", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("HTML in cells"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/html_in_header_cells.spec.js b/tests/playwright/Dashboard/Examples/customizing/html_in_header_cells.spec.js new file mode 100644 index 00000000..bae269e7 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/html_in_header_cells.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/html-header-cells"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: HTML in header cells", async ({ page }) => { + const title = page.getByRole("heading", { + name: "HTML in header cells", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("HTML in header cells"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/react_component_in_cells.spec.js b/tests/playwright/Dashboard/Examples/customizing/react_component_in_cells.spec.js new file mode 100644 index 00000000..8844b908 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/react_component_in_cells.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/react-cells"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: React Component in cells", async ({ page }) => { + const title = page.getByRole("heading", { + name: "React Component in cells", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("React Component in cells"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/customizing/row_buttons.spec.js b/tests/playwright/Dashboard/Examples/customizing/row_buttons.spec.js new file mode 100644 index 00000000..2ddf8b84 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/customizing/row_buttons.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/row-buttons"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Row buttons", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Row buttons", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Row buttons"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/data_source/async_data_import.spec.js b/tests/playwright/Dashboard/Examples/data_source/async_data_import.spec.js new file mode 100644 index 00000000..6e4e1660 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/data_source/async_data_import.spec.js @@ -0,0 +1,137 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/import-async"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Async data import", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Async data import", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Async data import"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/data_source/dynamic_data_import.spec.js b/tests/playwright/Dashboard/Examples/data_source/dynamic_data_import.spec.js new file mode 100644 index 00000000..e519454a --- /dev/null +++ b/tests/playwright/Dashboard/Examples/data_source/dynamic_data_import.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/import-function"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Dynamic data import", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Dynamic data import", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Dynamic data import"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/data_source/json.spec.js b/tests/playwright/Dashboard/Examples/data_source/json.spec.js new file mode 100644 index 00000000..2861b6af --- /dev/null +++ b/tests/playwright/Dashboard/Examples/data_source/json.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/import-json"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: JSON", async ({ page }) => { + const title = page.getByRole("heading", { + name: "JSON", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("JSON"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/data_source/xml.spec.js b/tests/playwright/Dashboard/Examples/data_source/xml.spec.js new file mode 100644 index 00000000..855a44cc --- /dev/null +++ b/tests/playwright/Dashboard/Examples/data_source/xml.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/import-xml"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: XML", async ({ page }) => { + const title = page.getByRole("heading", { + name: "XML", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("XML"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/server_side/custom_http_client.spec.js b/tests/playwright/Dashboard/Examples/server_side/custom_http_client.spec.js new file mode 100644 index 00000000..38f408b3 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/server_side/custom_http_client.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/custom-http-client"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Custom HTTP client", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Custom HTTP client", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Custom HTTP client"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/server_side/import_server_side_data.spec.js b/tests/playwright/Dashboard/Examples/server_side/import_server_side_data.spec.js new file mode 100644 index 00000000..b5b47499 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/server_side/import_server_side_data.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/server"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Import server-side data", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Import server-side data", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Import server-side data"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/server_side/server_side_pagination.spec.js b/tests/playwright/Dashboard/Examples/server_side/server_side_pagination.spec.js new file mode 100644 index 00000000..cb98775c --- /dev/null +++ b/tests/playwright/Dashboard/Examples/server_side/server_side_pagination.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/server-side-pagination"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Server Side Pagination", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Server Side Pagination", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Server Side Pagination"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/server_side/server_side_search.spec.js b/tests/playwright/Dashboard/Examples/server_side/server_side_search.spec.js new file mode 100644 index 00000000..68f76923 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/server_side/server_side_search.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/server-side-search"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Server Side Search", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Server Side Search", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Server Side Search"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/server_side/server_side_sorting.spec.js b/tests/playwright/Dashboard/Examples/server_side/server_side_sorting.spec.js new file mode 100644 index 00000000..80dedc75 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/server_side/server_side_sorting.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/server-side-sort"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: Server Side Sorting", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Server Side Sorting", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("Server Side Sorting"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/styling/css_classname.spec.js b/tests/playwright/Dashboard/Examples/styling/css_classname.spec.js new file mode 100644 index 00000000..ebb55fee --- /dev/null +++ b/tests/playwright/Dashboard/Examples/styling/css_classname.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/css-classname"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: CSS ClassName", async ({ page }) => { + const title = page.getByRole("heading", { + name: "CSS ClassName", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("CSS ClassName"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/styling/css_in_js.spec.js b/tests/playwright/Dashboard/Examples/styling/css_in_js.spec.js new file mode 100644 index 00000000..174dc86a --- /dev/null +++ b/tests/playwright/Dashboard/Examples/styling/css_in_js.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/css-in-js"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: CSS-in-JS", async ({ page }) => { + const title = page.getByRole("heading", { + name: "CSS-in-JS", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("CSS-in-JS"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Examples/styling/css_style.spec.js b/tests/playwright/Dashboard/Examples/styling/css_style.spec.js new file mode 100644 index 00000000..fcfe90c7 --- /dev/null +++ b/tests/playwright/Dashboard/Examples/styling/css_style.spec.js @@ -0,0 +1,136 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/examples/css-style"; + +test.describe("Grab the title", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("1. Grab the h1 title: CSS Style", async ({ page }) => { + const title = page.getByRole("heading", { + name: "CSS Style", + level: 1, + }); + await expect(title).toBeVisible(); + + await expect(title).toHaveText("CSS Style"); + }); +}); + +test.describe("All links on the blog page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); diff --git a/tests/playwright/Dashboard/Introduction/01_gridjs.spec.js b/tests/playwright/Dashboard/Introduction/01_gridjs.spec.js new file mode 100644 index 00000000..92dc2e25 --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/01_gridjs.spec.js @@ -0,0 +1,116 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Grid.js Documentation - Introduction Section', () => { + + test.describe('What is Grid.js Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs'); + }); + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[href="/"]').first(); + await expect(homeButton).toBeVisible(); + + await homeButton.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + }); + + test('should verify Preact link', async ({ page }) => { + const preactLink = page.locator('a[href*="preact"]'); + await expect(preactLink).toBeVisible(); + await expect(preactLink).toHaveText('Preact'); + + // Verify it's an external link + const href = await preactLink.getAttribute('href'); + expect(href).toBeTruthy(); + + // Click and verify opens in new tab or navigates + const [newPage] = await Promise.all([ + page.context().waitForEvent('page'), + preactLink.click() + ]); + + await newPage.waitForLoadState(); + expect(newPage.url()).toContain('preact'); + await newPage.close(); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + // Verify the link points to GitHub license.md file + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/index.md'); + }); + + test('should verify Next Philosophy link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Philosophy'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/philosophy'); + await expect(page.locator('h1')).toContainText('Philosophy'); + }); + }); + + test.describe('Complete Introduction Section Navigation', () => { + test('should navigate through all Introduction pages', async ({ page }) => { + // Start at Introduction landing page + await page.goto('https://gridjs.io/docs'); + + // Navigate through each page in order + const pages = [ + { name: 'What is Grid.js?', url: '/docs' }, + { name: 'Philosophy', url: '/philosophy' }, + { name: 'Sponsors', url: '/sponsors' }, + { name: 'Roadmap', url: '/roadmap' }, + { name: 'Community', url: '/community' }, + { name: 'License', url: '/license' } + ]; + + for (const pageInfo of pages) { + const link = page.locator(`a:has-text("${pageInfo.name}")`).first(); + await link.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain(pageInfo.url); + } + }); + }); + + test.describe('Navigation Bar Tests', () => { + test('should verify main navigation links', async ({ page }) => { + await page.goto('https://gridjs.io/docs'); + + const navLinks = [ + { text: 'Docs', href: '/docs' }, + { text: 'Examples', href: '/docs/examples' }, + { text: 'Support Grid.js', href: '/docs/support-gridjs' }, + { text: 'Community', href: '/docs/intro/community' }, + { text: 'Blog', href: '/blog' } + ]; + + for (const link of navLinks) { + const navLink = page.locator(`nav a:has-text("${link.text}")`).first(); + await expect(navLink).toBeVisible(); + } + }); + + test('should verify NPM and GitHub links in header', async ({ page }) => { + await page.goto('https://gridjs.io/docs'); + + const npmLink = page.locator('a:has-text("NPM")').first(); + await expect(npmLink).toBeVisible(); + + const githubLink = page.locator('a:has-text("GitHub")').first(); + await expect(githubLink).toBeVisible(); + }); + }); +}); diff --git a/tests/playwright/Dashboard/Introduction/01_gridjsNew.spec.js b/tests/playwright/Dashboard/Introduction/01_gridjsNew.spec.js new file mode 100644 index 00000000..61b6704c --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/01_gridjsNew.spec.js @@ -0,0 +1,79 @@ +const { test, expect } = require("playwright/test"); + +async function waitGridReady(page) { + await page.waitForSelector(".gridjs", { state: "visible" }); + await page.waitForSelector(".gridjs-tbody tr", { state: "visible" }); +} + +async function getNameList(page) { + await waitGridReady(page); + + const names = page.locator('td[data-column-id="name"]'); + const count = await names.count(); + + const result = []; + for (let i = 0; i < count; i++) { + result.push( + ((await names.nth(i).innerText()) || "") + .trim() + .replace(/\s+/g, " ") + ); + } + + return result.filter(Boolean); +} + +async function clickPage(page, pageNumber) { + const btn = page.locator(`.gridjs-pages button[aria-label="Page ${pageNumber}"]`); + await expect(btn).toBeVisible(); + await btn.click(); + await expect(btn).toHaveClass(/gridjs-currentPage/); + await waitGridReady(page); +} + +async function sortByName(page) { + const nameHeader = page.locator('th[data-column-id="name"]'); + await expect(nameHeader).toBeVisible(); + await nameHeader.click(); + + // After sorting, Grid.js resets to Page 1 (as shown in the video) + const page1Btn = page.locator('.gridjs-pages button[aria-label="Page 1"]'); + await expect(page1Btn).toHaveClass(/gridjs-currentPage/); + + await waitGridReady(page); +} + +test( + "BUG: After sorting by Name, Page 3 should not show the same data as Page 1", + async ({ page }) => { + // 1. Open Home page + await page.goto("https://gridjs.io/", { waitUntil: "domcontentloaded" }); + await waitGridReady(page); + + // 2. Navigate to Page 3 (before sorting) + await clickPage(page, 3); + const page3BeforeSort = await getNameList(page); + console.log("Page 3 before sort:", page3BeforeSort); + + // 3. Sort by Name (table resets to Page 1) + await sortByName(page); + const page1AfterSort = await getNameList(page); + console.log("Page 1 after sort:", page1AfterSort); + + // 4. Navigate to Page 3 again + await clickPage(page, 3); + const page3AfterSort = await getNameList(page); + console.log("Page 3 after sort:", page3AfterSort); + + // Safety checks + expect(page1AfterSort.length).toBeGreaterThan(0); + expect(page3AfterSort.length).toBeGreaterThan(0); + + // ๐Ÿ”ด BUG ASSERTION + // Page 3 must NOT be identical to Page 1 after sorting + expect( + page3AfterSort, + "Pagination bug: Page 3 displays the same rows as Page 1 after sorting" + ).not.toEqual(page1AfterSort); + } +); diff --git a/tests/playwright/Dashboard/Introduction/02_Philosophy.spec.js b/tests/playwright/Dashboard/Introduction/02_Philosophy.spec.js new file mode 100644 index 00000000..fc491636 --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/02_Philosophy.spec.js @@ -0,0 +1,94 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Philosophy Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/philosophy'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Philosophy/); + const heading = page.locator('h1:has-text("Philosophy")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Philosophy page title verified'); + }); + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[aria-label="Home page"]'); + await expect(homeButton).toBeVisible(); + + await homeButton.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button clicked - navigated to homepage'); + }); + + test('should verify section headings are present', async ({ page }) => { + const sections = [ + 'No vendor lock-in', + 'Browser support', + 'React Native support', + 'Developer Friendly' + ]; + + for (const section of sections) { + const heading = page.locator(`h2:has-text("${section}"), h3:has-text("${section}")`); + await expect(heading).toBeVisible(); + } + console.log('โœ“ All section headings verified'); + }); + + test('should verify data processing library link', async ({ page, context }) => { + const link = page.getByRole('link', { name: 'data processing library' }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('github.com/grid-js/gridjs/tree/master/src/pipeline'); + console.log('โœ“ Data processing library link clicked'); + + await newPage.close(); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/philosophy.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous (What is Grid.js?) link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('What is Grid.js?'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs'); + console.log('โœ“ Previous link clicked - navigated to What is Grid.js?'); + }); + + test('should verify Next (Sponsors) link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Sponsors'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/sponsors'); + console.log('โœ“ Next link clicked - navigated to Sponsors'); + }); + +}); + diff --git a/tests/playwright/Dashboard/Introduction/02_license.spec.js b/tests/playwright/Dashboard/Introduction/02_license.spec.js new file mode 100644 index 00000000..13018d6b --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/02_license.spec.js @@ -0,0 +1,147 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Grid.js Documentation - Introduction Section', () => { + + test.describe('License Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/license'); + }); + + test('should verify Gmail link opens correctly', async ({ page }) => { + const gmailLink = page.locator('a[href="mailto:afshin.meh@gmail.com"]'); + await expect(gmailLink).toBeVisible(); + await expect(gmailLink).toHaveAttribute('href', 'mailto:afshin.meh@gmail.com'); + await expect(gmailLink).toHaveText('afshin.meh@gmail.com'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + // Verify the link points to GitHub license.md file + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/license.md'); + }); + + test('should verify Previous page link to Community', async ({ page }) => { + // Find the Previous navigation link in pagination + const prevLink = page.locator('nav.pagination-nav a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + + // Verify the label + await expect(prevLink.locator('.pagination-nav__label')).toContainText('Community'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/community'); + await expect(page.locator('h1')).toContainText('Community'); + }); + + test('should verify Next page link to Install', async ({ page }) => { + // Find the Next navigation link in pagination + const nextLink = page.locator('nav.pagination-nav a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + + // Verify the label + await expect(nextLink.locator('.pagination-nav__label')).toContainText('Install'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/install'); + }); + + test('should navigate via right sidebar - Permissions', async ({ page }) => { + // The TOC is in a separate column on desktop + const permissionsLink = page.locator('.table-of-contents a[href="#permissions"]'); + await expect(permissionsLink).toBeVisible(); + await expect(permissionsLink).toContainText('Permissions'); + + await permissionsLink.click(); + + // Verify URL hash changed + await page.waitForTimeout(500); // Wait for scroll animation + expect(page.url()).toContain('#permissions'); + + // Verify the Permissions heading is visible (h3 not h2) + const permissionsHeading = page.locator('h3#permissions'); + await expect(permissionsHeading).toBeVisible(); + await expect(permissionsHeading).toContainText('Permissions'); + }); + + test('should navigate via right sidebar - Limitations', async ({ page }) => { + const limitationsLink = page.locator('.table-of-contents a[href="#limitations"]'); + await expect(limitationsLink).toBeVisible(); + await expect(limitationsLink).toContainText('Limitations'); + + await limitationsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#limitations'); + + const limitationsHeading = page.locator('h3#limitations'); + await expect(limitationsHeading).toBeVisible(); + await expect(limitationsHeading).toContainText('Limitations'); + }); + + test('should navigate via right sidebar - Conditions', async ({ page }) => { + const conditionsLink = page.locator('.table-of-contents a[href="#conditions"]'); + await expect(conditionsLink).toBeVisible(); + await expect(conditionsLink).toContainText('Conditions'); + + await conditionsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#conditions'); + + const conditionsHeading = page.locator('h3#conditions'); + await expect(conditionsHeading).toBeVisible(); + await expect(conditionsHeading).toContainText('Conditions'); + }); + + test('should navigate via right sidebar - Details', async ({ page }) => { + const detailsLink = page.locator('.table-of-contents a[href="#details"]'); + await expect(detailsLink).toBeVisible(); + await expect(detailsLink).toContainText('Details'); + + await detailsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#details'); + + const detailsHeading = page.locator('h3#details'); + await expect(detailsHeading).toBeVisible(); + await expect(detailsHeading).toContainText('Details'); + }); + + test('should verify page title and breadcrumbs', async ({ page }) => { + // Verify page title + await expect(page.locator('h1')).toContainText('License'); + + // Verify breadcrumbs + const breadcrumbs = page.locator('nav.theme-doc-breadcrumbs'); + await expect(breadcrumbs).toBeVisible(); + + const homeLink = breadcrumbs.locator('a[aria-label="Home page"]'); + await expect(homeLink).toBeVisible(); + + await expect(breadcrumbs.locator('span.breadcrumbs__link').first()).toContainText('๐Ÿ‘‹ Introduction'); + await expect(breadcrumbs.locator('.breadcrumbs__item--active span')).toContainText('License'); + }); + + test('should verify MIT License content is displayed', async ({ page }) => { + // Check for key MIT License text + await expect(page.locator('text=MIT License')).toBeVisible(); + await expect(page.locator('text=Copyright (c) Afshin Mehrabani')).toBeVisible(); + + // Verify the main license sections + const sections = ['Permissions', 'Limitations', 'Conditions', 'Details']; + for (const section of sections) { + await expect(page.locator(`h3:has-text("${section}")`)).toBeVisible(); + } + }); + }); +}); \ No newline at end of file diff --git a/tests/playwright/Dashboard/Introduction/03_Sponsors.spec.js b/tests/playwright/Dashboard/Introduction/03_Sponsors.spec.js new file mode 100644 index 00000000..14d68fb7 --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/03_Sponsors.spec.js @@ -0,0 +1,103 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Sponsors Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/sponsors'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Sponsors/); + const heading = page.locator('h1:has-text("Sponsors")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Sponsors page title verified'); + }); + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[aria-label="Home page"]'); + await expect(homeButton).toBeVisible(); + + await homeButton.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button clicked - navigated to homepage'); + }); + + test('should verify OpenCollective section', async ({ page, context }) => { + const openCollectiveHeading = page.locator('h2:has-text("OpenCollective"), h3:has-text("OpenCollective")'); + await expect(openCollectiveHeading).toBeVisible(); + + const openCollectiveImg = page.locator('img[src*="opencollective.com"]'); + const count = await openCollectiveImg.count(); + expect(count).toBeGreaterThan(0); + + const link = page.locator('a[href="https://opencollective.com/gridjs/donate"]'); + // await expect(link).toBeVisible(); + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('opencollective.com/gridjs/donate'); + console.log('โœ“ OpenCollective section verified'); + await newPage.close(); + }); + + test('should verify One-time donation section', async ({ page, context }) => { + const donationHeading = page.locator('h2:has-text("One-time donation"), h3:has-text("One-time donation")'); + await expect(donationHeading).toBeVisible(); + + const paypalImg = page.locator('img[src="/img/paypal.png"]'); + const count = await paypalImg.count(); + expect(count).toBeGreaterThan(0); + + const link = page.locator('a[href="https://www.paypal.me/afshinmeh"]'); + // await expect(link).toBeVisible(); + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('paypal.com/paypalme/afshinmeh'); + console.log('โœ“ One-time donation section verified'); + await newPage.close(); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/sponsors.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous (Philosophy) link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Philosophy'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/philosophy'); + console.log('โœ“ Previous link clicked - navigated to Philosophy'); + }); + + test('should verify Next (Roadmap) link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Roadmap'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/roadmap'); + console.log('โœ“ Next link clicked - navigated to Roadmap'); + }); +}); + diff --git a/tests/playwright/Dashboard/Introduction/04_Roadmap.spec.js b/tests/playwright/Dashboard/Introduction/04_Roadmap.spec.js new file mode 100644 index 00000000..a648ff30 --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/04_Roadmap.spec.js @@ -0,0 +1,87 @@ +import { test, expect } from '@playwright/test'; + +// ==================== ROADMAP PAGE ==================== +test.describe('Roadmap Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/roadmap'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Roadmap/); + const heading = page.locator('h1:has-text("Roadmap")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Roadmap page title verified'); + }); + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[aria-label="Home page"]'); + await expect(homeButton).toBeVisible(); + + await homeButton.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button clicked - navigated to homepage'); + }); + + test('should verify GitHub issues link', async ({ page, context }) => { + const githubLink = page.locator('a[href*="github.com/grid-js/gridjs/issues"]'); + await expect(githubLink).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + githubLink.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('github.com/grid-js/gridjs/issues'); + expect(newPage.url()).toContain('new+feature'); + console.log('โœ“ GitHub issues link clicked'); + + await newPage.close(); + }); + + test('should verify page content mentions feature requests', async ({ page }) => { + const content = page.locator('text=new feature'); + await expect(content).toBeVisible(); + console.log('โœ“ Feature request content verified'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/roadmap.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous (Sponsors) link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Sponsors'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/sponsors'); + console.log('โœ“ Previous link clicked - navigated to Sponsors'); + }); + + test('should verify Next (Community) link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Community'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/community'); + console.log('โœ“ Next link clicked - navigated to Community'); + }); +}); + + + diff --git a/tests/playwright/Dashboard/Introduction/05_Community.spec.js b/tests/playwright/Dashboard/Introduction/05_Community.spec.js new file mode 100644 index 00000000..6cd25cf9 --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/05_Community.spec.js @@ -0,0 +1,260 @@ +import { test, expect } from '@playwright/test'; + +// const BASE = 'http://localhost:3000/'; + +test.describe('Community Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/community'); + // await page.goto(`${BASE}docs/community`); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Community/); + const heading = page.locator('h1:has-text("Community")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Community page title verified'); + }); + + test('should verify home button navigation', async ({ page }) => { + await page.goto('https://gridjs.io/docs/community'); + const homeButton = page.locator('a[aria-label="Home page"]'); + await expect(homeButton).toBeVisible(); + + await homeButton.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button clicked - navigated to homepage'); + }); + + test('should navigate via right sidebar - Discussion Forums', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#discussion-forums"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#discussion-forums'); + }); + + test('should navigate via right sidebar - StackOverflow', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#stackoverflow"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#stackoverflow'); + }); + + test('should navigate via right sidebar - Github Discussions', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#github-discussions"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#github-discussions'); + }); + + test('should navigate via right sidebar - Bug Report', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#bug-report"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#bug-report'); + }); + + test('should navigate via right sidebar - Feature Request', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#feature-request"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#feature-request'); + }); + + test('should navigate via right sidebar - Chat', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#chat"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#chat'); + }); + + test('should navigate via right sidebar - Blog', async ({ page }) => { + const link = page.locator('.table-of-contents a[href="#blog"]'); + await expect(link).toBeVisible(); + await link.click(); + await expect(page.url()).toContain('#blog'); + }); + + test('should verify and click StackOverflow existing questions link', async ({ page, context }) => { + const link = page.getByRole('link', { name: 'existing questions' }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('stackoverflow.com/questions/tagged/gridjs'); + console.log('โœ“ StackOverflow existing questions link clicked'); + + await newPage.close(); + }); + + // need to login + test('should verify and click StackOverflow ask question link', async ({ page, context }) => { + const link = page.getByRole('link', { name: 'ask your own' }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + // expect(newPage.url()).toContain('stackoverflow.com/users/login'); + expect(newPage.url()).toContain('stackoverflow.com/questions/ask'); + console.log('โœ“ StackOverflow ask question link clicked'); + + await newPage.close(); + }); + + test('should verify and click GitHub Discussions existing link', async ({ page, context }) => { + const link = page.getByRole('link', { name: 'existing discussions' }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('github.com/grid-js/gridjs/discussions'); + console.log('โœ“ GitHub Discussions existing link clicked'); + + await newPage.close(); + }); + + // need to login + test('should verify and click GitHub start new discussion link', async ({ page, context }) => { + const link = page.getByRole('link', { name: 'start a new discussion' }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + // expect(newPage.url()).toContain('github.com/grid-js/gridjs/discussions/new/choose'); + expect(newPage.url()).toContain('/github.com/login'); + console.log('โœ“ GitHub start new discussion link clicked'); + + await newPage.close(); + }); + + // need to login + test('should verify and click bug report link', async ({ page, context }) => { + const link = page.locator('a[href*="github.com/grid-js/gridjs/issues/new"][href*="bug_report"]'); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + // expect(newPage.url()).toContain('github.com/grid-js/gridjs/issues/new'); + expect(newPage.url()).toContain('/github.com/login'); + console.log('โœ“ Bug report link clicked'); + + await newPage.close(); + }); + + // need to login + test('should verify and click feature request link', async ({ page, context }) => { + const link = page.locator('a[href*="github.com/grid-js/gridjs/issues/new"][href*="feature_request"]'); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + // expect(newPage.url()).toContain('github.com/grid-js/gridjs/issues/new'); + expect(newPage.url()).toContain('/github.com/login'); + console.log('โœ“ Feature request link clicked'); + + await newPage.close(); + }); + + test('should verify and click Discord link', async ({ page, context }) => { + const link = page.getByRole("link", { name: "Discord Channel" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('discord.com/invite/K55BwDY'); + console.log('โœ“ Discord link clicked'); + + await newPage.close(); + }); + + test('should verify and click Blog link', async ({ page }) => { + const link = page.locator('a[href="/blog"]').first(); + await expect(link).toBeVisible(); + + await link.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/blog'); + console.log('โœ“ Blog link clicked'); + }); + + test('should verify and click Twitter link', async ({ page, context }) => { + const link = page.getByRole("link", { name: "@grid_js" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + link.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('x.com/grid_js'); + console.log('โœ“ Twitter link clicked'); + + await newPage.close(); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/community.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous (Roadmap) link', async ({ page }) => { + const prevLink = page.locator('nav.pagination-nav a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Roadmap'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/roadmap'); + console.log('โœ“ Previous link clicked - navigated to Roadmap'); + }); + + test('should verify Next (License) link', async ({ page }) => { + const nextLink = page.locator('nav.pagination-nav a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('License'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/license'); + console.log('โœ“ Next link clicked - navigated to License'); + }); +}); \ No newline at end of file diff --git a/tests/playwright/Dashboard/Introduction/06_license.spec.js b/tests/playwright/Dashboard/Introduction/06_license.spec.js new file mode 100644 index 00000000..13018d6b --- /dev/null +++ b/tests/playwright/Dashboard/Introduction/06_license.spec.js @@ -0,0 +1,147 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Grid.js Documentation - Introduction Section', () => { + + test.describe('License Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/license'); + }); + + test('should verify Gmail link opens correctly', async ({ page }) => { + const gmailLink = page.locator('a[href="mailto:afshin.meh@gmail.com"]'); + await expect(gmailLink).toBeVisible(); + await expect(gmailLink).toHaveAttribute('href', 'mailto:afshin.meh@gmail.com'); + await expect(gmailLink).toHaveText('afshin.meh@gmail.com'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + // Verify the link points to GitHub license.md file + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/license.md'); + }); + + test('should verify Previous page link to Community', async ({ page }) => { + // Find the Previous navigation link in pagination + const prevLink = page.locator('nav.pagination-nav a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + + // Verify the label + await expect(prevLink.locator('.pagination-nav__label')).toContainText('Community'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/community'); + await expect(page.locator('h1')).toContainText('Community'); + }); + + test('should verify Next page link to Install', async ({ page }) => { + // Find the Next navigation link in pagination + const nextLink = page.locator('nav.pagination-nav a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + + // Verify the label + await expect(nextLink.locator('.pagination-nav__label')).toContainText('Install'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/install'); + }); + + test('should navigate via right sidebar - Permissions', async ({ page }) => { + // The TOC is in a separate column on desktop + const permissionsLink = page.locator('.table-of-contents a[href="#permissions"]'); + await expect(permissionsLink).toBeVisible(); + await expect(permissionsLink).toContainText('Permissions'); + + await permissionsLink.click(); + + // Verify URL hash changed + await page.waitForTimeout(500); // Wait for scroll animation + expect(page.url()).toContain('#permissions'); + + // Verify the Permissions heading is visible (h3 not h2) + const permissionsHeading = page.locator('h3#permissions'); + await expect(permissionsHeading).toBeVisible(); + await expect(permissionsHeading).toContainText('Permissions'); + }); + + test('should navigate via right sidebar - Limitations', async ({ page }) => { + const limitationsLink = page.locator('.table-of-contents a[href="#limitations"]'); + await expect(limitationsLink).toBeVisible(); + await expect(limitationsLink).toContainText('Limitations'); + + await limitationsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#limitations'); + + const limitationsHeading = page.locator('h3#limitations'); + await expect(limitationsHeading).toBeVisible(); + await expect(limitationsHeading).toContainText('Limitations'); + }); + + test('should navigate via right sidebar - Conditions', async ({ page }) => { + const conditionsLink = page.locator('.table-of-contents a[href="#conditions"]'); + await expect(conditionsLink).toBeVisible(); + await expect(conditionsLink).toContainText('Conditions'); + + await conditionsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#conditions'); + + const conditionsHeading = page.locator('h3#conditions'); + await expect(conditionsHeading).toBeVisible(); + await expect(conditionsHeading).toContainText('Conditions'); + }); + + test('should navigate via right sidebar - Details', async ({ page }) => { + const detailsLink = page.locator('.table-of-contents a[href="#details"]'); + await expect(detailsLink).toBeVisible(); + await expect(detailsLink).toContainText('Details'); + + await detailsLink.click(); + + await page.waitForTimeout(500); + expect(page.url()).toContain('#details'); + + const detailsHeading = page.locator('h3#details'); + await expect(detailsHeading).toBeVisible(); + await expect(detailsHeading).toContainText('Details'); + }); + + test('should verify page title and breadcrumbs', async ({ page }) => { + // Verify page title + await expect(page.locator('h1')).toContainText('License'); + + // Verify breadcrumbs + const breadcrumbs = page.locator('nav.theme-doc-breadcrumbs'); + await expect(breadcrumbs).toBeVisible(); + + const homeLink = breadcrumbs.locator('a[aria-label="Home page"]'); + await expect(homeLink).toBeVisible(); + + await expect(breadcrumbs.locator('span.breadcrumbs__link').first()).toContainText('๐Ÿ‘‹ Introduction'); + await expect(breadcrumbs.locator('.breadcrumbs__item--active span')).toContainText('License'); + }); + + test('should verify MIT License content is displayed', async ({ page }) => { + // Check for key MIT License text + await expect(page.locator('text=MIT License')).toBeVisible(); + await expect(page.locator('text=Copyright (c) Afshin Mehrabani')).toBeVisible(); + + // Verify the main license sections + const sections = ['Permissions', 'Limitations', 'Conditions', 'Details']; + for (const section of sections) { + await expect(page.locator(`h3:has-text("${section}")`)).toBeVisible(); + } + }); + }); +}); \ No newline at end of file diff --git a/tests/playwright/Dashboard/Localization/localization.spec.js b/tests/playwright/Dashboard/Localization/localization.spec.js new file mode 100644 index 00000000..11d610c0 --- /dev/null +++ b/tests/playwright/Dashboard/Localization/localization.spec.js @@ -0,0 +1,335 @@ +import { test, expect } from "@playwright/test"; + +const url = "http://localhost:3000/docs/localization/locales"; + +test.describe("Grab titles in the Localization page", () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Grab the h1 title: Locales", async ({ page }) => { + const title = page.getByRole("heading", { name: "Locales", level: 1 }); + await expect(title).toBeVisible(); + await expect(title).toHaveText("Locales"); + }); + + test("Grab the h2 title: Installing a Locale", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Installing a Locale", + level: 2, + }); + await expect(title).toBeVisible(); + await expect(title).toHaveText("Installing a Locale"); + }); + + test("Grab the listitems", async ({ page }) => { + // Not sure why there are two "tr_TR" in the list 0-0 + const items = [ + "ar_SA", + "cn_CN", + "de_De", + "en_US", + "es_ES", + "fa_IR", + "fr_FR", + "id_ID", + "it_IT", + "tr_TR", + "ja_JP", + "ko_KR", + "nb_NO", + "pt_BR", + "pt_PT", + "ru_RU", + "tr_TR", + "ua_UA", + ]; + + var tr_count = 0; + + for (const item of items) { + if (item === "tr_TR") { + const listitem = page + .getByRole("listitem") + .filter({ hasText: item }) + .nth(tr_count); + await expect(listitem).toBeVisible(); + await expect(listitem).toHaveText(item); + tr_count++; + } else { + const listitem = page + .getByRole("listitem") + .filter({ hasText: item }); + await expect(listitem).toBeVisible(); + await expect(listitem).toHaveText(item); + } + } + }); + + test("Grab the h2 title: Creating a Locale", async ({ page }) => { + const title = page.getByRole("heading", { + name: "Creating a Locale", + level: 2, + }); + await expect(title).toBeVisible(); + await expect(title).toHaveText("Creating a Locale"); + }); +}); + +test.describe("Check the links in the localozation page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL(url); + }); + + test("Check the link: Installing a Locale", async ({ page }) => { + const link = page.getByRole("link", { name: "Installing a Locale" }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/localization/locales#installing-a-locale", + ); + }); + + test("Check the link: Creating a Locale", async ({ page }) => { + const link = page.getByRole("link", { name: "Creating a Locale" }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/localization/locales#creating-a-locale", + ); + }); + + test("Check the link: en_US", async ({ page }) => { + const link = page.getByRole("link", { name: "en_US" }); + await expect(link).toBeVisible(); + await link.click(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL( + "https://github.com/grid-js/gridjs/blob/master/src/i18n/en_US.ts", + ); + }); + + test("Check the link: Previous << jQuery", async ({ page }) => { + const link = page.getByRole("link", { name: /Previous.*jQuery/ }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/integrations/jquery", + ); + }); + + test("Check the link: Next Hello, World! >>", async ({ page }) => { + const link = page.getByRole("link", { name: /Next Hello, World!.*/ }); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + test("Check the link in the Note section: https://unpkg.com/gridjs/l10n/dist/l10n.umd.js", async ({ + page, + }) => { + const link = page.getByRole("link", { + name: "https://unpkg.com/gridjs/l10n/dist/l10n.umd.js", + }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL( + "https://unpkg.com/gridjs@6.2.0/l10n/dist/l10n.umd.js", + ); + }); +}); + +test.describe("All links on the Locales page", async () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://localhost:3000/docs/localization/locales"); + }); + + test("1. NPM link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "NPM" }); + await expect(link).toBeVisible(); + await link.click(); + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://www.npmjs.com/package/gridjs"); + }); + + test("2. Github link on the upper right corner", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).first(); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); + + // 3, 4 for Docs section on the bottom of the blog page + test("3. Docs - Getting Started", async ({ page }) => { + const link = page.getByRole("link", { name: "Getting Started" }); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/docs"); + }); + + test("4. Docs - Examples", async ({ page }) => { + const link = page.getByRole("link", { name: "Examples" }).nth(2); + await expect(link).toBeVisible(); + + await link.click(); + await expect(page).toHaveURL( + "http://localhost:3000/docs/examples/hello-world", + ); + }); + + // 5 - 7 for the Community section + test("5. Community - Stack Overflow", async ({ page }) => { + const link = page.getByRole("link", { name: "Stack Overflow" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL( + "https://stackoverflow.com/questions/tagged/gridjs", + ); + }); + + test("6. Community - Discord", async ({ page }) => { + const link = page.getByRole("link", { name: "Discord" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://discord.com/invite/K55BwDY"); + }); + + test("7. Community - Twitter", async ({ page }) => { + const link = page.getByRole("link", { name: "Twitter" }); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + await expect(newPage).toHaveURL("https://x.com/grid_js"); + }); + + // 8, 9 for the More section + test("8. More - Blog", async ({ page }) => { + const link = page.getByRole("link", { name: "Blog" }).nth(1); + await expect(link).toBeVisible(); + await link.click(); + await expect(page).toHaveURL("http://localhost:3000/blog"); + }); + + test("9. More - Github", async ({ page }) => { + const link = page.getByRole("link", { name: "Github" }).nth(1); + await expect(link).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent("page"), + link.click(), + ]); + + await newPage.waitForLoadState(); + + await expect(newPage).toHaveURL("https://github.com/grid-js/gridjs"); + }); +}); + +test.describe("Scrolling test", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL( + "http://localhost:3000/docs/localization/locales", + ); + }); + + test("Test for the button that scoll to the top of page", async ({ + page, + }) => { + await page.keyboard.press("End"); + await page.waitForTimeout(500); + + await page.mouse.wheel(0, -300); + + // Now the button should appear + const button = page.getByRole("button", { name: "Scroll back to top" }); + await expect(button).toBeVisible(); + + await button.click(); + await page.waitForTimeout(500); + + const curr_y = await page.evaluate(() => window.scrollY); + expect(curr_y).toBe(0); + }); +}); + +// TODO: The table in the "Installing a Locale" section also has the same issue as the homepage one! +test.describe("Test for the sorting bug", async () => { + test.beforeEach(async ({ page }) => { + await page.goto(url); + await expect(page).toHaveURL( + "http://localhost:3000/docs/localization/locales", + ); + }); + + // ^ - toward, oppsite is backward + // 1. Click the button that toggle to last page of the table (10 and Suivant both worked) + // 2. Click the sorting button (default goes toward), this bug worked on Names, Email, and Title sections + // 3. Click the button that goes the first page of table, and the bug shows up + // The Bug is: When click to the page 10, and then click sort button, and it will show the content after sorting at the page 10 + // But the after show the content of page 10, it goes back to page 1, and the content on the page 1 is still the content on page 10 + /* + test("Test for click page 1 and 10 button", async({page}) => { + const lastPage = page.getByRole("button", { name: "Page 10" }); + await expect(lastPage).toBeVisible(); + + await lastPage.click(); + const pageNum = page.getByRole("generic").filter({ hasText: "sur" }); + await expect(pageNum).toHaveText("50"); + + const sortButton = page.getByRole("button", { name: "Trier la colonne dans l'ordre croissant" }); + await expect(sortButton).toBeVisible(); + + await sortButton.click(); + + }) + */ +}); diff --git a/tests/playwright/Dashboard/Plugins/01_Overview/01_PluginBasic.spec.js b/tests/playwright/Dashboard/Plugins/01_Overview/01_PluginBasic.spec.js new file mode 100644 index 00000000..f764ef9f --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/01_Overview/01_PluginBasic.spec.js @@ -0,0 +1,233 @@ +import { test, expect } from '@playwright/test'; + +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +async function goHomeThenDocs(page) { + await page.goto('https://gridjs.io', { waitUntil: 'domcontentloaded' }); + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await waitDocReady(page); +} + +async function navigateSidebar(page, menuText, submenuHref) { + const menu = page.locator('a.menu__link', { hasText: menuText }).first(); + await expect(menu).toBeVisible(); + await menu.click(); + + const submenu = page.locator(`a.menu__link[href="${submenuHref}"]`).first(); + await expect(submenu).toBeVisible(); + await submenu.click(); + + await waitDocReady(page); + + const escaped = submenuHref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + await expect(page).toHaveURL(new RegExp(`${escaped}(\\/)?(#.*)?$`)); +} + +async function verifyTitle(page, expectedH1) { + const h1 = page.locator('article h1').first(); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText(expectedH1); +} + +async function breadcrumbHomeCycle(page, menuText, submenuHref, expectedH1) { + const home = page.locator('a[aria-label="Home page"]').first(); + await expect(home).toBeVisible(); + await home.click(); + await page.waitForLoadState('domcontentloaded'); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); + await navigateSidebar(page, menuText, submenuHref); + await verifyTitle(page, expectedH1); +} + +async function verifyTableTexts(page, texts) { + const table = page.locator('article table').first(); + await expect(table).toBeVisible(); + for (const t of texts) { + await expect(table).toContainText(t); + } +} + +async function hoverAndClickAllCopyButtons(page) { + const article = page.locator('article'); + const codeBlocks = article.locator('div.theme-code-block, div[class*="codeBlock"], pre'); + + const blocksCount = await codeBlocks.count(); + for (let i = 0; i < blocksCount; i++) { + const block = codeBlocks.nth(i); + if (!(await block.isVisible().catch(() => false))) continue; + + await block.hover().catch(() => {}); + await page.waitForTimeout(150); + + const copyBtns = block.locator( + 'button[aria-label*="Copy"], button[class*="copyButton"], span[class*="copyButtonIcons"], button[class*="copy"], span[class*="copy"]' + ); + + const btnCount = await copyBtns.count(); + for (let j = 0; j < btnCount; j++) { + const btn = copyBtns.nth(j); + if (!(await btn.isVisible().catch(() => false))) continue; + await btn.click(); + } + } +} + +async function clickAllArticleLinksAndReturn(page) { + const article = page.locator('article'); + const links = article.locator('a[href]'); + + const total = await links.count(); + for (let i = 0; i < total; i++) { + const link = links.nth(i); + if (!(await link.isVisible().catch(() => false))) continue; + + const href = await link.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + const startUrl = page.url(); + await link.evaluate(el => el.removeAttribute('target')); + + await link.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${startUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)); + } +} + +async function clickArticleButtonsAndReturn(page) { + const article = page.locator('article'); + const buttons = article.locator('button'); + + const total = await buttons.count(); + for (let i = 0; i < total; i++) { + const btn = buttons.nth(i); + if (!(await btn.isVisible().catch(() => false))) continue; + + const startUrl = page.url(); + await btn.click({ trial: true }).catch(() => {}); + await btn.click().catch(() => {}); + await page.waitForTimeout(200); + + if (page.url() !== startUrl) { + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${startUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)); + } + } +} + +async function testEditThisPage(page) { + const edit = page.locator('a.theme-edit-this-page').first(); + if (!(await edit.isVisible().catch(() => false))) return; + + const startUrl = page.url(); + await edit.evaluate(el => el.removeAttribute('target')); + await edit.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${startUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)); +} + +async function testPrevNext(page) { + const startUrl = page.url(); + const escapedStart = startUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + if (await prev.isVisible().catch(() => false)) { + await prev.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if (await next.isVisible().catch(() => false)) { + await next.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } +} + +async function testHeaderAnchors(page) { + const article = page.locator('article'); + const headings = article.locator('h1, h2, h3, h4, h5, h6'); + const count = await headings.count(); + + for (let i = 0; i < count; i++) { + const heading = headings.nth(i); + if (!(await heading.isVisible().catch(() => false))) continue; + + const anchor = heading.locator('a[href^="#"], a.hash-link').first(); + if ((await anchor.count()) === 0) continue; + + if (!(await anchor.isVisible().catch(() => false))) { + await heading.hover().catch(() => {}); + } + + const id = await heading.getAttribute('id'); + const before = new URL(page.url()); + const baseUrl = before.origin + before.pathname; + + await anchor.click(); + await page.waitForTimeout(150); + + const after = new URL(page.url()); + + await expect(after.origin + after.pathname).toBe(baseUrl); + await expect(after.hash).not.toBe(''); + + if (id) { + await expect(after.hash.toLowerCase()).toBe(`#${id.toLowerCase()}`); + } + + await expect(heading).toBeVisible(); + } +} + +async function runDocsTest(page, cfg) { + await goHomeThenDocs(page); + await navigateSidebar(page, cfg.menu, cfg.submenu); + await verifyTitle(page, cfg.title); + await breadcrumbHomeCycle(page, cfg.menu, cfg.submenu, cfg.title); + if (cfg.tableTexts && cfg.tableTexts.length) { + await verifyTableTexts(page, cfg.tableTexts); + } + await testHeaderAnchors(page); + await hoverAndClickAllCopyButtons(page); + await clickAllArticleLinksAndReturn(page); + await clickArticleButtonsAndReturn(page); + await testEditThisPage(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Plugin basics (FULL)', async ({ page }) => { + await runDocsTest(page, { + menu: 'Plugins', + submenu: '/docs/plugins/basics', + title: 'Plugin basics', + tableTexts: [], + }); +}); diff --git a/tests/playwright/Dashboard/Plugins/01_Overview/02_WritingAPlugin.spec.js b/tests/playwright/Dashboard/Plugins/01_Overview/02_WritingAPlugin.spec.js new file mode 100644 index 00000000..beef247b --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/01_Overview/02_WritingAPlugin.spec.js @@ -0,0 +1,331 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +const BASE = 'https://gridjs.io'; + +/** + * @param {string} s + */ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +/** + * Wajib: Home -> Docs (sesuai pattern kamu) + * @param {import('@playwright/test').Page} page + */ +async function goHomeThenDocs(page) { + await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); +} + +/** + * Klik sidebar: Plugins -> Overview -> Writing a Plugin + * (karena "Plugins" itu category, "Overview" category level 2, baru link level 3) + * + * @param {import('@playwright/test').Page} page + * @param {string} menuText contoh: '๐Ÿงฉ Plugins' atau 'Plugins' (tergantung render) + * @param {string} submenuHref contoh: '/docs/plugins/writing-plugin' + */ +async function navigateSidebarToWritingPlugin(page, menuText, submenuHref) { + // 1) pastikan category "Plugins" kebuka + // di DOM kamu: ๐Ÿงฉ Plugins + const pluginsMenu = page.locator('a.menu__link--sublist', { hasText: menuText }).first(); + await expect(pluginsMenu).toBeVisible(); + await pluginsMenu.click(); + + // 2) pastikan "Overview" kebuka (category level 2) + const overviewMenu = page.locator('a.menu__link--sublist', { hasText: 'Overview' }).first(); + await expect(overviewMenu).toBeVisible(); + await overviewMenu.click(); + + // 3) klik link "Writing a Plugin" + const target = page.locator(`a.menu__link[href="${submenuHref}"]`, { hasText: 'Writing a Plugin' }).first(); + await expect(target).toBeVisible(); + await target.click(); + + await waitDocReady(page); + + const escaped = escapeRegex(submenuHref); + await expect(page).toHaveURL(new RegExp(`${escaped}(\\/)?(#.*)?$`)); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} expectedH1 + */ +async function verifyTitle(page, expectedH1) { + const h1 = page.locator('article h1').first(); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText(expectedH1); +} + +/** + * Cycle wajib: breadcrumb Home -> balik -> Docs -> nav sidebar lagi -> verif + * @param {import('@playwright/test').Page} page + * @param {string} menuText + * @param {string} submenuHref + * @param {string} expectedH1 + */ +async function breadcrumbHomeCycle(page, menuText, submenuHref, expectedH1) { + const home = page.locator('a[aria-label="Home page"]').first(); + await expect(home).toBeVisible(); + await home.click(); + await page.waitForLoadState('domcontentloaded'); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); + await navigateSidebarToWritingPlugin(page, menuText, submenuHref); + await verifyTitle(page, expectedH1); +} + +/** + * Test hash-link (#) di setiap header: ada? klik -> URL berubah hash -> tetap di page yg sama + * @param {import('@playwright/test').Page} page + */ +async function testHeaderAnchors(page) { + const article = page.locator('article'); + const headings = article.locator('h1, h2, h3, h4, h5, h6'); + const count = await headings.count(); + + for (let i = 0; i < count; i++) { + const heading = headings.nth(i); + if (!(await heading.isVisible().catch(() => false))) continue; + + const anchor = heading.locator('a[href^="#"], a.hash-link').first(); + if ((await anchor.count()) === 0) continue; + + if (!(await anchor.isVisible().catch(() => false))) { + await heading.hover().catch(() => {}); + } + + const id = await heading.getAttribute('id'); + const before = new URL(page.url()); + const baseUrl = before.origin + before.pathname; + + await anchor.click(); + await page.waitForTimeout(150); + + const after = new URL(page.url()); + await expect(after.origin + after.pathname).toBe(baseUrl); + await expect(after.hash).not.toBe(''); + + if (id) { + await expect(after.hash.toLowerCase()).toBe(`#${id.toLowerCase()}`); + } + + await expect(heading).toBeVisible(); + } +} + +/** + * FIX TIMEOUT: + * Hanya cari copy button di Docusaurus code blocks (.theme-code-block) + * Jangan sentuh Live Editor playground (react-simple-code-editor) + * + * @param {import('@playwright/test').Page} page + */ +async function hoverAndClickAllCopyButtons(page) { + const article = page.locator('article'); + + // ONLY code blocks, bukan playground + const codeBlocks = article.locator('div.theme-code-block'); + + const blocksCount = await codeBlocks.count(); + for (let i = 0; i < blocksCount; i++) { + const block = codeBlocks.nth(i); + if (!(await block.isVisible().catch(() => false))) continue; + + await block.scrollIntoViewIfNeeded().catch(() => {}); + await block.hover().catch(() => {}); + await page.waitForTimeout(100); + + const copyBtn = block.locator('button[aria-label="Copy code to clipboard"], button[title="Copy"]').first(); + if (await copyBtn.isVisible().catch(() => false)) { + await copyBtn.click().catch(() => {}); + await page.waitForTimeout(50); + } + } +} + +/** + * Klik semua link di article (kecuali hash) -> pindah -> balik -> lanjut + * @param {import('@playwright/test').Page} page + */ +async function clickAllArticleLinksAndReturn(page) { + const article = page.locator('article'); + const links = article.locator('a[href]'); + + const total = await links.count(); + for (let i = 0; i < total; i++) { + const link = links.nth(i); + if (!(await link.isVisible().catch(() => false))) continue; + + const href = await link.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + // jangan klik breadcrumbs home (kamu bilang itu paten, tapi home cycle udah di test sendiri) + const isBreadcrumbHome = await link.evaluate((el) => el.getAttribute('aria-label') === 'Home page').catch(() => false); + if (isBreadcrumbHome) continue; + + // buka di tab yg sama + await link.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + + const startUrl = page.url(); + await link.click().catch(() => {}); + await page.waitForLoadState('domcontentloaded'); + + // kalau gak pindah URL, skip + if (page.url() === startUrl) continue; + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); + } +} + +/** + * Klik Edit this page -> harus pindah (ke github) -> back + * @param {import('@playwright/test').Page} page + */ +async function testEditThisPage(page) { + const edit = page.locator('a.theme-edit-this-page').first(); + if (!(await edit.isVisible().catch(() => false))) return; + + const startUrl = page.url(); + await edit.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + await edit.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); +} + +/** + * Prev/Next -> pindah -> balik + * @param {import('@playwright/test').Page} page + */ +async function testPrevNext(page) { + const startUrl = page.url(); + const escapedStart = escapeRegex(startUrl); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + if (await prev.isVisible().catch(() => false)) { + await prev.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if (await next.isVisible().catch(() => false)) { + await next.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } +} + +/** + * LIVE EDITOR TEST (INI YANG KAMU MAU): + * - fokus ke textarea editor + * - Ctrl+A, Backspace -> jadi kosong + * - cek Result berubah (harus error / kosong / tidak "Hello World!") + * - ketik "a" + * - cek Result berubah lagi (tetap bukan Hello World!) + * + * Catatan: ini site playground, hasilnya bisa error silent. + * Jadi assertion kita: "Hello World!" harus hilang setelah dihapus & setelah ketik 'a' + * + * @param {import('@playwright/test').Page} page + */ +async function testLiveEditor_ClearAndTypeA(page) { + const article = page.locator('article'); + + // container playground: dari inspect kamu ada .playgroundContainer_X_Ta + const playground = article.locator('div[class*="playgroundContainer"]').first(); + await expect(playground).toBeVisible(); + + const editorTextarea = playground.locator('textarea.npm__react-simple-code-editor__textarea').first(); + await expect(editorTextarea).toBeVisible(); + + // result preview: dari inspect kamu ada .playgroundPreview_DzOI + const preview = playground.locator('div[class*="playgroundPreview"]').first(); + await expect(preview).toBeVisible(); + + // pastikan awalnya ada Hello World! (karena default code render itu) + await expect(preview).toContainText('Hello World!'); + + // klik editor -> select all -> delete + await editorTextarea.click(); + await editorTextarea.press(process.platform === 'darwin' ? 'Meta+A' : 'Control+A'); + await editorTextarea.press('Backspace'); + + // tunggu render + await page.waitForTimeout(500); + + // setelah clear: HARUS tidak ada Hello World! + await expect(preview).not.toContainText('Hello World!'); + + // ketik a + await editorTextarea.type('a', { delay: 30 }); + await page.waitForTimeout(500); + + // setelah ketik a: tetap tidak boleh balik ke Hello World! + await expect(preview).not.toContainText('Hello World!'); +} + +/** + * Runner full + * @param {import('@playwright/test').Page} page + */ +async function runWritingAPluginFull(page) { + await goHomeThenDocs(page); + + // menu text di sidebar bisa "๐Ÿงฉ Plugins" atau "Plugins" tergantung emoji kebaca + // supaya robust: kita cari yang contains "Plugins" + await navigateSidebarToWritingPlugin(page, 'Plugins', '/docs/plugins/writing-plugin'); + + await verifyTitle(page, 'Writing a Plugin'); + + await breadcrumbHomeCycle(page, 'Plugins', '/docs/plugins/writing-plugin', 'Writing a Plugin'); + + await testHeaderAnchors(page); + await hoverAndClickAllCopyButtons(page); + await clickAllArticleLinksAndReturn(page); + + // live editor test sesuai request kamu + await testLiveEditor_ClearAndTypeA(page); + + await testEditThisPage(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Overview > Writing a Plugin (FULL)', async ({ page }) => { + // biar gak kejam, tapi cukup buat live editor render + test.setTimeout(60_000); + await runWritingAPluginFull(page); +}); diff --git a/tests/playwright/Dashboard/Plugins/01_Overview/03_AdvancedPlugin.spec.js b/tests/playwright/Dashboard/Plugins/01_Overview/03_AdvancedPlugin.spec.js new file mode 100644 index 00000000..bcf64969 --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/01_Overview/03_AdvancedPlugin.spec.js @@ -0,0 +1,316 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +const BASE = 'https://gridjs.io'; + +/** + * @param {string} s + */ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function goHomeThenDocs(page) { + await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + await waitDocReady(page); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} expectedH1 + */ +async function verifyTitle(page, expectedH1) { + const h1 = page.locator('article h1').first(); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText(expectedH1); +} + +/** + * Sidebar: Plugins -> Overview -> Advanced Plugins + * @param {import('@playwright/test').Page} page + * @param {string} menuText + * @param {string} submenuHref + */ +async function navigateSidebarToAdvancedPlugins(page, menuText, submenuHref) { + const pluginsMenu = page.locator('a.menu__link--sublist', { hasText: menuText }).first(); + await expect(pluginsMenu).toBeVisible(); + await pluginsMenu.click(); + + const overviewMenu = page.locator('a.menu__link--sublist', { hasText: 'Overview' }).first(); + await expect(overviewMenu).toBeVisible(); + await overviewMenu.click(); + + const target = page.locator(`a.menu__link[href="${submenuHref}"]`, { hasText: 'Advanced Plugins' }).first(); + await expect(target).toBeVisible(); + await target.click(); + + await waitDocReady(page); + + const escaped = escapeRegex(submenuHref); + await expect(page).toHaveURL(new RegExp(`${escaped}(\\/)?(#.*)?$`)); +} + +/** + * Breadcrumb Home cycle wajib + * @param {import('@playwright/test').Page} page + * @param {string} menuText + * @param {string} submenuHref + * @param {string} expectedH1 + */ +async function breadcrumbHomeCycle(page, menuText, submenuHref, expectedH1) { + const home = page.locator('a[aria-label="Home page"]').first(); + await expect(home).toBeVisible(); + await home.click(); + await page.waitForLoadState('domcontentloaded'); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); + await navigateSidebarToAdvancedPlugins(page, menuText, submenuHref); + await verifyTitle(page, expectedH1); +} + +/** + * Test hash-link (#) + * @param {import('@playwright/test').Page} page + */ +async function testHeaderAnchors(page) { + const article = page.locator('article'); + const headings = article.locator('h1, h2, h3, h4, h5, h6'); + const count = await headings.count(); + + for (let i = 0; i < count; i++) { + const heading = headings.nth(i); + if (!(await heading.isVisible().catch(() => false))) continue; + + const anchor = heading.locator('a[href^="#"], a.hash-link').first(); + if ((await anchor.count()) === 0) continue; + + if (!(await anchor.isVisible().catch(() => false))) { + await heading.hover().catch(() => {}); + } + + const id = await heading.getAttribute('id'); + const before = new URL(page.url()); + const baseUrl = before.origin + before.pathname; + + await anchor.click(); + await page.waitForTimeout(150); + + const after = new URL(page.url()); + await expect(after.origin + after.pathname).toBe(baseUrl); + await expect(after.hash).not.toBe(''); + + if (id) { + await expect(after.hash.toLowerCase()).toBe(`#${id.toLowerCase()}`); + } + } +} + +/** + * Copy hanya theme-code-block + * @param {import('@playwright/test').Page} page + */ +async function hoverAndClickAllCopyButtons(page) { + const article = page.locator('article'); + const codeBlocks = article.locator('div.theme-code-block'); + + const blocksCount = await codeBlocks.count(); + for (let i = 0; i < blocksCount; i++) { + const block = codeBlocks.nth(i); + if (!(await block.isVisible().catch(() => false))) continue; + + await block.scrollIntoViewIfNeeded().catch(() => {}); + await block.hover().catch(() => {}); + await page.waitForTimeout(80); + + const copyBtn = block.locator('button[aria-label="Copy code to clipboard"], button[title="Copy"]').first(); + if (await copyBtn.isVisible().catch(() => false)) { + await copyBtn.click().catch(() => {}); + await page.waitForTimeout(40); + } + } +} + +/** + * Klik semua link di article (kecuali hash & breadcrumb home) + * @param {import('@playwright/test').Page} page + */ +async function clickAllArticleLinksAndReturn(page) { + const article = page.locator('article'); + const links = article.locator('a[href]'); + + const total = await links.count(); + for (let i = 0; i < total; i++) { + const link = links.nth(i); + if (!(await link.isVisible().catch(() => false))) continue; + + const href = await link.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + const isBreadcrumbHome = await link + .evaluate((el) => el.getAttribute('aria-label') === 'Home page') + .catch(() => false); + if (isBreadcrumbHome) continue; + + await link.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + + const startUrl = page.url(); + await link.click().catch(() => {}); + await page.waitForLoadState('domcontentloaded'); + + if (page.url() === startUrl) continue; + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); + } +} + +/** + * Edit this page + * @param {import('@playwright/test').Page} page + */ +async function testEditThisPage(page) { + const edit = page.locator('a.theme-edit-this-page').first(); + if (!(await edit.isVisible().catch(() => false))) return; + + const startUrl = page.url(); + await edit.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + await edit.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); +} + +/** + * Prev/Next + * @param {import('@playwright/test').Page} page + */ +async function testPrevNext(page) { + const startUrl = page.url(); + const escapedStart = escapeRegex(startUrl); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + if (await prev.isVisible().catch(() => false)) { + await prev.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if (await next.isVisible().catch(() => false)) { + await next.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } +} + +/** + * LIVE EDITOR TEST untuk Advanced Plugins (ANTI-STUCK): + * - loop semua playground container + * - CLEAR pakai fill('') (lebih reliable daripada Ctrl+A) + * - TYPE 'a' + * - ASSERT DARI TEXTAREA VALUE (ground truth) + * - PREVIEW hanya semantic check (bisa error yang sama, itu OK) + * + * @param {import('@playwright/test').Page} page + */ +async function testAllLiveEditors_ClearAndTypeA(page) { + const article = page.locator('article'); + + const playgrounds = article.locator('div[class*="playgroundContainer"]'); + const total = await playgrounds.count(); + await expect(total).toBeGreaterThan(0); + + for (let i = 0; i < total; i++) { + const pg = playgrounds.nth(i); + + const editorTextarea = pg.locator('textarea.npm__react-simple-code-editor__textarea').first(); + await expect(editorTextarea).toBeVisible(); + + await editorTextarea.scrollIntoViewIfNeeded().catch(() => {}); + await editorTextarea.click(); + + const beforeValue = await editorTextarea.inputValue(); + + // 1) CLEAR (pasti) + await editorTextarea.fill(''); + await expect(editorTextarea).toHaveValue(''); + + // 2) TYPE 'a' + await editorTextarea.type('a', { delay: 30 }); + await expect(editorTextarea).toHaveValue('a'); + + if (beforeValue !== 'a') { + await expect(editorTextarea).not.toHaveValue(beforeValue); + } + + // 3) PREVIEW (SEMANTIC ONLY, NO "MUST CHANGE"!) + const preview = pg.locator('div[class*="playgroundPreview"]').first(); + if (await preview.count()) { + const txt = (await preview.innerText().catch(() => '')) || ''; + + // Minimal: ada output (error juga valid) + expect(txt.length).toBeGreaterThan(0); + + // Optional tambahan: biasanya error akan mengandung kata-kata ini + // (kalau ternyata sukses render, ini tetap lolos karena '.' match) + expect(txt).toMatch(/reference|error|undefined|exception|./i); + } + } +} + +/** + * Runner full + * @param {import('@playwright/test').Page} page + */ +async function runAdvancedPluginsFull(page) { + await goHomeThenDocs(page); + + await navigateSidebarToAdvancedPlugins(page, 'Plugins', '/docs/plugins/advanced-plugins'); + await verifyTitle(page, 'Advanced Plugins'); + + await breadcrumbHomeCycle(page, 'Plugins', '/docs/plugins/advanced-plugins', 'Advanced Plugins'); + + await testHeaderAnchors(page); + await hoverAndClickAllCopyButtons(page); + await clickAllArticleLinksAndReturn(page); + + // CORE: live editor clear & type 'a' tanpa stuck + await testAllLiveEditors_ClearAndTypeA(page); + + await testEditThisPage(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Overview > Advanced Plugins (FULL)', async ({ page }) => { + test.setTimeout(90_000); + await runAdvancedPluginsFull(page); +}); diff --git a/tests/playwright/Dashboard/Plugins/02_Selection/01_SelectionPlugin.spec.js b/tests/playwright/Dashboard/Plugins/02_Selection/01_SelectionPlugin.spec.js new file mode 100644 index 00000000..6389b652 --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/02_Selection/01_SelectionPlugin.spec.js @@ -0,0 +1,282 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +const BASE = 'https://gridjs.io'; + +/** + * @param {string} s + */ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +/** + * Wajib: Home -> Docs + * @param {import('@playwright/test').Page} page + */ +async function goHomeThenDocs(page) { + await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} expectedH1 + */ +async function verifyTitle(page, expectedH1) { + const h1 = page.locator('article h1').first(); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText(expectedH1); +} + +/** + * Expand sidebar category if needed (aria-expanded) + * @param {import('@playwright/test').Locator} cat + */ +async function ensureExpanded(cat) { + const expanded = await cat.getAttribute('aria-expanded'); + if (expanded === 'false') { + await cat.click(); + } +} + +/** + * Sidebar: Plugins -> Selection -> Selection Plugin + * FIX: jangan hardcode href Selection Plugin (karena slug beda). + * + * @param {import('@playwright/test').Page} page + */ +async function navigateSidebarToSelectionPlugin(page) { + // expand Plugins + const pluginsCat = page.locator('a.menu__link--sublist', { hasText: 'Plugins' }).first(); + await expect(pluginsCat).toBeVisible(); + await ensureExpanded(pluginsCat); + + // expand Selection category + const selectionCat = page.locator('a.menu__link--sublist', { hasText: /^Selection$/ }).first(); + await expect(selectionCat).toBeVisible(); + await ensureExpanded(selectionCat); + + // click "Selection Plugin" by text (lebih robust daripada href) + const target = page.locator('a.menu__link', { hasText: /^Selection Plugin$/ }).first(); + await expect(target).toBeVisible(); + await target.click(); + + await waitDocReady(page); + + // URL verif yang fleksibel (slug bisa beda, tapi harus di area plugins + selection) + await expect(page).toHaveURL(/\/docs\/plugins\/.*selection/i); +} + +/** + * Breadcrumb Home cycle wajib + * @param {import('@playwright/test').Page} page + * @param {string} expectedH1 + */ +async function breadcrumbHomeCycle(page, expectedH1) { + const home = page.locator('a[aria-label="Home page"]').first(); + await expect(home).toBeVisible(); + await home.click(); + await page.waitForLoadState('domcontentloaded'); + + const docsLink = page.locator('a[href="/docs"]').first(); + await expect(docsLink).toBeVisible(); + await docsLink.click(); + + await waitDocReady(page); + await navigateSidebarToSelectionPlugin(page); + await verifyTitle(page, expectedH1); +} + +/** + * Test hash-link (#) + * @param {import('@playwright/test').Page} page + */ +async function testHeaderAnchors(page) { + const article = page.locator('article'); + const headings = article.locator('h1, h2, h3, h4, h5, h6'); + const count = await headings.count(); + + for (let i = 0; i < count; i++) { + const heading = headings.nth(i); + if (!(await heading.isVisible().catch(() => false))) continue; + + const anchor = heading.locator('a[href^="#"], a.hash-link').first(); + if ((await anchor.count()) === 0) continue; + + if (!(await anchor.isVisible().catch(() => false))) { + await heading.hover().catch(() => {}); + } + + const id = await heading.getAttribute('id'); + const before = new URL(page.url()); + const baseUrl = before.origin + before.pathname; + + await anchor.click(); + await page.waitForTimeout(150); + + const after = new URL(page.url()); + await expect(after.origin + after.pathname).toBe(baseUrl); + await expect(after.hash).not.toBe(''); + + if (id) { + await expect(after.hash.toLowerCase()).toBe(`#${id.toLowerCase()}`); + } + } +} + +/** + * Copy hanya theme-code-block + * @param {import('@playwright/test').Page} page + */ +async function hoverAndClickAllCopyButtons(page) { + const codeBlocks = page.locator('article div.theme-code-block'); + const blocksCount = await codeBlocks.count(); + + for (let i = 0; i < blocksCount; i++) { + const block = codeBlocks.nth(i); + if (!(await block.isVisible().catch(() => false))) continue; + + await block.scrollIntoViewIfNeeded().catch(() => {}); + await block.hover().catch(() => {}); + await page.waitForTimeout(80); + + const copyBtn = block + .locator('button[aria-label="Copy code to clipboard"], button[title="Copy"], button[aria-label*="Copy"]') + .first(); + + if (await copyBtn.isVisible().catch(() => false)) { + await copyBtn.click().catch(() => {}); + await page.waitForTimeout(40); + } + } +} + +/** + * Klik semua link di article (kecuali hash & breadcrumb home) + * @param {import('@playwright/test').Page} page + */ +async function clickAllArticleLinksAndReturn(page) { + const links = page.locator('article a[href]'); + const total = await links.count(); + + for (let i = 0; i < total; i++) { + const link = links.nth(i); + if (!(await link.isVisible().catch(() => false))) continue; + + const href = await link.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + const isBreadcrumbHome = await link + .evaluate((el) => el.getAttribute('aria-label') === 'Home page') + .catch(() => false); + if (isBreadcrumbHome) continue; + + await link.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + + const startUrl = page.url(); + await link.click().catch(() => {}); + await page.waitForLoadState('domcontentloaded'); + + if (page.url() === startUrl) continue; + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); + } +} + +/** + * Edit this page + * @param {import('@playwright/test').Page} page + */ +async function testEditThisPage(page) { + const edit = page.locator('a.theme-edit-this-page').first(); + if (!(await edit.isVisible().catch(() => false))) return; + + const startUrl = page.url(); + await edit.evaluate((el) => el.removeAttribute('target')).catch(() => {}); + await edit.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(startUrl); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(startUrl)}$`)); +} + +/** + * Prev/Next: + * Prev -> Advanced Plugins + * Next -> Row selection + * @param {import('@playwright/test').Page} page + */ +async function testPrevNext(page) { + const startUrl = page.url(); + const escapedStart = escapeRegex(startUrl); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + if (await prev.isVisible().catch(() => false)) { + await expect(prev).toContainText(/advanced plugins/i); + await prev.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await expect(page.locator('article h1').first()).toContainText(/advanced plugins/i); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if (await next.isVisible().catch(() => false)) { + await expect(next).toContainText(/row selection/i); + await next.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page).not.toHaveURL(new RegExp(`^${escapedStart}$`)); + await expect(page.locator('article h1').first()).toContainText(/row selection/i); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapedStart}$`)); + } +} + +/** + * Runner full + * @param {import('@playwright/test').Page} page + */ +async function runSelectionPluginFull(page) { + await goHomeThenDocs(page); + + await navigateSidebarToSelectionPlugin(page); + await verifyTitle(page, 'Selection Plugin'); + + await breadcrumbHomeCycle(page, 'Selection Plugin'); + + await testHeaderAnchors(page); + await hoverAndClickAllCopyButtons(page); + await clickAllArticleLinksAndReturn(page); + + await testEditThisPage(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Selection > Selection Plugin (FULL)', async ({ page }) => { + test.setTimeout(60_000); + await runSelectionPluginFull(page); +}); diff --git a/tests/playwright/Dashboard/Plugins/02_Selection/02_RowSelection.spec.js b/tests/playwright/Dashboard/Plugins/02_Selection/02_RowSelection.spec.js new file mode 100644 index 00000000..202f62e4 --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/02_Selection/02_RowSelection.spec.js @@ -0,0 +1,161 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +const BASE = 'https://gridjs.io'; + +/** + * @param {string} s + */ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +/** + * Home โ†’ Docs + * @param {import('@playwright/test').Page} page + */ +async function goHomeThenDocs(page) { + await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + const docs = page.locator('a[href="/docs"]').first(); + await expect(docs).toBeVisible(); + await docs.click(); + await waitDocReady(page); +} + +/** + * Expand sidebar category if collapsed + * @param {import('@playwright/test').Locator} cat + */ +async function ensureExpanded(cat) { + const expanded = await cat.getAttribute('aria-expanded'); + if (expanded === 'false') { + await cat.click(); + } +} + +/** + * Sidebar: Plugins โ†’ Selection โ†’ Row selection + * @param {import('@playwright/test').Page} page + */ +async function navigateSidebarToRowSelection(page) { + const plugins = page.locator('a.menu__link--sublist', { hasText: 'Plugins' }).first(); + await expect(plugins).toBeVisible(); + await ensureExpanded(plugins); + + const selection = page.locator('a.menu__link--sublist', { hasText: /^Selection$/ }).first(); + await expect(selection).toBeVisible(); + await ensureExpanded(selection); + + const rowSelection = page.locator('a.menu__link', { hasText: /^Row selection$/ }).first(); + await expect(rowSelection).toBeVisible(); + await rowSelection.click(); + + await waitDocReady(page); + await expect(page).toHaveURL(/\/docs\/plugins\/.*row/i); +} + +/** + * Verify H1 + * @param {import('@playwright/test').Page} page + */ +async function verifyTitle(page) { + const h1 = page.locator('article h1').first(); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText('Row selection'); +} + +/** + * LIVE EDITOR + RESULT GRID TEST + * - pastikan grid muncul + * - ada checkbox + * - klik checkbox โ†’ state berubah + * + * @param {import('@playwright/test').Page} page + */ +async function testRowSelectionGrid(page) { + const article = page.locator('article'); + + // RESULT container + const result = article.locator('text=RESULT').first(); + await expect(result).toBeVisible(); + + // Grid table + const table = article.locator('table').first(); + await expect(table).toBeVisible(); + + // Checkbox di kolom Select (row pertama) + const firstCheckbox = table.locator('input[type="checkbox"]').first(); + await expect(firstCheckbox).toBeVisible(); + + // initial state (boleh checked / unchecked, tergantung contoh) + const before = await firstCheckbox.isChecked(); + + // click checkbox + await firstCheckbox.click(); + + // state harus berubah + await expect(firstCheckbox).toHaveJSProperty('checked', !before); +} + +/** + * Prev / Next + * Prev โ†’ Selection Plugin + * Next โ†’ Selection events + * + * @param {import('@playwright/test').Page} page + */ +async function testPrevNext(page) { + const startUrl = page.url(); + const escaped = escapeRegex(startUrl); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + await expect(prev).toBeVisible(); + await expect(prev).toContainText(/selection plugin/i); + + await prev.click(); + await waitDocReady(page); + await expect(page.locator('article h1').first()).toContainText('Selection Plugin'); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escaped}$`)); + + const next = page.locator('a.pagination-nav__link--next').first(); + await expect(next).toBeVisible(); + await expect(next).toContainText(/selection events/i); + + await next.click(); + await waitDocReady(page); + await expect(page.locator('article h1').first()).toContainText('Selection events'); + + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escaped}$`)); +} + +/** + * Runner + * @param {import('@playwright/test').Page} page + */ +async function runRowSelectionFull(page) { + await goHomeThenDocs(page); + + await navigateSidebarToRowSelection(page); + await verifyTitle(page); + + await testRowSelectionGrid(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Selection > Row selection (FULL)', async ({ page }) => { + test.setTimeout(60_000); + await runRowSelectionFull(page); +}); diff --git a/tests/playwright/Dashboard/Plugins/02_Selection/03_SelectionEvents.spec.js b/tests/playwright/Dashboard/Plugins/02_Selection/03_SelectionEvents.spec.js new file mode 100644 index 00000000..06740026 --- /dev/null +++ b/tests/playwright/Dashboard/Plugins/02_Selection/03_SelectionEvents.spec.js @@ -0,0 +1,287 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +const BASE = 'https://gridjs.io'; + +/** + * @param {string} s + */ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function waitDocReady(page) { + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator('article')).toBeVisible(); +} + +/** + * Home -> Docs + * @param {import('@playwright/test').Page} page + */ +async function goHomeThenDocs(page) { + await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + const docs = page.locator('a[href="/docs"]').first(); + await expect(docs).toBeVisible(); + await docs.click(); + await waitDocReady(page); +} + +/** + * Expand sidebar category if collapsed + * @param {import('@playwright/test').Locator} el + */ +async function ensureExpanded(el) { + const expanded = await el.getAttribute('aria-expanded'); + if (expanded === 'false') await el.click(); +} + +/** + * Sidebar: Plugins -> Selection -> Selection events + * ROBUST (NO exact text) + * @param {import('@playwright/test').Page} page + */ +async function navigateSidebarToSelectionEvents(page) { + const sidebar = page.locator('nav.menu'); + + // Plugins + const plugins = sidebar.locator('a.menu__link--sublist', { hasText: /Plugins/i }).first(); + await expect(plugins).toBeVisible(); + await ensureExpanded(plugins); + + // Selection (bisa sublist / link) + const selection = sidebar + .locator('a.menu__link--sublist, a.menu__link', { hasText: /Selection/i }) + .first(); + await expect(selection).toBeVisible(); + + const cls = (await selection.getAttribute('class')) || ''; + if (cls.includes('menu__link--sublist')) { + await ensureExpanded(selection); + } else { + await selection.click(); + await waitDocReady(page); + } + + // Selection events + const target = sidebar.locator('a.menu__link', { hasText: /Selection events/i }).first(); + await expect(target).toBeVisible(); + await target.click(); + + await waitDocReady(page); + await expect(page).toHaveURL(/\/docs\/plugins\/.*events/i); +} + +/** + * @param {import('@playwright/test').Page} page + * @param {string} expected + */ +async function verifyTitle(page, expected) { + await expect(page.locator('article h1').first()).toHaveText(expected); +} + +/** + * Breadcrumb Home cycle + * @param {import('@playwright/test').Page} page + * @param {string} expectedH1 + */ +async function breadcrumbHomeCycle(page, expectedH1) { + const home = page.locator('a[aria-label="Home page"]').first(); + await expect(home).toBeVisible(); + await home.click(); + await page.waitForLoadState('domcontentloaded'); + + const docs = page.locator('a[href="/docs"]').first(); + await expect(docs).toBeVisible(); + await docs.click(); + + await waitDocReady(page); + await navigateSidebarToSelectionEvents(page); + await verifyTitle(page, expectedH1); +} + +/** + * Header anchors + * @param {import('@playwright/test').Page} page + */ +async function testHeaderAnchors(page) { + const headers = page.locator('article h1, h2, h3, h4'); + const count = await headers.count(); + + for (let i = 0; i < count; i++) { + const h = headers.nth(i); + const anchor = h.locator('a[href^="#"], a.hash-link').first(); + if (!(await anchor.count())) continue; + + const before = new URL(page.url()); + const base = before.origin + before.pathname; + + await anchor.click(); + await page.waitForTimeout(120); + + const after = new URL(page.url()); + await expect(after.origin + after.pathname).toBe(base); + await expect(after.hash).not.toBe(''); + } +} + +/** + * Copy buttons (ONLY theme-code-block) + * @param {import('@playwright/test').Page} page + */ +async function hoverAndClickAllCopyButtons(page) { + const blocks = page.locator('article div.theme-code-block'); + const count = await blocks.count(); + + for (let i = 0; i < count; i++) { + const b = blocks.nth(i); + if (!(await b.isVisible())) continue; + + await b.hover().catch(() => {}); + const btn = b.locator('button[aria-label*="Copy"], button[title="Copy"]').first(); + if (await btn.isVisible().catch(() => false)) { + await btn.click().catch(() => {}); + } + } +} + +/** + * Click all article links & return + * @param {import('@playwright/test').Page} page + */ +async function clickAllArticleLinksAndReturn(page) { + const links = page.locator('article a[href]'); + const total = await links.count(); + + for (let i = 0; i < total; i++) { + const a = links.nth(i); + const href = await a.getAttribute('href'); + if (!href || href.startsWith('#')) continue; + + const start = page.url(); + await a.evaluate(el => el.removeAttribute('target')).catch(() => {}); + await a.click().catch(() => {}); + await page.waitForLoadState('domcontentloaded'); + + if (page.url() !== start) { + await page.goBack(); + await waitDocReady(page); + await expect(page).toHaveURL(new RegExp(`^${escapeRegex(start)}$`)); + } + } +} + +/** + * Edit this page + * @param {import('@playwright/test').Page} page + */ +async function testEditThisPage(page) { + const edit = page.locator('a.theme-edit-this-page').first(); + if (!(await edit.isVisible().catch(() => false))) return; + + const start = page.url(); + await edit.evaluate(el => el.removeAttribute('target')); + await edit.click(); + await page.waitForLoadState('domcontentloaded'); + + await expect(page).not.toHaveURL(start); + await page.goBack(); + await waitDocReady(page); +} + +/** + * Prev / Next + * Prev -> Row selection + * Next -> React + * @param {import('@playwright/test').Page} page + */ +async function testPrevNext(page) { + const start = page.url(); + + const prev = page.locator('a.pagination-nav__link--prev').first(); + if (await prev.isVisible()) { + await prev.click(); + await waitDocReady(page); + await expect(page.locator('article h1').first()).toContainText(/row selection/i); + await page.goBack(); + await waitDocReady(page); + await expect(page.url()).toBe(start); + } + + const next = page.locator('a.pagination-nav__link--next').first(); + if (await next.isVisible()) { + await next.click(); + await waitDocReady(page); + await expect(page.locator('article h1').first()).toContainText(/react/i); + await page.goBack(); + await waitDocReady(page); + await expect(page.url()).toBe(start); + } +} + +/** + * LIVE EDITOR โ€” CLEAR + TYPE 'a' (WAJIB) + * @param {import('@playwright/test').Page} page + */ +async function testAllLiveEditors_ClearAndTypeA(page) { + const playgrounds = page.locator('article div[class*="playgroundContainer"]'); + const total = await playgrounds.count(); + await expect(total).toBeGreaterThan(0); + + for (let i = 0; i < total; i++) { + const pg = playgrounds.nth(i); + const editor = pg.locator('textarea.npm__react-simple-code-editor__textarea').first(); + const preview = pg.locator('div[class*="playgroundPreview"]').first(); + + await expect(editor).toBeVisible(); + await expect(preview).toBeVisible(); + + const before = await preview.innerText().catch(() => ''); + + await editor.click(); + await editor.press(process.platform === 'darwin' ? 'Meta+A' : 'Control+A'); + await editor.press('Backspace'); + + await expect + .poll(async () => (await preview.innerText().catch(() => '')) || '', { timeout: 8000 }) + .not.toBe(before); + + const afterClear = await preview.innerText().catch(() => ''); + + await editor.type('a', { delay: 25 }); + + await expect + .poll(async () => (await preview.innerText().catch(() => '')) || '', { timeout: 8000 }) + .not.toBe(afterClear); + } +} + +/** + * RUNNER FULL + * @param {import('@playwright/test').Page} page + */ +async function runSelectionEventsFull(page) { + await goHomeThenDocs(page); + + await navigateSidebarToSelectionEvents(page); + await verifyTitle(page, 'Selection events'); + + await breadcrumbHomeCycle(page, 'Selection events'); + await testHeaderAnchors(page); + await hoverAndClickAllCopyButtons(page); + await clickAllArticleLinksAndReturn(page); + + // WAJIB + await testAllLiveEditors_ClearAndTypeA(page); + + await testEditThisPage(page); + await testPrevNext(page); +} + +test('Grid.js Docs โ€” Plugins > Selection > Selection events (FULL)', async ({ page }) => { + test.setTimeout(90_000); + await runSelectionEventsFull(page); +}); diff --git a/tests/playwright/Dashboard/Usage/01_Install.spec.js b/tests/playwright/Dashboard/Usage/01_Install.spec.js new file mode 100644 index 00000000..683e1102 --- /dev/null +++ b/tests/playwright/Dashboard/Usage/01_Install.spec.js @@ -0,0 +1,109 @@ +import { test, expect } from '@playwright/test'; + +// ======================================== +// TEST 1: Install Page Tests +// ======================================== +test.describe('Install Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/install'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Install.*Grid\.js/); + const heading = page.locator('h1:has-text("Install")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Install page title and heading verified'); + }); + + test('should verify Node.js section exists', async ({ page }) => { + const nodejsHeading = page.locator('h2:has-text("Node.js"), h3:has-text("Node.js")'); + await expect(nodejsHeading).toBeVisible(); + console.log('โœ“ Node.js section verified'); + }); + + test('should verify Browser section exists', async ({ page }) => { + const browserHeading = page.locator('h2:has-text("Browser"), h3:has-text("Browser")'); + await expect(browserHeading).toBeVisible(); + console.log('โœ“ Browser section verified'); + }); + + test('should verify NPM installation command', async ({ page }) => { + const npmCommand = page.locator('text=npm install gridjs'); + await expect(npmCommand).toBeVisible(); + console.log('โœ“ NPM install command present'); + }); + + test('should verify and test unpkg link', async ({ page, context }) => { + const unpkgLink = page.locator('a[href*="unpkg.com"]').first(); + await expect(unpkgLink).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + unpkgLink.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('unpkg.com/gridjs@6.2.0/files/dist'); + console.log('โœ“ unpkg.com link navigation successful'); + + await newPage.close(); + }); + + test('should verify jsdelivr links present', async ({ page }) => { + const jsdelivrLink = page.locator('a[href*="jsdelivr.com"]').first(); + await expect(jsdelivrLink).toBeVisible(); + + const href = await jsdelivrLink.getAttribute('href'); + expect(href).toContain('jsdelivr.com/package/npm/gridjs'); + console.log('โœ“ jsdelivr.com link verified'); + }); + + test('should verify unpkg section heading', async ({ page }) => { + const unpkgHeading = page.locator('h2:has-text("unpkg"), h3:has-text("unpkg")'); + await expect(unpkgHeading).toBeVisible(); + console.log('โœ“ unpkg section heading verified'); + }); + + test('should verify jsdelivr section heading', async ({ page }) => { + const jsdelivrHeading = page.locator('h2:has-text("jsdelivr"), h3:has-text("jsdelivr")'); + await expect(jsdelivrHeading).toBeVisible(); + console.log('โœ“ jsdelivr section heading verified'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.getByRole('link', { name: 'Edit this page' }); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/install.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('License'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/license'); + console.log('โœ“ Previous link clicked - navigated to License'); + }); + + test('should verify Next link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Hello World'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/hello-world'); + console.log('โœ“ Next link clicked - navigated to Hello World'); + }); + +}); diff --git a/tests/playwright/Dashboard/Usage/02_Hello_World.spec.js b/tests/playwright/Dashboard/Usage/02_Hello_World.spec.js new file mode 100644 index 00000000..24632980 --- /dev/null +++ b/tests/playwright/Dashboard/Usage/02_Hello_World.spec.js @@ -0,0 +1,97 @@ +import { test, expect } from '@playwright/test'; + +// ======================================== +// TEST 2: Hello World Page Tests +// ======================================== +test.describe('Hello World Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/hello-world'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title', async ({ page }) => { + await expect(page).toHaveTitle(/Hello.*World/); + console.log('โœ“ Hello World page title verified'); + }); + + test('should verify Grid.js code example present', async ({ page }) => { + const newGrid = page.getByText('new Grid').first(); + await expect(newGrid).toBeVisible(); + console.log('โœ“ Grid.js code example verified'); + }); + + test('should verify columns configuration shown', async ({ page }) => { + const columns = page.getByText('columns').first(); + await expect(columns).toBeVisible(); + console.log('โœ“ Columns configuration verified'); + }); + + test('should verify data configuration shown', async ({ page }) => { + const data = page.getByText('data').first(); + await expect(data).toBeVisible(); + console.log('โœ“ Data configuration verified'); + }); + + test('should verify code blocks are present', async ({ page }) => { + const codeBlocks = page.locator('pre, code'); + await expect(codeBlocks.first()).toBeVisible(); + const count = await codeBlocks.count(); + expect(count).toBeGreaterThan(0); + console.log(`โœ“ Code blocks verified (${count} found)`); + }); + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[aria-label="Home page"]'); + if (await homeButton.isVisible()) { + await homeButton.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button navigation verified'); + } + }); + + test('should verify React integration section', async ({ page, context }) => { + const reactLink = page.getByRole('link', { name: 'React integration' }); + await expect(reactLink).toBeVisible(); + await reactLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/integrations/react'); + console.log('โœ“ React integration link verified'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/hello-world.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Install'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/install'); + console.log('โœ“ Previous link clicked - navigated to Install'); + }); + + test('should verify Next link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Configuration'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/config'); + console.log('โœ“ Next link clicked - navigated to Configuration'); + }); +}); diff --git a/tests/playwright/Dashboard/Usage/03_Configuration.spec.js b/tests/playwright/Dashboard/Usage/03_Configuration.spec.js new file mode 100644 index 00000000..c83430c9 --- /dev/null +++ b/tests/playwright/Dashboard/Usage/03_Configuration.spec.js @@ -0,0 +1,107 @@ +import { test, expect } from '@playwright/test'; + + +// ======================================== +// TEST 3: Configuration Page Tests +// ======================================== +test.describe('Configuration Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/config'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Configuration|Config/); + const heading = page.locator('h1:has-text("Configuration")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Configuration page title and heading verified'); + }); + + test('should verify Grid constructor section', async ({ page }) => { + const constructorHeading = page.locator('h2:has-text("Grid constructor"), h3:has-text("Grid constructor")'); + await expect(constructorHeading).toBeVisible(); + console.log('โœ“ Grid constructor section verified'); + }); + + test('should verify updateConfig section', async ({ page }) => { + const updateConfigHeading = page.locator('h2:has-text("updateConfig"), h3:has-text("updateConfig")'); + await expect(updateConfigHeading).toBeVisible(); + console.log('โœ“ updateConfig section verified'); + }); + + test('should verify new Grid code example', async ({ page }) => { + const newGrid = page.getByText('new Grid').first(); + await expect(newGrid).toBeVisible(); + console.log('โœ“ new Grid code example verified'); + }); + + test('should verify columns configuration example', async ({ page }) => { + const columnsExample = page.getByText('columns').first(); + await expect(columnsExample).toBeVisible(); + + const nameColumn = page.getByText(/Name|Email|Phone/); + await expect(nameColumn.first()).toBeVisible(); + console.log('โœ“ Columns configuration example verified'); + }); + + + test('should verify link to Config details', async ({ page }) => { + const configLink = page.locator('a[href*="/docs/config/data"]').first(); + if (await configLink.isVisible()) { + const href = await configLink.getAttribute('href'); + expect(href).toContain('/docs/config/data'); + console.log('โœ“ Link to Config details verified'); + } + }); + + test('should verify and test examples link', async ({ page }) => { + const examplesLink = page.locator('a[href*="/examples/hello-world"]').first(); + if (await examplesLink.isVisible()) { + await examplesLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('/examples/hello-world'); + console.log('โœ“ Examples link navigation successful'); + } + }); + + test('should verify render method mentioned', async ({ page }) => { + const render = page.locator('text=render'); + await expect(render).toBeVisible(); + console.log('โœ“ Render method mention verified'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/config.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Hello World'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/hello-world'); + console.log('โœ“ Previous link clicked - navigated to Hello World'); + }); + + test('should verify Next link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('Server-side setup'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/server-side'); + console.log('โœ“ Next link clicked - navigated to Server-side'); + }); +}); diff --git a/tests/playwright/Dashboard/Usage/04_Server-side_setup.spec.js b/tests/playwright/Dashboard/Usage/04_Server-side_setup.spec.js new file mode 100644 index 00000000..6120880b --- /dev/null +++ b/tests/playwright/Dashboard/Usage/04_Server-side_setup.spec.js @@ -0,0 +1,249 @@ +import { test, expect } from '@playwright/test'; + +// ======================================== +// TEST 4: Server-side Page Tests +// ======================================== +test.describe('Server-side Page Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/docs/server-side'); + await page.waitForLoadState('networkidle'); + }); + + test('should verify page title and heading', async ({ page }) => { + await expect(page).toHaveTitle(/Server-side setup/); + const heading = page.locator('h1:has-text("Server")'); + await expect(heading).toBeVisible(); + console.log('โœ“ Server-side page title and heading verified'); + }); + + test('should verify server config section', async ({ page }) => { + const serverConfigHeading = page.locator('h2:has-text("server"), h3:has-text("server")'); + await expect(serverConfigHeading.first()).toBeVisible(); + console.log('โœ“ Server config section verified'); + }); + + test('should verify URL configuration present', async ({ page }) => { + const urlConfig = page.locator('text=url:'); + await expect(urlConfig.first()).toBeVisible(); + console.log('โœ“ URL configuration verified'); + }); + + test('should verify SWAPI example present', async ({ page }) => { + const swapi = page.getByText('swapi.dev').first(); + await expect(swapi).toBeVisible(); + console.log('โœ“ SWAPI example verified'); + }); + + test('should verify films endpoint example', async ({ page }) => { + const films = page.locator('text=/films/'); + await expect(films.first()).toBeVisible(); + console.log('โœ“ Films endpoint example verified'); + }); + + test('should verify data mapping example', async ({ page }) => { + const mapping = page.locator('text=/movie\\.title|movie\\.director|movie\\.producer/'); + await expect(mapping.first()).toBeVisible(); + console.log('โœ“ Data mapping example verified'); + }); + + test('should verify Client-side search section', async ({ page }) => { + const clientSearch = page.locator('h2:has-text("Client-side search"), h3:has-text("Client-side search")'); + await expect(clientSearch).toBeVisible(); + console.log('โœ“ Client-side search section verified'); + }); + + test('should verify Server-side search section', async ({ page }) => { + const serverSearch = page.locator('h2:has-text("Server-side search"), h3:has-text("Server-side search")'); + await expect(serverSearch).toBeVisible(); + console.log('โœ“ Server-side search section verified'); + }); + + test('should verify HTTP method configuration', async ({ page }) => { + const method = page.locator('text=method:'); + await expect(method.first()).toBeVisible(); + + const postMethod = page.locator('text=/POST|GET/'); + await expect(postMethod.first()).toBeVisible(); + console.log('โœ“ HTTP method configuration verified'); + }); + + test('should verify search server configuration', async ({ page }) => { + const searchServer = page.locator('text=search:'); + await expect(searchServer.first()).toBeVisible(); + console.log('โœ“ Search server configuration verified'); + }); + + test('should verify keyword parameter example', async ({ page }) => { + const keyword = page.locator('text=/keyword|search=/'); + await expect(keyword.first()).toBeVisible(); + console.log('โœ“ Keyword parameter example verified'); + }); + + test('should verify columns example (Title, Director, Producer)', async ({ page }) => { + const columns = page.locator('text=/Title.*Director.*Producer/s'); + await expect(columns.first()).toBeVisible(); + console.log('โœ“ Columns example verified'); + }); + + test('should verify Edit this page link', async ({ page }) => { + const editLink = page.locator('a.theme-edit-this-page'); + await expect(editLink).toBeVisible(); + await expect(editLink).toContainText('Edit this page'); + + const href = await editLink.getAttribute('href'); + expect(href).toContain('github.com/grid-js/website'); + expect(href).toContain('/edit/master/docs/server-side.md'); + console.log('โœ“ Edit this page link verified'); + }); + + test('should verify Previous link', async ({ page }) => { + const prevLink = page.locator('a.pagination-nav__link--prev'); + await expect(prevLink).toBeVisible(); + await expect(prevLink).toContainText('Configuration'); + + await prevLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/config'); + console.log('โœ“ Previous link clicked - navigated to Configuration'); + }); + + test('should verify Next link', async ({ page }) => { + const nextLink = page.locator('a.pagination-nav__link--next'); + await expect(nextLink).toBeVisible(); + await expect(nextLink).toContainText('data'); + + await nextLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/config/data'); + console.log('โœ“ Next link clicked - navigated to Data'); + }); + + + test('should verify home button navigation', async ({ page }) => { + const homeButton = page.locator('a[aria-label="Home page"]'); + if (await homeButton.isVisible()) { + await homeButton.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toBe('https://gridjs.io/'); + console.log('โœ“ Home button navigation verified'); + } + }); + + test('should verify live editor search functionality', async ({ page }) => { + // Wait for the grid to be rendered + await page.waitForTimeout(2000); + + // Find the search input in the live editor + const searchInput = page.locator('input[type="search"]').first(); + + if (await searchInput.isVisible()) { + // Test 1: Search for "Attack" + await searchInput.fill('Attack'); + await page.waitForTimeout(1500); + + // get the second table rows + const tableRows = page.locator('table').nth(1).locator('tbody tr'); + const rowCount = await tableRows.count(); + + if (rowCount > 0) { + const firstRowText = await tableRows.first().textContent(); + expect(firstRowText).toContain('Attack'); + console.log('โœ“ Search for "Attack" - filtered results verified'); + } + + // Test 2: Search for "George" + await searchInput.fill('George'); + await page.waitForTimeout(1000); + + const rowsAfterGeorge = await tableRows.count(); + if (rowsAfterGeorge > 0) { + const rows = await tableRows.all(); + let foundGeorge = false; + for (const row of rows) { + const text = await row.textContent(); + if (text?.includes('George')) { + foundGeorge = true; + break; + } + } + expect(foundGeorge).toBe(true); + console.log('โœ“ Search for "George" - filtered results verified'); + } + + // Test 3: Search for "Phantom" + await searchInput.fill('Phantom'); + await page.waitForTimeout(1000); + + const rowsAfterPhantom = await tableRows.count(); + if (rowsAfterPhantom > 0) { + const firstRow = await tableRows.first().textContent(); + expect(firstRow).toContain('Phantom'); + console.log('โœ“ Search for "Phantom" - filtered results verified'); + } + + // Test 4: Clear search and verify all results return + await searchInput.clear(); + await page.waitForTimeout(1000); + + const allRowsCount = await tableRows.count(); + expect(allRowsCount).toBeGreaterThan(3); + console.log(`โœ“ Clear search - all results returned (${allRowsCount} rows)`); + + // Test 5: Search for non-existent term + await searchInput.fill('xyz123'); + await page.waitForTimeout(1000); + + const noResultsCount = await tableRows.count(); + expect(noResultsCount).toBe(1); + console.log('โœ“ Search for non-existent term - no results shown'); + } + }); + + test('should verify live editor grid displays correct data', async ({ page }) => { + // Wait for the grid to be rendered + await page.waitForTimeout(2000); + + const table = page.locator('table').first(); + if (await table.isVisible()) { + // Verify table headers + const headers = page.locator('thead th'); + const headerCount = await headers.count(); + expect(headerCount).toBeGreaterThanOrEqual(3); + + const headerTexts = await headers.allTextContents(); + expect(headerTexts.join(' ')).toContain('Title'); + expect(headerTexts.join(' ')).toContain('Director'); + expect(headerTexts.join(' ')).toContain('Producer'); + console.log('โœ“ Grid headers verified (Title, Director, Producer)'); + + // Verify some Star Wars movies are present + const tableBody = page.locator('table').first().locator('tbody'); + const bodyText = await tableBody.textContent(); + + const expectedMovies = ['A New Hope', 'Empire', 'Jedi', 'Phantom']; + let moviesFound = 0; + for (const movie of expectedMovies) { + if (bodyText?.includes(movie)) { + moviesFound++; + } + } + + expect(moviesFound).toBeGreaterThan(0); + console.log(`โœ“ Grid data verified (${moviesFound} Star Wars movies found)`); + } + }); + + test('should verify search input placeholder', async ({ page }) => { + await page.waitForTimeout(2000); + + const searchInput = page.locator('input[type="search"]').first(); + if (await searchInput.isVisible()) { + const placeholder = await searchInput.getAttribute('placeholder'); + expect(placeholder).toBeTruthy(); + expect(placeholder?.toLowerCase()).toContain('keyword'); + console.log(`โœ“ Search input placeholder verified: "${placeholder}"`); + } + }); +}); \ No newline at end of file diff --git a/tests/playwright/Homepage/TableHome.spec.js b/tests/playwright/Homepage/TableHome.spec.js new file mode 100644 index 00000000..1bf2d687 --- /dev/null +++ b/tests/playwright/Homepage/TableHome.spec.js @@ -0,0 +1,70 @@ +import { test, expect } from "@playwright/test"; + +test.describe("BUG: Sort + Pagination (Homepage table)", () => { + test("BUG: After sorting by Name, Page 3 should not show the same data as Page 1", async ({ page }, testInfo) => { + test.setTimeout(120_000); + + const logs = []; + const log = (msg) => { + logs.push(msg); + console.log(msg); + }; + + page.on("console", (m) => log(`[console:${m.type()}] ${m.text()}`)); + page.on("pageerror", (e) => log(`[pageerror] ${e.message}`)); + page.on("requestfailed", (req) => + log(`[requestfailed] ${req.method()} ${req.url()} :: ${req.failure()?.errorText || ""}`), + ); + + await page.goto("https://gridjs.io/", { waitUntil: "domcontentloaded" }); + await expect(page.locator(".gridjs-table")).toBeVisible(); + + const pageBtn = (n) => page.locator(`.gridjs-pages button:has-text("${n}")`).first(); + const nameHeader = page.locator('th:has-text("Name")').first(); + + const getNames = async () => { + const names = await page.locator("tbody tr td:nth-child(1)").allInnerTexts(); + return names.map((x) => x.trim()).filter(Boolean); + }; + + const printArr = (arr) => `[ ${arr.map((x) => `'${x}'`).join(", ")} ]`; + + const clickPage = async (n) => { + await expect(pageBtn(n)).toBeVisible(); + await pageBtn(n).click(); + await page.waitForTimeout(900); + }; + + // Go Page 3 + await clickPage(3); + const page3BeforeSort = await getNames(); + log(`Page 3 before sort: ${printArr(page3BeforeSort)}`); + + // Sort by Name (usually resets to Page 1) + await expect(nameHeader).toBeVisible(); + await nameHeader.click(); + await page.waitForTimeout(900); + + const page1AfterSort = await getNames(); + log(`Page 1 after sort: ${printArr(page1AfterSort)}`); + + // Back to Page 3 again + await clickPage(3); + const page3AfterSort = await getNames(); + log(`Page 3 after sort: ${printArr(page3AfterSort)}`); + + // BUG catcher (exact vibe like your screenshot) + try { + expect( + page3AfterSort, + "Pagination bug: Page 3 displays the same rows as Page 1 after sorting", + ).not.toEqual(page1AfterSort); + } catch (err) { + await page.screenshot({ path: testInfo.outputPath("pagination-sort-bug.png"), fullPage: true }); + testInfo.attach("console-log.txt", { body: logs.join("\n"), contentType: "text/plain" }); + throw err; + } + + testInfo.attach("console-log.txt", { body: logs.join("\n"), contentType: "text/plain" }); + }); +}); diff --git a/tests/playwright/Homepage/homepage.spec.js b/tests/playwright/Homepage/homepage.spec.js new file mode 100644 index 00000000..d697e22c --- /dev/null +++ b/tests/playwright/Homepage/homepage.spec.js @@ -0,0 +1,174 @@ +// tests/Homepage/homepage.spec.js +import { test, expect } from "@playwright/test"; + +const HOME = (process.env.BASE_URL || "https://gridjs.io/").replace(/\/+$/, "/"); + +test.describe("Homepage - nav + CTAs + NPM + footer bug", () => { + test.setTimeout(200_000); + + test("should validate homepage flows and catch footer bug", async ({ page }, testInfo) => { + const logs = []; + page.on("console", (m) => logs.push(`[console:${m.type()}] ${m.text()}`)); + page.on("pageerror", (e) => logs.push(`[pageerror] ${e.message}`)); + page.on("requestfailed", (req) => + logs.push(`[requestfailed] ${req.method()} ${req.url()} :: ${req.failure()?.errorText || ""}`), + ); + + // ---------- NAVBAR: GitHub ---------- + await goHome(page); + + const githubNav = page.locator('nav a:has-text("GitHub")').first(); + await expect(githubNav).toBeVisible({ timeout: 20_000 }); + + await clickLinkExpectNewPageOrTab({ + page, + locator: githubNav, + expectedUrlContains: "github.com", + label: "Navbar GitHub", + returnTo: HOME, + }); + + // ---------- CTA: Get started ---------- + await goHome(page); + + const getStarted = page.locator('a:has-text("Get started")').first(); + await expect(getStarted).toBeVisible({ timeout: 20_000 }); + + await clickLinkExpectNavigation({ + page, + locator: getStarted, + expectedUrlContains: "/docs", + label: "CTA Get started", + returnTo: HOME, + }); + + // ---------- CTA: Examples ---------- + await goHome(page); + + const examples = page.locator('a:has-text("Examples")').first(); + await expect(examples).toBeVisible({ timeout: 20_000 }); + + await clickLinkExpectNavigation({ + page, + locator: examples, + expectedUrlContains: "/docs/examples", + label: "CTA Examples", + returnTo: HOME, + }); + + // ---------- NPM link inside text ("Grid.js is available on NPM...") ---------- + await goHome(page); + + const npmInParagraph = page.locator('a[href*="npmjs.com/package/gridjs"]').first(); + await expect(npmInParagraph, "NPM link should exist in homepage section").toBeVisible({ timeout: 20_000 }); + + await clickLinkExpectNewPageOrTab({ + page, + locator: npmInParagraph, + expectedUrlContains: "npmjs.com/package/gridjs", + label: "Homepage NPM link", + returnTo: HOME, + }); + + // ---------- FOOTER BUG: 3 links must NOT be "#" and must point correct domains ---------- + await goHome(page); + + const footer = page.locator("div.footer_SBgd").first(); + await expect(footer).toBeVisible({ timeout: 20_000 }); + + await assertFooterCriticalLinks(footer); + + testInfo.attach("console-log.txt", { + body: logs.join("\n") || "(no logs)", + contentType: "text/plain", + }); + }); +}); + +async function goHome(page) { + await page.goto(HOME, { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(500); // small settle +} + +async function clickLinkExpectNavigation({ page, locator, expectedUrlContains, label, returnTo }) { + const before = page.url(); + + const nav = page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 30_000 }).catch(() => null); + + await locator.click({ timeout: 20_000 }); + + const navigated = await nav; + await page.waitForTimeout(700); + + const after = page.url(); + + if (!navigated && after === before) { + throw new Error(`${label}: click did not navigate (URL stayed the same: ${after})`); + } + + expect(after.toLowerCase()).toContain(expectedUrlContains.toLowerCase()); + + await page.goto(returnTo, { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(500); +} + +async function clickLinkExpectNewPageOrTab({ page, locator, expectedUrlContains, label, returnTo }) { + const before = page.url(); + + const popupPromise = page.waitForEvent("popup", { timeout: 15_000 }).catch(() => null); + const navPromise = page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 30_000 }).catch(() => null); + + await locator.click({ timeout: 20_000 }); + + const popup = await popupPromise; + + if (popup) { + await popup.waitForLoadState("domcontentloaded", { timeout: 30_000 }); + await popup.waitForTimeout(900); // more time for heavy sites + const u = popup.url().toLowerCase(); + + expect(u).toContain(expectedUrlContains.toLowerCase()); + + await popup.close(); + await page.bringToFront(); + } else { + await navPromise; + await page.waitForLoadState("domcontentloaded", { timeout: 30_000 }); + await page.waitForTimeout(900); + + const after = page.url().toLowerCase(); + if (after === before.toLowerCase()) { + throw new Error(`${label}: no popup and no navigation happened (URL stayed: ${after})`); + } + + expect(after).toContain(expectedUrlContains.toLowerCase()); + } + + await page.goto(returnTo, { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(500); +} + +async function assertFooterCriticalLinks(footer) { + const required = [ + { name: "Contribute", re: /Contribute/i, hostContains: "github.com" }, + { name: "StackOverflow", re: /Stack\s*Overflow/i, hostContains: "stackoverflow.com" }, + { name: "Contributors", re: /Contributors/i, hostContains: "github.com" }, + ]; + + for (const r of required) { + const a = footer.locator("a").filter({ hasText: r.re }).first(); + await expect(a, `Footer link "${r.name}" should exist`).toBeVisible({ timeout: 15_000 }); + + const href = await a.getAttribute("href"); + + // This is the actual bug catcher (homepage HTML currently uses "#") + expect(href, `Footer "${r.name}" should NOT be "#"`).not.toBe("#"); + expect(href, `Footer "${r.name}" should have href`).toBeTruthy(); + + if (!href.startsWith("http")) { + throw new Error(`Footer "${r.name}" should point to ${r.hostContains}, but got non-http href: ${href}`); + } + + expect(new URL(href).hostname).toContain(r.hostContains); + } +} diff --git a/tests/playwright/README.md b/tests/playwright/README.md new file mode 100644 index 00000000..aac77a44 --- /dev/null +++ b/tests/playwright/README.md @@ -0,0 +1,98 @@ +# End-to-End Testing for Grid.js Interactive Documentation + +## Overview +This repository contains a comprehensive **End-to-End (E2E) testing suite** for the **Grid.js Interactive Documentation Website**, built using **Playwright**. + +The purpose of this test suite is to continuously validate critical interactive features demonstrated in the Grid.js documentation and to help prevent regressions that could negatively impact developer experience and trust. + +--- + +## Background +Grid.js is an open-source JavaScript table plugin whose documentation website showcases live, interactive examples such as searching, sorting, pagination, and live configuration. + +Because these demos reflect real-world usage, regressions in the documentation can be easily mistaken as core library failures. This E2E test suite acts as a safety net to ensure the stability and correctness of those interactive examples. + +--- + +## Objectives +- Validate core interactive behaviors in the documentation site +- Detect regressions caused by UI or internal logic changes +- Reduce reliance on manual verification +- Improve long-term maintainability of interactive demos +- Ensure consistent behavior across major browsers + +--- + +## Features & Test Coverage + +### Functional Coverage +The test suite covers the following key features: + +- **Global Search** + - Input handling and filtering behavior + - Asynchronous update validation + +- **Column Sorting** + - Ascending and descending ordering + - Numeric and string sorting validation + +- **Pagination** + - Page navigation + - Row count consistency + +- **Live Editor** + - Real-time configuration updates + - Table re-rendering behavior + +### Non-Functional Coverage +- Cross-browser compatibility (Chromium, Firefox, WebKit) +- Stability against asynchronous client-side rendering +- Deterministic and repeatable test execution + +--- + +## Handling Dynamic Client-Side Data +Some Grid.js documentation demos rely on dynamically generated client-side data. To ensure stable and repeatable E2E tests, this test suite: + +- Controls the data used by documentation demos +- Validates behavior against predictable, deterministic datasets +- Uses logic-based assertions rather than fragile text comparisons + +This approach prevents flaky tests while remaining faithful to real user interactions. + +--- + +## Technical Approach +- **Framework:** Playwright +- **Architecture:** Page Object Model (POM) +- **Selectors:** User-centric locators (role- and text-based) +- **Assertions:** Logic-driven validation of table behavior +- **Waiting Strategy:** Native Playwright auto-waiting and explicit UI state checks + +--- + +## Setup + +### Prerequisites +- **Node.js** v16 or later +- **npm** or **yarn** +- Git + +### Installation +Clone the Grid.js repository and install all required dependencies: + +``` +$ npm install +``` + +### Local Development + +``` +$ npm run start +``` + +### Run the tests + +``` +$ npx playwright test +```