From 70344c9c48a263e7bdea5c058dbd9257a167ed57 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Tue, 13 Jan 2026 17:56:52 -0500 Subject: [PATCH] Port to TypeScript & Playwright --- .github/workflows/build.yml | 14 + .gitignore | 2 + README.md | 120 +- api.md | 339 - build.js => build.mjs | 30 +- declarationsconfig.json | 17 - docs/docusaurus.config.js | 7 +- examples/2d_array.md | 102 - examples/2d_array/2d_array.js | 56 + examples/2d_array/index.html | 12 + examples/3d_array.html | 79 - examples/benchmark.html | 5 +- .../canvas_data_model/canvas_data_model.css | 67 + .../canvas_data_model.js} | 113 +- examples/canvas_data_model/index.html | 16 + examples/file_browser.md | 327 - examples/file_browser/file_browser.css | 25 + examples/file_browser/file_browser.js | 225 + examples/file_browser/index.html | 12 + examples/literally.config.js | 12 - examples/minesweeper.md | 364 -- examples/minesweeper/index.html | 12 + examples/minesweeper/minesweeper.css | 92 + examples/minesweeper/minesweeper.js | 200 + examples/react.md | 56 - examples/react/index.html | 12 + examples/react/react.css | 3 + examples/react/react.js | 29 + examples/spreadsheet/index.html | 12 + examples/spreadsheet/spreadsheet.css | 14 + .../spreadsheet.js} | 238 +- examples/two_billion_rows.md | 131 - examples/two_billion_rows/index.html | 16 + .../two_billion_rows/two_billion_rows.css | 9 + examples/two_billion_rows/two_billion_rows.js | 68 + examples/web_worker.html | 5 +- features/__template__.md | 85 - features/area_clipboard.js | 306 + features/area_clipboard.md | 415 -- features/area_mouse_selection.js | 188 + features/area_mouse_selection.md | 246 - ...selection.md => column_mouse_selection.js} | 265 +- features/fixed_column_widths.js | 134 + features/fixed_column_widths.md | 185 - features/literally.config.js | 19 - features/row_column_area_selection.md | 121 - features/row_mouse_selection.js | 410 ++ features/row_mouse_selection.md | 519 -- features/row_stripes.js | 63 + features/row_stripes.md | 123 - index.d.ts | 490 -- jest.config.js | 17 - package.json | 37 +- ...uppeteer.config.js => playwright.config.js | 31 +- pnpm-lock.yaml | 5760 ++--------------- scripts/babel-plugin-html-template.js | 34 - scripts/sync_gist.js | 22 - src/js/index.js | 595 -- src/js/table.js | 443 -- src/{js/constants.js => ts/constants.ts} | 0 src/{js/events.js => ts/events.ts} | 170 +- src/ts/regular-table.ts | 379 ++ .../scroll_panel.js => ts/scroll_panel.ts} | 281 +- src/ts/table.ts | 728 +++ src/{js/tbody.js => ts/tbody.ts} | 175 +- src/{js/thead.js => ts/thead.ts} | 121 +- src/ts/types.ts | 360 ++ src/{js/utils.js => ts/utils.ts} | 60 +- src/{js/view_model.js => ts/view_model.ts} | 73 +- test/examples/file_browser.test.js | 96 - test/examples/react.test.js | 42 - test/examples/spreadsheet.test.js | 400 -- test/examples/two_billion_rows.test.js | 221 - test/examples/web_worker.test.js | 71 - test/features/api.test.js | 48 - test/features/area_clipboard.test.js | 348 - test/features/area_mouse_selection.test.js | 180 - .../selecting_column_headers.test.js | 123 - .../selecting_one_column.test.js | 69 - .../selecting_one_column_range.test.js | 65 - .../selecting_two_columns.test.js | 84 - .../splitting_one_column_range.test.js | 87 - test/features/fixed_column_widths.test.js | 119 - .../area_selection.test.js | 43 - .../column_selection.test.js | 65 - .../row_selection.test.js | 63 - .../selecting_grouped_row_headers.test.js | 131 - .../selecting_one_row.test.js | 58 - .../selecting_one_row_range.test.js | 57 - .../selecting_row_headers.test.js | 105 - .../selecting_two_rows.test.js | 75 - .../splitting_one_row_range.test.js | 68 - test/features/row_stripes.test.js | 101 - test/features/scrollTo.test.js | 86 - test/features/scrolling.test.js | 102 - .../2_row_2_column_headers.html | 4 +- .../2d_array.spec.js | 100 +- tests/addStyleListener.spec.js | 531 ++ {test/features => tests}/api.html | 4 +- tests/columnResize.spec.js | 683 ++ .../getMeta.test.js => tests/getMeta.spec.js | 156 +- tests/react.spec.js | 208 + tests/scrollToCell.spec.js | 365 ++ tests/scrollingDOM.spec.js | 754 +++ tests/setDataListener.spec.js | 216 + tests/sub_cell_scrolling.spec.js | 437 ++ tsconfig.json | 17 + typedoc.json | 15 + 108 files changed, 8146 insertions(+), 13947 deletions(-) delete mode 100644 api.md rename build.js => build.mjs (83%) delete mode 100644 declarationsconfig.json delete mode 100644 examples/2d_array.md create mode 100644 examples/2d_array/2d_array.js create mode 100644 examples/2d_array/index.html delete mode 100644 examples/3d_array.html create mode 100644 examples/canvas_data_model/canvas_data_model.css rename examples/{canvas_data_model.md => canvas_data_model/canvas_data_model.js} (54%) create mode 100644 examples/canvas_data_model/index.html delete mode 100644 examples/file_browser.md create mode 100644 examples/file_browser/file_browser.css create mode 100644 examples/file_browser/file_browser.js create mode 100644 examples/file_browser/index.html delete mode 100644 examples/literally.config.js delete mode 100644 examples/minesweeper.md create mode 100644 examples/minesweeper/index.html create mode 100644 examples/minesweeper/minesweeper.css create mode 100644 examples/minesweeper/minesweeper.js delete mode 100644 examples/react.md create mode 100644 examples/react/index.html create mode 100644 examples/react/react.css create mode 100644 examples/react/react.js create mode 100644 examples/spreadsheet/index.html create mode 100644 examples/spreadsheet/spreadsheet.css rename examples/{spreadsheet.md => spreadsheet/spreadsheet.js} (58%) delete mode 100644 examples/two_billion_rows.md create mode 100644 examples/two_billion_rows/index.html create mode 100644 examples/two_billion_rows/two_billion_rows.css create mode 100644 examples/two_billion_rows/two_billion_rows.js delete mode 100644 features/__template__.md create mode 100644 features/area_clipboard.js delete mode 100644 features/area_clipboard.md create mode 100644 features/area_mouse_selection.js delete mode 100644 features/area_mouse_selection.md rename features/{column_mouse_selection.md => column_mouse_selection.js} (59%) create mode 100644 features/fixed_column_widths.js delete mode 100644 features/fixed_column_widths.md delete mode 100644 features/literally.config.js delete mode 100644 features/row_column_area_selection.md create mode 100644 features/row_mouse_selection.js delete mode 100644 features/row_mouse_selection.md create mode 100644 features/row_stripes.js delete mode 100644 features/row_stripes.md delete mode 100644 index.d.ts delete mode 100644 jest.config.js rename jest-puppeteer.config.js => playwright.config.js (77%) delete mode 100644 scripts/babel-plugin-html-template.js delete mode 100644 src/js/index.js delete mode 100644 src/js/table.js rename src/{js/constants.js => ts/constants.ts} (100%) rename src/{js/events.js => ts/events.ts} (73%) create mode 100644 src/ts/regular-table.ts rename src/{js/scroll_panel.js => ts/scroll_panel.ts} (73%) create mode 100644 src/ts/table.ts rename src/{js/tbody.js => ts/tbody.ts} (54%) rename src/{js/thead.js => ts/thead.ts} (70%) create mode 100644 src/ts/types.ts rename src/{js/utils.js => ts/utils.ts} (70%) rename src/{js/view_model.js => ts/view_model.ts} (72%) delete mode 100644 test/examples/file_browser.test.js delete mode 100644 test/examples/react.test.js delete mode 100644 test/examples/spreadsheet.test.js delete mode 100644 test/examples/two_billion_rows.test.js delete mode 100644 test/examples/web_worker.test.js delete mode 100644 test/features/api.test.js delete mode 100644 test/features/area_clipboard.test.js delete mode 100644 test/features/area_mouse_selection.test.js delete mode 100644 test/features/column_mouse_selection/selecting_column_headers.test.js delete mode 100644 test/features/column_mouse_selection/selecting_one_column.test.js delete mode 100644 test/features/column_mouse_selection/selecting_one_column_range.test.js delete mode 100644 test/features/column_mouse_selection/selecting_two_columns.test.js delete mode 100644 test/features/column_mouse_selection/splitting_one_column_range.test.js delete mode 100644 test/features/fixed_column_widths.test.js delete mode 100644 test/features/row_column_area_selection/area_selection.test.js delete mode 100644 test/features/row_column_area_selection/column_selection.test.js delete mode 100644 test/features/row_column_area_selection/row_selection.test.js delete mode 100644 test/features/row_mouse_selection/selecting_grouped_row_headers.test.js delete mode 100644 test/features/row_mouse_selection/selecting_one_row.test.js delete mode 100644 test/features/row_mouse_selection/selecting_one_row_range.test.js delete mode 100644 test/features/row_mouse_selection/selecting_row_headers.test.js delete mode 100644 test/features/row_mouse_selection/selecting_two_rows.test.js delete mode 100644 test/features/row_mouse_selection/splitting_one_row_range.test.js delete mode 100644 test/features/row_stripes.test.js delete mode 100644 test/features/scrollTo.test.js delete mode 100644 test/features/scrolling.test.js rename {test/features => tests}/2_row_2_column_headers.html (97%) rename test/examples/2d_array.test.js => tests/2d_array.spec.js (50%) create mode 100644 tests/addStyleListener.spec.js rename {test/features => tests}/api.html (97%) create mode 100644 tests/columnResize.spec.js rename test/features/getMeta.test.js => tests/getMeta.spec.js (61%) create mode 100644 tests/react.spec.js create mode 100644 tests/scrollToCell.spec.js create mode 100644 tests/scrollingDOM.spec.js create mode 100644 tests/setDataListener.spec.js create mode 100644 tests/sub_cell_scrolling.spec.js create mode 100644 tsconfig.json create mode 100644 typedoc.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bad6517f..f5185e51 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,14 @@ +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +# ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ +# ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ +# ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ +# ┃ * of the Regular Table library, distributed under the terms of the * ┃ +# ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + name: Build Status on: @@ -40,6 +51,9 @@ jobs: run: | pnpm run build + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps + - name: Test run: | pnpm run test diff --git a/.gitignore b/.gitignore index 339d8835..aa7677ad 100644 --- a/.gitignore +++ b/.gitignore @@ -197,3 +197,5 @@ python_junit.xml docs/.docusaurus docs/static/blocks +test-results +playwright-report diff --git a/README.md b/README.md index 6d73a1f4..93614864 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,9 @@

-FINOS active badge -NPM Version -NPM Version -Build Status +Build Status +NPM Version +Bundlephobia (Minified)

