From 430798051848c2d97dd5f74a05317f441ece52a0 Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 18:11:36 +0800 Subject: [PATCH 1/6] feat: add e2e testing --- .gitignore | 8 + README.md | 16 +- package-lock.json | 68 ++- package.json | 4 +- playwright.config.js | 81 +++ tests/Blog/blog.spec.js | 440 +++++++++++++++ .../01_Introduction/01_gridjs.spec.js | 116 ++++ .../01_Introduction/01_gridjsNew.spec.js | 79 +++ .../01_Introduction/02_Philosophy.spec.js | 94 ++++ .../01_Introduction/02_license.spec.js | 147 +++++ .../01_Introduction/03_Sponsors.spec.js | 103 ++++ .../01_Introduction/04_Roadmap.spec.js | 87 +++ .../01_Introduction/05_Community.spec.js | 260 +++++++++ .../01_Introduction/06_license.spec.js | 147 +++++ tests/Dashboard/02_Usage/01_Install.spec.js | 109 ++++ .../Dashboard/02_Usage/02_Hello_World.spec.js | 97 ++++ .../02_Usage/03_Configuration.spec.js | 107 ++++ .../02_Usage/04_Server-side_setup.spec.js | 249 +++++++++ tests/Dashboard/03_Config/01_Data.js | 83 +++ tests/Dashboard/03_Config/01_Data.spec.js | 83 +++ tests/Dashboard/03_Config/02_From.js | 68 +++ tests/Dashboard/03_Config/02_From.spec.js | 68 +++ tests/Dashboard/03_Config/03_Columns.js | 95 ++++ tests/Dashboard/03_Config/03_Columns.spec.js | 95 ++++ tests/Dashboard/03_Config/04_Server.js | 100 ++++ tests/Dashboard/03_Config/04_Server.spec.js | 100 ++++ tests/Dashboard/03_Config/05_Style.js | 108 ++++ tests/Dashboard/03_Config/05_Style.spec.js | 108 ++++ tests/Dashboard/03_Config/06_ClassName.js | 114 ++++ .../Dashboard/03_Config/06_ClassName.spec.js | 114 ++++ tests/Dashboard/03_Config/07_Language.js | 213 ++++++++ tests/Dashboard/03_Config/07_Language.spec.js | 213 ++++++++ tests/Dashboard/03_Config/08_Width.js | 163 ++++++ tests/Dashboard/03_Config/08_Width.spec.js | 163 ++++++ tests/Dashboard/03_Config/09_Height.js | 173 ++++++ tests/Dashboard/03_Config/09_Height.spec.js | 173 ++++++ tests/Dashboard/03_Config/10_AutoWidth.js | 137 +++++ .../Dashboard/03_Config/10_AutoWidth.spec.js | 137 +++++ tests/Dashboard/03_Config/11_FixedHeader.js | 95 ++++ .../03_Config/11_FixedHeader.spec.js | 95 ++++ tests/Dashboard/03_Config/12_Search.js | 290 ++++++++++ tests/Dashboard/03_Config/12_Search.spec.js | 290 ++++++++++ tests/Dashboard/03_Config/13_Sort.js | 293 ++++++++++ tests/Dashboard/03_Config/13_Sort.spec.js | 293 ++++++++++ .../Dashboard/03_Config/14_Pagination.spec.js | 138 +++++ .../01_Overview/01_PluginBasic.spec.js | 233 ++++++++ .../01_Overview/02_WritingAPlugin.spec.js | 331 ++++++++++++ .../01_Overview/03_AdvancedPlugin.spec.js | 316 +++++++++++ .../02_Selection/01_SelectionPlugin.spec.js | 282 ++++++++++ .../02_Selection/02_RowSelection.spec.js | 161 ++++++ .../02_Selection/03_SelectionEvents.spec.js | 287 ++++++++++ .../06_Localization/localization.spec.js | 335 ++++++++++++ .../07_Examples/Basic/fixed_header.spec.js | 262 +++++++++ .../07_Examples/Basic/from_html_table.spec.js | 156 ++++++ .../07_Examples/Basic/hello_world.spec.js | 179 +++++++ .../07_Examples/Basic/hidden_columns.spec.js | 171 ++++++ .../07_Examples/Basic/loading_state.spec.js | 180 +++++++ .../07_Examples/Basic/pagination.spec.js | 194 +++++++ .../Basic/resizable_columns.spec.js | 202 +++++++ .../07_Examples/Basic/search.spec.js | 245 +++++++++ .../07_Examples/Basic/sorting.spec.js | 198 +++++++ .../07_Examples/Basic/wide_table.spec.js | 204 +++++++ .../07_Examples/advanced/custon_sort.spec.js | 145 +++++ .../07_Examples/advanced/events.spec.js | 145 +++++ .../07_Examples/advanced/forceRender.spec.js | 145 +++++ .../advanced/multi_column_sort.spec.js | 145 +++++ .../advanced/nested_header.spec.js | 145 +++++ .../07_Examples/advanced/stock_market.spec.js | 145 +++++ .../07_Examples/advanced/virtual_DOM.spec.js | 145 +++++ .../customizing/cell_attributes.spec.js | 219 ++++++++ .../customizing/cell_formatting.spec.js | 136 +++++ .../customizing/html_in_cells.spec.js | 136 +++++ .../customizing/html_in_header_cells.spec.js | 136 +++++ .../react_component_in_cells.spec.js | 136 +++++ .../customizing/row_buttons.spec.js | 136 +++++ .../data_source/async_data_import.spec.js | 137 +++++ .../data_source/dynamic_data_import.spec.js | 136 +++++ .../07_Examples/data_source/json.spec.js | 136 +++++ .../07_Examples/data_source/xml.spec.js | 136 +++++ .../server_side/custom_http_client.spec.js | 136 +++++ .../import_server_side_data.spec.js | 136 +++++ .../server_side_pagination.spec.js | 136 +++++ .../server_side/server_side_search.spec.js | 136 +++++ .../server_side/server_side_sorting.spec.js | 136 +++++ .../07_Examples/styling/css_classname.spec.js | 136 +++++ .../07_Examples/styling/css_in_js.spec.js | 136 +++++ .../07_Examples/styling/css_style.spec.js | 136 +++++ tests/Homepage/homepage.spec.js | 501 ++++++++++++++++++ 88 files changed, 14188 insertions(+), 10 deletions(-) create mode 100644 playwright.config.js create mode 100644 tests/Blog/blog.spec.js create mode 100644 tests/Dashboard/01_Introduction/01_gridjs.spec.js create mode 100644 tests/Dashboard/01_Introduction/01_gridjsNew.spec.js create mode 100644 tests/Dashboard/01_Introduction/02_Philosophy.spec.js create mode 100644 tests/Dashboard/01_Introduction/02_license.spec.js create mode 100644 tests/Dashboard/01_Introduction/03_Sponsors.spec.js create mode 100644 tests/Dashboard/01_Introduction/04_Roadmap.spec.js create mode 100644 tests/Dashboard/01_Introduction/05_Community.spec.js create mode 100644 tests/Dashboard/01_Introduction/06_license.spec.js create mode 100644 tests/Dashboard/02_Usage/01_Install.spec.js create mode 100644 tests/Dashboard/02_Usage/02_Hello_World.spec.js create mode 100644 tests/Dashboard/02_Usage/03_Configuration.spec.js create mode 100644 tests/Dashboard/02_Usage/04_Server-side_setup.spec.js create mode 100644 tests/Dashboard/03_Config/01_Data.js create mode 100644 tests/Dashboard/03_Config/01_Data.spec.js create mode 100644 tests/Dashboard/03_Config/02_From.js create mode 100644 tests/Dashboard/03_Config/02_From.spec.js create mode 100644 tests/Dashboard/03_Config/03_Columns.js create mode 100644 tests/Dashboard/03_Config/03_Columns.spec.js create mode 100644 tests/Dashboard/03_Config/04_Server.js create mode 100644 tests/Dashboard/03_Config/04_Server.spec.js create mode 100644 tests/Dashboard/03_Config/05_Style.js create mode 100644 tests/Dashboard/03_Config/05_Style.spec.js create mode 100644 tests/Dashboard/03_Config/06_ClassName.js create mode 100644 tests/Dashboard/03_Config/06_ClassName.spec.js create mode 100644 tests/Dashboard/03_Config/07_Language.js create mode 100644 tests/Dashboard/03_Config/07_Language.spec.js create mode 100644 tests/Dashboard/03_Config/08_Width.js create mode 100644 tests/Dashboard/03_Config/08_Width.spec.js create mode 100644 tests/Dashboard/03_Config/09_Height.js create mode 100644 tests/Dashboard/03_Config/09_Height.spec.js create mode 100644 tests/Dashboard/03_Config/10_AutoWidth.js create mode 100644 tests/Dashboard/03_Config/10_AutoWidth.spec.js create mode 100644 tests/Dashboard/03_Config/11_FixedHeader.js create mode 100644 tests/Dashboard/03_Config/11_FixedHeader.spec.js create mode 100644 tests/Dashboard/03_Config/12_Search.js create mode 100644 tests/Dashboard/03_Config/12_Search.spec.js create mode 100644 tests/Dashboard/03_Config/13_Sort.js create mode 100644 tests/Dashboard/03_Config/13_Sort.spec.js create mode 100644 tests/Dashboard/03_Config/14_Pagination.spec.js create mode 100644 tests/Dashboard/04_Plugins/01_Overview/01_PluginBasic.spec.js create mode 100644 tests/Dashboard/04_Plugins/01_Overview/02_WritingAPlugin.spec.js create mode 100644 tests/Dashboard/04_Plugins/01_Overview/03_AdvancedPlugin.spec.js create mode 100644 tests/Dashboard/04_Plugins/02_Selection/01_SelectionPlugin.spec.js create mode 100644 tests/Dashboard/04_Plugins/02_Selection/02_RowSelection.spec.js create mode 100644 tests/Dashboard/04_Plugins/02_Selection/03_SelectionEvents.spec.js create mode 100644 tests/Dashboard/06_Localization/localization.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/fixed_header.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/from_html_table.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/hello_world.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/hidden_columns.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/loading_state.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/pagination.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/resizable_columns.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/search.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/sorting.spec.js create mode 100644 tests/Dashboard/07_Examples/Basic/wide_table.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/custon_sort.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/events.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/forceRender.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/multi_column_sort.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/nested_header.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/stock_market.spec.js create mode 100644 tests/Dashboard/07_Examples/advanced/virtual_DOM.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/cell_attributes.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/cell_formatting.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/html_in_cells.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/html_in_header_cells.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/react_component_in_cells.spec.js create mode 100644 tests/Dashboard/07_Examples/customizing/row_buttons.spec.js create mode 100644 tests/Dashboard/07_Examples/data_source/async_data_import.spec.js create mode 100644 tests/Dashboard/07_Examples/data_source/dynamic_data_import.spec.js create mode 100644 tests/Dashboard/07_Examples/data_source/json.spec.js create mode 100644 tests/Dashboard/07_Examples/data_source/xml.spec.js create mode 100644 tests/Dashboard/07_Examples/server_side/custom_http_client.spec.js create mode 100644 tests/Dashboard/07_Examples/server_side/import_server_side_data.spec.js create mode 100644 tests/Dashboard/07_Examples/server_side/server_side_pagination.spec.js create mode 100644 tests/Dashboard/07_Examples/server_side/server_side_search.spec.js create mode 100644 tests/Dashboard/07_Examples/server_side/server_side_sorting.spec.js create mode 100644 tests/Dashboard/07_Examples/styling/css_classname.spec.js create mode 100644 tests/Dashboard/07_Examples/styling/css_in_js.spec.js create mode 100644 tests/Dashboard/07_Examples/styling/css_style.spec.js create mode 100644 tests/Homepage/homepage.spec.js diff --git a/.gitignore b/.gitignore index 3ced74ee..14515921 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,11 @@ yarn-debug.log* yarn-error.log* .idea + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/README.md b/README.md index 108cfd0c..1da709a5 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,29 @@ Grid.js website is built using [Docusaurus 2](https://v2.docusaurus.io/). ### Installation ``` -$ yarn +$ npm install ``` ### Local Development ``` -$ yarn start +$ npm run start ``` -This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. +### Run the tests + +``` +$ npx playwright test +``` -### Build +### Show the test report ``` -$ yarn build +$ npx playwright show-report ``` +This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. + This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment diff --git a/package-lock.json b/package-lock.json index 9c8a3cb0..505b3d74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,9 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@types/faker": "^5.5.6" + "@playwright/test": "^1.57.0", + "@types/faker": "^5.5.6", + "@types/node": "^25.0.3" } }, "node_modules/@algolia/autocomplete-core": { @@ -3131,6 +3133,22 @@ "node": ">=4" } }, + "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/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -3592,9 +3610,13 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -9112,6 +9134,38 @@ "node": ">=4" } }, + "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/postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -11940,6 +11994,12 @@ "node": "*" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unescape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", diff --git a/package.json b/package.json index 8cf75d43..1cea5790 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ ] }, "devDependencies": { - "@types/faker": "^5.5.6" + "@playwright/test": "^1.57.0", + "@types/faker": "^5.5.6", + "@types/node": "^25.0.3" } } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000..3967e588 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,81 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); + diff --git a/tests/Blog/blog.spec.js b/tests/Blog/blog.spec.js new file mode 100644 index 00000000..9477afef --- /dev/null +++ b/tests/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/Dashboard/01_Introduction/01_gridjs.spec.js b/tests/Dashboard/01_Introduction/01_gridjs.spec.js new file mode 100644 index 00000000..92dc2e25 --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/01_gridjsNew.spec.js b/tests/Dashboard/01_Introduction/01_gridjsNew.spec.js new file mode 100644 index 00000000..61b6704c --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/02_Philosophy.spec.js b/tests/Dashboard/01_Introduction/02_Philosophy.spec.js new file mode 100644 index 00000000..fc491636 --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/02_license.spec.js b/tests/Dashboard/01_Introduction/02_license.spec.js new file mode 100644 index 00000000..13018d6b --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/03_Sponsors.spec.js b/tests/Dashboard/01_Introduction/03_Sponsors.spec.js new file mode 100644 index 00000000..14d68fb7 --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/04_Roadmap.spec.js b/tests/Dashboard/01_Introduction/04_Roadmap.spec.js new file mode 100644 index 00000000..a648ff30 --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/05_Community.spec.js b/tests/Dashboard/01_Introduction/05_Community.spec.js new file mode 100644 index 00000000..6cd25cf9 --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/01_Introduction/06_license.spec.js b/tests/Dashboard/01_Introduction/06_license.spec.js new file mode 100644 index 00000000..13018d6b --- /dev/null +++ b/tests/Dashboard/01_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/Dashboard/02_Usage/01_Install.spec.js b/tests/Dashboard/02_Usage/01_Install.spec.js new file mode 100644 index 00000000..683e1102 --- /dev/null +++ b/tests/Dashboard/02_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/Dashboard/02_Usage/02_Hello_World.spec.js b/tests/Dashboard/02_Usage/02_Hello_World.spec.js new file mode 100644 index 00000000..24632980 --- /dev/null +++ b/tests/Dashboard/02_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/Dashboard/02_Usage/03_Configuration.spec.js b/tests/Dashboard/02_Usage/03_Configuration.spec.js new file mode 100644 index 00000000..c83430c9 --- /dev/null +++ b/tests/Dashboard/02_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/Dashboard/02_Usage/04_Server-side_setup.spec.js b/tests/Dashboard/02_Usage/04_Server-side_setup.spec.js new file mode 100644 index 00000000..6120880b --- /dev/null +++ b/tests/Dashboard/02_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/Dashboard/03_Config/01_Data.js b/tests/Dashboard/03_Config/01_Data.js new file mode 100644 index 00000000..0d8e9195 --- /dev/null +++ b/tests/Dashboard/03_Config/01_Data.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/Dashboard/03_Config/01_Data.spec.js b/tests/Dashboard/03_Config/01_Data.spec.js new file mode 100644 index 00000000..0d8e9195 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/02_From.js b/tests/Dashboard/03_Config/02_From.js new file mode 100644 index 00000000..a4ced91d --- /dev/null +++ b/tests/Dashboard/03_Config/02_From.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/Dashboard/03_Config/02_From.spec.js b/tests/Dashboard/03_Config/02_From.spec.js new file mode 100644 index 00000000..a4ced91d --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/03_Columns.js b/tests/Dashboard/03_Config/03_Columns.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/Dashboard/03_Config/03_Columns.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/Dashboard/03_Config/03_Columns.spec.js b/tests/Dashboard/03_Config/03_Columns.spec.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/04_Server.js b/tests/Dashboard/03_Config/04_Server.js new file mode 100644 index 00000000..81625948 --- /dev/null +++ b/tests/Dashboard/03_Config/04_Server.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/Dashboard/03_Config/04_Server.spec.js b/tests/Dashboard/03_Config/04_Server.spec.js new file mode 100644 index 00000000..81625948 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/05_Style.js b/tests/Dashboard/03_Config/05_Style.js new file mode 100644 index 00000000..d462709a --- /dev/null +++ b/tests/Dashboard/03_Config/05_Style.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/Dashboard/03_Config/05_Style.spec.js b/tests/Dashboard/03_Config/05_Style.spec.js new file mode 100644 index 00000000..d462709a --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/06_ClassName.js b/tests/Dashboard/03_Config/06_ClassName.js new file mode 100644 index 00000000..8f2c8466 --- /dev/null +++ b/tests/Dashboard/03_Config/06_ClassName.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/Dashboard/03_Config/06_ClassName.spec.js b/tests/Dashboard/03_Config/06_ClassName.spec.js new file mode 100644 index 00000000..8f2c8466 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/07_Language.js b/tests/Dashboard/03_Config/07_Language.js new file mode 100644 index 00000000..259e9f8a --- /dev/null +++ b/tests/Dashboard/03_Config/07_Language.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/Dashboard/03_Config/07_Language.spec.js b/tests/Dashboard/03_Config/07_Language.spec.js new file mode 100644 index 00000000..259e9f8a --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/08_Width.js b/tests/Dashboard/03_Config/08_Width.js new file mode 100644 index 00000000..9bbef50a --- /dev/null +++ b/tests/Dashboard/03_Config/08_Width.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/Dashboard/03_Config/08_Width.spec.js b/tests/Dashboard/03_Config/08_Width.spec.js new file mode 100644 index 00000000..9bbef50a --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/09_Height.js b/tests/Dashboard/03_Config/09_Height.js new file mode 100644 index 00000000..b755b773 --- /dev/null +++ b/tests/Dashboard/03_Config/09_Height.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/Dashboard/03_Config/09_Height.spec.js b/tests/Dashboard/03_Config/09_Height.spec.js new file mode 100644 index 00000000..b755b773 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/10_AutoWidth.js b/tests/Dashboard/03_Config/10_AutoWidth.js new file mode 100644 index 00000000..51ceadaf --- /dev/null +++ b/tests/Dashboard/03_Config/10_AutoWidth.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/Dashboard/03_Config/10_AutoWidth.spec.js b/tests/Dashboard/03_Config/10_AutoWidth.spec.js new file mode 100644 index 00000000..51ceadaf --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/11_FixedHeader.js b/tests/Dashboard/03_Config/11_FixedHeader.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/Dashboard/03_Config/11_FixedHeader.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/Dashboard/03_Config/11_FixedHeader.spec.js b/tests/Dashboard/03_Config/11_FixedHeader.spec.js new file mode 100644 index 00000000..feb0bbc2 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/12_Search.js b/tests/Dashboard/03_Config/12_Search.js new file mode 100644 index 00000000..d8b5d121 --- /dev/null +++ b/tests/Dashboard/03_Config/12_Search.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/Dashboard/03_Config/12_Search.spec.js b/tests/Dashboard/03_Config/12_Search.spec.js new file mode 100644 index 00000000..d8b5d121 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/13_Sort.js b/tests/Dashboard/03_Config/13_Sort.js new file mode 100644 index 00000000..8e2516c4 --- /dev/null +++ b/tests/Dashboard/03_Config/13_Sort.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/Dashboard/03_Config/13_Sort.spec.js b/tests/Dashboard/03_Config/13_Sort.spec.js new file mode 100644 index 00000000..8e2516c4 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/03_Config/14_Pagination.spec.js b/tests/Dashboard/03_Config/14_Pagination.spec.js new file mode 100644 index 00000000..42633063 --- /dev/null +++ b/tests/Dashboard/03_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/Dashboard/04_Plugins/01_Overview/01_PluginBasic.spec.js b/tests/Dashboard/04_Plugins/01_Overview/01_PluginBasic.spec.js new file mode 100644 index 00000000..f764ef9f --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/04_Plugins/01_Overview/02_WritingAPlugin.spec.js b/tests/Dashboard/04_Plugins/01_Overview/02_WritingAPlugin.spec.js new file mode 100644 index 00000000..beef247b --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/04_Plugins/01_Overview/03_AdvancedPlugin.spec.js b/tests/Dashboard/04_Plugins/01_Overview/03_AdvancedPlugin.spec.js new file mode 100644 index 00000000..bcf64969 --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/04_Plugins/02_Selection/01_SelectionPlugin.spec.js b/tests/Dashboard/04_Plugins/02_Selection/01_SelectionPlugin.spec.js new file mode 100644 index 00000000..6389b652 --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/04_Plugins/02_Selection/02_RowSelection.spec.js b/tests/Dashboard/04_Plugins/02_Selection/02_RowSelection.spec.js new file mode 100644 index 00000000..202f62e4 --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/04_Plugins/02_Selection/03_SelectionEvents.spec.js b/tests/Dashboard/04_Plugins/02_Selection/03_SelectionEvents.spec.js new file mode 100644 index 00000000..06740026 --- /dev/null +++ b/tests/Dashboard/04_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/Dashboard/06_Localization/localization.spec.js b/tests/Dashboard/06_Localization/localization.spec.js new file mode 100644 index 00000000..11d610c0 --- /dev/null +++ b/tests/Dashboard/06_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/Dashboard/07_Examples/Basic/fixed_header.spec.js b/tests/Dashboard/07_Examples/Basic/fixed_header.spec.js new file mode 100644 index 00000000..8ae60b9e --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/from_html_table.spec.js b/tests/Dashboard/07_Examples/Basic/from_html_table.spec.js new file mode 100644 index 00000000..52fdd57f --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/hello_world.spec.js b/tests/Dashboard/07_Examples/Basic/hello_world.spec.js new file mode 100644 index 00000000..2a783ba1 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/hidden_columns.spec.js b/tests/Dashboard/07_Examples/Basic/hidden_columns.spec.js new file mode 100644 index 00000000..e87e5ca7 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/loading_state.spec.js b/tests/Dashboard/07_Examples/Basic/loading_state.spec.js new file mode 100644 index 00000000..667435bf --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/pagination.spec.js b/tests/Dashboard/07_Examples/Basic/pagination.spec.js new file mode 100644 index 00000000..39cfbdeb --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/resizable_columns.spec.js b/tests/Dashboard/07_Examples/Basic/resizable_columns.spec.js new file mode 100644 index 00000000..ce7c3d48 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/search.spec.js b/tests/Dashboard/07_Examples/Basic/search.spec.js new file mode 100644 index 00000000..90ecad9c --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/sorting.spec.js b/tests/Dashboard/07_Examples/Basic/sorting.spec.js new file mode 100644 index 00000000..36d827a8 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/Basic/wide_table.spec.js b/tests/Dashboard/07_Examples/Basic/wide_table.spec.js new file mode 100644 index 00000000..e123b1fd --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/custon_sort.spec.js b/tests/Dashboard/07_Examples/advanced/custon_sort.spec.js new file mode 100644 index 00000000..a846b657 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/events.spec.js b/tests/Dashboard/07_Examples/advanced/events.spec.js new file mode 100644 index 00000000..58e94698 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/forceRender.spec.js b/tests/Dashboard/07_Examples/advanced/forceRender.spec.js new file mode 100644 index 00000000..96623aa2 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/multi_column_sort.spec.js b/tests/Dashboard/07_Examples/advanced/multi_column_sort.spec.js new file mode 100644 index 00000000..ef2b1426 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/nested_header.spec.js b/tests/Dashboard/07_Examples/advanced/nested_header.spec.js new file mode 100644 index 00000000..0f8fb930 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/stock_market.spec.js b/tests/Dashboard/07_Examples/advanced/stock_market.spec.js new file mode 100644 index 00000000..696bb65f --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/advanced/virtual_DOM.spec.js b/tests/Dashboard/07_Examples/advanced/virtual_DOM.spec.js new file mode 100644 index 00000000..7ab43e05 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/cell_attributes.spec.js b/tests/Dashboard/07_Examples/customizing/cell_attributes.spec.js new file mode 100644 index 00000000..e0a738fc --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/cell_formatting.spec.js b/tests/Dashboard/07_Examples/customizing/cell_formatting.spec.js new file mode 100644 index 00000000..c236a108 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/html_in_cells.spec.js b/tests/Dashboard/07_Examples/customizing/html_in_cells.spec.js new file mode 100644 index 00000000..a3b51967 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/html_in_header_cells.spec.js b/tests/Dashboard/07_Examples/customizing/html_in_header_cells.spec.js new file mode 100644 index 00000000..bae269e7 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/react_component_in_cells.spec.js b/tests/Dashboard/07_Examples/customizing/react_component_in_cells.spec.js new file mode 100644 index 00000000..8844b908 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/customizing/row_buttons.spec.js b/tests/Dashboard/07_Examples/customizing/row_buttons.spec.js new file mode 100644 index 00000000..2ddf8b84 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/data_source/async_data_import.spec.js b/tests/Dashboard/07_Examples/data_source/async_data_import.spec.js new file mode 100644 index 00000000..6e4e1660 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/data_source/dynamic_data_import.spec.js b/tests/Dashboard/07_Examples/data_source/dynamic_data_import.spec.js new file mode 100644 index 00000000..e519454a --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/data_source/json.spec.js b/tests/Dashboard/07_Examples/data_source/json.spec.js new file mode 100644 index 00000000..2861b6af --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/data_source/xml.spec.js b/tests/Dashboard/07_Examples/data_source/xml.spec.js new file mode 100644 index 00000000..855a44cc --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/server_side/custom_http_client.spec.js b/tests/Dashboard/07_Examples/server_side/custom_http_client.spec.js new file mode 100644 index 00000000..38f408b3 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/server_side/import_server_side_data.spec.js b/tests/Dashboard/07_Examples/server_side/import_server_side_data.spec.js new file mode 100644 index 00000000..b5b47499 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/server_side/server_side_pagination.spec.js b/tests/Dashboard/07_Examples/server_side/server_side_pagination.spec.js new file mode 100644 index 00000000..cb98775c --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/server_side/server_side_search.spec.js b/tests/Dashboard/07_Examples/server_side/server_side_search.spec.js new file mode 100644 index 00000000..68f76923 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/server_side/server_side_sorting.spec.js b/tests/Dashboard/07_Examples/server_side/server_side_sorting.spec.js new file mode 100644 index 00000000..80dedc75 --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/styling/css_classname.spec.js b/tests/Dashboard/07_Examples/styling/css_classname.spec.js new file mode 100644 index 00000000..ebb55fee --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/styling/css_in_js.spec.js b/tests/Dashboard/07_Examples/styling/css_in_js.spec.js new file mode 100644 index 00000000..174dc86a --- /dev/null +++ b/tests/Dashboard/07_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/Dashboard/07_Examples/styling/css_style.spec.js b/tests/Dashboard/07_Examples/styling/css_style.spec.js new file mode 100644 index 00000000..fcfe90c7 --- /dev/null +++ b/tests/Dashboard/07_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/Homepage/homepage.spec.js b/tests/Homepage/homepage.spec.js new file mode 100644 index 00000000..2e57cff0 --- /dev/null +++ b/tests/Homepage/homepage.spec.js @@ -0,0 +1,501 @@ +import { test, expect, Page } from '@playwright/test'; + +const BASE = 'http://localhost:3000/'; + +test.describe('Grid.js Homepage E2E Tests', () => { + + test.beforeEach(async ({ page }) => { + await page.goto('https://gridjs.io/'); + // await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + await page.waitForLoadState('networkidle'); + }); + + test.describe('Navigation Bar - Desktop Links', () => { + + test('should verify all navigation links are present, have correct hrefs, and navigate correctly', async ({ page }) => { + const navLinks = [ + { text: 'Install', href: '/docs/install' }, + { text: 'Docs', href: '/docs' }, + { text: 'Sponsors', href: '/docs/sponsors' }, + { text: 'Community', href: '/docs/community' } + // , + // { text: 'GitHub', href: 'github.com/grid-js/gridjs' } + ]; + + for (const pageInfo of navLinks) { + const link = page.locator(`a:has-text("${pageInfo.text}")`).first(); + await link.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain(pageInfo.href); + } + + }); + + test('should click Grid.js logo and navigate to homepage', async ({ page }) => { + // Navigate away from homepage + await page.goto('https://gridjs.io/docs'); + await page.waitForLoadState('networkidle'); + + const logoLink = page.locator('nav a[href="/"]').first(); + await expect(logoLink).toBeVisible(); + + await logoLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toBe('https://gridjs.io/'); + }); + + test('should click GitHub link and open in new tab', async ({ page, context }) => { + const githubLink = page.locator('nav a[href="https://github.com/grid-js/gridjs"]').first(); + await expect(githubLink).toBeVisible(); + await githubLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('github.com/grid-js/gridjs'); + }); + + test('should click Chat (Discord) button and open Discord', async ({ page, context }) => { + const chatButton = page.locator('nav a[href="https://discord.com/invite/K55BwDY"]').first(); + await expect(chatButton).toBeVisible(); + await chatButton.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('discord.com/invite/K55BwDY'); + }); + }); + + test.describe('Hero Section CTA Buttons', () => { + + test('should click "Get started" button and navigate to docs', async ({ page }) => { + const getStartedBtn = page.locator('a:has-text("Get started")').first(); + await expect(getStartedBtn).toBeVisible(); + await expect(getStartedBtn).toHaveAttribute('href', '/docs'); + + await getStartedBtn.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs'); + await expect(page.locator('h1')).toBeVisible(); + }); + + test('should click "Examples" button and navigate to examples page', async ({ page }) => { + const examplesBtn = page.locator('a[href="/docs/examples/hello-world"]').first(); + await expect(examplesBtn).toBeVisible(); + await expect(examplesBtn).toHaveText('Examples'); + + await examplesBtn.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/examples/hello-world'); + await expect(page.locator('h1')).toBeVisible(); + }); + }); + + test.describe('Install Section Interactive Elements', () => { + + test('should click NPM link and open NPM page', async ({ page, context }) => { + const npmLink = page.locator('a[href="https://www.npmjs.com/package/gridjs"]'); + await expect(npmLink).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + npmLink.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('npmjs.com/package/gridjs'); + + await newPage.close(); + }); + + test('should click copy button for code example', async ({ page }) => { + const copyButton = page.locator('button[aria-label="Copy code to clipboard"]'); + await expect(copyButton).toBeVisible(); + + await copyButton.click(); + + // Wait for the copy animation + await page.waitForTimeout(500); + }); + + test('should verify JavaScript CDN input is readonly and selectable', async ({ page }) => { + const jsInput = page.locator('input[value="https://unpkg.com/gridjs/dist/gridjs.umd.js"]'); + await expect(jsInput).toBeVisible(); + await expect(jsInput).toHaveAttribute('readonly', ''); + + // Click and focus on the input + await jsInput.click(); + + // Triple click to select all text + await jsInput.click({ clickCount: 3 }); + }); + + test('should verify CSS CDN input is readonly and selectable', async ({ page }) => { + const cssInput = page.locator('input[value="https://unpkg.com/gridjs/dist/theme/mermaid.min.css"]'); + await expect(cssInput).toBeVisible(); + await expect(cssInput).toHaveAttribute('readonly', ''); + + // Click and focus on the input + await cssInput.click(); + + // Triple click to select all text + await cssInput.click({ clickCount: 3 }); + }); + }); + + // NEW TEST: Table Header Column Sort Buttons + test.describe('Table Header Column Sort Buttons', () => { + + test('should verify Name column sort button is visible and clickable', async ({ page }) => { + // Locate the Name column header with data-column-id="name" + const nameColumnHeader = page.locator('th[data-column-id="name"]'); + await expect(nameColumnHeader).toBeVisible(); + + // Locate the sort button inside the Name column + const nameSortButton = nameColumnHeader.locator('button.gridjs-sort'); + await expect(nameSortButton).toBeVisible(); + await expect(nameSortButton).toHaveAttribute('aria-label', 'Sort column ascending'); + await expect(nameSortButton).toHaveAttribute('title', 'Sort column ascending'); + + // Click the sort button + await nameSortButton.click(); + await page.waitForTimeout(500); + + // After clicking, the aria-label should change to "Sort column descending" + // (This may vary based on Grid.js implementation) + }); + + test('should verify Job column sort button is visible and clickable', async ({ page }) => { + const jobColumnHeader = page.locator('th[data-column-id="job"]'); + await expect(jobColumnHeader).toBeVisible(); + + const jobSortButton = jobColumnHeader.locator('button.gridjs-sort'); + await expect(jobSortButton).toBeVisible(); + await expect(jobSortButton).toHaveAttribute('aria-label', 'Sort column ascending'); + + await jobSortButton.click(); + await page.waitForTimeout(500); + }); + + test('should verify Country column sort button is visible and clickable', async ({ page }) => { + const countryColumnHeader = page.locator('th[data-column-id="country"]'); + await expect(countryColumnHeader).toBeVisible(); + + const countrySortButton = countryColumnHeader.locator('button.gridjs-sort'); + await expect(countrySortButton).toBeVisible(); + await expect(countrySortButton).toHaveAttribute('aria-label', 'Sort column ascending'); + + await countrySortButton.click(); + await page.waitForTimeout(500); + }); + + test('should click all three sort buttons in sequence', async ({ page }) => { + // Click Name column sort + await page.locator('th[data-column-id="name"] button.gridjs-sort').click(); + await page.waitForTimeout(300); + + // Click Job column sort + await page.locator('th[data-column-id="job"] button.gridjs-sort').click(); + await page.waitForTimeout(300); + + // Click Country column sort + await page.locator('th[data-column-id="country"] button.gridjs-sort').click(); + await page.waitForTimeout(300); + }); + }); + + // NEW TEST: Pagination Buttons + test.describe('Pagination Buttons', () => { + + test('should verify Previous button is disabled initially', async ({ page }) => { + const previousButton = page.locator('.gridjs-pages button[title="Previous"]'); + await expect(previousButton).toBeVisible(); + await expect(previousButton).toBeDisabled(); + await expect(previousButton).toHaveText('Previous'); + }); + + test('should verify Page 1 button is active initially', async ({ page }) => { + const page1Button = page.locator('.gridjs-pages button[title="Page 1"]'); + await expect(page1Button).toBeVisible(); + await expect(page1Button).toHaveClass(/gridjs-currentPage/); + await expect(page1Button).toHaveText('1'); + }); + + test('should verify Page 2 button is visible and clickable', async ({ page }) => { + const page2Button = page.locator('.gridjs-pages button[title="Page 2"]'); + await expect(page2Button).toBeVisible(); + await expect(page2Button).not.toBeDisabled(); + await expect(page2Button).toHaveText('2'); + + // Click Page 2 button + await page2Button.click(); + await page.waitForTimeout(500); + + // After clicking, Page 2 should become active + await expect(page2Button).toHaveClass(/gridjs-currentPage/); + }); + + test('should verify Page 3 button is visible and clickable', async ({ page }) => { + const page3Button = page.locator('.gridjs-pages button[title="Page 3"]'); + await expect(page3Button).toBeVisible(); + await expect(page3Button).not.toBeDisabled(); + await expect(page3Button).toHaveText('3'); + + await page3Button.click(); + await page.waitForTimeout(500); + }); + + test('should verify Next button is visible and clickable', async ({ page }) => { + const nextButton = page.locator('.gridjs-pages button[title="Next"]'); + await expect(nextButton).toBeVisible(); + await expect(nextButton).not.toBeDisabled(); + await expect(nextButton).toHaveText('Next'); + + await nextButton.click(); + await page.waitForTimeout(500); + + // After clicking Next, we should be on page 2 + const page2Button = page.locator('.gridjs-pages button[title="Page 2"]'); + await expect(page2Button).toHaveClass(/gridjs-currentPage/); + }); + + test('should test pagination flow: Next -> Page 3 -> Previous', async ({ page }) => { + // Click Next button + await page.locator('.gridjs-pages button[title="Next"]').click(); + await page.waitForTimeout(300); + + // Verify we're on page 2 + await expect(page.locator('.gridjs-pages button[title="Page 2"]')).toHaveClass(/gridjs-currentPage/); + + // Click Page 3 + await page.locator('.gridjs-pages button[title="Page 3"]').click(); + await page.waitForTimeout(300); + + // Verify we're on page 3 + await expect(page.locator('.gridjs-pages button[title="Page 3"]')).toHaveClass(/gridjs-currentPage/); + + // Click Previous button + await page.locator('.gridjs-pages button[title="Previous"]').click(); + await page.waitForTimeout(300); + + // Verify we're back on page 2 + await expect(page.locator('.gridjs-pages button[title="Page 2"]')).toHaveClass(/gridjs-currentPage/); + }); + }); + + // FIXED: Footer Links - Using div.bg-gray-800 instead of footer tag + test.describe('Bottom Section Links - Grid.js Section', () => { + + test('should scroll to bottom and click Install link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + // Use the bg-gray-800 div instead of footer + const installLink = page.locator('div.bg-gray-800 a[href="/docs/install"]'); + await expect(installLink).toBeVisible(); + + await installLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/install'); + }); + + test('should scroll to bottom and click Examples link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const examplesLink = page.locator('div.bg-gray-800 a[href="/docs/examples/hello-world"]'); + await expect(examplesLink).toBeVisible(); + + await examplesLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/examples/hello-world'); + }); + + test('should scroll to bottom and click Contribute link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const contributeLink = page.locator('div.bg-gray-800 a:has-text("Contribute")'); + await expect(contributeLink).toBeVisible(); + + await contributeLink.click(); + await page.waitForTimeout(1000); + }); + }); + + test.describe('Bottom Section Links - Support Section', () => { + + test('should scroll to bottom and click Documentation link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + // More specific selector for Documentation under Support section + const docsLink = page.locator('div.bg-gray-800 h4:has-text("Support") + ul a[href="/docs"]'); + await expect(docsLink).toBeVisible(); + + await docsLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs'); + }); + + test('should scroll to bottom and click Community link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const communityLink = page.locator('div.bg-gray-800 a[href="/docs/community"]'); + await expect(communityLink).toBeVisible(); + + await communityLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/community'); + }); + + test('should scroll to bottom and click Chat link', async ({ page, context }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const chatLink = page.locator('div.bg-gray-800 a[href="https://discord.com/invite/K55BwDY"]'); + await expect(chatLink).toBeVisible(); + await chatLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('discord.com/invite/K55BwDY'); + }); + + test('should scroll to bottom and verify StackOverflow link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const stackOverflowLink = page.locator('div.bg-gray-800 a:has-text("StackOverflow")'); + await expect(stackOverflowLink).toBeVisible(); + await stackOverflowLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('stackoverflow.com'); + }); + }); + + test.describe('Bottom Section Links - Team Section', () => { + + test('should scroll to bottom and click Blog link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const blogLink = page.locator('div.bg-gray-800 a[href="/blog"]'); + await expect(blogLink).toBeVisible(); + + await blogLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/blog'); + }); + + test('should scroll to bottom and verify Contributors link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const contributorsLink = page.locator('div.bg-gray-800 a:has-text("Contributors")'); + await expect(contributorsLink).toBeVisible(); + }); + + test('should scroll to bottom and click GitHub link', async ({ page, context }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const githubLink = page.locator('div.bg-gray-800 h4:has-text("Team") + ul a[href="https://github.com/grid-js/gridjs"]'); + 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'); + + await newPage.close(); + }); + + test('should scroll to bottom and click Intro.js link', async ({ page, context }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const introLink = page.locator('div.bg-gray-800 a[href="https://introjs.com"]'); + await expect(introLink).toBeVisible(); + + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + introLink.click() + ]); + + await newPage.waitForLoadState('domcontentloaded'); + expect(newPage.url()).toContain('introjs.com'); + + await newPage.close(); + }); + }); + + test.describe('Bottom Section Links - Legal Section', () => { + + test('should scroll to bottom and click License link', async ({ page }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const licenseLink = page.locator('div.bg-gray-800 a[href="/docs/license"]'); + await expect(licenseLink).toBeVisible(); + + await licenseLink.click(); + await page.waitForLoadState('networkidle'); + + expect(page.url()).toContain('/docs/license'); + await expect(page.locator('h1:has-text("License")')).toBeVisible(); + }); + }); + + test.describe('Bottom Section Social Media Links', () => { + + test('should scroll to bottom and click Twitter link', async ({ page, context }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + const twitterLink = page.locator('div.bg-gray-800 a[href="https://twitter.com/grid_js"]'); + await expect(twitterLink).toBeVisible(); + await twitterLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('x.com/grid_js'); + }); + + test('should scroll to bottom and click GitHub social link', async ({ page, context }) => { + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + await page.waitForTimeout(500); + + // More specific selector for social media GitHub link at the bottom + const githubLink = page.locator('div.bg-gray-800 div.flex a[href="https://github.com/grid-js/gridjs"]'); + await expect(githubLink).toBeVisible(); + await githubLink.click(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain('github.com/grid-js/gridjs'); + }); + }); + + test.describe('Mobile Menu Interaction', () => { + + test('should click mobile menu toggle button', async ({ page }) => { + // Set viewport to mobile size + await page.setViewportSize({ width: 375, height: 667 }); + await page.waitForTimeout(500); + + const menuButton = page.locator('nav button[type="button"]').first(); + await expect(menuButton).toBeVisible(); + + // Click to open mobile menu + await menuButton.click(); + await page.waitForTimeout(500); + + // Verify mobile menu is displayed + const mobileMenu = page.locator('.rounded-lg.shadow-md'); + // Note: The menu visibility may vary + }); + }); + +}); From f3b700ebe7afa1ae174c432195701cc9ab05e4d6 Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 18:12:37 +0800 Subject: [PATCH 2/6] feat: add e2e testing --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 1da709a5..4359f981 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,6 @@ $ npx playwright test $ npx playwright show-report ``` -This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - ### Deployment ``` From 7e52a157a3d693b4b387d836932479f39806bed6 Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 18:13:41 +0800 Subject: [PATCH 3/6] feat: add e2e testing --- playwright.config.js | 107 +++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/playwright.config.js b/playwright.config.js index 3967e588..e3a77e19 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,5 +1,5 @@ // @ts-check -import { defineConfig, devices } from '@playwright/test'; +import { defineConfig, devices } from "@playwright/test"; /** * Read environment variables from file. @@ -13,69 +13,58 @@ import { defineConfig, devices } from '@playwright/test'; * @see https://playwright.dev/docs/test-configuration */ export default defineConfig({ - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('')`. */ - // baseURL: 'http://localhost:3000', + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, // }, - ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://localhost:3000', - // reuseExistingServer: !process.env.CI, - // }, }); - From 9e335d479ad28e152c6a7393859ce0bb7dbf7ac2 Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 21:49:29 +0800 Subject: [PATCH 4/6] feat: add e2e testing --- README.md | 6 ------ playwright.config.js | 10 ++++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4359f981..bc0b3722 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,6 @@ $ npm run start $ npx playwright test ``` -### Show the test report - -``` -$ npx playwright show-report -``` - ### Deployment ``` diff --git a/playwright.config.js b/playwright.config.js index e3a77e19..3cdf0914 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -40,6 +40,16 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + /* Test against mobile viewports. */ // { // name: 'Mobile Chrome', From 09e1d215942f904fd76d5286259487daa73cce88 Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 21:52:03 +0800 Subject: [PATCH 5/6] feat: add e2e testing --- README.md | 12 ++++++++---- tests/README.md | 0 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 tests/README.md diff --git a/README.md b/README.md index bc0b3722..108cfd0c 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,25 @@ Grid.js website is built using [Docusaurus 2](https://v2.docusaurus.io/). ### Installation ``` -$ npm install +$ yarn ``` ### Local Development ``` -$ npm run start +$ yarn start ``` -### Run the tests +This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. + +### Build ``` -$ npx playwright test +$ yarn build ``` +This command generates static content into the `build` directory and can be served using any static contents hosting service. + ### Deployment ``` diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..e69de29b From cd537bfcbc83d7e88276efd75a4e70467564eaff Mon Sep 17 00:00:00 2001 From: Han Date: Wed, 17 Dec 2025 21:53:23 +0800 Subject: [PATCH 6/6] feat: add README.md in tests/ --- tests/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/README.md b/tests/README.md index e69de29b..7395d89a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -0,0 +1,17 @@ +### Installation + +``` +$ npm install +``` + +### Local Development + +``` +$ npm run start +``` + +### Run the tests + +``` +$ npx playwright test +```