# @@ -19,12 +18,12 @@ queried from a natively `async` virtual data model, making `regular-table` ideal for enormous or remote data sets. Use it to build Data Grids, Spreadsheets, Pivot Tables, File Trees, or anytime you need: -- Just a regular ``. -- Virtually rendered for high-performance. -- `async` data model handles slow, remote, enormous, and/or distributed - backends. -- Easy to style, works with any regular CSS for `
`. -- Small bundle size, no dependencies. +- Just a regular `
`. +- Virtually rendered for high-performance. +- `async` data model handles slow, remote, enormous, and/or distributed + backends. +- Easy to style, works with any regular CSS for `
`. +- Small bundle size, no dependencies. ## Examples @@ -57,13 +56,13 @@ Pivot Tables, File Trees, or anytime you need:
-- [2d_array.md](examples/2d_array.md) -- [canvas_data_model.md](examples/canvas_data_model.md) -- [file_browser.md](examples/file_browser.md) -- [minesweeper.md](examples/minesweeper.md) -- [react.md](examples/react.md) -- [spreadsheet.md](examples/spreadsheet.md) -- [two_billion_rows.md](examples/two_billion_rows.md) +- [2d_array.md](examples/2d_array/2d_array.js) +- [canvas_data_model.md](examples/canvas_data_model/canvas_data_model.js) +- [file_browser.md](examples/file_browser/file_browser.js) +- [minesweeper.md](examples/minesweeper/minesweeper.js) +- [react.md](examples/react/react.js) +- [spreadsheet.md](examples/spreadsheet/spreadsheet.js) +- [two_billion_rows.md](examples/two_billion_rows/two_billion_rows.js) ## Documentation @@ -74,38 +73,37 @@ documented [examples](https://github.com/finos/regular-table/tree/master/examples) are also available. -- QuickStart - - - [Installation](#installation) - - [`` Custom Element](#regular-table-custom-element) - - [`.setDataListener()` Virtual Data Model](#setdatalistener-virtual-data-model) - - [Column and Row Headers](#column-and-row-headers) - - [Hierarchial/Group Headers](#hierarchialgroup-headers) - - [`async` Data Models](#async-data-models) - - [`.addStyleListener()` and `getMeta()` Styling](#addstylelistener-and-getmeta-styling) - - [`.invalidate()`](#invalidate) - - [`.addEventListener()` Interaction](#addeventlistener-interaction) - - [Scrolling](#scrolling) - - [Pivots, Filters, Sorts, and Column Expressions with `perspective`](#pivots-filters-sorts-and-column-expressions-with-perspective) - - [Development](#development) - -- [API Docs](https://github.com/finos/regular-table/blob/master/api.md) - -- Annotated Examples - - [2d_array.md](examples/2d_array.md) - - [canvas_data_model.md](examples/canvas_data_model.md) - - [file_browser.md](examples/file_browser.md) - - [minesweeper.md](examples/minesweeper.md) - - [react.md](examples/react.md) - - [spreadsheet.md](examples/spreadsheet.md) - - [two_billion_rows.md](examples/two_billion_rows.md) +- QuickStart + - [Installation](#installation) + - [`` Custom Element](#regular-table-custom-element) + - [`.setDataListener()` Virtual Data Model](#setdatalistener-virtual-data-model) + - [Column and Row Headers](#column-and-row-headers) + - [Hierarchial/Group Headers](#hierarchialgroup-headers) + - [`async` Data Models](#async-data-models) + - [`.addStyleListener()` and `getMeta()` Styling](#addstylelistener-and-getmeta-styling) + - [`.invalidate()`](#invalidate) + - [`.addEventListener()` Interaction](#addeventlistener-interaction) + - [Scrolling](#scrolling) + - [Pivots, Filters, Sorts, and Column Expressions with `perspective`](#pivots-filters-sorts-and-column-expressions-with-perspective) + - [Development](#development) + +- [API Docs](https://github.com/finos/regular-table/blob/master/api.md) + +- Annotated Examples + - [2d_array.md](examples/2d_array/2d_array.js) + - [canvas_data_model.md](examples/canvas_data_model/canvas_data_model.js) + - [file_browser.md](examples/file_browser/file_browser.js) + - [minesweeper.md](examples/minesweeper/minesweeper.js) + - [react.md](examples/react/react.js) + - [spreadsheet.md](examples/spreadsheet/spreadsheet.js) + - [two_billion_rows.md](examples/two_billion_rows/two_billion_rows.js) ## Installation Include via a CDN like [JSDelivr](https://cdn.jsdelivr.net/npm/regular-table): ```html - + ` along the non-virtual axis(es), and may cause rendering performance degradation. -- "both" (default) virtualizes scrolling on both axes. -- "vertical" only virtualizes vertical (y) scrolling. -- "horizontal" only virtualizes horizontal (x) scrolling. -- "none" disable all scroll virtualization. +- "both" (default) virtualizes scrolling on both axes. +- "vertical" only virtualizes vertical (y) scrolling. +- "horizontal" only virtualizes horizontal (x) scrolling. +- "none" disable all scroll virtualization. ```javascript table.setDataListener(listener, { virtual_mode: "vertical" }); @@ -383,14 +381,14 @@ field will accompany the metadata records returned by `regular-table`'s Additional rendering options which can be set on the object returned by a `setDataListener` callback include: -- `column_header_merge_depth: number` configures the number of rows to include - from `colspan` merging. This defaults to `header_length - 1`. -- `row_height: number` configures the pixel height of a row for virtual - scrolling calculation. This is typically auto-detected from the DOM, but can - be overridden if needed. -- `merge_headers: "column" | "row" | "both" | "none"` configures whether - equivalent, contiguous `
` elements are merged via `rowspan` or `colspan` - for `"row"` and `"column"` respectively (defaults to `"both"`). +- `column_header_merge_depth: number` configures the number of rows to include + from `colspan` merging. This defaults to `header_length - 1`. +- `row_height: number` configures the pixel height of a row for virtual + scrolling calculation. This is typically auto-detected from the DOM, but can + be overridden if needed. +- `merge_headers: "column" | "row" | "both" | "none"` configures whether + equivalent, contiguous `` elements are merged via `rowspan` or `colspan` + for `"row"` and `"column"` respectively (defaults to `"both"`). ### `async` Data Models @@ -445,11 +443,11 @@ However, CSS alone cannot select on properties of your _data_ - if you scroll this example, the 2nd row will always be the striped one. Some other data-reliant style examples include: -- Styling a specific column in the virtual data set, as `` may represent a - different column based on horizontal scroll position. -- Styling cells by value, +/-, heatmaps, categories, etc. -- Styling cells based on data within-or-outside of the virtual viewport, - grouping depth, grouping categories, etc. +- Styling a specific column in the virtual data set, as `` may represent a + different column based on horizontal scroll position. +- Styling cells by value, +/-, heatmaps, categories, etc. +- Styling cells based on data within-or-outside of the virtual viewport, + grouping depth, grouping categories, etc. To make CSS that is virtual-data-model-aware, you'll need to use `addStyleListener()`, which invokes a callback whenever the `` is @@ -605,4 +603,4 @@ The Regular Table project achieves the ## License This software is licensed under the Apache 2.0 license. See the -[LICENSE](LICENSE) and [AUTHORS](AUTHORS) files for details. +[LICENSE](LICENSE) file for details. diff --git a/api.md b/api.md deleted file mode 100644 index 595e38ea..00000000 --- a/api.md +++ /dev/null @@ -1,339 +0,0 @@ -## Classes - -
-
RegularTableElementHTMLElement
-

The <regular-table> custom element.

-

This module has no exports, but importing it has a side effect: the -RegularTableElement class is registered as a custom element, after which -it can be used as a standard DOM element.

-

The documentation in this module defines the instance structure of a -<regular-table> DOM object instantiated typically, through HTML or any -relevent DOM method e.g. document.createElement("perspective-viewer") or -document.getElementsByTagName("perspective-viewer").

-
-
- -## Typedefs - -
-
Performance : object
-

An object with performance statistics about calls to -draw() from some time interval (captured in milliseconds by the -elapsed proprty).

-
-
MetaData : object
-

An object describing virtual rendering metadata about an -HTMLTableCellElement, use this object to map rendered <th> or <td> -elements back to your data, row_headers or column_headers within -listener functions for addStyleListener() and addEventListener().

-
-
DataResponse : object
-

The DataResponse object describes a rectangular region of a virtual -data set, and some associated metadata. <regular-table> will use this -object to render the <table>, though it may make multiple requests for -different regions to achieve a compelte render as it must estimate -certain dimensions. You must construct a DataResponse object to -implement a DataListener.

-
-
DataListenerPromise.<DataResponse>
-

The DataListener is similar to a normal event listener function. -Unlike a normal event listener, it takes regular arguments (not an -Event); and returns a Promise for a DataResponse object for this -region (as opposed to returning void as a standard event listener).

-
-
- - - -## RegularTableElement ⇐ HTMLElement -The `` custom element. - -This module has no exports, but importing it has a side effect: the -`RegularTableElement` class is registered as a custom element, after which -it can be used as a standard DOM element. - -The documentation in this module defines the instance structure of a -`` DOM object instantiated typically, through HTML or any -relevent DOM method e.g. `document.createElement("perspective-viewer")` or -`document.getElementsByTagName("perspective-viewer")`. - -**Kind**: global class -**Extends**: HTMLElement -**Access**: public - -* [RegularTableElement](#RegularTableElement) ⇐ HTMLElement - * [.addStyleListener(styleListener)](#RegularTableElement+addStyleListener) ⇒ number - * [.getMeta(element)](#RegularTableElement+getMeta) ⇒ [MetaData](#MetaData) - * [.getDrawFPS()](#RegularTableElement+getDrawFPS) ⇒ [Performance](#Performance) - * [.scrollToCell(x, y, ncols, nrows)](#RegularTableElement+scrollToCell) - * [.setDataListener(dataListener)](#RegularTableElement+setDataListener) - - -* * * - - - -### regularTableElement.addStyleListener(styleListener) ⇒ number -Adds a style listener callback. The style listeners are called -whenever the
is re-rendered, such as through API invocations -of draw() and user-initiated events such as scrolling. Within this -optionally async callback, you can select
, , etc. elements -via regular DOM API methods like querySelectorAll(). - -**Kind**: instance method of [RegularTableElement](#RegularTableElement) -**Returns**: number - The index of the added listener. -**Access**: public - -| Param | Type | Description | -| --- | --- | --- | -| styleListener | function | A (possibly async) function that styles the inner . | - -**Example** -```js -table.addStyleListener(() => { - for (const td of table.querySelectorAll("td")) { - td.setAttribute("contenteditable", true); - } -}); -``` - -* * * - - - -### regularTableElement.getMeta(element) ⇒ [MetaData](#MetaData) -Returns the `MetaData` object associated with a `
` or ``. When -your `StyleListener` is invoked, use this method to look up additional -`MetaData` about any `HTMLTableCellElement` in the rendered ``. - -**Kind**: instance method of [RegularTableElement](#RegularTableElement) -**Returns**: [MetaData](#MetaData) - The metadata associated with the element. -**Access**: public - -| Param | Type | Description | -| --- | --- | --- | -| element | HTMLTableCellElement \| [Partial.<MetaData>](#MetaData) | The child element of this `` for which to look up metadata, or a coordinates-like object to refer to metadata by logical position. | - -**Example** -```js -const elems = document.querySelector("td:last-child td:last_child"); -const metadata = table.getMeta(elems); -console.log(`Viewport corner is ${metadata.x}, ${metadata.y}`); -``` -**Example** -```js -const header = table.getMeta({row_header_x: 1, y: 3}).row_header; -``` - -* * * - - - -### regularTableElement.getDrawFPS() ⇒ [Performance](#Performance) -Get performance statistics about this ``. Calling this -method resets the internal state, which makes it convenient to measure -performance at regular intervals (see example). - -**Kind**: instance method of [RegularTableElement](#RegularTableElement) -**Returns**: [Performance](#Performance) - Performance data aggregated since the last -call to `getDrawFPS()`. -**Access**: public -**Example** -```js -const table = document.getElementById("my_regular_table"); -setInterval(() => { - const {real_fps} = table.getDrawFPS(); - console.log(`Measured ${fps} fps`) -}); -``` - -* * * - - - -### regularTableElement.scrollToCell(x, y, ncols, nrows) -Call this method to set the `scrollLeft` and `scrollTop` for this -`` by calculating the position of this `scrollLeft` -and `scrollTop` relative to the underlying widths of its columns -and heights of its rows. - -**Kind**: instance method of [RegularTableElement](#RegularTableElement) -**Access**: public - -| Param | Type | Description | -| --- | --- | --- | -| x | number | The left most `x` index column to scroll into view. | -| y | number | The top most `y` index row to scroll into view. | -| ncols | number | Total number of columns in the data model. | -| nrows | number | Total number of rows in the data model. | - -**Example** -```js -table.scrollToCell(1, 3, 10, 30); -``` - -* * * - - - -### regularTableElement.setDataListener(dataListener) -Call this method to set `DataListener` for this ``, -which will be called whenever a new data slice is needed to render. -Calls to `draw()` will fail if no `DataListener` has been set - -**Kind**: instance method of [RegularTableElement](#RegularTableElement) -**Access**: public - -| Param | Type | Description | -| --- | --- | --- | -| dataListener | [DataListener](#DataListener) | `dataListener` is called by to request a rectangular section of data for a virtual viewport, (x0, y0, x1, y1), and returns a `DataReponse` object. | - -**Example** -```js -table.setDataListener((x0, y0, x1, y1) => { - return { - num_rows: num_rows = DATA[0].length, - num_columns: DATA.length, - data: DATA.slice(x0, x1).map(col => col.slice(y0, y1)) - }; -}) -``` - -* * * - - - -## Performance : object -An object with performance statistics about calls to -`draw()` from some time interval (captured in milliseconds by the -`elapsed` proprty). - -**Kind**: global typedef -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| avg | number | Avergage milliseconds per call. | -| real_fps | number | `num_frames` / `elapsed` | -| virtual_fps | number | `elapsed` / `avg` | -| num_frames | number | Number of frames rendered. | -| elapsed | number | Number of milliseconds since last call to `getDrawFPS()`. | - - -* * * - - - -## MetaData : object -An object describing virtual rendering metadata about an -`HTMLTableCellElement`, use this object to map rendered ` view model. This model accumulates state in the form of @@ -19,91 +25,104 @@ import { ViewModel } from "./view_model"; * @class RegularHeaderViewModel */ export class RegularHeaderViewModel extends ViewModel { - constructor(...args) { - super(...args); + private _group_header_cache: [CellMetadata, HTMLTableCellElement, number][]; + private _offset_cache: number[]; + + constructor( + column_sizes: ColumnSizes, + container: HTMLElement, + table: HTMLElement, + ) { + super(column_sizes, container, table); this._group_header_cache = []; this._offset_cache = []; } - _draw_group_th(offset_cache, d, column) { + _draw_group_th( + offset_cache: number[], + d: number, + column: unknown, + ): HTMLTableCellElement { const th = this._get_cell("TH", d, offset_cache[d] || 0); offset_cache[d] += 1; th.removeAttribute("colspan"); - th.style.minWidth = "0"; - th.textContent = ""; if (column instanceof HTMLElement) { th.appendChild(column); } else { - const span = this._span_factory.get("span"); - span.textContent = column; + const span = this._span_factory.get(); + span.textContent = String(column ?? ""); th.appendChild(span); } - const resizeSpan = this._span_factory.get("span"); + const resizeSpan = this._span_factory.get(); resizeSpan.className = "rt-column-resize"; th.appendChild(resizeSpan); - return th; } - _draw_group(column, column_name, th) { + _draw_group( + column: CellScalar[], + column_name: unknown, + th: HTMLTableCellElement, + ): CellMetadata { const metadata = this._get_or_create_metadata(th); metadata.column_header = column; metadata.value = column_name; - metadata.value = column_name; return metadata; } - _draw_th(column, column_name, th, cidx, size_key) { + _draw_th( + column: CellScalar[], + column_name: unknown, + th: HTMLTableCellElement, + size_key: number | number[], + ): CellMetadata { const metadata = this._get_or_create_metadata(th); metadata.column_header = column; metadata.value = column_name; - metadata.size_key = size_key.length ? size_key[0] : size_key; // FIXME - - if (!(size_key.length > 1)) { + metadata.size_key = Array.isArray(size_key) ? size_key[0] : size_key; + if (!Array.isArray(size_key) || size_key.length <= 1) { const override_width = - this._column_sizes.override[metadata.size_key]; - const auto_width = this._column_sizes.auto[metadata.size_key]; + this._column_sizes.override[metadata.size_key || 0]; + const auto_width = + this._column_sizes.auto[metadata.size_key || 0] || 0; + + // Handle clipping class for overridden columns if (override_width) { th.classList.toggle( "rt-cell-clip", auto_width > override_width, ); - th.style.minWidth = override_width + "px"; - th.style.maxWidth = override_width + "px"; - } else if (auto_width) { - th.classList.remove("rt-cell-clip"); - th.style.maxWidth = ""; - th.style.minWidth = auto_width + "px"; } else { - th.style.maxWidth = ""; - th.style.maxWidth = ""; + th.classList.remove("rt-cell-clip"); } } return metadata; } - get_column_header(cidx) { + get_column_header(cidx: number): HTMLTableCellElement { return this._get_cell("TH", this.num_rows() - 1, cidx); } draw( - alias, - parts, - colspan, - x, - size_key, - x0, - _virtual_x, - column_header_merge_depth, - merge_headers, - ) { + alias: CellScalar[], + parts: CellScalar[], + colspan: number | undefined, + x: number | undefined, + size_key: number | number[], + x0: number, + _virtual_x: number, + column_header_merge_depth: number | undefined, + merge_headers: boolean, + ): HeaderDrawResult | undefined { const header_levels = parts?.length; //config.column_pivots.length + 1; if (header_levels === 0) return; - let th, metadata, column_name; - let output = undefined; + let th: HTMLTableCellElement | undefined; + let metadata: CellMetadata | undefined; + let column_name: unknown; + let output: HeaderDrawResult | undefined = undefined; column_header_merge_depth = typeof column_header_merge_depth === "undefined" ? header_levels - 1 @@ -120,9 +139,13 @@ export class RegularHeaderViewModel extends ViewModel { th = this._group_header_cache[d][1]; this._group_header_cache[d][2] += 1; if (colspan === 1) { - this._group_header_cache[d][0].row_header_x = size_key; + this._group_header_cache[d][0].row_header_x = + Array.isArray(size_key) ? size_key[0] : size_key; } - th.setAttribute("colspan", this._group_header_cache[d][2]); + th.setAttribute( + "colspan", + String(this._group_header_cache[d][2]), + ); } else { th = this._draw_group_th( this._offset_cache, @@ -139,12 +162,12 @@ export class RegularHeaderViewModel extends ViewModel { // header has the same metadata coordinates of its rightmost // column. metadata = this._draw_th( - alias || parts, + alias.length > 0 ? alias : parts, column_name, th, - x, size_key, ); + if (typeof output === "undefined") { output = { th, metadata }; } @@ -152,6 +175,7 @@ export class RegularHeaderViewModel extends ViewModel { for (const [group_meta] of this._group_header_cache) { group_meta.size_key = metadata.size_key; } + th.removeAttribute("colspan"); } @@ -159,28 +183,31 @@ export class RegularHeaderViewModel extends ViewModel { "rt-autosize", d === column_header_merge_depth, ); + th.classList.toggle("rt-group-corner", x === undefined); if (metadata) { metadata.x = typeof x === "undefined" ? x : Math.floor(x); metadata.column_header_y = d; metadata.x0 = Math.floor(x0); - metadata._virtual_x = _virtual_x; + metadata.virtual_x = _virtual_x; if (colspan === 1) { - metadata.row_header_x = size_key; + metadata.row_header_x = Array.isArray(size_key) + ? size_key[0] + : size_key; } } } this._clean_rows(this._offset_cache.length); - output = output || { th, metadata }; + output = output || { th: th!, metadata: metadata! }; return output; } - clean() { + clean(): void { this._clean_columns(this._offset_cache); } - reset_header_cache() { + reset_header_cache(): void { this._offset_cache = []; this._group_header_cache = []; } diff --git a/src/ts/types.ts b/src/ts/types.ts new file mode 100644 index 00000000..b0a2d7d6 --- /dev/null +++ b/src/ts/types.ts @@ -0,0 +1,360 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ +// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ +// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ +// ┃ * of the Regular Table library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import type { RegularTableElement } from "./regular-table.ts"; + +/** + * The `DataListener` is similar to a normal event listener function. + * Unlike a normal event listener, it takes regular arguments (not an + * `Event`); and returns a `Promise` for a `DataResponse` object for this + * region (as opposed to returning `void` as a standard event listener). + * + * @param {number} x0 - The origin `x` index (column). + * @param {number} y0 - The origin `y` index (row). + * @param {number} x1 - The corner `x` index (column). + * @param {number} y1 - The corner `y` index (row). + * @returns {Promise} The resulting `DataResponse`. Make sure + * to `resolve` or `reject` the `Promise`, or your `` will + * never render! + */ +export type DataListener = ( + x0: number, + y0: number, + x1: number, + y1: number, +) => Promise; + +/** + * An object describing virtual rendering metadata about an + * `HTMLTableCellElement`, use this object to map rendered `
` or `` -elements back to your `data`, `row_headers` or `column_headers` within -listener functions for `addStyleListener()` and `addEventListener()`. - -**Kind**: global typedef -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| [x] | number | The `x` index in your virtual data model. property is only generated for ``, `` from `row_headers`. | -| [y] | number | The `y` index in your virtual data model. property is only generated for ``, `` from `row_headers`. | -| [x0] | number | The `x` index of the viewport origin in your data model, e.g. what was passed to `x0` when your `dataListener` was invoked. | -| [y0] | number | The `y` index of the viewport origin in your data model, e.g. what was passed to `y0` when your `dataListener` was invoked. | -| [x1] | number | The `x` index of the viewport corner in your data model, e.g. what was passed to `x1` when your `dataListener` was invoked. | -| [y1] | number | The `y` index of the viewport origin in your data model, e.g. what was passed to `y1` when your `dataListener` was invoked. | -| [dx] | number | The `x` index in `DataResponse.data`, this property is only generated for ``, and `` from `column_headers`. | -| [dy] | number | The `y` index in `DataResponse.data`, this property is only generated for ``, `` from `row_headers`. | -| [column_header_y] | number | The `y` index in `DataResponse.column_headers[x]`, this property is only generated for `` from `column_headers`. | -| [column_header_x] | number | The `x` index in `DataResponse.row_headers[y]`, this property is only generated for `` from `row_headers`. | -| size_key | number | The unique index of this column in a full ``, which is `x` + (Total Row Header Columns). | -| [row_header] | Array.<object> | The `Array` for this `y` in `DataResponse.row_headers`, if it was provided. | -| [column_header] | Array.<object> | The `Array` for this `x` in `DataResponse.column_headers`, if it was provided. | - -**Example** -```js -MetaData (x = 0, column_header_y = 0) - *-------------------------------------+ - | | - | | - +-------------------------------------+ -(row_header_x = 0, y = 0) (x = 0, y = 0) -*------------------------+ *-------------------------------------+ -| | | | -| | | (x0, y0) | -| | | *---------------* | -| | | | | | -| | | | * (x, y) | | -| | | | | | -| | | *---------------* (x1, y1) | -| | | | -+------------------------+ +-------------------------------------+ -``` - -* * * - - - -## DataResponse : object -The `DataResponse` object describes a rectangular region of a virtual -data set, and some associated metadata. `` will use this -object to render the `
`, though it may make multiple requests for -different regions to achieve a compelte render as it must estimate -certain dimensions. You must construct a `DataResponse` object to -implement a `DataListener`. - -**Kind**: global typedef -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| [column_headers] | Array.<Array.<object>> | A two dimensional `Array` of column group headers, in specificity order. No `` will be generated if this property is not provided. | -| [row_headers] | Array.<Array.<object>> | A two dimensional `Array` of row group headers, in specificity order. No `` will be generated if this property is not provided. | -| data | Array.<Array.<object>> | A two dimensional `Array` representing a rectangular section of the underlying data set from (x0, y0) to (x1, y1), arranged in columnar fashion such that `data[x][y]` returns the `y`th row of the `x`th column of the slice. | -| num_rows | number | Total number of rows in the underlying data set. | -| num_columns | number | Total number of columns in the underlying data set. | - -**Example** -```js -{ - "num_rows": 26, - "num_columns": 3, - "data": [ - [0, 1], - ["A", "B"] - ], - "row_headers": [ - ["Rowgroup 1", "Row 1"], - ["Rowgroup 1", "Row 2"] - ], - "column_headers": [ - ["Colgroup 1", "Column 1"], - ["Colgroup 1", "Column 2"] - ] -} -``` - -* * * - - - -## DataListener ⇒ [Promise.<DataResponse>](#DataResponse) -The `DataListener` is similar to a normal event listener function. -Unlike a normal event listener, it takes regular arguments (not an -`Event`); and returns a `Promise` for a `DataResponse` object for this -region (as opposed to returning `void` as a standard event listener). - -**Kind**: global typedef -**Returns**: [Promise.<DataResponse>](#DataResponse) - The resulting `DataResponse`. Make sure -to `resolve` or `reject` the `Promise`, or your `` will -never render! - -| Param | Type | Description | -| --- | --- | --- | -| x0 | number | The origin `x` index (column). | -| y0 | number | The origin `y` index (row). | -| x1 | number | The corner `x` index (column). | -| y1 | number | The corner `y` index (row). | - - -* * * - diff --git a/build.js b/build.mjs similarity index 83% rename from build.js rename to build.mjs index 9524e141..d3a08c43 100644 --- a/build.js +++ b/build.mjs @@ -9,24 +9,28 @@ // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -const esbuild = require("esbuild"); -const { BuildCss } = require("@prospective.co/procss/target/cjs/procss.js"); -const fs = require("fs"); -const path_mod = require("path"); +import esbuild from "esbuild"; +import { BuildCss } from "@prospective.co/procss/target/cjs/procss.js"; +import * as fs from "node:fs"; +import * as path_mod from "node:path"; +import { execSync } from "node:child_process"; const BUILD = [ { - entryPoints: ["src/js/index.js"], + entryPoints: ["src/ts/regular-table.ts"], format: "esm", loader: { ".css": "text", ".html": "text", }, outfile: "dist/esm/regular-table.js", - target: ["es2021"], + target: ["ESNext"], bundle: true, minify: !process.env.PSP_DEBUG, - minify: true, + minifySyntax: true, + minifyIdentifiers: true, + minifyWhitespace: true, + mangleProps: /^[_#]/, sourcemap: true, metafile: true, entryNames: "[name]", @@ -71,8 +75,20 @@ async function compile_css() { ); } +async function compile_types() { + console.log("\nCompiling TypeScript declarations..."); + try { + execSync("tsc -p tsconfig.json", { stdio: "inherit" }); + console.log("TypeScript declarations compiled successfully"); + } catch (error) { + console.error("Failed to compile TypeScript declarations"); + throw error; + } +} + async function build_all() { await compile_css(); + await compile_types(); await Promise.all( BUILD.map(async (x) => { console.log(""); diff --git a/declarationsconfig.json b/declarationsconfig.json deleted file mode 100644 index d5448f55..00000000 --- a/declarationsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/tsconfig", - "compilerOptions": { - "allowJs": true, - "checkJs": false, - "declaration": true, - "emitDeclarationOnly": true, - "experimentalDecorators": true, - "module": "esnext", - "moduleResolution": "node", - "target": "esnext", - - "outDir": "declarations", - "rootDir": "src/js" - }, - "include": ["src/js/index.js"] - } diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index c4cb69ef..74a29f25 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -6,7 +6,7 @@ const darkCodeTheme = require("prism-react-renderer/themes/dracula"); const fs = require("fs"); -fs.cpSync("../dist/examples", "static/blocks", { recursive: true }); +fs.cpSync("../examples", "static/blocks", { recursive: true }); console.log("\n"); function make(name, type = "examples") { @@ -25,7 +25,7 @@ function make(name, type = "examples") { console.log( ` -` +`, ); return record; @@ -75,8 +75,7 @@ const config = { ], stylesheets: [ { - href: - "https://fonts.googleapis.com/css?display=block&family=Roboto+Mono:400", + href: "https://fonts.googleapis.com/css?display=block&family=Roboto+Mono:400", type: "text/css", }, ], diff --git a/examples/2d_array.md b/examples/2d_array.md deleted file mode 100644 index 9c82b857..00000000 --- a/examples/2d_array.md +++ /dev/null @@ -1,102 +0,0 @@ -# Simple Example - -You'll need to create a ``. The easiest way is to just add one -directly to your page's HTML, and we'll give it an `id` attribute to refer to it -easily later. Fun fact - elements with `id` attributes are accessible on the -global `window` Object in Javascript via `window.${id}`, at least -[maybe](https://stackoverflow.com/questions/18713272/why-do-dom-elements-exist-as-properties-on-the-window-object). - -```html - -``` - -Let's start with with a simple data model, a two dimensional `Array`. This one -is very small at 3 columns x 26 rows, but even for very small data sets, -`regular-table` won't read your entire dataset at once. Instead, we'll need to -write a simple _virtual_ data model to access `DATA` and `COLUMN_NAMES` -indirectly. - -```javascript -const DATA = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], - ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"], - Array.from(Array(15).keys()).map((value) => value % 2 === 0), -]; -``` - -When clipped by the scrollable viewport, you may end up with a `
` elements within `
` of just -a rectangular region of `DATA`, rather than the entire set. A simple viewport -2x2 may yield this `
`: - -
- - - - - - - - - - -
0A
1B
- -```json -{ - "num_rows": 15, - "num_columns": 3, - "data": [ - [0, 1], - ["A", "B"] - ] -} -``` - -Here's a an implementation for this simple _virtual_ data model, the function -`dataListener()`. This function is called by your `` whenever it -needs more data, with coordinate arguments, `(x0, y0)` to `(x1, y1)`. Only this -region is needed to render the viewport, so `dataListener()` returns this -rectangular `slice` of `DATA`. For the window (0, 0) to (2, 2), `dataListener()` -would generate an Object as above, containing the `data` slice, as well as the -overall dimensions of `DATA` itself ( `num_rows`, `num_columns`), for sizing the -scroll area. To render this virtual data model to a regular HTML ``, -register this data model via the `setDataListener()` method: - -```javascript -export function dataListener(x0, y0, x1, y1) { - return { - num_rows: DATA[0].length, - num_columns: DATA.length, - data: DATA.slice(x0, x1).map((col) => col.slice(y0, y1)), - }; -} -``` - -You can register and invoke this table thusly: - -```javascript -export function init() { - window.regularTable.setDataListener(dataListener); - window.regularTable.draw(); -} -``` - -... which we'll do on the Window `"load"` event. - -```html - -``` - -# Appendix (Dependencies) - -```html - - -``` - -```block -license: apache-2.0 -``` diff --git a/examples/2d_array/2d_array.js b/examples/2d_array/2d_array.js new file mode 100644 index 00000000..9e9a5ec9 --- /dev/null +++ b/examples/2d_array/2d_array.js @@ -0,0 +1,56 @@ +// # Simple Example + +import "/dist/esm/regular-table.js"; + +// Let's start with with a simple data model, a two dimensional `Array`. This one +// is very small at 3 columns x 26 rows, but even for very small data sets, +// `regular-table` won't read your entire dataset at once. Instead, we'll need to +// write a simple _virtual_ data model to access `DATA` and `COLUMN_NAMES` +// indirectly. + +const DATA = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"], + Array.from(Array(15).keys()).map((value) => value % 2 === 0), +]; + +// When clipped by the scrollable viewport, you may end up with a `
` of just +// a rectangular region of `DATA`, rather than the entire set. A simple viewport +// 2x2 may yield this `
`: +// +// ```html +//
+// +// +// +// +// +// +// +// +// +// +//
0A
1B
+// ``` +// +// ```json +// { +// "num_rows": 15, +// "num_columns": 3, +// "data": [ +// [0, 1], +// ["A", "B"] +// ] +// } +// ``` + +export function dataListener(x0, y0, x1, y1) { + return { + num_rows: DATA[0].length, + num_columns: DATA.length, + data: DATA.slice(x0, x1).map((col) => col.slice(y0, y1)), + }; +} + +window.regularTable.setDataListener(dataListener); +window.regularTable.draw(); diff --git a/examples/2d_array/index.html b/examples/2d_array/index.html new file mode 100644 index 00000000..f00324c9 --- /dev/null +++ b/examples/2d_array/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/3d_array.html b/examples/3d_array.html deleted file mode 100644 index 6447d150..00000000 --- a/examples/3d_array.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/examples/benchmark.html b/examples/benchmark.html index c123c5ea..e8171b18 100644 --- a/examples/benchmark.html +++ b/examples/benchmark.html @@ -18,7 +18,7 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - + - + this.shadowRoot.adoptedStyleSheets = [ + containerStyleSheet, + this._sub_cell_style, + ]; + + const slot = ``; + this.shadowRoot.innerHTML = `
${slot}
`; - const [, style, virtual_panel, table_clip] = this.shadowRoot.children; - this._sub_cell_style = style; - this._table_clip = table_clip; - this._virtual_panel = virtual_panel; + const [virtual_panel, table_clip] = this.shadowRoot!.children; + this._table_clip = table_clip as HTMLElement; + this._virtual_panel = virtual_panel as HTMLElement; this._setup_virtual_scroll(); } @@ -120,17 +157,14 @@ export class RegularVirtualTableViewModel extends HTMLElement { /** * Calculates the `viewport` argument for perspective's `to_columns` method. * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @param {*} nrows * @returns */ - _calculate_viewport(nrows, num_columns) { + _calculate_viewport(nrows: number, num_columns: number): Viewport { const { start_row, end_row } = this._calculate_row_range(nrows); const { start_col, end_col } = this._calculate_column_range(num_columns); - this._nrows = nrows; + return { start_col, end_col, start_row, end_row }; } @@ -167,16 +201,16 @@ export class RegularVirtualTableViewModel extends HTMLElement { * | | * 600px +--------------------------+ * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @param {*} nrows * @returns */ - _calculate_row_range(nrows) { + _calculate_row_range(nrows: number): { + start_row: number; + end_row: number; + } { const { height, containerHeight } = this._container_size; const row_height = this._column_sizes.row_height || 19; - const header_levels = this._view_cache.config.column_pivots.length; + const header_levels = this._view_cache.column_headers_length; const total_scroll_height = Math.max( 1, this._virtual_panel.offsetHeight - containerHeight, @@ -199,8 +233,8 @@ export class RegularVirtualTableViewModel extends HTMLElement { return { start_row, end_row }; } - _calc_start_column() { - const scroll_index_offset = this._view_cache.config.row_pivots.length; + _calc_start_column(): number { + const scroll_index_offset = this._view_cache.row_headers_length; let start_col = 0; let offset_width = 0; let diff = 0; @@ -224,12 +258,12 @@ export class RegularVirtualTableViewModel extends HTMLElement { * details of which are actually calculated in `_max_column`, the equivalent * of `total_scroll_height` from `_calculate_row_range`. * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @returns */ - _calculate_column_range(num_columns) { + _calculate_column_range(num_columns: number): { + start_col: number; + end_col: number; + } { if ( this._virtual_mode === "none" || this._virtual_mode === "vertical" @@ -268,22 +302,20 @@ export class RegularVirtualTableViewModel extends HTMLElement { * | | 80px | 110px | 100px | * | | | | | * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @returns */ - _max_scroll_column(num_columns) { + _max_scroll_column(num_columns: number): number { let width = 0; - if (this._view_cache.config.row_pivots.length > 0) { + if (this._view_cache.row_headers_length > 0) { for (const w of this._column_sizes.indices.slice( 0, - this._view_cache.config.row_pivots.length, + this._view_cache.row_headers_length, )) { - width += w; + width += w || 0; } } - let scroll_index_offset = this._view_cache.config.row_pivots.length; + + let scroll_index_offset = this._view_cache.row_headers_length; let max_scroll_column = num_columns; while (width < this._container_size.width && max_scroll_column >= 0) { max_scroll_column--; @@ -302,13 +334,15 @@ export class RegularVirtualTableViewModel extends HTMLElement { * e.g. when the logical (row-wise) viewport does not change, but the pixel * viewport has moved a few px. * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @param {*} {start_col, end_col, start_row, end_row} * @returns */ - _validate_viewport({ start_col, end_col, start_row, end_row }) { + _validate_viewport({ + start_col, + end_col, + start_row, + end_row, + }: Viewport): ViewportValidation { start_row = Math.floor(start_row); end_row = Math.ceil(end_row); start_col = Math.floor(start_col); @@ -325,8 +359,8 @@ export class RegularVirtualTableViewModel extends HTMLElement { return { invalid_column, invalid_row }; } - _calc_scrollable_column_width(num_columns) { - let scroll_index_offset = this._view_cache.config.row_pivots.length; + _calc_scrollable_column_width(num_columns: number): number { + let scroll_index_offset = this._view_cache.row_headers_length; const max_scroll_column = this._max_scroll_column(num_columns); let cidx = scroll_index_offset, virtual_width = 0; @@ -338,12 +372,12 @@ export class RegularVirtualTableViewModel extends HTMLElement { if (cidx < this._column_sizes.indices.length) { let viewport_width = this._column_sizes.indices - .slice(0, this._view_cache.config.row_pivots.length) - .reduce((x, y) => x + y, 0); + .slice(0, this._view_cache.row_headers_length) + .reduce((x, y) => (x || 0) + (y || 0), 0); virtual_width += Math.max( 0, - this._column_sizes.indices[cidx] - - (this._container_size.width - viewport_width) || 0, + (this._column_sizes.indices[cidx] || 0) - + (this._container_size.width - (viewport_width || 0)) || 0, ); } @@ -353,20 +387,19 @@ export class RegularVirtualTableViewModel extends HTMLElement { /** * Updates the `virtual_panel` width based on view state. * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @param {*} invalid */ - _update_virtual_panel_width(invalid, num_columns) { + _update_virtual_panel_width(invalid: boolean, num_columns: number): void { if (invalid) { if ( this._virtual_mode === "vertical" || this._virtual_mode === "none" ) { this._virtual_panel.style.width = - this._column_sizes.indices.reduce((x, y) => x + y, 0) + - "px"; + this._column_sizes.indices.reduce( + (x, y) => (x || 0) + (y || 0), + 0, + ) + "px"; } else { const virtual_width = this._calc_scrollable_column_width(num_columns); @@ -384,15 +417,12 @@ export class RegularVirtualTableViewModel extends HTMLElement { /** * Updates the `virtual_panel` height based on the view state. * - * @internal - * @private - * @memberof RegularVirtualTableViewModel * @param {*} nrows */ - _update_virtual_panel_height(nrows) { + _update_virtual_panel_height(nrows: number): void { const { row_height = 19 } = this._column_sizes; const header_height = - this._view_cache.config.column_pivots.length * row_height; + this._view_cache.column_headers_length * row_height; let virtual_panel_px_size; virtual_panel_px_size = Math.min( BROWSER_MAX_HEIGHT, @@ -406,14 +436,12 @@ export class RegularVirtualTableViewModel extends HTMLElement { * the implementor to fine tune the individual render frames based on the * interaction and previous render state. * - * @public - * @memberof RegularVirtualTableViewModel * @param {DrawOptions} [options] * @param {boolean} [options.invalid_viewport=true] * @param {boolean} [options.preserve_width=false] * @param {boolean} [options.throttle=true] */ - async draw(options = {}) { + async draw(options: DrawOptions = {}): Promise { if (typeof options.throttle !== "undefined" && !options.throttle) { return await internal_draw.call(this, options); } else { @@ -423,30 +451,31 @@ export class RegularVirtualTableViewModel extends HTMLElement { } } - async _draw_flush() { + async flush(): Promise { await flush_tag(this); } - update_sub_cell_offset(viewport) { + update_sub_cell_offset(viewport: Viewport): void { const y_offset = - this._column_sizes.row_height * (viewport.start_row % 1) || 0; + (this._column_sizes.row_height || 20) * (viewport.start_row % 1) || + 0; + const x_offset = - this._column_sizes.indices[ + (this._column_sizes.indices[ (this.table_model._row_headers_length || 0) + Math.floor(viewport.start_col) - ] * + ] || 0) * (viewport.start_col % 1) || 0; - let style = this._sub_cell_style.sheet?.cssRules[0].style; - if (style) { - style.setProperty(`--regular-table--clip-x`, `${x_offset}px`); - style.setProperty(`--regular-table--clip-y`, `${y_offset}px`); - style.setProperty(`--regular-table--transform-x`, `-${x_offset}px`); - style.setProperty(`--regular-table--transform-y`, `-${y_offset}px`); - } + + const cssText = CSS_TEMPLATE(x_offset, y_offset); + this._sub_cell_style.replaceSync(cssText); } } -async function internal_draw(options) { +async function internal_draw( + this: RegularVirtualTableViewModel, + options: DrawOptions, +): Promise { const __debug_start_time__ = DEBUG && performance.now(); const { invalid_viewport = true, preserve_width = false } = options; const { @@ -458,66 +487,67 @@ async function internal_draw(options) { } = await this._view_cache.view(0, 0, 0, 0); this._column_sizes.row_height = row_height || this._column_sizes.row_height; if (num_row_headers !== undefined) { - this._view_cache.row_pivots = Array(num_row_headers).fill(0); + this._view_cache.row_headers_length = num_row_headers; } if (num_column_headers !== undefined) { - this._view_cache.column_pivots = Array(num_column_headers).fill(0); + this._view_cache.column_headers_length = num_column_headers; } + // Cache virtual mode checks and default values + const is_non_vertical = + this._virtual_mode === "none" || this._virtual_mode === "horizontal"; + const is_non_horizontal = + this._virtual_mode === "none" || this._virtual_mode === "vertical"; + const safe_num_rows = num_rows || 0; + const safe_num_columns = num_columns || 0; this._container_size = { - width: - this._virtual_mode === "none" || this._virtual_mode === "vertical" - ? Infinity - : this._table_clip.clientWidth, - height: - this._virtual_mode === "none" || this._virtual_mode === "horizontal" - ? Infinity - : this._table_clip.clientHeight, - containerHeight: - this._virtual_mode === "none" || this._virtual_mode === "horizontal" - ? Infinity - : this.clientHeight, + width: is_non_horizontal ? Infinity : this._table_clip.clientWidth, + height: is_non_vertical ? Infinity : this._table_clip.clientHeight, + containerHeight: is_non_vertical ? Infinity : this.clientHeight, }; - this._update_virtual_panel_height(num_rows); + this._update_virtual_panel_height(safe_num_rows); if (!preserve_width) { - this._update_virtual_panel_width(invalid_viewport, num_columns); + this._update_virtual_panel_width(invalid_viewport, safe_num_columns); } - const viewport = this._calculate_viewport(num_rows, num_columns); + + const viewport = this._calculate_viewport(safe_num_rows, safe_num_columns); + // this.table_model.clearWidthStyles(); + this.table_model.updateColumnWidthStyles( + viewport, + this._view_cache.row_headers_length, + ); + const { invalid_row, invalid_column } = this._validate_viewport(viewport); - if ( - this._invalid_schema || - invalid_row || - invalid_column || - invalid_viewport - ) { - let autosize_cells = [], - needs_sub_cell_update = true; + const invalid_schema_or_column = this._invalid_schema || invalid_column; + if (invalid_schema_or_column || invalid_row || invalid_viewport) { + let autosize_cells: CellTuple[] = []; + let first_iteration = true; for await (let last_cells of this.table_model.draw( this._container_size, this._view_cache, this._selected_id, preserve_width, viewport, - num_columns, + safe_num_columns, )) { if (last_cells !== undefined) { - autosize_cells = autosize_cells.concat(last_cells); + autosize_cells.push(...last_cells); } // We want to perform this before the next event loop so there // is no scroll jitter, but only on the first iteration as // subsequent viewports are incorrect. - if (needs_sub_cell_update) { + if (first_iteration) { this.update_sub_cell_offset(viewport); - needs_sub_cell_update = false; + first_iteration = false; } this._is_styling = true; const callbacks = this._style_callbacks; for (const callback of callbacks) { - await callback({ detail: this }); + await callback({ detail: this as RegularTableElement }); } this._is_styling = false; @@ -528,19 +558,20 @@ async function internal_draw(options) { this._invalidated = false; } - const old_height = this.table_model._column_sizes.row_height; + const old_height = this._column_sizes.row_height; this.table_model.autosize_cells(autosize_cells, row_height); this.table_model.header.reset_header_cache(); - if (old_height !== this.table_model._column_sizes.row_height) { - this._update_virtual_panel_height(num_rows); + if (old_height !== this._column_sizes.row_height) { + this._update_virtual_panel_height(safe_num_rows); } if (!preserve_width) { this._update_virtual_panel_width( - this._invalid_schema || invalid_column, - num_columns, + invalid_schema_or_column, + safe_num_columns, ); } + this._invalid_schema = false; } else { this.update_sub_cell_offset(viewport); @@ -550,13 +581,3 @@ async function internal_draw(options) { log_perf(performance.now() - __debug_start_time__); } } - -/** - * Options for the draw method. - * - * @public - * @typedef DrawOptions - * @type {object} - * @property {boolean} [invalid_viewport] - * @property {boolean} [preserve_width] - */ diff --git a/src/ts/table.ts b/src/ts/table.ts new file mode 100644 index 00000000..dcacfb6e --- /dev/null +++ b/src/ts/table.ts @@ -0,0 +1,728 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ +// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ +// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ +// ┃ * of the Regular Table library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import { RegularHeaderViewModel } from "./thead"; +import { RegularBodyViewModel } from "./tbody"; +import { + BodyDrawResult, + CellScalar, + CellTuple, + CellMetadata, + ColumnState, + DataColumnDrawResult, + FetchResult, + RowHeadersResult, + ViewCache, + ViewFunction, + Viewport, + DataResponse, + ViewState, +} from "./types"; +import { ColumnSizes } from "./types"; + +/** + * Base class containing protected helper methods for table rendering. + * This class provides the internal implementation details for drawing + * and managing table view state. + * + * @class RegularTableViewModelBase + */ +abstract class RegularTableViewModelBase { + public _column_sizes!: ColumnSizes; + public _row_headers_length: number = 0; + public header!: RegularHeaderViewModel; + public body!: RegularBodyViewModel; + + /** + * Initializes view state with viewport and sizing information. + */ + protected _initializeViewState( + viewport: Viewport, + selected_id: number | undefined, + ): ViewState { + const { + start_row: ridx_offset = 0, + start_col: x0 = 0, + end_col: x1 = 0, + end_row: y1 = 0, + } = viewport; + + const sub_cell_offset = + this._column_sizes.indices[ + (this._row_headers_length || 0) + Math.floor(viewport.start_col) + ] || 0; + + return { + viewport_width: 0, + selected_id, + ridx_offset, + sub_cell_offset, + x0: x0, + x1: x1, + y1: y1, + row_height: this._column_sizes.row_height, + row_headers_length: this._row_headers_length, + }; + } + + /** + * Draws row headers and returns updated state. + */ + protected _drawRowHeaders( + row_headers: CellScalar[][], + row_headers_length: number, + column_headers_length: number, + container_height: number, + view_state: ViewState, + preserve_width: boolean, + x0: number, + column_header_merge_depth: number | undefined, + merge_column_headers: boolean, + merge_row_headers: boolean, + ): RowHeadersResult { + const column_name = [`${row_headers_length}`]; + const last_cells: CellTuple[] = []; + + const column_state: ColumnState = { + column_name, + cidx: 0, + column_data: row_headers, + row_headers, + first_col: true, + }; + + const size_key = Math.floor(x0); + const cont_body = this.body.draw( + container_height, + column_state, + { ...view_state, x0: 0 }, + true, + undefined, + undefined, + size_key, + merge_row_headers, + ); + + const cont_heads = []; + for (let i = 0; i < row_headers_length; i++) { + const header = this.header.draw( + column_name, + Array(column_headers_length).fill(""), + 1, + undefined, + i, + x0, + i, + column_header_merge_depth, + merge_column_headers, + ); + if (!!header) { + cont_heads.push(header); + } + } + + view_state.viewport_width += cont_heads.reduce( + (total, { th }, i) => + total + (this._column_sizes.indices[i] || th.offsetWidth), + 0, + ); + view_state.row_height = view_state.row_height || cont_body.row_height; + + const _virtual_x = row_headers[0].length; + + if (!preserve_width) { + for (let i = 0; i < row_headers_length; i++) { + const { td, metadata } = cont_body.tds[i] || {}; + const { th, metadata: hmetadata } = cont_heads[i] || {}; + if (!!td || !!th) { + last_cells.push([th || td, hmetadata || metadata]); + } + } + } + + return { cont_body, first_col: false, _virtual_x, last_cells }; + } + + /** + * Calculates how many additional columns are needed to fill the viewport. + */ + protected _calculateViewportExtension( + viewport: Viewport, + view_state: ViewState, + container_width: number, + num_columns: number, + _virtual_x: number, + x0: number, + ): void { + let end_col_offset = 0, + size_extension = 0; + + while ( + this._column_sizes.indices.length > + _virtual_x + x0 + end_col_offset + 1 && + size_extension + view_state.viewport_width < container_width + ) { + end_col_offset++; + size_extension += + this._column_sizes.indices[_virtual_x + x0 + end_col_offset] || + 0; + } + + if (size_extension + view_state.viewport_width < container_width) { + const estimate = Math.min(num_columns, viewport.start_col + 5); + viewport.end_col = Math.max(1, Math.min(num_columns, estimate)); + } else { + viewport.end_col = Math.max( + 1, + Math.min(num_columns, viewport.start_col + end_col_offset), + ); + } + } + + /** + * Draws a single data column and returns rendering information. + */ + protected _drawDataColumn( + dcidx: number, + view_response: DataResponse, + _virtual_x: number, + x0: number, + container_height: number, + view_state: ViewState, + first_col: boolean, + column_header_merge_depth: number | undefined, + merge_column_headers: boolean, + merge_row_headers: boolean, + ): DataColumnDrawResult { + const column_name = view_response.column_headers?.[dcidx] || []; + const column_data = view_response.data[dcidx]; + const column_data_listener_metadata = view_response.metadata?.[dcidx]; + const column_state: ColumnState = { + column_name, + cidx: _virtual_x, + column_data, + column_data_listener_metadata, + row_headers: view_response.row_headers, + first_col, + }; + + const x = dcidx + x0; + const size_key = _virtual_x + Math.floor(x0); + const cont_head = this.header.draw( + column_name, + column_name, + undefined, + x, + size_key, + x0, + _virtual_x, + column_header_merge_depth, + merge_column_headers, + ); + + const cont_body = this.body.draw( + container_height, + column_state, + view_state, + false, + x, + x0, + size_key, + merge_row_headers, + ); + + return { cont_head, cont_body }; + } + + /** + * Fetches additional columns when data is missing during rendering. + */ + protected async _fetchMissingColumns( + viewport: Viewport, + view: ViewFunction, + view_response: DataResponse, + dcidx: number, + view_state: ViewState, + container_width: number, + num_columns: number, + _virtual_x: number, + x0: number, + ): Promise { + let missing_cidx = Math.max(viewport.end_col, 0); + viewport.start_col = missing_cidx; + this._calculateViewportExtension( + viewport, + view_state, + container_width, + num_columns, + _virtual_x, + x0, + ); + + const new_col = await view( + Math.floor(viewport.start_col), + Math.floor(viewport.start_row), + Math.ceil(viewport.end_col), + Math.ceil(viewport.end_row), + ); + + let column_header_merge_depth: number | undefined; + let merge_headers: "both" | "row" | "column" | undefined; + + if (typeof new_col.column_header_merge_depth !== "undefined") { + column_header_merge_depth = new_col.column_header_merge_depth; + } + + if (typeof new_col.merge_headers !== "undefined") { + merge_headers = new_col.merge_headers; + } + + if (new_col.data.length === 0) { + return { column_header_merge_depth, merge_headers }; + } + + viewport.end_col = viewport.start_col + new_col.data.length; + for (let i = 0; i < new_col.data.length; i++) { + view_response.data[dcidx + i] = new_col.data[i]; + if (new_col.metadata && view_response.metadata) { + view_response.metadata[dcidx + i] = new_col.metadata[i]; + } + + if (view_response.column_headers && new_col.column_headers?.[i]) { + view_response.column_headers[dcidx + i] = + new_col.column_headers[i]; + } + } + + return { column_header_merge_depth, merge_headers }; + } + + /** + * Cleans up body and header after drawing. + */ + protected _cleanupAfterDraw( + cont_body: BodyDrawResult | undefined, + _virtual_x: number, + ): void { + this.body.clean({ ridx: cont_body?.ridx || 0, cidx: _virtual_x }); + this.header.clean(); + this.body._span_factory.reset(); + this.header._span_factory.reset(); + } +} + +/** + * view model. In order to handle unknown column width when `draw()` + * is called, this model will iteratively fetch more data to fill in columns + * until the page is complete, and makes some column viewport estimations + * when this information is not availble. + * + * @class RegularTableViewModel + */ +export class RegularTableViewModel extends RegularTableViewModelBase { + public table: HTMLTableElement; + public fragment: DocumentFragment; + + constructor( + table_clip: HTMLElement, + column_sizes: ColumnSizes, + element: HTMLElement, + ) { + super(); + this._column_sizes = column_sizes; + this.clear(element); + const [table] = element.children as HTMLCollectionOf; + const [thead, tbody] = + table.children as HTMLCollectionOf; + + this.table = table; + this.header = new RegularHeaderViewModel( + column_sizes, + table_clip, + thead, + ); + + this.body = new RegularBodyViewModel(column_sizes, table_clip, tbody); + this.fragment = document.createDocumentFragment(); + } + + num_columns(): number { + return this.header.num_columns(); + } + + clear(element: HTMLElement): void { + element.innerHTML = + '
'; + } + + /** + * Calculate amendments to auto size from this render pass. + * Uses adoptedStyleSheets with :nth-child selectors for optimal performance. + * + * This method separates DOM reads (getBoundingClientRect) from DOM writes + * (CSS stylesheet updates) to minimize forced reflows. All measurements are + * collected first, then column size data is updated, and finally all column + * widths (both auto and override) are applied via a single stylesheet update. + */ + autosize_cells( + last_cells: CellTuple[], + override_row_height?: number, + ): void { + // PHASE 1: READ - Collect all layout measurements first + const measurements: Array<{ + cell: HTMLElement; + metadata: CellMetadata | undefined; + box: DOMRect; + }> = []; + + for (const [cell, metadata] of last_cells) { + const box = cell.getBoundingClientRect(); + measurements.push({ cell, metadata, box }); + } + + // PHASE 2: PROCESS - Update column sizes + for (const { metadata, box } of measurements) { + this._column_sizes.row_height = + override_row_height ?? + Math.max( + 10, + Math.min( + this._column_sizes.row_height ?? box.height, + box.height, + ), + ); + + if (metadata?.size_key !== undefined) { + this._column_sizes.indices[metadata.size_key] = box.width; + if ( + box.width && + this._column_sizes.override[metadata.size_key] === undefined + ) { + this._column_sizes.auto[metadata.size_key] = box.width; + } + } + } + } + + clearWidthStyles() { + this._columnWidthStyleSheet?.replaceSync(""); + } + + /** + * Updates column width styles for all columns using adoptedStyleSheets. + * Generates CSS rules with :nth-child selectors for both auto-sized and + * overridden column widths, applying them all in a single stylesheet update. + * + * This method should be called whenever column sizes change, including: + * - After autosize_cells() measurements + * - When user resizes columns + * - When resetAutoSize() is called + */ + updateColumnWidthStyles( + viewport: Viewport, + row_headers_length: number, + ): void { + const cssRules: string[] = []; + + let row_headers_size_key; + for ( + row_headers_size_key = 0; + row_headers_size_key < row_headers_length; + row_headers_size_key++ + ) { + const override_width = + this._column_sizes.override[row_headers_size_key]; + const auto_width = this._column_sizes.auto[row_headers_size_key]; + if (override_width !== undefined) { + // Override width takes precedence + // CSS :nth-child is 1-indexed + const columnIndex = row_headers_size_key + 1; // - Math.floor(viewport.start_col); + + cssRules.push( + `thead tr.rt-autosize th:nth-child(${columnIndex}),`, + `tbody td.rt-cell-clip:nth-child(${columnIndex})`, + `{min-width:${override_width}px;max-width:${override_width}px;}`, + ); + } else if (auto_width !== undefined) { + // Auto width applies when no override + const columnIndex = row_headers_size_key + 1; // - Math.floor(viewport.start_col); + cssRules.push( + `thead tr.rt-autosize th:nth-child(${columnIndex}),`, + `tbody td.rt-cell-clip:nth-child(${columnIndex})`, + `{min-width:${auto_width}px;max-width:none;}`, + ); + } + } + + for ( + let size_key = row_headers_size_key; //Math.floor(viewport.start_col); + size_key < + row_headers_size_key + + (Math.ceil(viewport.end_col) - Math.floor(viewport.start_col)); + size_key++ + ) { + const override_width = + this._column_sizes.override[ + size_key + Math.floor(viewport.start_col) + ]; + const auto_width = + this._column_sizes.auto[ + size_key + Math.floor(viewport.start_col) + ]; + + if (override_width !== undefined) { + // Override width takes precedence + // CSS :nth-child is 1-indexed + const columnIndex = size_key + 1; // Math.floor(viewport.start_col); + + cssRules.push( + `thead tr.rt-autosize th:nth-child(${columnIndex}),`, + `tbody td.rt-cell-clip:nth-child(${columnIndex})`, + `{min-width:${override_width}px;max-width:${override_width}px;}`, + ); + } else if (auto_width !== undefined) { + // Auto width applies when no override + const columnIndex = size_key + 1; // Math.floor(viewport.start_col); + cssRules.push( + `thead tr.rt-autosize th:nth-child(${columnIndex}),`, + `tbody td.rt-cell-clip:nth-child(${columnIndex})`, + `{min-width:${auto_width}px;max-width:none;}`, + ); + } + } + + // Apply all rules via single stylesheet update + if (cssRules.length > 0) { + this._applyColumnWidthStyles(cssRules.join("\n")); + } else if (this._columnWidthStyleSheet) { + // Clear stylesheet if no rules + this._columnWidthStyleSheet.replaceSync(""); + } + } + + /** + * Applies column width styles using adoptedStyleSheets. + * Creates or updates a dedicated stylesheet for column widths. + */ + private _applyColumnWidthStyles(css: string): void { + // Access the shadowRoot through the table's container + const shadowRoot = this.table.getRootNode() as ShadowRoot; + + if (!shadowRoot || !shadowRoot.adoptedStyleSheets) { + return; + } + + // Find or create the column width stylesheet + if (!this._columnWidthStyleSheet) { + this._columnWidthStyleSheet = new CSSStyleSheet(); + shadowRoot.adoptedStyleSheets = [ + ...shadowRoot.adoptedStyleSheets, + this._columnWidthStyleSheet, + ]; + } + + this._columnWidthStyleSheet.replaceSync(css); + } + + private _columnWidthStyleSheet?: CSSStyleSheet; + + async *draw( + container_size: { width: number; height: number }, + view_cache: ViewCache, + selected_id: number | undefined, + preserve_width: boolean, + viewport: Viewport, + num_columns: number, + ): AsyncGenerator { + const { width: container_width, height: container_height } = + container_size; + + // Fetch and prepare initial data + const view_response = await view_cache.view( + Math.floor(viewport.start_col), + Math.floor(viewport.start_row), + Math.ceil(viewport.end_col), + Math.ceil(viewport.end_row), + ); + + let { column_header_merge_depth, merge_headers = "both" } = + view_response; + + const merge_row_headers = + merge_headers === "both" || merge_headers === "row"; + const merge_column_headers = + merge_headers === "both" || merge_headers === "column"; + const x0 = viewport.start_col ?? 0; + + if (view_response.row_headers) { + this._row_headers_length = view_response.row_headers.reduce( + (max, x) => Math.max(max, x.length), + 0, + ); + + for (let i = 0; i < view_response.row_headers.length; i++) { + view_response.row_headers[i].length = + this._row_headers_length || 0; + } + } + + // Update view cache lengths + view_cache.row_headers_length = + view_response.num_row_headers ?? + view_response.row_headers?.[0]?.length ?? + 0; + + view_cache.column_headers_length = + view_response.num_column_headers ?? + view_response.column_headers?.[0]?.length ?? + 0; + + const { view, row_headers_length, column_headers_length } = view_cache; + const view_state = this._initializeViewState(viewport, selected_id); + + // Draw row headers + let cont_body: BodyDrawResult | undefined; + let _virtual_x = 0; + let last_cells: CellTuple[] = []; + let first_col = true; + + if (view_response.row_headers?.length) { + const row_header_result = this._drawRowHeaders( + view_response.row_headers, + row_headers_length, + column_headers_length, + container_height, + view_state, + preserve_width, + x0, + column_header_merge_depth, + merge_column_headers, + merge_row_headers, + ); + cont_body = row_header_result.cont_body; + first_col = row_header_result.first_col; + _virtual_x = row_header_result._virtual_x; + last_cells = row_header_result.last_cells; + } + + // Draw data columns + try { + let dcidx = 0; + const num_visible_columns = num_columns - viewport.start_col; + + while (dcidx < num_visible_columns) { + // Fetch missing columns if needed + if (!view_response.data[dcidx]) { + const fetch_result = await this._fetchMissingColumns( + viewport, + view, + view_response, + dcidx, + view_state, + container_width, + num_columns, + _virtual_x, + x0, + ); + + yield undefined; + + if (fetch_result.column_header_merge_depth !== undefined) { + column_header_merge_depth = + fetch_result.column_header_merge_depth; + } + if (fetch_result.merge_headers !== undefined) { + merge_headers = fetch_result.merge_headers; + } + if (!view_response.data[dcidx]) { + this._cleanupAfterDraw(cont_body, _virtual_x); + yield last_cells; + return; + } + } + + // Draw column + const { cont_head, cont_body: drawn_body } = + this._drawDataColumn( + dcidx, + view_response, + _virtual_x, + x0, + container_height, + view_state, + first_col, + column_header_merge_depth, + merge_column_headers, + merge_row_headers, + ); + + cont_body = drawn_body; + first_col = false; + + // Collect cells for autosizing + if (!preserve_width) { + for (const { td, metadata } of cont_body.tds) { + last_cells.push([ + cont_head?.th || td, + cont_head?.metadata || metadata, + ]); + } + } + + // Update dimensions + const col_width = + this._column_sizes.indices[_virtual_x + Math.floor(x0)] || + cont_head?.th?.offsetWidth || + cont_body.tds.reduce( + (sum, { td }) => sum + (td?.offsetWidth || 0), + 0, + ); + view_state.viewport_width += col_width; + view_state.row_height = + view_state.row_height || cont_body.row_height; + + _virtual_x++; + dcidx++; + + // Check if viewport filled + if (this._isViewportFilled(view_state, container_width)) { + this._cleanupAfterDraw(cont_body, _virtual_x); + yield last_cells; + + // Recalculate after style listeners + view_state.viewport_width = last_cells.reduce( + (sum, [td]) => sum + td.offsetWidth, + 0, + ); + + if (this._isViewportFilled(view_state, container_width)) { + return; + } + } + } + + this._cleanupAfterDraw(cont_body, _virtual_x); + yield last_cells; + } finally { + this._cleanupAfterDraw(cont_body, _virtual_x); + } + } + + private _isViewportFilled( + view_state: ViewState, + container_width: number, + ): boolean { + return ( + view_state.viewport_width - view_state.sub_cell_offset > + container_width + ); + } +} diff --git a/src/js/tbody.js b/src/ts/tbody.ts similarity index 54% rename from src/js/tbody.js rename to src/ts/tbody.ts index e4a93d1a..4d460755 100644 --- a/src/js/tbody.js +++ b/src/ts/tbody.ts @@ -9,6 +9,13 @@ // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +import { + BodyDrawResult, + CellMetadata, + CellScalar, + ColumnState, + ViewState, +} from "./types"; import { ViewModel } from "./view_model"; /** @@ -18,31 +25,32 @@ import { ViewModel } from "./view_model"; */ export class RegularBodyViewModel extends ViewModel { _draw_td( - tagName, - ridx, - val, - cidx, - { column_name }, - { ridx_offset }, - size_key, - ) { + tagName: string, + ridx: number, + val: unknown, + cidx: number, + { column_name }: ColumnState, + { ridx_offset }: ViewState, + size_key: number, + ): { td: HTMLTableCellElement; metadata: CellMetadata } { const td = this._get_cell(tagName, ridx, cidx); const metadata = this._get_or_create_metadata(td); metadata.y = ridx + Math.floor(ridx_offset); - metadata.size_key = size_key; + const key = (metadata.size_key = size_key || 0); if (tagName === "TD") { metadata.column_header = column_name; } - const override_width = this._column_sizes.override[metadata.size_key]; + + // Handle clipping class for overridden columns + const override_width = this._column_sizes.override[key]; if (override_width) { - const auto_width = this._column_sizes.auto[metadata.size_key]; - td.classList.toggle("rt-cell-clip", auto_width > override_width); - td.style.minWidth = override_width + "px"; - td.style.maxWidth = override_width + "px"; + const auto_width = this._column_sizes.auto[key] || 0; + const clip = auto_width > override_width; + if (td.classList.contains("rt-cell-clip") !== clip) { + td.classList.toggle("rt-cell-clip", clip); + } } else { td.classList.remove("rt-cell-clip"); - td.style.minWidth = ""; - td.style.maxWidth = ""; } if (metadata.value !== val) { @@ -50,7 +58,7 @@ export class RegularBodyViewModel extends ViewModel { td.textContent = ""; td.appendChild(val); } else { - td.textContent = val; + td.textContent = String(val ?? ""); } } @@ -58,16 +66,16 @@ export class RegularBodyViewModel extends ViewModel { return { td, metadata }; } - draw( - container_height, - column_state, - view_state, - th = false, - x, - x0, - size_key, - merge_headers, - ) { + draw( + container_height: number, + column_state: ColumnState, + view_state: ViewState, + th: boolean = false, + x?: number, + x0?: number, + size_key?: number, + merge_headers?: boolean, + ): BodyDrawResult { const { cidx, column_data, @@ -75,29 +83,43 @@ export class RegularBodyViewModel extends ViewModel { column_data_listener_metadata, } = column_state; let { row_height } = view_state; - let metadata; - const ridx_offset = [], - tds = []; + let metadata: CellMetadata | undefined; + const ridx_offset: number[] = []; + const tds: Array<{ td: HTMLTableCellElement; metadata: CellMetadata }> = + []; let ridx = 0; - const cidx_offset = []; - for (let i = 0; i < (th ? view_state.row_headers_length : 1); i++) { + const cidx_offset: number[] = []; + const loops = th ? (view_state.row_headers_length ?? 1) : 1; + const y0_floor = Math.floor(view_state.ridx_offset); + const y1_ceil = Math.ceil(view_state.y1); + const x1_ceil = Math.ceil(view_state.x1); + const x_floor = x === undefined ? x : Math.floor(x); + const x0_floor = x0 === undefined ? undefined : Math.floor(x0); + const dx = Math.floor((x ?? 0) - (x0 ?? 0)); + + for (let i = 0; i < loops; i++) { ridx = 0; + const cidx_i = cidx + i; for (const val of column_data) { - const id = row_headers?.[ridx]; - let obj; + let obj: + | { td: HTMLTableCellElement; metadata: CellMetadata } + | undefined; if (th) { - const row_header = val[i]; + const valArray = val as CellScalar[]; + const row_header = valArray[i]; + const ridx_off_i = ridx_offset[i] || 1; const prev_row = this._fetch_cell( - ridx - (ridx_offset[i] || 1), - cidx + i, + ridx - ridx_off_i, + cidx_i, ); const prev_row_metadata = this._get_or_create_metadata(prev_row); + const cidx_off_ridx = cidx_offset[ridx] || 1; const prev_col = this._fetch_cell( ridx, - cidx + i - (cidx_offset[ridx] || 1), + cidx_i - cidx_off_ridx, ); const prev_col_metadata = this._get_or_create_metadata(prev_col); @@ -109,42 +131,40 @@ export class RegularBodyViewModel extends ViewModel { row_header === undefined) && !prev_col.hasAttribute("rowspan") ) { - cidx_offset[ridx] = cidx_offset[ridx] - ? cidx_offset[ridx] + 1 - : 2; - prev_col.setAttribute("colspan", cidx_offset[ridx]); - this._replace_cell(ridx, cidx + i); + const offset = (cidx_offset[ridx] = cidx_off_ridx + 1); + prev_col.setAttribute("colspan", String(offset)); + this._replace_cell(ridx, cidx_i); } else if ( merge_headers && prev_row && prev_row_metadata.value === row_header && !prev_row.hasAttribute("colspan") ) { - ridx_offset[i] = ridx_offset[i] - ? ridx_offset[i] + 1 - : 2; - prev_row.setAttribute("rowspan", ridx_offset[i]); - this._replace_cell(ridx, cidx + i); + const offset = (ridx_offset[i] = ridx_off_i + 1); + prev_row.setAttribute("rowspan", String(offset)); + this._replace_cell(ridx, cidx_i); } else { obj = this._draw_td( "TH", ridx, row_header, - cidx + i, - column_state, + cidx_i, + column_state as ColumnState, view_state, i, ); - obj.td.style.display = ""; - obj.td.removeAttribute("rowspan"); - obj.td.removeAttribute("colspan"); - obj.metadata.row_header = val; - obj.metadata.row_header_x = i; - obj.metadata.y0 = Math.floor(view_state.ridx_offset); - obj.metadata.y1 = Math.ceil(view_state.y1); - obj.metadata._virtual_x = i; - if (typeof x0 !== "undefined") { - obj.metadata.x0 = Math.floor(x0); + const td = obj.td; + const meta = obj.metadata; + td.style.display = ""; + td.removeAttribute("rowspan"); + td.removeAttribute("colspan"); + meta.row_header = valArray; + meta.row_header_x = i; + meta.y0 = y0_floor; + meta.y1 = y1_ceil; + meta.virtual_x = i; + if (x0_floor !== undefined) { + meta.x0 = x0_floor; } ridx_offset[i] = 1; cidx_offset[ridx] = 1; @@ -156,26 +176,25 @@ export class RegularBodyViewModel extends ViewModel { ridx, val, cidx, - column_state, + column_state as ColumnState, view_state, - size_key, + size_key ?? 0, ); + const meta = obj.metadata; if (column_data_listener_metadata) { - obj.metadata.user = column_data_listener_metadata[ridx]; + meta.user = column_data_listener_metadata[ridx]; } - obj.metadata.x = - typeof x === "undefined" ? x : Math.floor(x); - obj.metadata.x1 = Math.ceil(view_state.x1); - obj.metadata.row_header = id || []; - obj.metadata.y0 = Math.floor(view_state.ridx_offset); - obj.metadata.y1 = Math.ceil(view_state.y1); - obj.metadata.dx = Math.floor(x - x0); - obj.metadata.dy = - obj.metadata.y - Math.floor(obj.metadata.y0); - obj.metadata._virtual_x = cidx; - if (typeof x0 !== "undefined") { - obj.metadata.x0 = Math.floor(x0); + meta.x = x_floor; + meta.x1 = x1_ceil; + meta.row_header = row_headers?.[ridx] || []; + meta.y0 = y0_floor; + meta.y1 = y1_ceil; + meta.dx = dx; + meta.dy = (meta.y ?? 0) - y0_floor; + meta.virtual_x = cidx; + if (x0_floor !== undefined) { + meta.x0 = x0_floor; } tds[0] = obj; @@ -184,16 +203,16 @@ export class RegularBodyViewModel extends ViewModel { ridx++; metadata = obj ? obj.metadata : metadata; row_height = row_height || obj?.td.offsetHeight; - if (ridx * row_height > container_height) { + if (ridx * (row_height ?? 0) > container_height) { break; } } } this._clean_rows(ridx); - return { tds, ridx, metadata, row_height }; + return { tds, ridx, row_height }; } - clean({ ridx, cidx }) { + clean({ ridx, cidx }: { ridx: number; cidx: number }): void { this._clean_rows(ridx); this._clean_columns(cidx); } diff --git a/src/js/thead.js b/src/ts/thead.ts similarity index 70% rename from src/js/thead.js rename to src/ts/thead.ts index 36f338b4..485e7f92 100644 --- a/src/js/thead.js +++ b/src/ts/thead.ts @@ -10,6 +10,12 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { ViewModel } from "./view_model"; +import { + HeaderDrawResult, + ColumnSizes, + CellMetadata, + CellScalar, +} from "./types"; /** *
` or `` + * elements back to your `data`, `row_headers` or `column_headers` within + * listener functions for `addStyleListener()` and `addEventListener()`. + * + * MetaData (x = 0, column_header_y = 0) + * *-------------------------------------+ + * | | + * | | + * +-------------------------------------+ + * (row_header_x = 0, y = 0) (x = 0, y = 0) + * *------------------------+ *-------------------------------------+ + * | | | | + * | | | (x0, y0) | + * | | | *---------------* | + * | | | | | | + * | | | | * (x, y) | | + * | | | | | | + * | | | *---------------* (x1, y1) | + * | | | | + * +------------------------+ +-------------------------------------+ + * + * @property {number} [x] - The `x` index in your virtual data model. + * property is only generated for ``, `` from `row_headers`. + * @property {number} [y] - The `y` index in your virtual data model. + * property is only generated for ``, `` from `row_headers`. + * @property {number} [x0] - The `x` index of the viewport origin in + * your data model, e.g. what was passed to `x0` when your + * `dataListener` was invoked. + * @property {number} [y0] - The `y` index of the viewport origin in + * your data model, e.g. what was passed to `y0` when your + * `dataListener` was invoked. + * @property {number} [x1] - The `x` index of the viewport corner in + * your data model, e.g. what was passed to `x1` when your + * `dataListener` was invoked. + * @property {number} [y1] - The `y` index of the viewport corner in + * your data model, e.g. what was passed to `y1` when your + * `dataListener` was invoked. + * @property {number} [dx] - The `x` index in `DataResponse.data`, this + * property is only generated for ``, and `` from `column_headers`. + * @property {number} [dy] - The `y` index in `DataResponse.data`, this + * property is only generated for ``, `` from `row_headers`. + * @property {number} [column_header_y] - The `y` index in + * `DataResponse.column_headers[x]`, this property is only generated for `` + * from `column_headers`. + * @property {number} [row_header_x] - The `x` index in + * `DataResponse.row_headers[y]`, this property is only generated for `` + * from `row_headers`. + * @property {number} size_key - The unique index of this column in a full + * ``, which is `x` + (Total Row Header Columns). + * @property {(string|HTMLElement)[]} [row_header] - The `Array` for this `y` in + * `DataResponse.row_headers`, if it was provided. + * @property {(string|HTMLElement)[]} [column_header] - The `Array` for this `x` + * in `DataResponse.column_headers`, if it was provided. + * @property {(string|HTMLElement)} [value] - The value dispalyed in the cell or + * header. + */ + +export interface CellMetadata { + column_header?: CellScalar[]; + row_header?: CellScalar[]; + value: unknown; + size_key?: number; + x?: number; + column_header_y?: number; + x0?: number; + virtual_x?: number; + row_header_x?: number; + y?: number; + y0?: number; + y1?: number; + x1?: number; + user?: unknown; + dx?: number; + dy?: number; +} + +/** + * The `DataResponse` object describes a rectangular region of a virtual + * data set, and some associated metadata. `` will use this + * object to render the `
`, though it may make multiple requests for + * different regions to achieve a compelte render as it must estimate + * certain dimensions. You must construct a `DataResponse` object to + * implement a `DataListener`. + * + * # Examples + * + * ```json + * { + * "num_rows": 26, + * "num_columns": 3, + * "data": [ + * [0, 1], + * ["A", "B"] + * ], + * "row_headers": [ + * ["Rowgroup 1", "Row 1"], + * ["Rowgroup 1", "Row 2"] + * ], + * "column_headers": [ + * ["Colgroup 1", "Column 1"], + * ["Colgroup 1", "Column 2"] + * ] + * } + * ``` + * + * @property {(string|HTMLElement)[][]} [column_headers] - A two dimensional + * `Array` of column group headers, in specificity order. No `` + * will be generated if this property is not provided. + * @property {(string|HTMLElement)[][]} [row_headers] - A two dimensional + * `Array` of row group headers, in specificity order. No `` will be generated if this property is not + * provided. + * @property {number?} num_row_headers - Optional number of row headers. + * @property {number?} num_row_headers - Optional number of column headers. + * @property {(string|HTMLElement)[][]} data - A two dimensional `Array` + * representing a rectangular section of the underlying data set from + * (x0, y0) to (x1, y1), arranged in columnar fashion such that + * `data[x][y]` returns the `y`th row of the `x`th column of the slice. + * @property {number} num_rows - Total number of rows in the underlying + * data set. + * @property {number} num_columns - Total number of columns in the + * underlying data set. + */ +export interface DataResponse { + data: CellScalar[][]; + num_columns: number; + num_rows: number; + row_height?: number; + row_headers?: CellScalar[][]; + column_headers?: CellScalar[][]; + metadata?: unknown[][]; + num_row_headers?: number; + num_column_headers?: number; + column_header_merge_depth?: number; + merge_headers?: "both" | "row" | "column"; +} + +/** + * + * @param {boolean} options.preserve_state If `setDataListener` has already been + * called, setting this flag will prevent the internal state from being + * reset (other than the callback itself). + * @param {("both"|"horizontal"|"vertical"|"none")} options.virtual_mode + * The `virtual_mode` options flag may be one of "both", "horizontal", + * "vertical", or "none" indicating which dimensions of the table should be + * virtualized (vs. rendering completely). + */ +export interface SetDataListenerOptions { + virtual_mode?: VirtualMode; + preserve_state?: boolean; +} + +/** + * An object with performance statistics about calls to + * `draw()` from some time interval (captured in milliseconds by the + * `elapsed` proprty). + * + * @property {number} avg - Avergage milliseconds per call. + * @property {number} real_fps - `num_frames` / `elapsed` + * @property {number} virtual_fps - `elapsed` / `avg` + * @property {number} num_frames - Number of frames rendered. + * @property {number} elapsed - Number of milliseconds since last call + * to `getDrawFPS()`. + */ +export interface FPSRecord { + avg: number; + real_fps: number; + virtual_fps: number; + num_frames: number; + elapsed: number; +} + +export type StyleCallback = (event: { + detail: RegularTableElement; +}) => void | Promise; + +/** + * Virtual mode type + */ +export type VirtualMode = "both" | "horizontal" | "vertical" | "none"; + +/** + * View cache containing view function and configuration + */ +export interface ViewCache { + view: ViewFunction; + row_headers_length: number; + column_headers_length: number; +} + +export type CellScalar = number | string | boolean | null; + +/** + * Container size information + */ +export interface ContainerSize { + width: number; + height: number; + containerHeight: number; +} + +/** + * Viewport range + */ +export interface Viewport { + start_col: number; + end_col: number; + start_row: number; + end_row: number; +} + +/** + * Validation result for viewport + */ +export interface ViewportValidation { + invalid_column: boolean; + invalid_row: boolean; +} + +/** + * Options for the draw method + */ +export interface DrawOptions { + invalid_viewport?: boolean; + preserve_width?: boolean; + throttle?: boolean; +} + +/** + * View state containing viewport and rendering information + */ +export interface ViewState { + viewport_width: number; + selected_id: number | undefined; + ridx_offset: number; + sub_cell_offset: number; + x0: number; + x1: number; + y1: number; + row_height: number | undefined; + row_headers_length: number | undefined; +} + +/** + * View function type that fetches data for a given viewport range + */ +export type ViewFunction = ( + start_col: number, + start_row: number, + end_col: number, + end_row: number, +) => Promise; + +/** + * Column state for rendering a single column. + */ +export interface ColumnState { + column_name: CellScalar[]; + cidx: number; + column_data: T[]; + column_data_listener_metadata?: unknown[]; + row_headers?: CellScalar[][]; + first_col: boolean; +} + +export type RowHeaderColumnState = ColumnState; + +/** + * Result from drawing row headers + */ +export interface RowHeadersResult { + cont_body: BodyDrawResult; + first_col: boolean; + _virtual_x: number; + last_cells: CellTuple[]; +} + +/** + * Tuple of cell element and its metadata for autosizing + */ +export type CellTuple = [HTMLTableCellElement, CellMetadata | undefined]; + +/** + * Result from drawing header cells + */ +export interface HeaderDrawResult { + th: HTMLTableCellElement; + metadata: CellMetadata; +} + +/** + * Result from drawing body cells + */ +export interface BodyDrawResult { + tds: Array<{ td: HTMLTableCellElement; metadata: CellMetadata }>; + row_height?: number; + ridx?: number; +} + +/** + * Result from drawing a data column (header and body) + */ +export interface DataColumnDrawResult { + cont_head: HeaderDrawResult | undefined; + cont_body: BodyDrawResult; +} + +/** + * Result from fetching missing columns + */ +export interface FetchResult { + column_header_merge_depth: number | undefined; + merge_headers: "both" | "row" | "column" | undefined; +} + +/** + * Column sizing information for the table + */ +export interface ColumnSizes { + auto: (number | undefined)[]; + override: Record; + indices: (number | undefined)[]; + row_height?: number; +} diff --git a/src/js/utils.js b/src/ts/utils.ts similarity index 70% rename from src/js/utils.js rename to src/ts/utils.ts index 8a0d5b86..bccb3432 100644 --- a/src/js/utils.js +++ b/src/ts/utils.ts @@ -9,6 +9,8 @@ // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +import { FPSRecord } from "./types"; + /****************************************************************************** * * Profling @@ -19,7 +21,7 @@ let AVG = 0, TOTAL = 0, START = performance.now(); -export function get_draw_fps() { +export function get_draw_fps(): FPSRecord { const now = performance.now(); const elapsed = now - START; const avg = AVG; @@ -32,7 +34,7 @@ export function get_draw_fps() { return { avg, real_fps, virtual_fps, num_frames, elapsed }; } -export function log_perf(x) { +export function log_perf(x: number) { AVG = (AVG * TOTAL + x) / (TOTAL + 1); TOTAL += 1; } @@ -43,53 +45,19 @@ export function log_perf(x) { * */ -/** - * A class method decorate for memoizing method results. Only works on one - * arg. - */ -export function memoize(_target, _property, descriptor) { - const cache = new Map(); - const func = descriptor.value; - descriptor.value = new_func; - return descriptor; - function new_func(arg) { - if (cache.has(arg)) { - return cache.get(arg); - } else { - const res = func.call(this, arg); - cache.set(arg, res); - return res; - } - } -} - -/** - * Identical to a non-tagger template literal, this is only used to indicate to - * babel that this string should be HTML-minified on production builds. - */ -export const html = (strings, ...args) => - strings - .map((str, i) => [str, args[i]]) - .flat() - .filter((a) => !!a) - .join(""); - -const invertPromise = () => { - let _resolve; - const promise = new Promise((resolve) => { - _resolve = resolve; - }); - promise.resolve = _resolve; - return promise; -}; -const TAGS = new Map(); +const TAGS: Map> = new Map(); -export async function flush_tag(tag) { +export async function flush_tag( + tag: Element, +): Promise | undefined> { await new Promise(requestAnimationFrame); return await TAGS.get(tag); } -export async function throttle_tag(tag, f) { +export async function throttle_tag( + tag: Element, + f: () => Promise, +): Promise { if (TAGS.has(tag)) { await TAGS.get(tag); if (TAGS.has(tag)) { @@ -98,12 +66,12 @@ export async function throttle_tag(tag, f) { } } - TAGS.set(tag, invertPromise()); + TAGS.set(tag, Promise.withResolvers()); try { return await f(); } finally { const l = TAGS.get(tag); TAGS.delete(tag); - l.resolve(); + l?.resolve(undefined); } } diff --git a/src/js/view_model.js b/src/ts/view_model.ts similarity index 72% rename from src/js/view_model.js rename to src/ts/view_model.ts index 145979e2..01f50098 100644 --- a/src/js/view_model.js +++ b/src/ts/view_model.ts @@ -10,6 +10,7 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { METADATA_MAP } from "./constants"; +import { CellMetadata, ColumnSizes } from "./types"; /****************************************************************************** * @@ -18,7 +19,10 @@ import { METADATA_MAP } from "./constants"; */ class ElemFactory { - constructor(name) { + private _name: string; + private _elements: HTMLElement[]; + private _index: number; + constructor(name: string) { this._name = name; this._elements = []; this._index = 0; @@ -40,7 +44,18 @@ class ElemFactory { } export class ViewModel { - constructor(column_sizes, container, table) { + protected _column_sizes: ColumnSizes; + protected _container: HTMLElement; + public _span_factory: ElemFactory; + public table: HTMLElement; + public cells: (HTMLTableCellElement | undefined)[][]; + public rows: HTMLTableRowElement[]; + + constructor( + column_sizes: ColumnSizes, + container: HTMLElement, + table: HTMLElement, + ) { this._column_sizes = column_sizes; this._container = container; this._span_factory = new ElemFactory("span"); @@ -49,32 +64,37 @@ export class ViewModel { this.rows = []; } - num_columns() { + num_columns(): number { return this._get_row(Math.max(0, this.rows.length - 1)).row_container .length; } - num_rows() { + num_rows(): number { return this.cells.length; } - _set_metadata(td, metadata) { + _set_metadata(td: HTMLTableCellElement, metadata: CellMetadata): void { METADATA_MAP.set(td, metadata); } - _get_or_create_metadata(td) { + _get_or_create_metadata( + td: HTMLTableCellElement | undefined, + ): CellMetadata { if (td === undefined) { - return {}; + return { value: undefined }; } else if (METADATA_MAP.has(td)) { return METADATA_MAP.get(td); } else { - const metadata = {}; + const metadata: CellMetadata = { value: undefined }; METADATA_MAP.set(td, metadata); return metadata; } } - _replace_cell(ridx, cidx) { + _replace_cell( + ridx: number, + cidx: number, + ): HTMLTableCellElement | undefined { const { tr, row_container } = this._get_row(ridx); let td = row_container[cidx]; if (td) { @@ -84,7 +104,7 @@ export class ViewModel { return td; } - _fetch_cell(ridx, cidx) { + _fetch_cell(ridx: number, cidx: number): HTMLTableCellElement | undefined { if (ridx < 0 || cidx < 0) { return; } @@ -92,7 +112,10 @@ export class ViewModel { return this.cells[ridx]?.[cidx]; } - _get_row(ridx) { + _get_row(ridx: number): { + tr: HTMLTableRowElement; + row_container: (HTMLTableCellElement | undefined)[]; + } { let tr = this.rows[ridx]; if (!tr) { tr = this.rows[ridx] = document.createElement("tr"); @@ -107,23 +130,25 @@ export class ViewModel { return { tr, row_container }; } - _get_cell(tag = "TD", ridx, cidx) { + _get_cell(tag = "TD", ridx: number, cidx: number): HTMLTableCellElement { const { tr, row_container } = this._get_row(ridx); let td = row_container[cidx]; if (!td) { if (cidx < row_container.length) { - td = row_container[cidx] = document.createElement(tag); - tr.insertBefore( - td, - row_container.slice(cidx + 1).find((x) => x), - ); + td = row_container[cidx] = document.createElement( + tag, + ) as HTMLTableCellElement; + const nextCell = row_container.slice(cidx + 1).find((x) => x); + tr.insertBefore(td, nextCell || null); } else { - td = row_container[cidx] = document.createElement(tag); + td = row_container[cidx] = document.createElement( + tag, + ) as HTMLTableCellElement; tr.appendChild(td); } } if (td.tagName !== tag) { - const new_td = document.createElement(tag); + const new_td = document.createElement(tag) as HTMLTableCellElement; tr.replaceChild(new_td, td); this.cells[ridx].splice(cidx, 1, new_td); td = new_td; @@ -131,11 +156,15 @@ export class ViewModel { return td; } - _clean_columns(cidx) { + _clean_columns(cidx: number | number[]): void { for (let i = 0; i < this.rows.length; i++) { const tr = this.rows[i]; const row_container = this.cells[i]; - this.cells[i] = row_container.slice(0, cidx[i] || cidx); + this.cells[i] = row_container.slice( + 0, + (Array.isArray(cidx) ? cidx[i] : cidx) || 0, + ); + const idx = this.cells[i].filter((x) => x !== undefined).length; while (tr.children[idx]) { tr.removeChild(tr.children[idx]); @@ -143,7 +172,7 @@ export class ViewModel { } } - _clean_rows(ridx) { + _clean_rows(ridx: number): void { while (this.table.children[ridx]) { this.table.removeChild(this.table.children[ridx]); } diff --git a/test/examples/file_browser.test.js b/test/examples/file_browser.test.js deleted file mode 100644 index ae18e672..00000000 --- a/test/examples/file_browser.test.js +++ /dev/null @@ -1,96 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("file_browser.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 400, height: 100 }); - }); - - describe("creates a `
` + * elements within `
` body when attached to `document`", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/file_browser.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - x.textContent.trim(), - ), - first_tr, - ); - expect(cell_values.slice(0, 1)).toEqual(["Dir_0"]); - expect(cell_values.length).toEqual(5); - }); - }); - - describe("when a directory row header is clicked", () => { - describe("in a collapsed state", () => { - beforeAll(async () => { - let dir = await page.$( - "regular-table tbody tr:first-child th:last-of-type", - ); - await dir.click(); - }); - - afterAll(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = 0; - await table.draw(); - }, table); - }); - - test("it expands, inserting contents in the next rows", async () => { - const first_row_header_icon = await page.$( - "regular-table tbody tr:nth-child(2) th:last-of-type", - ); - const icon_text = await page.evaluate( - (x) => x.innerText, - first_row_header_icon, - ); - expect(icon_text).toEqual("Dir_0"); - }); - }); - - describe("in an expanded state", () => { - beforeAll(async () => { - let dir = await page.$( - "regular-table tbody tr:first-child th:last-of-type", - ); - await dir.click(); - }); - - afterAll(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = 0; - await table.draw(); - }, table); - }); - - test("it collapses, removing the inserted elements", async () => { - const first_row_header_icon = await page.$( - "regular-table tbody tr:nth-child(2) th:last-of-type", - ); - const icon_text = await page.evaluate( - (x) => x.innerText, - first_row_header_icon, - ); - expect(icon_text).toEqual("Dir_1"); - }); - }); - }); -}); diff --git a/test/examples/react.test.js b/test/examples/react.test.js deleted file mode 100644 index 639ffb3a..00000000 --- a/test/examples/react.test.js +++ /dev/null @@ -1,42 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("react.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 200, height: 100 }); - }); - - describe("creates a `
` body when attached to `document`", () => { - beforeAll(async () => { - await page.goto("http://localhost:8081/dist/examples/react.html"); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(5); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["Group 0", "Row 0", "0", "1"]); - }); - }); -}); diff --git a/test/examples/spreadsheet.test.js b/test/examples/spreadsheet.test.js deleted file mode 100644 index c8844e50..00000000 --- a/test/examples/spreadsheet.test.js +++ /dev/null @@ -1,400 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("spreadsheet.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 150, height: 150 }); - }); - - describe("Navigating with the arrow keys", () => { - const sayHello = async (table) => { - await page.evaluate(async (table) => { - const target = document.activeElement; - target.textContent = "Hello, World!"; - const event = document.createEvent("HTMLEvents"); - event.initEvent("keypress", false, true); - event.ctrlKey = true; - event.keyCode = 13; - target.dispatchEvent(event); - await table.draw(); - }, table); - }; - - const keypressReturn = async (table, times = 1) => { - await Promise.all( - Array.from(Array(times)).map(async () => { - await page.evaluate(async (table) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keypress", false, true); - event.ctrlKey = true; - event.keyCode = 13; - table.dispatchEvent(event); - await table.draw(); - }, table); - }), - ); - }; - - const keydownLeftArrow = async (table, times = 1) => { - await Promise.all( - Array.from(Array(times)).map(async () => { - await page.evaluate(async (table) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.keyCode = 37; - table.dispatchEvent(event); - await table.draw(); - }, table); - }), - ); - }; - - const keydownUpArrow = async (table, times = 1) => { - await Promise.all( - Array.from(Array(times)).map(async () => { - await page.evaluate(async (table) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.keyCode = 38; - table.dispatchEvent(event); - await table.draw(); - }, table); - }), - ); - }; - - const keydownRightArrow = async (table, times = 1) => { - Array.from(Array(times)).forEach(async () => { - await page.evaluate(async (table) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.keyCode = 39; - table.dispatchEvent(event); - await table.draw(); - }, table); - }); - }; - - const keydownDownArrow = async (table, times = 1) => { - await Promise.all( - Array.from(Array(times)).map(async () => { - await page.evaluate(async (table) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.keyCode = 40; - table.dispatchEvent(event); - await table.draw(); - }, table); - }), - ); - }; - - beforeEach(async () => { - await page.goto( - "http://localhost:8081/dist/examples/spreadsheet.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("initializes with focus on (0,0)", async () => { - const table = await page.$("regular-table"); - await sayHello(table); - const tr = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - const cell_values = []; - for (const td of tr) { - cell_values.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(cell_values).toEqual([ - "Hello, World!", - "", - "", - "", - "", - "", - "", - ]); - }); - - // // Skip due to puppeteer text measure float between CI and local - // test("scrolls as right arrow is down", async () => { - // const table = await page.$("regular-table"); - // await keydownRightArrow(table, 5); - // await sayHello(table); - // const tds = await page.$$( - // "regular-table tbody tr:nth-of-type(1) td", - // ); - // const cells = []; - // for (const td of tds) { - // cells.push(await page.evaluate((td) => td.innerHTML, td)); - // } - - // expect(cells.slice(cells.length - 5)).toEqual([ - // "", - // "", - // "", - // "", - // "Hello, World!", - // ]); - - // const ths = await page.$$( - // "regular-table tbody tr:nth-of-type(1) th", - // ); - // const th_value = await page.evaluate((th) => th.innerHTML, ths[0]); - // expect(th_value).toEqual("0"); - // }); - - test("scrolls as down arrow is down", async () => { - const table = await page.$("regular-table"); - await keydownDownArrow(table, 5); - await sayHello(table); - - const tds = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const cells = []; - for (const td of tds) { - cells.push(await page.evaluate((td) => td.innerHTML, td)); - } - - expect(cells).toEqual(["Hello, World!", "", "", "", "", "", ""]); - - const ths = await page.$$( - "regular-table tbody tr:nth-of-type(3) th", - ); - const th = await page.evaluate((th) => th.innerHTML, ths[0]); - expect(th).toEqual("5"); - }); - - test("scrolls down and back up", async () => { - const table = await page.$("regular-table"); - await keydownDownArrow(table, 5); - await keydownUpArrow(table, 2); - await sayHello(table); - - const tds = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const cells = []; - for (const td of tds) { - cells.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(cells).toEqual(["Hello, World!", "", "", "", "", "", ""]); - - const ths = await page.$$( - "regular-table tbody tr:nth-of-type(3) th", - ); - const th = await page.evaluate((th) => th.innerHTML, ths[0]); - expect(th).toEqual("3"); - }); - - test("scrolls right and back left", async () => { - const table = await page.$("regular-table"); - await keydownRightArrow(table, 10); - await keydownLeftArrow(table, 7); - await sayHello(table); - - const tds = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - const cells = []; - for (const td of tds) { - cells.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(cells).toEqual(["", "", "", "Hello, World!", "", "", ""]); - - const ths = await page.$$( - "regular-table tbody tr:nth-of-type(1) th", - ); - const th = await page.evaluate((th) => th.innerHTML, ths[0]); - expect(th).toEqual("0"); - }); - - test("scrolls as return is pressed", async () => { - const table = await page.$("regular-table"); - await keypressReturn(table, 4); - await sayHello(table); - - const tds = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const cells = []; - for (const td of tds) { - cells.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(cells).toEqual(["Hello, World!", "", "", "", "", "", ""]); - - const ths = await page.$$( - "regular-table tbody tr:nth-of-type(3) th", - ); - const th = await page.evaluate((th) => th.innerHTML, ths[0]); - expect(th).toEqual("4"); - }); - }); - - describe("Makes a simple edit", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/spreadsheet.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - const cell = - table.querySelector("table tbody").children[2].children[1]; - cell.textContent = "Hello, World!"; - cell.focus(); - const event = document.createEvent("HTMLEvents"); - event.initEvent("keypress", false, true); - event.ctrlKey = true; - event.keyCode = 13; - table.dispatchEvent(event); - }, table); - }); - - test("displays edited input", async () => { - const tr = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const cell_values = []; - for (const td of tr) { - cell_values.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(cell_values).toEqual([ - "Hello, World!", - "", - "", - "", - "", - "", - "", - ]); - }); - - test("next cell has focus", async () => { - const contents = await page.evaluate( - () => document.activeElement.textContent, - ); - expect(contents).toEqual(""); - }); - - describe("on scroll", () => { - beforeAll(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = table.scrollTop + 30; - await table.draw(); - }, table); - }); - - test("preserves input", async () => { - const tr = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - const cell_values = []; - for (const td of tr) { - cell_values.push( - await page.evaluate((td) => td.innerHTML, td), - ); - } - expect(cell_values).toEqual([ - "Hello, World!", - "", - "", - "", - "", - "", - "", - ]); - }); - }); - }); - - describe("Evaluates an expression", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/spreadsheet.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - for (const [x, y, v] of [ - [2, 1, "1"], - [3, 1, "2"], - [3, 2, "=sum(A0..A3)"], - ]) { - const cell = - table.querySelector("table tbody").children[x].children[ - y - ]; - cell.textContent = v; - cell.focus(); - const event = document.createEvent("HTMLEvents"); - event.initEvent("keypress", false, true); - event.ctrlKey = true; - event.keyCode = 13; - table.dispatchEvent(event); - await new Promise((x) => setTimeout(x, 100)); - } - }, table); - }); - - test("displays evaluated expression", async () => { - const tr2 = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const tds2 = []; - for (const td of tr2) { - tds2.push(await page.evaluate((td) => td.innerHTML, td)); - } - - expect(tds2).toEqual(["2", "", "", ""]); - const tr3 = await page.$$( - "regular-table tbody tr:nth-of-type(4) td", - ); - const tds3 = []; - for (const td of tr3) { - tds3.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(tds3).toEqual(["", "1", "", ""]); - }); - - describe("on scroll", () => { - beforeAll(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = table.scrollTop + 25; - await table.draw(); - }, table); - }); - - test("preserves evaluated expression", async () => { - const tr1 = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - const tds1 = []; - for (const td of tr1) { - tds1.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(tds1).toEqual(["2", "", "", ""]); - const tr2 = await page.$$( - "regular-table tbody tr:nth-of-type(3) td", - ); - const tds2 = []; - for (const td of tr2) { - tds2.push(await page.evaluate((td) => td.innerHTML, td)); - } - expect(tds2).toEqual(["", "1", "", ""]); - }); - }); - }); -}); diff --git a/test/examples/two_billion_rows.test.js b/test/examples/two_billion_rows.test.js deleted file mode 100644 index 53a437e5..00000000 --- a/test/examples/two_billion_rows.test.js +++ /dev/null @@ -1,221 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("two_billion_rows.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 200, height: 100 }); - }); - - describe("creates a `
` body when attached to `document`", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(5); - }); - - test("with the correct # of columns", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const num_cells = await page.evaluate( - (first_tr) => first_tr.children.length, - first_tr, - ); - expect(num_cells).toEqual(4); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["Group 0", "Row 0", "0", "1"]); - }); - }); - - describe("scrolls down", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = 1000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(4); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual([ - "Group 200,000", - "Row 200,001", - "200,001", - "200,002", - "200,003", - ]); - }); - }); - - describe("scrolls right", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollLeft = 1000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of columns", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const num_cells = await page.evaluate( - (first_tr) => first_tr.children.length, - first_tr, - ); - expect(num_cells).toEqual(4); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["Group 0", "Row 0", "16", "17"]); - }); - }); - - describe("scrolls via scrollToCell() method", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test.skip("https://github.com/finos/regular-table/issues/15", async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollToCell(0, 250500, 1000, 2000000000); - await table.draw({ invalid_viewport: true }); - }, table); - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["250,501", "250,502", "250,503"]); - }); - }); - - describe("scrolls down beyond bottom threshold", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = table.scrollHeight + 100000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(3); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual([ - "Group 1,999,999,990", - "Row 1,999,999,997", - "1,999,999,997", - "1,999,999,998", - "1,999,999,999", - ]); - }); - }); - - describe("scrolls rights beyond right border threshold", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/examples/two_billion_rows.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollLeft = table.scrollWidth + 100000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of columns", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const num_cells = await page.evaluate( - (first_tr) => first_tr.children.length, - first_tr, - ); - expect(num_cells).toEqual(4); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["Group 0", "Row 0", "998", "999"]); - }); - }); -}); diff --git a/test/examples/web_worker.test.js b/test/examples/web_worker.test.js deleted file mode 100644 index 5b0773ff..00000000 --- a/test/examples/web_worker.test.js +++ /dev/null @@ -1,71 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("web_worker.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 250, height: 100 }); - }); - - // TODO don't run these, they depend on unpkg.com - describe("creates a `
` body when attached to `document`", () => { - beforeAll(async () => { - await page.goto("http://localhost:8081/examples/web_worker.html"); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(5); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["0", "1", "2", "3"]); - }); - }); - - describe("scrolls down", () => { - beforeAll(async () => { - await page.goto("http://localhost:8081/examples/web_worker.html"); - const table = await page.$("regular-table"); - await page.waitForSelector("regular-table table tbody tr td"); - await page.evaluate(async (table) => { - table.scrollTop = 1000; - await table.draw(); - }, table); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual([ - "200,001", - "200,002", - "200,003", - "200,004", - "200,005", - ]); - }); - }); -}); diff --git a/test/features/api.test.js b/test/features/api.test.js deleted file mode 100644 index 138fb520..00000000 --- a/test/features/api.test.js +++ /dev/null @@ -1,48 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("JS API", () => { - beforeAll(async () => { - await page.goto("http://localhost:8081/test/features/api.html"); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("preserve_state works", async () => { - const table = await page.$("regular-table"); - const meta = await page.evaluate(async (table) => { - await table.draw(); - table._column_sizes.override[0] = 200; - await table.draw(); - table.setDataListener(window.dataListener, { - preserve_state: true, - }); - - await table.draw(); - return JSON.stringify(table.getMeta(document.querySelector("td"))); - }, table); - - expect(JSON.parse(meta)).toEqual({ - column_header: ["Group 0", "Column 0"], - row_header: ["Group 0", "Row 0"], - dx: 0, - dy: 0, - size_key: 2, - _virtual_x: 2, - value: "0", - x: 0, - x0: 0, - x1: 15, - y: 0, - y0: 0, - y1: 29, - }); - }); -}); diff --git a/test/features/area_clipboard.test.js b/test/features/area_clipboard.test.js deleted file mode 100644 index eaed20ca..00000000 --- a/test/features/area_clipboard.test.js +++ /dev/null @@ -1,348 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe.skip("area_clipboard.html", () => { - const cellValues = async (cssClass) => { - const selectedCells = await page.$$( - `regular-table tbody tr td.${cssClass}`, - ); - const values = []; - for (const td of selectedCells) { - values.push(await page.evaluate((td) => td.innerHTML, td)); - } - return values; - }; - - const positions = async (cssClass) => { - const table = await page.$("regular-table"); - let metas = []; - const selectedCells = await page.$$( - `regular-table tbody tr td.${cssClass}`, - ); - for (const td of selectedCells) { - metas.push( - await page.evaluate( - (table, td) => table.getMeta(td), - table, - td, - ), - ); - } - return metas.map(({ x, y }) => [x, y]); - }; - - const draw = async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - await table.draw(); - }, table); - }; - - const copy = async (multi = true) => { - const table = await page.$("regular-table"); - await page.evaluate( - async (table, multi) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.ctrlKey = multi; - event.keyCode = 67; - table.dispatchEvent(event); - }, - table, - multi, - ); - }; - - const cut = async (multi = true) => { - const table = await page.$("regular-table"); - await page.evaluate( - async (table, multi) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.ctrlKey = multi; - event.keyCode = 88; - table.dispatchEvent(event); - }, - table, - multi, - ); - }; - - const paste = async (multi = true) => { - const table = await page.$("regular-table"); - await page.evaluate( - async (table, multi) => { - const event = document.createEvent("HTMLEvents"); - event.initEvent("keydown", false, true); - event.ctrlKey = multi; - event.keyCode = 86; - table.dispatchEvent(event); - }, - table, - multi, - ); - }; - - const makeSelection = async (el1, el2, multi = false) => { - await page.evaluate( - async (td, multi) => { - const event = new MouseEvent("mousedown", { - bubbles: true, - ctrlKey: multi, - }); - td.dispatchEvent(event); - }, - el1, - multi, - ); - - await page.evaluate( - async (td, multi) => { - const event = new MouseEvent("mouseup", { - bubbles: true, - ctrlKey: multi, - }); - td.dispatchEvent(event); - }, - el2, - multi, - ); - }; - - beforeEach(async () => { - await page.setViewport({ width: 100, height: 100 }); - // const context = await browser.defaultBrowserContext(); - // await context.overridePermissions("http://localhost:8081/dist/examples/area_clipboard.html", ["clipboard-write", "clipboard-read"]); - await page.goto( - "http://localhost:8081/dist/features/area_clipboard.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("copy/paste", () => { - test("copies one area", async () => { - const col1Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - const col2Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(2)", - ); - - makeSelection(col1Tds[0], col2Tds[1]); - await copy(); - - const selectedCells = await cellValues("mouse-selected-area"); - expect(selectedCells).toEqual(["0, 0", "1, 0", "0, 1", "1, 1"]); - - makeSelection(col1Tds[2], col1Tds[2]); - - await paste(); - - expect(await positions("clipboard-paste-selected-area")).toEqual([ - [0, 2], - [1, 2], - [0, 3], - [1, 3], - ]); - - expect(await cellValues("clipboard-paste-selected-area")).toEqual([ - "0, 0", - "1, 0", - "0, 1", - "1, 1", - ]); - }); - - test("copies multi select areas to multiple targets", async () => { - const col1Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - const col2Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(2)", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[0]); - - makeSelection(col2Tds[2], col2Tds[2], true); - - await copy(); - - await draw(); - - expect(await positions("clipboard-copy-selected-area")).toEqual([ - [0, 0], - [1, 2], - ]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - makeSelection(col2Tds[1], col2Tds[1], true); - - await draw(); - - expect(await positions("mouse-selected-area")).toEqual([ - [0, 1], - [1, 1], - ]); - - await paste(); - - await draw(); - - expect(await positions("clipboard-paste-selected-area")).toEqual([ - [0, 1], - [1, 1], - ]); - - expect(await cellValues("clipboard-paste-selected-area")).toEqual([ - "0, 0", - "1, 2", - ]); - }); - - test("repeates on multi select area", async () => { - const col1Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - const col2Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(2)", - ); - - makeSelection(col1Tds[0], col1Tds[0]); - - await copy(); - - await draw(); - - expect(await positions("clipboard-copy-selected-area")).toEqual([ - [0, 0], - ]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - makeSelection(col2Tds[2], col2Tds[2], true); - - await draw(); - - expect(await positions("mouse-selected-area")).toEqual([ - [0, 1], - [1, 2], - ]); - - await paste(); - - await draw(); - - expect(await positions("clipboard-paste-selected-area")).toEqual([ - [0, 1], - [1, 2], - ]); - - expect(await cellValues("clipboard-paste-selected-area")).toEqual([ - "0, 0", - "0, 0", - ]); - }); - }); - - describe("cut/paste", () => { - test("cuts multi select areas to multiple targets", async () => { - const col1Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - const col2Tds = await page.$$( - "regular-table tbody tr td:nth-of-type(2)", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[0]); - - makeSelection(col2Tds[2], col2Tds[2], true); - - await cut(); - - await draw(); - - expect(await positions("clipboard-copy-selected-area")).toEqual([ - [0, 0], - [1, 2], - ]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, col1Tds[1]); - - makeSelection(col2Tds[1], col2Tds[1], true); - - await draw(); - - expect(await positions("mouse-selected-area")).toEqual([ - [0, 1], - [1, 1], - ]); - - await paste(); - - await draw(); - - expect(await positions("clipboard-paste-selected-area")).toEqual([ - [0, 1], - [1, 1], - ]); - - expect(await cellValues("clipboard-paste-selected-area")).toEqual([ - "0, 0", - "1, 2", - ]); - expect(await cellValues("clipboard-copy-selected-area")).toEqual([ - "", - "", - ]); - }); - }); -}); diff --git a/test/features/area_mouse_selection.test.js b/test/features/area_mouse_selection.test.js deleted file mode 100644 index 29dbe4cb..00000000 --- a/test/features/area_mouse_selection.test.js +++ /dev/null @@ -1,180 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("area_mouse_selection.html", () => { - const selectedCellValues = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-area", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 300, height: 300 }); - await page.goto( - "http://localhost:8081/dist/features/area_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("initial view", () => { - test("includes no selection", async () => { - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-area", - ); - expect(selectedCells.length).toEqual(0); - }); - }); - - describe("selecting one cell", () => { - test("includes one selection", async () => { - const tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, tds[0]); - - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-area", - ); - expect(selectedCells.length).toEqual(1); - }); - }); - - describe("selecting an area", () => { - test("selects along a row", async () => { - const row1Tds = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, row1Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, row1Tds[2]); - - expect(await selectedCellValues()).toEqual(["0", "1", "2"]); - }); - - test("selects along a column, bottom to top", async () => { - const row1Tds = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - const row5Tds = await page.$$( - "regular-table tbody tr:nth-of-type(5) td", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, row5Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, row1Tds[0]); - - expect(await selectedCellValues()).toEqual([ - "0", - "1", - "2", - "3", - "4", - ]); - }); - - test("selects in a rectangle, bottom-right to top-left", async () => { - const row2Tds = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - const row5Tds = await page.$$( - "regular-table tbody tr:nth-of-type(5) td", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, row5Tds[2]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, row2Tds[1]); - - expect(await selectedCellValues()).toEqual([ - "2", - "3", - "3", - "4", - "4", - "5", - "5", - "6", - ]); - }); - - test("keeps selection on CTRL", async () => { - const row2Tds = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, row2Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, row2Tds[1]); - - expect(await selectedCellValues()).toEqual(["1", "2"]); - - const row5Tds = await page.$$( - "regular-table tbody tr:nth-of-type(5) td", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { - bubbles: true, - ctrlKey: true, - }); - td.dispatchEvent(event); - }, row5Tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { - bubbles: true, - ctrlKey: true, - }); - td.dispatchEvent(event); - }, row5Tds[1]); - - expect(await selectedCellValues()).toEqual(["1", "2", "4", "5"]); - }); - }); -}); diff --git a/test/features/column_mouse_selection/selecting_column_headers.test.js b/test/features/column_mouse_selection/selecting_column_headers.test.js deleted file mode 100644 index 74b3a8dd..00000000 --- a/test/features/column_mouse_selection/selecting_column_headers.test.js +++ /dev/null @@ -1,123 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("column_mouse_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/column_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("initial view", () => { - test("includes defaults", async () => { - expect(await selectedColumns()).toEqual([ - "Column 6", - "Column 8", - "Column 9", - ]); - }); - }); - - describe("column selection", () => { - describe("selecting the origin header", () => { - test("includes no selection", async () => { - const ths = await page.$$("regular-table thead th"); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[0]); - - expect(await selectedColumns()).toEqual([]); - }); - }); - - describe("selecting the first column group", () => { - let ths; - - beforeAll(async () => { - ths = await page.$$("regular-table thead th"); - }); - - test("includes the group and the columns", async () => { - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[1]); - - expect(await selectedColumns()).toEqual([ - "Group 0", - "Column 0", - "Column 1", - "Column 2", - "Column 3", - "Column 4", - "Column 5", - "Column 6", - "Column 7", - "Column 8", - "Column 9", - ]); - }); - - test("splitting the group with ctrl", async () => { - expect(await selectedColumns()).toEqual([ - "Group 0", - "Column 0", - "Column 1", - "Column 2", - "Column 3", - "Column 4", - "Column 5", - "Column 6", - "Column 7", - "Column 8", - "Column 9", - ]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, ths[9]); - - expect(await selectedColumns()).toEqual([ - "Column 0", - "Column 1", - "Column 3", - "Column 4", - "Column 5", - "Column 6", - "Column 7", - "Column 8", - "Column 9", - ]); - }); - }); - }); -}); diff --git a/test/features/column_mouse_selection/selecting_one_column.test.js b/test/features/column_mouse_selection/selecting_one_column.test.js deleted file mode 100644 index ed07618b..00000000 --- a/test/features/column_mouse_selection/selecting_one_column.test.js +++ /dev/null @@ -1,69 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("column_mouse_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/column_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting one column", () => { - beforeAll(async () => { - const ths = await page.$$( - "regular-table thead tr:nth-of-type(2) th", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[4]); - }); - - test("selects the cells", async () => { - expect(await selectedColumns()).toEqual(["Column 2"]); - - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate( - (td) => - td.innerHTML - .trim() - .split(" ") - .slice(0, 2) - .join(" "), - td, - ), - ); - } - expect(selectedValues.length).toEqual(129); - }); - }); -}); diff --git a/test/features/column_mouse_selection/selecting_one_column_range.test.js b/test/features/column_mouse_selection/selecting_one_column_range.test.js deleted file mode 100644 index 1a27b7a9..00000000 --- a/test/features/column_mouse_selection/selecting_one_column_range.test.js +++ /dev/null @@ -1,65 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("column_mouse_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/column_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting a column range", () => { - let ths; - - beforeAll(async () => { - ths = await page.$$("regular-table thead th"); - }); - - test("selects the columns' headers and cells", async () => { - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[9]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, ths[11]); - - await page.waitForSelector( - "regular-table td.mouse-selected-column", - ); - expect(await selectedColumns()).toEqual([ - "Column 2", - "Column 3", - "Column 4", - ]); - }); - }); -}); diff --git a/test/features/column_mouse_selection/selecting_two_columns.test.js b/test/features/column_mouse_selection/selecting_two_columns.test.js deleted file mode 100644 index 72eeb4e1..00000000 --- a/test/features/column_mouse_selection/selecting_two_columns.test.js +++ /dev/null @@ -1,84 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("column_mouse_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/column_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting two columns", () => { - describe("without CTRL pressed", () => { - test("includes only the most recent selection", async () => { - const ths = await page.$$( - "regular-table thead tr:nth-of-type(2) th", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[3]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: false, - }); - th.dispatchEvent(event); - }, ths[5]); - - expect(await selectedColumns()).toEqual(["Column 3"]); - }); - }); - - describe("with CTRL pressed", () => { - test("includes both columns", async () => { - const ths = await page.$$( - "regular-table thead tr:nth-of-type(2) th", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[3]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, ths[5]); - - expect(await selectedColumns()).toEqual([ - "Column 1", - "Column 3", - ]); - }); - }); - }); -}); diff --git a/test/features/column_mouse_selection/splitting_one_column_range.test.js b/test/features/column_mouse_selection/splitting_one_column_range.test.js deleted file mode 100644 index 3c740764..00000000 --- a/test/features/column_mouse_selection/splitting_one_column_range.test.js +++ /dev/null @@ -1,87 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("column_mouse_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/column_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("splitting a column range", () => { - let ths; - - beforeAll(async () => { - ths = await page.$$("regular-table thead th"); - }); - - test("selects the columns' headers and cells", async () => { - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, ths[17]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, ths[13]); - - await page.waitForSelector( - "regular-table td.mouse-selected-column", - ); - expect(await selectedColumns()).toEqual([ - "Column 6", - "Column 7", - "Column 8", - "Column 9", - "Column 10", - ]); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, ths[15]); - - await page.waitForSelector( - "regular-table td.mouse-selected-column", - ); - expect(await selectedColumns()).toEqual([ - "Column 6", - "Column 7", - "Column 9", - "Column 10", - ]); - }); - }); -}); diff --git a/test/features/fixed_column_widths.test.js b/test/features/fixed_column_widths.test.js deleted file mode 100644 index 4dc6a9aa..00000000 --- a/test/features/fixed_column_widths.test.js +++ /dev/null @@ -1,119 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe.skip("fixed_column_widths.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 400, height: 100 }); - }); - - describe("creates a `
` with fixed column widths", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/dist/features/fixed_column_widths.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - test("fixed th has min-width", async () => { - const first_tr = await page.$("regular-table thead tr:first-child"); - const minWidths = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - getComputedStyle(x).getPropertyValue("min-width"), - ), - first_tr, - ); - const fixedWidth = minWidths[0]; - const notSetWidth = minWidths[1]; - expect(fixedWidth).toEqual("100px"); - expect(notSetWidth).toEqual("0px"); - }); - - test("fixed th has max-width", async () => { - const first_tr = await page.$("regular-table thead tr:first-child"); - const max_widths = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - getComputedStyle(x).getPropertyValue("max-width"), - ), - first_tr, - ); - const fixed_width = max_widths[0]; - const not_set_width = max_widths[1]; - expect(fixed_width).toEqual("100px"); - expect(not_set_width).toEqual("none"); - }); - - test("fixed td has min-width", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const minWidths = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - getComputedStyle(x).getPropertyValue("min-width"), - ), - first_tr, - ); - const fixedWidth = minWidths[0]; - const notSetWidth = minWidths[1]; - expect(fixedWidth).toEqual("100px"); - expect(notSetWidth).toEqual("0px"); - }); - - test("fixed td has max-width", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const max_widths = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - getComputedStyle(x).getPropertyValue("max-width"), - ), - first_tr, - ); - const fixed_width = max_widths[0]; - const not_set_width = max_widths[1]; - expect(fixed_width).toEqual("100px"); - expect(not_set_width).toEqual("none"); - }); - - test("cell value do not overflow", async () => { - const first_td = await page.$( - "regular-table tbody tr td:first-child", - ); - const { text_overflow, overflow, white_space } = - await page.evaluate((first_td) => { - first_td.text_content = "ABCDEFGHABCDEFGHABCDEFGHABCDEFGH"; - const styles = getComputedStyle(first_td); - return { - text_overflow: styles.getPropertyValue("text-overflow"), - overflow: styles.getPropertyValue("overflow"), - white_space: styles.getPropertyValue("white-space"), - }; - }, first_td); - expect(text_overflow).toEqual("ellipsis"); - expect(overflow).toEqual("hidden"); - expect(white_space).toEqual("nowrap"); - }); - - test("ths do not allow text selection", async () => { - const first_tr = await page.$("regular-table thead tr:first-child"); - const user_selects = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => - getComputedStyle(x).getPropertyValue("user-select"), - ), - first_tr, - ); - - // The viewport is 400px wide and the fixed columns are 100px - // accross, and ~20px of padding is four columns. - expect(user_selects).toEqual(["none", "none", "none", "none"]); - }); - }); -}); diff --git a/test/features/row_column_area_selection/area_selection.test.js b/test/features/row_column_area_selection/area_selection.test.js deleted file mode 100644 index a0ebbd27..00000000 --- a/test/features/row_column_area_selection/area_selection.test.js +++ /dev/null @@ -1,43 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe.skip("row_column_area_selection.html", () => { - beforeEach(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/examples/row_column_area_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting one cell", () => { - test("includes one selection", async () => { - const tds = await page.$$( - "regular-table tbody tr td:nth-of-type(1)", - ); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mousedown", { bubbles: true }); - td.dispatchEvent(event); - }, tds[0]); - - await page.evaluate(async (td) => { - const event = new MouseEvent("mouseup", { bubbles: true }); - td.dispatchEvent(event); - }, tds[0]); - - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-area", - ); - expect(selectedCells.length).toEqual(1); - }); - }); -}); diff --git a/test/features/row_column_area_selection/column_selection.test.js b/test/features/row_column_area_selection/column_selection.test.js deleted file mode 100644 index 392da98e..00000000 --- a/test/features/row_column_area_selection/column_selection.test.js +++ /dev/null @@ -1,65 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe.skip("row_column_area_selection.html", () => { - const selectedColumns = async () => { - const selectedCells = await page.$$( - "regular-table thead th.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate((td) => td.firstChild.innerHTML, td), - ); - } - return selectedValues; - }; - - beforeEach(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/examples/row_column_area_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting one column", () => { - test("selects the cells", async () => { - const ths = await page.$$( - "regular-table thead tr:nth-of-type(2) th", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[4]); - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-column", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate( - (td) => - td.innerHTML - .trim() - .split(" ") - .slice(0, 2) - .join(" "), - td, - ), - ); - } - expect(selectedValues.length > 0).toEqual(true); - expect(await selectedColumns()).toEqual(["Column 2"]); - }); - }); -}); diff --git a/test/features/row_column_area_selection/row_selection.test.js b/test/features/row_column_area_selection/row_selection.test.js deleted file mode 100644 index fd9307c7..00000000 --- a/test/features/row_column_area_selection/row_selection.test.js +++ /dev/null @@ -1,63 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe.skip("row_column_area_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - let ths; - - beforeEach(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/examples/row_column_area_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - ths = await page.$$("regular-table tbody tr th:nth-of-type(2)"); - }); - - describe("selecting one row", () => { - test("selects the cells", async () => { - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[0]); - - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push( - await page.evaluate( - (td) => - td.innerHTML - .trim() - .split(" ") - .slice(0, 2) - .join(" "), - td, - ), - ); - } - expect(selectedValues.length > 0).toEqual(true); - expect(await selectedRows()).toEqual(["Row 0"]); - }); - }); -}); diff --git a/test/features/row_mouse_selection/selecting_grouped_row_headers.test.js b/test/features/row_mouse_selection/selecting_grouped_row_headers.test.js deleted file mode 100644 index 98bc562e..00000000 --- a/test/features/row_mouse_selection/selecting_grouped_row_headers.test.js +++ /dev/null @@ -1,131 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting a group range", () => { - describe("both selections are group headers", () => { - test("selects the groups' headers, rows' headers and cells", async () => { - const groupHeader0 = await page.$( - "regular-table tbody tr th:nth-of-type(1)", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, groupHeader0); - - const ths = await page.$$("regular-table tbody th"); - const groupHeader10 = ths[11]; - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, groupHeader10); - - await page.waitForSelector( - "regular-table td.mouse-selected-row", - ); - expect(await selectedRows()).toEqual([ - "Group 0", - "Row 0", - "Row 1", - "Row 2", - "Row 3", - "Row 4", - "Row 5", - "Row 6", - "Row 7", - "Row 8", - "Row 9", - "Group 10", - "Row 10", - "Row 11", - "Row 12", - "Row 13", - "Row 14", - "Row 15", - "Row 16", - "Row 17", - "Row 18", - "Row 19", - ]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[8]); - }); - }); - - describe("second selection is a row header", () => { - test("selects the rows' headers and cells", async () => { - const groupHeader0 = await page.$( - "regular-table tbody tr th:nth-of-type(1)", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, groupHeader0); - - const rowHeader11 = await page.$( - "regular-table tbody tr:nth-of-type(12) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, rowHeader11); - - await page.waitForSelector( - "regular-table td.mouse-selected-row", - ); - expect(await selectedRows()).toEqual([ - "Row 0", - "Row 1", - "Row 2", - "Row 3", - "Row 4", - "Row 5", - "Row 6", - "Row 7", - "Row 8", - "Row 9", - "Row 10", - "Row 11", - ]); - }); - }); - }); -}); diff --git a/test/features/row_mouse_selection/selecting_one_row.test.js b/test/features/row_mouse_selection/selecting_one_row.test.js deleted file mode 100644 index 74f07256..00000000 --- a/test/features/row_mouse_selection/selecting_one_row.test.js +++ /dev/null @@ -1,58 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting one row", () => { - test("selects the row header and cells then deselects", async () => { - const rowHeader1 = await page.$( - "regular-table tbody tr:nth-of-type(2) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, rowHeader1); - await page.waitForSelector("regular-table td.mouse-selected-row"); - const selectedCells = await page.$$( - "regular-table tbody tr td.mouse-selected-row", - ); - expect(await selectedRows()).toEqual(["Row 1"]); - expect(selectedCells.length > 0).toEqual(true); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, rowHeader1); - expect(await selectedRows()).toEqual([]); - }); - }); -}); diff --git a/test/features/row_mouse_selection/selecting_one_row_range.test.js b/test/features/row_mouse_selection/selecting_one_row_range.test.js deleted file mode 100644 index 8cc3ed77..00000000 --- a/test/features/row_mouse_selection/selecting_one_row_range.test.js +++ /dev/null @@ -1,57 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting a row range", () => { - test("selects the rows' headers and cells", async () => { - const rowHeader1 = await page.$( - "regular-table tbody tr:nth-of-type(2) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, rowHeader1); - - const rowHeader3 = await page.$( - "regular-table tbody tr:nth-of-type(4) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, rowHeader3); - - await page.waitForSelector("regular-table td.mouse-selected-row"); - expect(await selectedRows()).toEqual(["Row 1", "Row 2", "Row 3"]); - }); - }); -}); diff --git a/test/features/row_mouse_selection/selecting_row_headers.test.js b/test/features/row_mouse_selection/selecting_row_headers.test.js deleted file mode 100644 index 9a0a016c..00000000 --- a/test/features/row_mouse_selection/selecting_row_headers.test.js +++ /dev/null @@ -1,105 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("initial view", () => { - test("includes no selection", async () => { - expect(await selectedRows()).toEqual([]); - }); - }); - - describe("row selection", () => { - describe("selecting one row group", () => { - test("includes the group and the rows", async () => { - const groupHeader0 = await page.$( - "regular-table tbody tr th:nth-of-type(1)", - ); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, groupHeader0); - - expect(await selectedRows()).toEqual([ - "Group 0", - "Row 0", - "Row 1", - "Row 2", - "Row 3", - "Row 4", - "Row 5", - "Row 6", - "Row 7", - "Row 8", - "Row 9", - ]); - }); - - test("splitting the group with ctrl", async () => { - expect(await selectedRows()).toEqual([ - "Group 0", - "Row 0", - "Row 1", - "Row 2", - "Row 3", - "Row 4", - "Row 5", - "Row 6", - "Row 7", - "Row 8", - "Row 9", - ]); - - const rowHeader3 = await page.$( - "regular-table tbody tr:nth-of-type(4) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, rowHeader3); - - expect(await selectedRows()).toEqual([ - "Row 0", - "Row 1", - "Row 2", - "Row 4", - "Row 5", - "Row 6", - "Row 7", - "Row 8", - "Row 9", - ]); - }); - }); - }); -}); diff --git a/test/features/row_mouse_selection/selecting_two_rows.test.js b/test/features/row_mouse_selection/selecting_two_rows.test.js deleted file mode 100644 index 40f98174..00000000 --- a/test/features/row_mouse_selection/selecting_two_rows.test.js +++ /dev/null @@ -1,75 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("selecting two rows", () => { - describe("without CTRL pressed", () => { - test("includes only the most recent selection", async () => { - const ths = await page.$$("regular-table tbody tr th"); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[3]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: false, - }); - th.dispatchEvent(event); - }, ths[5]); - - expect(await selectedRows()).toEqual(["Row 4"]); - }); - }); - - describe("with CTRL pressed", () => { - test("includes the rows", async () => { - const ths = await page.$$("regular-table tbody tr th"); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, ths[3]); - - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, ths[5]); - - expect(await selectedRows()).toEqual(["Row 2", "Row 4"]); - }); - }); - }); -}); diff --git a/test/features/row_mouse_selection/splitting_one_row_range.test.js b/test/features/row_mouse_selection/splitting_one_row_range.test.js deleted file mode 100644 index 41551e8d..00000000 --- a/test/features/row_mouse_selection/splitting_one_row_range.test.js +++ /dev/null @@ -1,68 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_mouse_selection.html", () => { - const selectedRows = async () => { - const selectedCells = await page.$$( - "regular-table tbody tr th.mouse-selected-row", - ); - const selectedValues = []; - for (const td of selectedCells) { - selectedValues.push(await page.evaluate((td) => td.innerHTML, td)); - } - return selectedValues; - }; - - beforeAll(async () => { - await page.setViewport({ width: 2500, height: 2500 }); - await page.goto( - "http://localhost:8081/dist/features/row_mouse_selection.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("splitting a row range", () => { - test("selects the rows' headers and cells", async () => { - const rowHeader1 = await page.$( - "regular-table tbody tr:nth-of-type(2) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { bubbles: true }); - th.dispatchEvent(event); - }, rowHeader1); - - const rowHeader3 = await page.$( - "regular-table tbody tr:nth-of-type(4) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - shiftKey: true, - }); - th.dispatchEvent(event); - }, rowHeader3); - - const rowHeader2 = await page.$( - "regular-table tbody tr:nth-of-type(3) th", - ); - await page.evaluate(async (th) => { - const event = new MouseEvent("click", { - bubbles: true, - ctrlKey: true, - }); - th.dispatchEvent(event); - }, rowHeader2); - - await page.waitForSelector("regular-table td.mouse-selected-row"); - expect(await selectedRows()).toEqual(["Row 1", "Row 3"]); - }); - }); -}); diff --git a/test/features/row_stripes.test.js b/test/features/row_stripes.test.js deleted file mode 100644 index 4b176913..00000000 --- a/test/features/row_stripes.test.js +++ /dev/null @@ -1,101 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("row_stripes.html", () => { - beforeAll(async () => { - await page.setViewport({ width: 200, height: 100 }); - await page.goto("http://localhost:8081/dist/features/row_stripes.html"); - await page.waitForSelector("regular-table table tbody tr td"); - }); - - describe("initial view", () => { - test("row style alternates", async () => { - const tds1 = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - const backgroundColor1 = await page.evaluate( - (td) => - getComputedStyle(td).getPropertyValue("background-color"), - tds1[0], - ); - expect(backgroundColor1).toEqual("rgb(234, 237, 239)"); - - const tds2 = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - const backgroundColor2 = await page.evaluate( - (td) => - getComputedStyle(td).getPropertyValue("background-color"), - tds2[0], - ); - expect(backgroundColor2).toEqual("rgb(255, 255, 255)"); - }); - }); - - describe("initial view", () => { - beforeAll(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = table.scrollTop + 23; - await table._draw_flush(); - }, table); - }); - - test("row style alternates in reverse", async () => { - const tds1 = await page.$$( - "regular-table tbody tr:nth-of-type(1) td", - ); - const backgroundColor1 = await page.evaluate( - (td) => - getComputedStyle(td).getPropertyValue("background-color"), - tds1[0], - ); - expect(backgroundColor1).toEqual("rgb(255, 255, 255)"); - - const tds2 = await page.$$( - "regular-table tbody tr:nth-of-type(2) td", - ); - const backgroundColor2 = await page.evaluate( - (td) => - getComputedStyle(td).getPropertyValue("background-color"), - tds2[0], - ); - expect(backgroundColor2).toEqual("rgb(234, 237, 239)"); - }); - }); - - test("removes style listener", async () => { - await page.evaluate(() => { - window.removeStripes(); - }); - - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - // Scroll a few pages down to verify that the style listener wasn't called - table.scrollBy(0, window.innerHeight * 10); - await table._draw_flush(); - }, table); - - const tds1 = await page.$$("regular-table tbody tr:nth-of-type(1) td"); - const backgroundColor1 = await page.evaluate( - (td) => getComputedStyle(td).getPropertyValue("background-color"), - tds1[0], - ); - expect(backgroundColor1).toEqual("rgba(0, 0, 0, 0)"); - - const tds2 = await page.$$("regular-table tbody tr:nth-of-type(2) td"); - const backgroundColor2 = await page.evaluate( - (td) => getComputedStyle(td).getPropertyValue("background-color"), - tds2[0], - ); - expect(backgroundColor2).toEqual("rgba(0, 0, 0, 0)"); - }); -}); diff --git a/test/features/scrollTo.test.js b/test/features/scrollTo.test.js deleted file mode 100644 index 2efa7c4a..00000000 --- a/test/features/scrollTo.test.js +++ /dev/null @@ -1,86 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("scrollToCell", () => { - beforeAll(async () => { - await page.setViewport({ width: 200, height: 200 }); - await page.goto( - "http://localhost:8081/test/features/2_row_2_column_headers.html", - ); - await page.evaluate(async () => { - await document.querySelector("regular-table").draw(); - }); - }); - - afterEach(async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = 0; - await table._draw_flush(); - }, table); - }); - - describe("sets the correct position", () => { - test("for scrollToCell position {x: 2, y: 0}", async () => { - const table = await page.$("regular-table"); - - await page.evaluate(async (table) => { - await table.scrollToCell(2, 0, 1000, 1000); - }, table); - - const meta = await page.evaluate((table) => { - return table.getMeta(document.querySelector("td")); - }, table); - expect(meta.x).toEqual(2); - }); - - test("for scrollToCell position {x: 1000, y: 0}", async () => { - const table = await page.$("regular-table"); - - await page.evaluate(async (table) => { - await table.scrollToCell(1000, 0, 1000, 1000); - await table.draw(); - }, table); - - const meta = await page.evaluate((table) => { - return table.getMeta(document.querySelector("td")); - }, table); - expect(meta.x).toEqual(998); - }); - - test("for scrollToCell position {x: 0, y: 1}", async () => { - const table = await page.$("regular-table"); - - await page.evaluate(async (table) => { - await table.scrollToCell(0, 1, 1000, 1000); - }, table); - - const meta = await page.evaluate((table) => { - return table.getMeta(document.querySelector("td")); - }, table); - expect(meta.y).toEqual(1); - }); - - test("for scrollToCell position {x: 211, y: 647}", async () => { - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - await table.scrollToCell(211, 647, 1000, 1000); - }, table); - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.children).map((x) => x.textContent), - first_tr, - ); - expect(cell_values).toEqual(["Group 640", "Row 647", "858", "859"]); - }); - }); -}); diff --git a/test/features/scrolling.test.js b/test/features/scrolling.test.js deleted file mode 100644 index 263e5f75..00000000 --- a/test/features/scrolling.test.js +++ /dev/null @@ -1,102 +0,0 @@ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░ -// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░ -// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░ -// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃ -// ┃ * of the Regular Table library, distributed under the terms of the * ┃ -// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ -// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -describe("scrolling", () => { - beforeAll(async () => { - await page.setViewport({ width: 260, height: 200 }); - }); - - describe("scrolls down", () => { - beforeAll(async () => { - await page.goto( - "http://localhost:8081/test/features/2_row_2_column_headers.html", - ); - await page.waitForSelector("regular-table table tbody tr td"); - const table = await page.$("regular-table"); - await page.evaluate(async (table) => { - table.scrollTop = 1000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(9); - }); - - test("with the correct # of columns", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const num_cells = await page.evaluate( - (first_tr) => first_tr.children.length, - first_tr, - ); - expect(num_cells).toEqual(6); - }); - - test("with the first row's
test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.querySelectorAll("td")).map( - (x) => x.textContent, - ), - first_tr, - ); - expect(cell_values).toEqual(["52", "53", "54", "55"]); - }); - }); - - describe("scrolls right", () => { - beforeAll(async () => { - const table = await page.$("regular-table"); - await page.waitForSelector("regular-table table tbody tr td"); - await page.evaluate(async (table) => { - table.scrollTop = 0; - table.scrollLeft = 1000; - await table._draw_flush(); - }, table); - }); - - test("with the correct # of rows", async () => { - const tbody = await page.$("regular-table tbody"); - const num_rows = await page.evaluate( - (tbody) => tbody.children.length, - tbody, - ); - expect(num_rows).toEqual(8); - }); - - test("with the correct # of columns", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const num_cells = await page.evaluate( - (first_tr) => first_tr.children.length, - first_tr, - ); - expect(num_cells).toEqual(4); - }); - - test("with the first row's cell test correct", async () => { - const first_tr = await page.$("regular-table tbody tr:first-child"); - const cell_values = await page.evaluate( - (first_tr) => - Array.from(first_tr.querySelectorAll("td")).map( - (x) => x.textContent, - ), - first_tr, - ); - expect(cell_values).toEqual(["16", "17"]); - }); - }); -}); diff --git a/test/features/2_row_2_column_headers.html b/tests/2_row_2_column_headers.html similarity index 97% rename from test/features/2_row_2_column_headers.html rename to tests/2_row_2_column_headers.html index 6cf25ca5..ed46af90 100644 --- a/test/features/2_row_2_column_headers.html +++ b/tests/2_row_2_column_headers.html @@ -18,13 +18,13 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - - -