From 28f4e2e4fd438f07b177ea6bbc7e56e92c787c63 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Wed, 23 Nov 2022 15:55:48 +0000 Subject: [PATCH 01/55] feat: functional header component --- package-lock.json | 26 ++++----------- package.json | 3 +- src/config.ts | 4 ++- src/hooks/useConfig.ts | 6 ++++ src/util/className.ts | 7 +++- src/view/container.tsx | 15 +++------ src/view/headerContainer.tsx | 63 +++++++++++++----------------------- 7 files changed, 51 insertions(+), 73 deletions(-) create mode 100644 src/hooks/useConfig.ts diff --git a/package-lock.json b/package-lock.json index e05ed1d4..63a0688c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "5.1.0", "license": "MIT", "dependencies": { - "preact": "^10.10.6" + "preact": "^10.11.3" }, "devDependencies": { "@types/enzyme": "^3.10.5", @@ -6522,12 +6522,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/functions-have-names": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz", @@ -11579,9 +11573,9 @@ "dev": true }, "node_modules/preact": { - "version": "10.10.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.10.6.tgz", - "integrity": "sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==", + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -19295,12 +19289,6 @@ "functions-have-names": "^1.2.2" } }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "functions-have-names": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz", @@ -23037,9 +23025,9 @@ "dev": true }, "preact": { - "version": "10.10.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.10.6.tgz", - "integrity": "sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==" + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" }, "prelude-ls": { "version": "1.2.1", diff --git a/package.json b/package.json index 46cc2f28..ab90d24a 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "build": "run-p build:grid build:i18n build:plugins build:themes", "build:grid": "microbundle build --raw --external none --tsconfig tsconfig.release.json", "build:i18n": "microbundle build --raw --cwd l10n --tsconfig l10n/tsconfig.release.json", + "build:watch": "microbundle build -w --raw --external none --tsconfig tsconfig.release.json", "build:plugins": "microbundle build --raw --cwd plugins/selection --tsconfig plugins/selection/tsconfig.release.json", "postbuild": "node build/node-13-exports.js", "prebuild:themes": "sass src/theme/mermaid/index.scss dist/theme/mermaid.css", @@ -123,7 +124,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "preact": "^10.10.6" + "preact": "^10.11.3" }, "changelog": { "labels": { diff --git a/src/config.ts b/src/config.ts index 1b3558ff..129a9b67 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,7 +15,7 @@ import { ServerStorageOptions } from './storage/server'; import Dispatcher from './util/dispatcher'; import { GenericSortConfig } from './view/plugin/sort/sort'; import { Language, Translator } from './i18n/language'; -import { Component, ComponentChild, createRef, RefObject } from 'preact'; +import { Component, ComponentChild, createContext, createRef, RefObject } from 'preact'; import StorageUtils from './storage/storageUtils'; import PipelineUtils from './pipeline/pipelineUtils'; import { EventEmitter } from './util/eventEmitter'; @@ -23,6 +23,8 @@ import { GridEvents } from './events'; import { PluginManager, PluginPosition, Plugin } from './plugin'; import Grid from './grid'; +export const ConfigContext = createContext(null); + // Config type used internally export interface Config { // a reference to the current Grid.js instance diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts new file mode 100644 index 00000000..157ad409 --- /dev/null +++ b/src/hooks/useConfig.ts @@ -0,0 +1,6 @@ +import { useContext } from 'preact/hooks'; +import { ConfigContext } from '../config'; + +export function useConfig() { + return useContext(ConfigContext) +} diff --git a/src/util/className.ts b/src/util/className.ts index 12106c24..f6b0d211 100644 --- a/src/util/className.ts +++ b/src/util/className.ts @@ -1,3 +1,5 @@ +import { JSXInternal } from 'preact/src/jsx'; + export function className(...args: string[]): string { const prefix = 'gridjs'; @@ -7,10 +9,13 @@ export function className(...args: string[]): string { )}`; } -export function classJoin(...classNames: string[]): string { +export function classJoin( + ...classNames: (string | JSXInternal.SignalLike)[] +): string { return ( classNames .filter((x) => x) + .map((x) => x.toString()) .reduce((className, prev) => `${className || ''} ${prev}`, '') .trim() || null ); diff --git a/src/view/container.tsx b/src/view/container.tsx index f5dc6b71..238d5635 100644 --- a/src/view/container.tsx +++ b/src/view/container.tsx @@ -1,5 +1,4 @@ -import { h, createContext, Context } from 'preact'; - +import { h } from 'preact'; import Tabular from '../tabular'; import { BaseComponent, BaseProps } from './base'; import { classJoin, className } from '../util/className'; @@ -9,7 +8,7 @@ import { HeaderContainer } from './headerContainer'; import { FooterContainer } from './footerContainer'; import Pipeline from '../pipeline/pipeline'; import Header from '../header'; -import { Config } from '../config'; +import { Config, ConfigContext } from '../config'; import log from '../util/log'; import { PipelineProcessor } from '../pipeline/processor'; @@ -28,15 +27,11 @@ interface ContainerState { } export class Container extends BaseComponent { - private readonly configContext: Context; private processPipelineFn: (processor: PipelineProcessor) => void; constructor(props, context) { super(props, context); - // global Config context which is passed to all components - this.configContext = createContext(null); - this.state = { status: Status.Loading, header: props.header, @@ -109,10 +104,8 @@ export class Container extends BaseComponent { } render() { - const configContext = this.configContext; - return ( - +
{ className={className('temp')} />
-
+
); } } diff --git a/src/view/headerContainer.tsx b/src/view/headerContainer.tsx index d0227cd7..ee90e164 100644 --- a/src/view/headerContainer.tsx +++ b/src/view/headerContainer.tsx @@ -1,48 +1,31 @@ -import { createRef, h } from 'preact'; - -import { BaseComponent } from './base'; +import { h } from 'preact'; import { classJoin, className } from '../util/className'; import { PluginPosition, PluginRenderer } from '../plugin'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { useConfig } from '../hooks/useConfig'; -interface HeaderContainerState { - isActive: boolean; -} - -export class HeaderContainer extends BaseComponent< - Record, - HeaderContainerState -> { - private headerRef = createRef(); +export function HeaderContainer() { + const [isActive, setIsActive] = useState(true); + const headerRef = useRef(null); + const config = useConfig(); - constructor(props, context) { - super(props, context); - - this.state = { - isActive: true, - }; - } - - componentDidMount() { - if (this.headerRef.current.children.length === 0) { - this.setState({ - isActive: false, - }); + useEffect(() => { + if (headerRef.current.children.length === 0) { + setIsActive(false); } + }, []); + + if (isActive) { + return ( +
+ +
+ ); } - render() { - if (this.state.isActive) { - return ( -
- -
- ); - } - - return null; - } + return null; } From 386d480a9a267848df0a65bbbf02e6597a6541f1 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Wed, 23 Nov 2022 22:29:36 +0000 Subject: [PATCH 02/55] feat: functional footer component --- src/config.ts | 8 +++- src/hooks/useConfig.ts | 2 +- src/view/footerContainer.tsx | 69 +++++++++++------------------- src/view/headerContainer.tsx | 2 +- tests/dev-server/package-lock.json | 5 +-- 5 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/config.ts b/src/config.ts index 129a9b67..1556e5be 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,7 +15,13 @@ import { ServerStorageOptions } from './storage/server'; import Dispatcher from './util/dispatcher'; import { GenericSortConfig } from './view/plugin/sort/sort'; import { Language, Translator } from './i18n/language'; -import { Component, ComponentChild, createContext, createRef, RefObject } from 'preact'; +import { + Component, + ComponentChild, + createContext, + createRef, + RefObject, +} from 'preact'; import StorageUtils from './storage/storageUtils'; import PipelineUtils from './pipeline/pipelineUtils'; import { EventEmitter } from './util/eventEmitter'; diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts index 157ad409..f76450db 100644 --- a/src/hooks/useConfig.ts +++ b/src/hooks/useConfig.ts @@ -2,5 +2,5 @@ import { useContext } from 'preact/hooks'; import { ConfigContext } from '../config'; export function useConfig() { - return useContext(ConfigContext) + return useContext(ConfigContext); } diff --git a/src/view/footerContainer.tsx b/src/view/footerContainer.tsx index eddeab43..56d1c148 100644 --- a/src/view/footerContainer.tsx +++ b/src/view/footerContainer.tsx @@ -1,51 +1,34 @@ -import { createRef, h } from 'preact'; - -import { BaseComponent } from './base'; +import { h } from 'preact'; import { classJoin, className } from '../util/className'; import { PluginPosition, PluginRenderer } from '../plugin'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { useConfig } from '../hooks/useConfig'; -interface FooterContainerState { - isActive: boolean; -} - -export class FooterContainer extends BaseComponent< - Record, - FooterContainerState -> { - private footerRef = createRef(); +export function FooterContainer() { + const footerRef = useRef(null); + const [isActive, setIsActive] = useState(true); + const config = useConfig(); - constructor(props, context) { - super(props, context); - - this.state = { - isActive: true, - }; - } - - componentDidMount() { - if (this.footerRef.current.children.length === 0) { - this.setState({ - isActive: false, - }); + useEffect(() => { + if (footerRef.current.children.length === 0) { + setIsActive(false); } + }, [footerRef]); + + if (isActive) { + return ( +
+ +
+ ); } - render() { - if (this.state.isActive) { - return ( -
- -
- ); - } - - return null; - } + return null; } diff --git a/src/view/headerContainer.tsx b/src/view/headerContainer.tsx index ee90e164..3d104983 100644 --- a/src/view/headerContainer.tsx +++ b/src/view/headerContainer.tsx @@ -13,7 +13,7 @@ export function HeaderContainer() { if (headerRef.current.children.length === 0) { setIsActive(false); } - }, []); + }, [headerRef]); if (isActive) { return ( diff --git a/tests/dev-server/package-lock.json b/tests/dev-server/package-lock.json index 211251a6..8626412d 100644 --- a/tests/dev-server/package-lock.json +++ b/tests/dev-server/package-lock.json @@ -19,11 +19,10 @@ } }, "../..": { - "name": "gridjs", "version": "5.1.0", "license": "MIT", "dependencies": { - "preact": "^10.10.6" + "preact": "^10.11.3" }, "devDependencies": { "@types/enzyme": "^3.10.5", @@ -24081,7 +24080,7 @@ "postcss-nested": "^5.0.6", "postcss-scss": "^4.0.4", "postcss-sort-media-queries": "^4.1.0", - "preact": "^10.10.6", + "preact": "^10.11.3", "prettier": "~2.7.1", "rimraf": "~3.0.2", "sass": "^1.54.5", From c8949e4b286ded7f1c483b03408ce07760480f32 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 09:56:30 +0000 Subject: [PATCH 03/55] chore: typescript > 4.1 config --- plugins/selection/src/rowSelection/rowSelection.tsx | 1 - src/view/container.tsx | 1 - src/view/footerContainer.tsx | 1 - src/view/headerContainer.tsx | 1 - src/view/plugin/pagination.tsx | 2 +- src/view/plugin/resize/resize.tsx | 2 +- src/view/plugin/search/search.tsx | 1 - src/view/plugin/sort/sort.tsx | 2 +- src/view/table/messageRow.tsx | 2 -- src/view/table/shadow.tsx | 2 +- src/view/table/table.tsx | 2 -- src/view/table/tbody.tsx | 2 -- src/view/table/td.tsx | 2 +- src/view/table/th.tsx | 2 +- src/view/table/thead.tsx | 2 +- src/view/table/tr.tsx | 2 +- tests/jest/view/container.test.tsx | 1 - tests/jest/view/plugin/pagination.test.tsx | 2 +- tests/jest/view/plugin/search/search.test.tsx | 2 +- tests/jest/view/table/message-row.test.tsx | 2 +- tests/jest/view/table/table.test.tsx | 2 +- tests/jest/view/table/td.test.tsx | 2 +- tests/jest/view/table/tr.test.tsx | 2 +- tsconfig.json | 4 ++-- 24 files changed, 16 insertions(+), 28 deletions(-) diff --git a/plugins/selection/src/rowSelection/rowSelection.tsx b/plugins/selection/src/rowSelection/rowSelection.tsx index 6631bf0c..75a80e43 100644 --- a/plugins/selection/src/rowSelection/rowSelection.tsx +++ b/plugins/selection/src/rowSelection/rowSelection.tsx @@ -1,4 +1,3 @@ -import { h } from 'gridjs'; import { RowSelectionStore, RowSelectionStoreState } from './store'; import { RowSelectionActions } from './actions'; import { className } from 'gridjs'; diff --git a/src/view/container.tsx b/src/view/container.tsx index 238d5635..7534e555 100644 --- a/src/view/container.tsx +++ b/src/view/container.tsx @@ -1,4 +1,3 @@ -import { h } from 'preact'; import Tabular from '../tabular'; import { BaseComponent, BaseProps } from './base'; import { classJoin, className } from '../util/className'; diff --git a/src/view/footerContainer.tsx b/src/view/footerContainer.tsx index 56d1c148..5efc7f0d 100644 --- a/src/view/footerContainer.tsx +++ b/src/view/footerContainer.tsx @@ -1,4 +1,3 @@ -import { h } from 'preact'; import { classJoin, className } from '../util/className'; import { PluginPosition, PluginRenderer } from '../plugin'; import { useEffect, useRef, useState } from 'preact/hooks'; diff --git a/src/view/headerContainer.tsx b/src/view/headerContainer.tsx index 3d104983..5656b039 100644 --- a/src/view/headerContainer.tsx +++ b/src/view/headerContainer.tsx @@ -1,4 +1,3 @@ -import { h } from 'preact'; import { classJoin, className } from '../util/className'; import { PluginPosition, PluginRenderer } from '../plugin'; import { useEffect, useRef, useState } from 'preact/hooks'; diff --git a/src/view/plugin/pagination.tsx b/src/view/plugin/pagination.tsx index dfb3253b..c3a40799 100644 --- a/src/view/plugin/pagination.tsx +++ b/src/view/plugin/pagination.tsx @@ -1,4 +1,4 @@ -import { h, Fragment } from 'preact'; +import { Fragment } from 'preact'; import PaginationLimit from '../../pipeline/limit/pagination'; import { classJoin, className } from '../../util/className'; import ServerPaginationLimit from '../../pipeline/limit/serverPagination'; diff --git a/src/view/plugin/resize/resize.tsx b/src/view/plugin/resize/resize.tsx index 82abf8dd..b93f5e43 100644 --- a/src/view/plugin/resize/resize.tsx +++ b/src/view/plugin/resize/resize.tsx @@ -1,4 +1,4 @@ -import { h, RefObject } from 'preact'; +import { RefObject } from 'preact'; import { classJoin, className } from '../../../util/className'; import { BaseComponent } from '../../base'; import { TColumn } from '../../../types'; diff --git a/src/view/plugin/search/search.tsx b/src/view/plugin/search/search.tsx index ae7b8c31..8bf6f3d2 100644 --- a/src/view/plugin/search/search.tsx +++ b/src/view/plugin/search/search.tsx @@ -1,4 +1,3 @@ -import { h } from 'preact'; import GlobalSearchFilter from '../../../pipeline/filter/globalSearch'; import { classJoin, className } from '../../../util/className'; import { SearchStore, SearchStoreState } from './store'; diff --git a/src/view/plugin/sort/sort.tsx b/src/view/plugin/sort/sort.tsx index 16e56378..11fd393c 100644 --- a/src/view/plugin/sort/sort.tsx +++ b/src/view/plugin/sort/sort.tsx @@ -1,4 +1,4 @@ -import { h, JSX } from 'preact'; +import { JSX } from 'preact'; import { BaseComponent, BaseProps } from '../../base'; import { classJoin, className } from '../../../util/className'; diff --git a/src/view/table/messageRow.tsx b/src/view/table/messageRow.tsx index b1ae34b6..1c20c5a8 100644 --- a/src/view/table/messageRow.tsx +++ b/src/view/table/messageRow.tsx @@ -1,5 +1,3 @@ -import { h } from 'preact'; - import Cell from '../../cell'; import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; diff --git a/src/view/table/shadow.tsx b/src/view/table/shadow.tsx index 67f85b43..b3a31119 100644 --- a/src/view/table/shadow.tsx +++ b/src/view/table/shadow.tsx @@ -1,4 +1,4 @@ -import { Component, h, RefObject } from 'preact'; +import { Component, RefObject } from 'preact'; import { BaseComponent, BaseProps } from '../base'; import { className } from '../../util/className'; diff --git a/src/view/table/table.tsx b/src/view/table/table.tsx index 0d29425d..9246cead 100644 --- a/src/view/table/table.tsx +++ b/src/view/table/table.tsx @@ -1,5 +1,3 @@ -import { h } from 'preact'; - import Tabular from '../../tabular'; import { TBody } from './tbody'; import { THead } from './thead'; diff --git a/src/view/table/tbody.tsx b/src/view/table/tbody.tsx index 124ff6dd..f776bdfb 100644 --- a/src/view/table/tbody.tsx +++ b/src/view/table/tbody.tsx @@ -1,5 +1,3 @@ -import { h } from 'preact'; - import Row from '../../row'; import { TR } from './tr'; import Tabular from '../../tabular'; diff --git a/src/view/table/td.tsx b/src/view/table/td.tsx index 423712c1..375449a6 100644 --- a/src/view/table/td.tsx +++ b/src/view/table/td.tsx @@ -1,4 +1,4 @@ -import { ComponentChild, h, JSX } from 'preact'; +import { ComponentChild, JSX } from 'preact'; import Cell from '../../cell'; import { BaseComponent, BaseProps } from '../base'; diff --git a/src/view/table/th.tsx b/src/view/table/th.tsx index 8610e6e2..1673e49f 100644 --- a/src/view/table/th.tsx +++ b/src/view/table/th.tsx @@ -1,4 +1,4 @@ -import { ComponentChild, createRef, h, JSX } from 'preact'; +import { ComponentChild, createRef, JSX } from 'preact'; import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; diff --git a/src/view/table/thead.tsx b/src/view/table/thead.tsx index 2d77dd8e..d288c3cf 100644 --- a/src/view/table/thead.tsx +++ b/src/view/table/thead.tsx @@ -1,4 +1,4 @@ -import { ComponentChild, h } from 'preact'; +import { ComponentChild } from 'preact'; import { TR } from './tr'; import { BaseComponent, BaseProps } from '../base'; diff --git a/src/view/table/tr.tsx b/src/view/table/tr.tsx index cae09c7f..39b83de5 100644 --- a/src/view/table/tr.tsx +++ b/src/view/table/tr.tsx @@ -1,4 +1,4 @@ -import { h, JSX, Fragment, ComponentChildren } from 'preact'; +import { JSX, Fragment, ComponentChildren } from 'preact'; import Row from '../../row'; import Cell from '../../cell'; diff --git a/tests/jest/view/container.test.tsx b/tests/jest/view/container.test.tsx index d2b5da26..393b11e5 100644 --- a/tests/jest/view/container.test.tsx +++ b/tests/jest/view/container.test.tsx @@ -1,5 +1,4 @@ import { mount } from 'enzyme'; -import { h } from 'preact'; import { axe, toHaveNoViolations } from 'jest-axe'; import { Config } from '../../../src/config'; import { Container } from '../../../src/view/container'; diff --git a/tests/jest/view/plugin/pagination.test.tsx b/tests/jest/view/plugin/pagination.test.tsx index 6c763bcf..9e4e7bb2 100644 --- a/tests/jest/view/plugin/pagination.test.tsx +++ b/tests/jest/view/plugin/pagination.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { Config } from '../../../../src/config'; import { Plugin, PluginPosition } from '../../../../src/plugin'; import { Pagination } from '../../../../src/view/plugin/pagination'; diff --git a/tests/jest/view/plugin/search/search.test.tsx b/tests/jest/view/plugin/search/search.test.tsx index a2969763..e085fc71 100644 --- a/tests/jest/view/plugin/search/search.test.tsx +++ b/tests/jest/view/plugin/search/search.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { Config } from '../../../../../src/config'; import Dispatcher from '../../../../../src/util/dispatcher'; import { EventEmitter } from '../../../../../src/util/eventEmitter'; diff --git a/tests/jest/view/table/message-row.test.tsx b/tests/jest/view/table/message-row.test.tsx index 04a9b5f9..ced498d1 100644 --- a/tests/jest/view/table/message-row.test.tsx +++ b/tests/jest/view/table/message-row.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { Config } from '../../../../src/config'; import { EventEmitter } from '../../../../src/util/eventEmitter'; import { TableEvents } from '../../../../src/view/table/events'; diff --git a/tests/jest/view/table/table.test.tsx b/tests/jest/view/table/table.test.tsx index 1d7fc17b..a0f8df59 100644 --- a/tests/jest/view/table/table.test.tsx +++ b/tests/jest/view/table/table.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { Table } from '../../../../src/view/table/table'; import Header from '../../../../src/header'; import { Config } from '../../../../src/config'; diff --git a/tests/jest/view/table/td.test.tsx b/tests/jest/view/table/td.test.tsx index d1aaf317..ff058338 100644 --- a/tests/jest/view/table/td.test.tsx +++ b/tests/jest/view/table/td.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { TD } from '../../../../src/view/table/td'; import Cell from '../../../../src/cell'; import { Config } from '../../../../src/config'; diff --git a/tests/jest/view/table/tr.test.tsx b/tests/jest/view/table/tr.test.tsx index 166d4d24..f3e1a167 100644 --- a/tests/jest/view/table/tr.test.tsx +++ b/tests/jest/view/table/tr.test.tsx @@ -1,5 +1,5 @@ import { mount } from 'enzyme'; -import { createContext, h } from 'preact'; +import { createContext } from 'preact'; import { Config } from '../../../../src/config'; import { EventEmitter } from '../../../../src/util/eventEmitter'; import { TableEvents } from '../../../../src/view/table/events'; diff --git a/tsconfig.json b/tsconfig.json index 362b20e6..7516230e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,8 @@ "allowSyntheticDefaultImports": true, "allowJs": true, "importHelpers": true, - "jsx": "react", - "jsxFactory": "h", + "jsx": "react-jsx", + "jsxImportSource": "preact", "alwaysStrict": true, "sourceMap": true, "forceConsistentCasingInFileNames": true, From e97dc15e5aded5d5ecf323b7514401e3d8a9ed57 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 10:18:10 +0000 Subject: [PATCH 04/55] chore: functional td component --- package-lock.json | 4 +- package.json | 2 +- src/view/footerContainer.tsx | 5 +- src/view/table/td.tsx | 97 ++++++++++------------ tests/jest/view/table/message-row.test.tsx | 12 ++- tests/jest/view/table/table.test.tsx | 80 +++++++++--------- tests/jest/view/table/td.test.tsx | 12 ++- tests/jest/view/table/tr.test.tsx | 16 ++-- 8 files changed, 102 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63a0688c..e6482e0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gridjs", - "version": "5.1.0", + "version": "6.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gridjs", - "version": "5.1.0", + "version": "6.0.0", "license": "MIT", "dependencies": { "preact": "^10.11.3" diff --git a/package.json b/package.json index ab90d24a..12e2cdd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridjs", - "version": "5.1.0", + "version": "6.0.0", "description": "Advanced table plugin", "author": "Afshin Mehrabani ", "license": "MIT", diff --git a/src/view/footerContainer.tsx b/src/view/footerContainer.tsx index 5efc7f0d..0b6faac8 100644 --- a/src/view/footerContainer.tsx +++ b/src/view/footerContainer.tsx @@ -18,10 +18,7 @@ export function FooterContainer() { return (
diff --git a/src/view/table/td.tsx b/src/view/table/td.tsx index 375449a6..9593916b 100644 --- a/src/view/table/td.tsx +++ b/src/view/table/td.tsx @@ -1,16 +1,14 @@ import { ComponentChild, JSX } from 'preact'; import Cell from '../../cell'; -import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; import { CSSDeclaration, TColumn } from '../../types'; import Row from '../../row'; import { JSXInternal } from 'preact/src/jsx'; import { PluginRenderer } from '../../plugin'; +import { useConfig } from '../../hooks/useConfig'; -export interface TDProps - extends BaseProps, - JSX.HTMLAttributes { +export interface TDProps extends JSX.HTMLAttributes { cell: Cell; row?: Row; column?: TColumn; @@ -18,82 +16,71 @@ export interface TDProps messageCell?: boolean; } -export class TD extends BaseComponent { - private content(): ComponentChild { - if ( - this.props.column && - typeof this.props.column.formatter === 'function' - ) { - return this.props.column.formatter( - this.props.cell.data, - this.props.row, - this.props.column, - ); +export function TD(props: TDProps) { + const config = useConfig(); + + function content(): ComponentChild { + if (props.column && typeof props.column.formatter === 'function') { + return props.column.formatter(props.cell.data, props.row, props.column); } - if (this.props.column && this.props.column.plugin) { + if (props.column && props.column.plugin) { return ( ); } - return this.props.cell.data; + return props.cell.data; } - private handleClick(e: JSX.TargetedMouseEvent): void { - if (this.props.messageCell) return; - this.config.eventEmitter.emit( + function handleClick(e: JSX.TargetedMouseEvent): void { + if (props.messageCell) return; + config.eventEmitter.emit( 'cellClick', e, - this.props.cell, - this.props.column, - this.props.row, + props.cell, + props.column, + props.row, ); } - private getCustomAttributes( + function getCustomAttributes( column: TColumn | null, ): JSXInternal.HTMLAttributes { if (!column) return {}; if (typeof column.attributes === 'function') { - return column.attributes( - this.props.cell.data, - this.props.row, - this.props.column, - ); + return column.attributes(props.cell.data, props.row, props.column); } else { return column.attributes; } } - render() { - return ( - - {this.content()} - - ); - } + return ( + + {content()} + + ); } diff --git a/tests/jest/view/table/message-row.test.tsx b/tests/jest/view/table/message-row.test.tsx index ced498d1..524b17bc 100644 --- a/tests/jest/view/table/message-row.test.tsx +++ b/tests/jest/view/table/message-row.test.tsx @@ -1,13 +1,11 @@ import { mount } from 'enzyme'; -import { createContext } from 'preact'; -import { Config } from '../../../../src/config'; +import { Config, ConfigContext } from '../../../../src/config'; import { EventEmitter } from '../../../../src/util/eventEmitter'; import { TableEvents } from '../../../../src/view/table/events'; import { MessageRow } from '../../../../src/view/table/messageRow'; describe('MessageRow component', () => { let config: Config; - const configContext = createContext(null); beforeEach(() => { config = new Config(); @@ -15,9 +13,9 @@ describe('MessageRow component', () => { it('should match the snapshot', () => { const td = mount( - + - , + , ); expect(td.html()).toMatchSnapshot(); }); @@ -27,9 +25,9 @@ describe('MessageRow component', () => { const onClick = jest.fn(); const rows = mount( - + - , + , ).find('tr'); config.eventEmitter.on('rowClick', onClick); diff --git a/tests/jest/view/table/table.test.tsx b/tests/jest/view/table/table.test.tsx index a0f8df59..cfb98fee 100644 --- a/tests/jest/view/table/table.test.tsx +++ b/tests/jest/view/table/table.test.tsx @@ -1,8 +1,7 @@ import { mount } from 'enzyme'; -import { createContext } from 'preact'; import { Table } from '../../../../src/view/table/table'; import Header from '../../../../src/header'; -import { Config } from '../../../../src/config'; +import { Config, ConfigContext } from '../../../../src/config'; import StorageUtils from '../../../../src/storage/storageUtils'; import Pipeline from '../../../../src/pipeline/pipeline'; import StorageExtractor from '../../../../src/pipeline/extractor/storage'; @@ -16,7 +15,6 @@ import Row from '../../../../src/row'; describe('Table component', () => { let config: Config; - const configContext = createContext(null); beforeEach(() => { config = new Config(); @@ -37,14 +35,14 @@ describe('Table component', () => { it('should render a table', async () => { const table = mount( - + - , + , ); expect(table.html()).toMatchSnapshot(); @@ -56,14 +54,14 @@ describe('Table component', () => { }; const table = mount( - +
- , + , ); expect(table.find('.my-loading-class').hostNodes().name()).toBe('td'); @@ -75,7 +73,7 @@ describe('Table component', () => { it('should render a table with header', async () => { const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -91,7 +89,7 @@ describe('Table component', () => { it('should render a table with width', async () => { const table = mount( - +
{ header={Header.fromUserConfig({ columns: ['h1', 'h2', 'h3'] })} status={Status.Rendered} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -111,7 +109,7 @@ describe('Table component', () => { header.columns[2].width = '300px'; const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -135,7 +133,7 @@ describe('Table component', () => { }; const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -156,7 +154,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -180,7 +178,7 @@ describe('Table component', () => { }; const table = mount( - +
([])} header={header} @@ -188,7 +186,7 @@ describe('Table component', () => { width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -200,7 +198,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -230,7 +228,7 @@ describe('Table component', () => { }; const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -260,7 +258,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -289,7 +287,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -315,7 +313,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); return new Promise((resolve) => { @@ -353,7 +351,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); return new Promise((resolve) => { @@ -409,7 +407,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); return new Promise((resolve) => { @@ -465,7 +463,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); return new Promise((resolve) => { @@ -532,7 +530,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -577,7 +575,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); @@ -616,7 +614,7 @@ describe('Table component', () => { }); const table = mount( - +
{ width={config.width} height={config.height} /> - , + , ); expect(table.html()).toMatchSnapshot(); diff --git a/tests/jest/view/table/td.test.tsx b/tests/jest/view/table/td.test.tsx index ff058338..5d275454 100644 --- a/tests/jest/view/table/td.test.tsx +++ b/tests/jest/view/table/td.test.tsx @@ -1,14 +1,12 @@ import { mount } from 'enzyme'; -import { createContext } from 'preact'; import { TD } from '../../../../src/view/table/td'; import Cell from '../../../../src/cell'; -import { Config } from '../../../../src/config'; +import { Config, ConfigContext } from '../../../../src/config'; import { EventEmitter } from '../../../../src/util/eventEmitter'; import { TableEvents } from '../../../../src/view/table/events'; describe('TD component', () => { let config: Config; - const configContext = createContext(null); beforeEach(() => { config = new Config(); @@ -16,9 +14,9 @@ describe('TD component', () => { it('should match the snapshot', () => { const td = mount( - + - , + , ); expect(tr.html()).toMatchSnapshot(); }); @@ -31,11 +29,11 @@ describe('TR component', () => { const onClick = jest.fn(); const rows = mount( - + - , + , ).find('tr'); config.eventEmitter.on('rowClick', onClick); @@ -47,7 +45,7 @@ describe('TR component', () => { it('should attach the custom tr className', async () => { const tr = mount( - { - , + , ); expect(tr.find('tr.custom-tr-classname')).toHaveLength(1); From faa42ce7d8348fb32a9296b7eb1e111f85f890a0 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 11:17:36 +0000 Subject: [PATCH 05/55] feat: functional TH component --- src/hooks/useConfig.ts | 4 +- src/view/table/td.tsx | 35 +++---- src/view/table/th.tsx | 206 ++++++++++++++++++----------------------- 3 files changed, 110 insertions(+), 135 deletions(-) diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts index f76450db..1f454ab9 100644 --- a/src/hooks/useConfig.ts +++ b/src/hooks/useConfig.ts @@ -1,6 +1,6 @@ import { useContext } from 'preact/hooks'; -import { ConfigContext } from '../config'; +import { Config, ConfigContext } from '../config'; -export function useConfig() { +export function useConfig(): Config { return useContext(ConfigContext); } diff --git a/src/view/table/td.tsx b/src/view/table/td.tsx index 9593916b..baebcff0 100644 --- a/src/view/table/td.tsx +++ b/src/view/table/td.tsx @@ -8,18 +8,18 @@ import { JSXInternal } from 'preact/src/jsx'; import { PluginRenderer } from '../../plugin'; import { useConfig } from '../../hooks/useConfig'; -export interface TDProps extends JSX.HTMLAttributes { - cell: Cell; - row?: Row; - column?: TColumn; - style?: CSSDeclaration; - messageCell?: boolean; -} - -export function TD(props: TDProps) { +export function TD( + props: { + cell: Cell; + row?: Row; + column?: TColumn; + style?: CSSDeclaration; + messageCell?: boolean; + } & Omit, 'style'>, +) { const config = useConfig(); - function content(): ComponentChild { + const content = (): ComponentChild => { if (props.column && typeof props.column.formatter === 'function') { return props.column.formatter(props.cell.data, props.row, props.column); } @@ -38,10 +38,13 @@ export function TD(props: TDProps) { } return props.cell.data; - } + }; - function handleClick(e: JSX.TargetedMouseEvent): void { + const handleClick = ( + e: JSX.TargetedMouseEvent, + ): void => { if (props.messageCell) return; + config.eventEmitter.emit( 'cellClick', e, @@ -49,11 +52,11 @@ export function TD(props: TDProps) { props.column, props.row, ); - } + }; - function getCustomAttributes( + const getCustomAttributes = ( column: TColumn | null, - ): JSXInternal.HTMLAttributes { + ): JSXInternal.HTMLAttributes => { if (!column) return {}; if (typeof column.attributes === 'function') { @@ -61,7 +64,7 @@ export function TD(props: TDProps) { } else { return column.attributes; } - } + }; return ( - ); - } + + ); } From b17c3584649c8e6c5c16aa3196851e1413c30372 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 13:33:30 +0000 Subject: [PATCH 06/55] feat: functional TR component --- src/view/plugin/resize/resize.tsx | 2 +- src/view/table/tr.tsx | 64 ++++++++-------- tests/dev-server/package-lock.json | 117 ++++------------------------- 3 files changed, 46 insertions(+), 137 deletions(-) diff --git a/src/view/plugin/resize/resize.tsx b/src/view/plugin/resize/resize.tsx index b93f5e43..ea1579aa 100644 --- a/src/view/plugin/resize/resize.tsx +++ b/src/view/plugin/resize/resize.tsx @@ -7,7 +7,7 @@ import { throttle } from '../../../util/throttle'; type ResizeProps = { column: TColumn; - thRef: RefObject - {this.getChildren()} - - ); - } + return ( + + {getChildren()} + + ); } diff --git a/tests/dev-server/package-lock.json b/tests/dev-server/package-lock.json index 8626412d..fc7e9813 100644 --- a/tests/dev-server/package-lock.json +++ b/tests/dev-server/package-lock.json @@ -18,56 +18,6 @@ "sirv-cli": "^1.0.3" } }, - "../..": { - "version": "5.1.0", - "license": "MIT", - "dependencies": { - "preact": "^10.11.3" - }, - "devDependencies": { - "@types/enzyme": "^3.10.5", - "@types/jest": "^28.1.4", - "@types/jest-axe": "^3.2.2", - "@types/node": "^18.7.8", - "@typescript-eslint/eslint-plugin": "^5.28.0", - "@typescript-eslint/parser": "^5.28.0", - "autoprefixer": "^10.4.8", - "axe-core": "^4.4.2", - "check-export-map": "^1.1.1", - "cssnano": "^5.0.5", - "cypress": "^8.1.0", - "cypress-visual-regression": "^1.5.7", - "enzyme": "^3.11.0", - "enzyme-adapter-preact-pure": "^3.0.0", - "eslint": "^8.18.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-jest": "~26.8.7", - "identity-obj-proxy": "^3.0.0", - "jest": "^28.1.3", - "jest-axe": "^6.0.0", - "jest-extended": "^2.0.0", - "jsdom": "^19.0.0", - "jsdom-global": "^3.0.2", - "lerna-changelog": "^2.1.0", - "microbundle": "^0.15.1", - "npm-run-all": "^4.1.5", - "postcss": "^8.4.16", - "postcss-cli": "^9.1.0", - "postcss-nested": "^5.0.6", - "postcss-scss": "^4.0.4", - "postcss-sort-media-queries": "^4.1.0", - "prettier": "~2.7.1", - "rimraf": "~3.0.2", - "sass": "^1.54.5", - "source-map-loader": "^4.0.0", - "start-server-and-test": "^1.12.3", - "ts-jest": "^28.0.8", - "ts-loader": "^9.1.1", - "tslib": "^2.4.0", - "tsutils": "~3.21.0", - "typescript": "^4.7.4" - } - }, "node_modules/@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -6961,8 +6911,12 @@ "dev": true }, "node_modules/gridjs": { - "resolved": "../..", - "link": true + "version": "6.0.0", + "resolved": "file:../..", + "license": "MIT", + "dependencies": { + "preact": "^10.11.3" + } }, "node_modules/gzip-size": { "version": "6.0.0", @@ -12824,9 +12778,9 @@ "dev": true }, "node_modules/preact": { - "version": "10.5.13", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.13.tgz", - "integrity": "sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==", + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -24047,50 +24001,9 @@ "dev": true }, "gridjs": { - "version": "file:../..", - "requires": { - "@types/enzyme": "^3.10.5", - "@types/jest": "^28.1.4", - "@types/jest-axe": "^3.2.2", - "@types/node": "^18.7.8", - "@typescript-eslint/eslint-plugin": "^5.28.0", - "@typescript-eslint/parser": "^5.28.0", - "autoprefixer": "^10.4.8", - "axe-core": "^4.4.2", - "check-export-map": "^1.1.1", - "cssnano": "^5.0.5", - "cypress": "^8.1.0", - "cypress-visual-regression": "^1.5.7", - "enzyme": "^3.11.0", - "enzyme-adapter-preact-pure": "^3.0.0", - "eslint": "^8.18.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-jest": "~26.8.7", - "identity-obj-proxy": "^3.0.0", - "jest": "^28.1.3", - "jest-axe": "^6.0.0", - "jest-extended": "^2.0.0", - "jsdom": "^19.0.0", - "jsdom-global": "^3.0.2", - "lerna-changelog": "^2.1.0", - "microbundle": "^0.15.1", - "npm-run-all": "^4.1.5", - "postcss": "^8.4.16", - "postcss-cli": "^9.1.0", - "postcss-nested": "^5.0.6", - "postcss-scss": "^4.0.4", - "postcss-sort-media-queries": "^4.1.0", - "preact": "^10.11.3", - "prettier": "~2.7.1", - "rimraf": "~3.0.2", - "sass": "^1.54.5", - "source-map-loader": "^4.0.0", - "start-server-and-test": "^1.12.3", - "ts-jest": "^28.0.8", - "ts-loader": "^9.1.1", - "tslib": "^2.4.0", - "tsutils": "~3.21.0", - "typescript": "^4.7.4" + "version": "6.0.0", + "requires": { + "preact": "^10.11.3" } }, "gzip-size": { @@ -28556,9 +28469,9 @@ "dev": true }, "preact": { - "version": "10.5.13", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.13.tgz", - "integrity": "sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==" + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" }, "preact-cli": { "version": "3.2.0", From a80bb7f2fdf3a5672cdfb5610f4fdc9280921e92 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 13:50:59 +0000 Subject: [PATCH 07/55] feat: functional Resize component --- src/i18n/language.ts | 7 ++- src/plugin.ts | 1 - src/view/base.tsx | 28 ---------- src/view/plugin/resize/resize.tsx | 86 ++++++++++++---------------- src/view/table/messageRow.tsx | 37 ++++++------ src/view/table/tbody.tsx | 93 +++++++++++++++---------------- src/view/table/thead.tsx | 60 ++++++++------------ 7 files changed, 126 insertions(+), 186 deletions(-) delete mode 100644 src/view/base.tsx diff --git a/src/i18n/language.ts b/src/i18n/language.ts index aca64b53..e659897d 100644 --- a/src/i18n/language.ts +++ b/src/i18n/language.ts @@ -1,3 +1,4 @@ +import { useConfig } from '../hooks/useConfig'; import enUS from './en_US'; type MessageFormat = (...args) => string; type Message = string | MessageFormat; @@ -58,8 +59,10 @@ export class Translator { } } -export function useTranslator(translator: Translator) { +export function useTranslator() { + const config = useConfig(); + return function (message: string, ...args): string { - return translator.translate(message, ...args); + return config.translator.translate(message, ...args); }; } diff --git a/src/plugin.ts b/src/plugin.ts index 14b5f27c..128fcc41 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,3 @@ -import { BaseComponent, BaseProps } from './view/base'; import { Component, ComponentProps, Fragment, h } from 'preact'; import log from './util/log'; diff --git a/src/view/base.tsx b/src/view/base.tsx deleted file mode 100644 index db4984f8..00000000 --- a/src/view/base.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Component } from 'preact'; -import { Config } from '../config'; -import getConfig from '../util/getConfig'; -import { useTranslator } from '../i18n/language'; - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface BaseProps {} - -export abstract class BaseComponent< - P extends BaseProps = unknown, - S = unknown, -> extends Component { - protected config: Config; - protected _: (message: string, ...args) => string; - - constructor(props: P, context: any) { - super(props, context); - this.config = getConfig(context); - - if (this.config) { - this._ = useTranslator(this.config.translator); - } - } -} - -export interface BaseComponent

{ - new (props: P, context?: any): Component

; -} diff --git a/src/view/plugin/resize/resize.tsx b/src/view/plugin/resize/resize.tsx index ea1579aa..65c7d493 100644 --- a/src/view/plugin/resize/resize.tsx +++ b/src/view/plugin/resize/resize.tsx @@ -1,84 +1,72 @@ import { RefObject } from 'preact'; import { classJoin, className } from '../../../util/className'; -import { BaseComponent } from '../../base'; import { TColumn } from '../../../types'; import { TH } from '../../table/th'; import { throttle } from '../../../util/throttle'; +import { useState } from 'preact/hooks'; -type ResizeProps = { +export function Resize(props: { column: TColumn; thRef: RefObject; -}; +}) { + const [offsetStart, setOffsetStart] = useState(0); -type ResizeState = { - width: string; - offsetStart: number; -}; + let moveFn: (e) => void; + let upFn: (e) => void; -export class Resize extends BaseComponent { - private moveFn: (e) => void; - private upFn: (e) => void; - - private getPageX(e: MouseEvent | TouchEvent): number { + const getPageX = (e: MouseEvent | TouchEvent) => { if (e instanceof MouseEvent) { return Math.floor(e.pageX); } else { return Math.floor(e.changedTouches[0].pageX); } - } + }; - private start(e: MouseEvent | TouchEvent): void { + const start = (e: MouseEvent | TouchEvent) => { e.stopPropagation(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const thElement: HTMLElement = this.props.thRef.current; + const thElement: HTMLElement = props.thRef.current; - this.setState({ - offsetStart: parseInt(thElement.style.width, 10) - this.getPageX(e), - }); + setOffsetStart(parseInt(thElement.style.width, 10) - getPageX(e)); - this.upFn = this.end.bind(this); - this.moveFn = throttle(this.move.bind(this), 10); + upFn = end; + moveFn = throttle(move, 10); - document.addEventListener('mouseup', this.upFn); - document.addEventListener('touchend', this.upFn); - document.addEventListener('mousemove', this.moveFn); - document.addEventListener('touchmove', this.moveFn); - } + document.addEventListener('mouseup', upFn); + document.addEventListener('touchend', upFn); + document.addEventListener('mousemove', moveFn); + document.addEventListener('touchmove', moveFn); + }; - private move(e: MouseEvent | TouchEvent): void { + const move = (e: MouseEvent | TouchEvent) => { e.stopPropagation(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const thElement: HTMLElement = this.props.thRef.current; + const thElement: HTMLElement = props.thRef.current; - if ( - this.state.offsetStart + this.getPageX(e) >= - parseInt(thElement.style.minWidth, 10) - ) { - thElement.style.width = `${this.state.offsetStart + this.getPageX(e)}px`; + if (offsetStart + getPageX(e) >= parseInt(thElement.style.minWidth, 10)) { + thElement.style.width = `${offsetStart + getPageX(e)}px`; } - } + }; - private end(e: MouseEvent | TouchEvent): void { + const end = (e: MouseEvent | TouchEvent) => { e.stopPropagation(); - document.removeEventListener('mouseup', this.upFn); - document.removeEventListener('mousemove', this.moveFn); - document.removeEventListener('touchmove', this.moveFn); - document.removeEventListener('touchend', this.upFn); - } + document.removeEventListener('mouseup', upFn); + document.removeEventListener('mousemove', moveFn); + document.removeEventListener('touchmove', moveFn); + document.removeEventListener('touchend', upFn); + }; - render() { - return ( -

e.stopPropagation()} - /> - ); - } + return ( +
e.stopPropagation()} + /> + ); } diff --git a/src/view/table/messageRow.tsx b/src/view/table/messageRow.tsx index 1c20c5a8..e5332991 100644 --- a/src/view/table/messageRow.tsx +++ b/src/view/table/messageRow.tsx @@ -1,30 +1,25 @@ import Cell from '../../cell'; -import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; import { TR } from './tr'; import { TD } from './td'; -export interface MessageRowProps extends BaseProps { +export function MessageRow(props: { message: string; colSpan?: number; className?: string; -} - -export class MessageRow extends BaseComponent { - render() { - return ( -
- - ); - } +}) { + return ( + + + ); } diff --git a/src/view/table/tbody.tsx b/src/view/table/tbody.tsx index f776bdfb..5a75d665 100644 --- a/src/view/table/tbody.tsx +++ b/src/view/table/tbody.tsx @@ -1,72 +1,67 @@ import Row from '../../row'; import { TR } from './tr'; import Tabular from '../../tabular'; -import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; import { Status } from '../../types'; import Header from '../../header'; import { MessageRow } from './messageRow'; +import { useConfig } from '../../hooks/useConfig'; +import { useTranslator } from '../../i18n/language'; -interface TBodyProps extends BaseProps { +export function TBody(props: { data: Tabular; status: Status; header?: Header; -} +}) { + const config = useConfig(); + const _ = useTranslator(); -export class TBody extends BaseComponent { - private headerLength(): number { - if (this.props.header) { - return this.props.header.visibleColumns.length; + const headerLength = () => { + if (props.header) { + return props.header.visibleColumns.length; } return 0; - } - - render() { - return ( - - {this.props.data && - this.props.data.rows.map((row: Row) => { - return ; - })} + }; - {this.props.status === Status.Loading && - (!this.props.data || this.props.data.length === 0) && ( - - )} + return ( + + {props.data && + props.data.rows.map((row: Row) => { + return ; + })} - {this.props.status === Status.Rendered && - this.props.data && - this.props.data.length === 0 && ( - - )} + {props.status === Status.Loading && + (!props.data || props.data.length === 0) && ( + + )} - {this.props.status === Status.Error && ( + {props.status === Status.Rendered && + props.data && + props.data.length === 0 && ( )} - - ); - } + + {props.status === Status.Error && ( + + )} + + ); } diff --git a/src/view/table/thead.tsx b/src/view/table/thead.tsx index d288c3cf..eb4e4b38 100644 --- a/src/view/table/thead.tsx +++ b/src/view/table/thead.tsx @@ -1,24 +1,20 @@ -import { ComponentChild } from 'preact'; - import { TR } from './tr'; -import { BaseComponent, BaseProps } from '../base'; import { TH } from './th'; import { classJoin, className } from '../../util/className'; import Header from '../../header'; import { TColumn } from '../../types'; import { calculateRowColSpans } from '../../util/table'; +import { useConfig } from '../../hooks/useConfig'; -interface THeadProps extends BaseProps { - header: Header; -} +export function THead(props: { header: Header }) { + const config = useConfig(); -export class THead extends BaseComponent { - private renderColumn( + const renderColumn = ( column: TColumn, rowIndex: number, columnIndex: number, totalRows: number, - ): ComponentChild { + ) => { const { rowSpan, colSpan } = calculateRowColSpans( column, rowIndex, @@ -33,22 +29,18 @@ export class THead extends BaseComponent { rowSpan={rowSpan} /> ); - } + }; - private renderRow( - row: TColumn[], - rowIndex: number, - totalRows: number, - ): ComponentChild { + const renderRow = (row: TColumn[], rowIndex: number, totalRows: number) => { // because the only sortable columns are leaf columns (not parents) - const leafColumns = Header.leafColumns(this.props.header.columns); + const leafColumns = Header.leafColumns(props.header.columns); return ( {row.map((col) => { if (col.hidden) return null; - return this.renderColumn( + return renderColumn( col, rowIndex, leafColumns.indexOf(col), @@ -57,28 +49,24 @@ export class THead extends BaseComponent { })} ); - } + }; + + const renderRows = () => { + const rows = Header.tabularFormat(props.header.columns); - private renderRows(): ComponentChild { - const rows = Header.tabularFormat(this.props.header.columns); + return rows.map((row, rowIndex) => renderRow(row, rowIndex, rows.length)); + }; - return rows.map((row, rowIndex) => - this.renderRow(row, rowIndex, rows.length), + if (props.header) { + return ( + + {renderRows()} + ); } - render() { - if (this.props.header) { - return ( - - {this.renderRows()} - - ); - } - - return null; - } + return null; } From 8c9b0cf8fd3948d7595e3fd7ae49e94b5e7e5b32 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 14:41:36 +0000 Subject: [PATCH 08/55] feat: functional Shadow table --- index.ts | 3 - src/config.ts | 10 +- src/header.ts | 2 +- src/plugin.ts | 65 +++++------ src/view/container.tsx | 211 +++++++++++++++------------------- src/view/plugin/sort/sort.tsx | 168 +++++++++++++-------------- src/view/table/shadow.tsx | 104 ++++++++--------- src/view/table/table.tsx | 47 ++++---- 8 files changed, 272 insertions(+), 338 deletions(-) diff --git a/index.ts b/index.ts index 99173324..1ab58824 100644 --- a/index.ts +++ b/index.ts @@ -4,7 +4,6 @@ import { html } from './src/util/html'; import { h, createElement, Component, createRef } from 'preact'; import { useEffect, useRef } from 'preact/hooks'; import { UserConfig, Config } from './src/config'; -import { BaseComponent, BaseProps } from './src/view/base'; import { PluginPosition, PluginBaseComponent, @@ -30,8 +29,6 @@ export { html, UserConfig, Config, - BaseComponent, - BaseProps, PluginPosition, PluginBaseComponent, PluginBaseProps, diff --git a/src/config.ts b/src/config.ts index 1556e5be..3c7eb329 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,13 +15,7 @@ import { ServerStorageOptions } from './storage/server'; import Dispatcher from './util/dispatcher'; import { GenericSortConfig } from './view/plugin/sort/sort'; import { Language, Translator } from './i18n/language'; -import { - Component, - ComponentChild, - createContext, - createRef, - RefObject, -} from 'preact'; +import { ComponentChild, createContext, createRef, RefObject } from 'preact'; import StorageUtils from './storage/storageUtils'; import PipelineUtils from './pipeline/pipelineUtils'; import { EventEmitter } from './util/eventEmitter'; @@ -42,7 +36,7 @@ export interface Config { // TODO: change this to an element reference container?: Element; /** pointer to the main table element */ - tableRef?: RefObject; + tableRef?: RefObject; /** gridjs-temp div which is used internally */ tempRef?: RefObject; data?: TData | (() => TData) | (() => Promise); diff --git a/src/header.ts b/src/header.ts index 3e0e057d..08fc43c9 100644 --- a/src/header.ts +++ b/src/header.ts @@ -48,7 +48,7 @@ class Header extends Base { */ adjustWidth(config: Config): this { const container: Element = config.container; - const tableRef: RefObject = config.tableRef; + const tableRef: RefObject = config.tableRef; const tempRef: RefObject = config.tempRef; const autoWidth = config.tempRef || true; diff --git a/src/plugin.ts b/src/plugin.ts index 128fcc41..65caf523 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,5 @@ import { Component, ComponentProps, Fragment, h } from 'preact'; +import { useConfig } from './hooks/useConfig'; import log from './util/log'; /** @@ -89,44 +90,40 @@ export class PluginManager { } } -export interface PluginRendererProps extends BaseProps { +export function PluginRenderer(props: { props?: any; // to render a single plugin pluginId?: string; // to render all plugins in this PluginPosition position?: PluginPosition; -} - -export class PluginRenderer extends BaseComponent { - render() { - if (this.props.pluginId) { - // render a single plugin - const plugin = this.config.plugin.get(this.props.pluginId); - - if (!plugin) return null; - - return h( - Fragment, - {}, - h(plugin.component, { - plugin: plugin, - ...plugin.props, - ...this.props.props, - }), - ); - } else if (this.props.position !== undefined) { - // render using a specific plugin position - return h( - Fragment, - {}, - this.config.plugin - .list(this.props.position) - .map((p) => - h(p.component, { plugin: p, ...p.props, ...this.props.props }), - ), - ); - } - - return null; +}) { + const config = useConfig(); + + if (props.pluginId) { + // render a single plugin + const plugin = config.plugin.get(props.pluginId); + + if (!plugin) return null; + + return h( + Fragment, + {}, + h(plugin.component, { + plugin: plugin, + ...plugin.props, + ...props.props, + }), + ); + } else if (props.position !== undefined) { + // render using a specific plugin position + return h( + Fragment, + {}, + config.plugin + .list(props.position) + .map((p) => h(p.component, { plugin: p, ...p.props, ...props.props })), + ); } + + return null; } diff --git a/src/view/container.tsx b/src/view/container.tsx index 7534e555..2d36b7e2 100644 --- a/src/view/container.tsx +++ b/src/view/container.tsx @@ -1,5 +1,4 @@ import Tabular from '../tabular'; -import { BaseComponent, BaseProps } from './base'; import { classJoin, className } from '../util/className'; import { Status } from '../types'; import { Table } from './table/table'; @@ -10,145 +9,115 @@ import Header from '../header'; import { Config, ConfigContext } from '../config'; import log from '../util/log'; import { PipelineProcessor } from '../pipeline/processor'; +import { useEffect, useRef, useState } from 'preact/hooks'; +import { useConfig } from '../hooks/useConfig'; -interface ContainerProps extends BaseProps { +export function Container(props: { config: Config; pipeline: Pipeline; header?: Header; width: string; height: string; -} - -interface ContainerState { - status: Status; - header?: Header; - data?: Tabular; -} +}) { + const config = useConfig(); + const [status, setStatus] = useState(Status.Loading); + const prevStatusRef = useRef(Status.Loading); + const [header, setHeader] = useState(props.header); + const [data, setData] = useState(null); + let processPipelineFn: (processor: PipelineProcessor) => void; + + useEffect(() => { + (async function () { + // for the initial load + await processPipeline(); + + if (config.header && data && data.length) { + // now that we have the data, let's adjust columns width + // NOTE: that we only calculate the columns width once + setHeader(config.header.adjustWidth(config)); + } + + processPipelineFn = processPipeline.bind(this); + props.pipeline.on('updated', processPipelineFn); + })(); + + return () => { + props.pipeline.off('updated', processPipelineFn); + }; + }, []); -export class Container extends BaseComponent { - private processPipelineFn: (processor: PipelineProcessor) => void; + useEffect(() => { + // we can't jump to the Status.Rendered if previous status is not Status.Loaded + if (prevStatusRef.current != Status.Rendered && status == Status.Loaded) { + setStatus(Status.Rendered); - constructor(props, context) { - super(props, context); + props.config.eventEmitter.emit('ready'); + } - this.state = { - status: Status.Loading, - header: props.header, - data: null, - }; - } + prevStatusRef.current = status; + }, [status]); - private async processPipeline() { - this.props.config.eventEmitter.emit('beforeLoad'); + const processPipeline = async () => { + props.config.eventEmitter.emit('beforeLoad'); - this.setState({ - status: Status.Loading, - }); + setStatus(Status.Loading); try { - const data = await this.props.pipeline.process(); - this.setState({ - data: data, - status: Status.Loaded, - }); + const data = await props.pipeline.process(); + setData(data); + setStatus(Status.Loaded); - this.props.config.eventEmitter.emit('load', data); + props.config.eventEmitter.emit('load', data); } catch (e) { log.error(e); - this.setState({ - status: Status.Error, - data: null, - }); - } - } - - async componentDidMount() { - const config = this.props.config; - - // for the initial load - await this.processPipeline(); - - if (config.header && this.state.data && this.state.data.length) { - // now that we have the data, let's adjust columns width - // NOTE: that we only calculate the columns width once - this.setState({ - header: config.header.adjustWidth(config), - }); + setData(null); + setStatus(Status.Error); } + }; + + return ( + +
+ {status === Status.Loading && ( +
+ )} + + + +
+
- , + , ); expect(td.html()).toMatchSnapshot(); }); @@ -28,9 +26,9 @@ describe('TD component', () => { const onClick = jest.fn(); const cells = mount( - + - , + , ).find('td'); config.eventEmitter.on('cellClick', onClick); diff --git a/tests/jest/view/table/tr.test.tsx b/tests/jest/view/table/tr.test.tsx index f3e1a167..144959ac 100644 --- a/tests/jest/view/table/tr.test.tsx +++ b/tests/jest/view/table/tr.test.tsx @@ -1,6 +1,5 @@ import { mount } from 'enzyme'; -import { createContext } from 'preact'; -import { Config } from '../../../../src/config'; +import { Config, ConfigContext } from '../../../../src/config'; import { EventEmitter } from '../../../../src/util/eventEmitter'; import { TableEvents } from '../../../../src/view/table/events'; import { TD } from '../../../../src/view/table/td'; @@ -9,7 +8,6 @@ import Cell from '../../../../src/cell'; describe('TR component', () => { let config: Config; - const configContext = createContext(null); beforeEach(() => { config = new Config(); @@ -17,11 +15,11 @@ describe('TR component', () => { it('should match the snapshot', () => { const tr = mount( - +
{ - index: number; - column: TColumn; - style?: CSSDeclaration; -} - -export interface THState { - style: CSSDeclaration; -} - -export class TH extends BaseComponent { - private sortRef = createRef(); - private thRef = createRef(); - - constructor(props, context) { - super(props, context); - - this.state = { - style: {}, - }; - } - - private isSortable(): boolean { - return this.props.column.sort.enabled; - } - - private isResizable(): boolean { - return this.props.column.resizable; - } - - private onClick(e: JSX.TargetedMouseEvent): void { - e.stopPropagation(); - - if (this.isSortable()) { - this.sortRef.current.changeDirection(e); - } - } - - private keyDown(e: JSX.TargetedMouseEvent): void { - if (this.isSortable() && e.which === 13) { - this.onClick(e); - } - } - - componentDidMount(): void { +import { useEffect, useRef, useState } from 'preact/hooks'; +import { useConfig } from '../../hooks/useConfig'; + +export function TH( + props: { + index: number; + column: TColumn; + style?: CSSDeclaration; + } & Omit, 'style'>, +) { + const config = useConfig(); + const sortRef = useRef(null); + const thRef = useRef(null); + const [style, setStyle] = useState({}); + + useEffect(() => { setTimeout(() => { // sets the `top` style if the current TH is fixed - if (this.props.column.fixedHeader && this.thRef.current) { - const offsetTop = this.thRef.current.offsetTop; + if (props.column.fixedHeader && thRef.current) { + const offsetTop = thRef.current.offsetTop; if (typeof offsetTop === 'number') { - this.setState({ - style: { - top: offsetTop, - }, + setStyle({ + top: offsetTop, }); } } }, 0); - } + }, [thRef]); + + const isSortable = (): boolean => props.column.sort.enabled; + const isResizable = (): boolean => props.column.resizable; + const onClick = ( + e: + | JSX.TargetedMouseEvent + | JSX.TargetedKeyboardEvent, + ) => { + e.stopPropagation(); - private content(): ComponentChild { - if (this.props.column.name !== undefined) { - return this.props.column.name; + if (isSortable()) { + sortRef.current.changeDirection(e); } + }; - if (this.props.column.plugin !== undefined) { + const keyDown = (e: JSX.TargetedKeyboardEvent) => { + if (isSortable() && e.which === 13) { + onClick(e); + } + }; + + const content = (): ComponentChild => { + if (props.column.name !== undefined) { + return props.column.name; + } + + if (props.column.plugin !== undefined) { return ( ); } return null; - } + }; - private getCustomAttributes(): JSXInternal.HTMLAttributes { - const column = this.props.column; + const getCustomAttributes = () => { + const column = props.column; if (!column) return {}; if (typeof column.attributes === 'function') { - return column.attributes(null, null, this.props.column); + return column.attributes(null, null, props.column); } else { return column.attributes; } - } - - render() { - const props = {}; - - if (this.isSortable()) { - props['tabIndex'] = 0; - } - - return ( - 1 ? this.props.rowSpan : undefined} - colSpan={this.props.colSpan > 1 ? this.props.colSpan : undefined} - {...this.getCustomAttributes()} - {...props} - > -
{this.content()}
- {this.isSortable() && ( - + }; + + return ( +
1 ? props.rowSpan : undefined} + colSpan={props.colSpan > 1 ? props.colSpan : undefined} + {...getCustomAttributes()} + {...(isSortable() ? { tabIndex: 0 } : {})} + > +
{content()}
+ {isSortable() && ( + + )} + {isResizable() && + props.index < config.header.visibleColumns.length - 1 && ( + )} - {this.isResizable() && - this.props.index < this.config.header.visibleColumns.length - 1 && ( - - )} -
; + thRef: RefObject; }; type ResizeState = { diff --git a/src/view/table/tr.tsx b/src/view/table/tr.tsx index 39b83de5..2c596a4a 100644 --- a/src/view/table/tr.tsx +++ b/src/view/table/tr.tsx @@ -2,22 +2,23 @@ import { JSX, Fragment, ComponentChildren } from 'preact'; import Row from '../../row'; import Cell from '../../cell'; -import { BaseComponent, BaseProps } from '../base'; import { classJoin, className } from '../../util/className'; import { TColumn } from '../../types'; import { TD } from './td'; import Header from '../../header'; +import { useConfig } from '../../hooks/useConfig'; -export interface TRProps extends BaseProps { +export function TR(props: { row?: Row; header?: Header; messageRow?: boolean; -} + children?: ComponentChildren; +}) { + const config = useConfig(); -export class TR extends BaseComponent { - private getColumn(cellIndex: number): TColumn { - if (this.props.header) { - const cols = Header.leafColumns(this.props.header.columns); + const getColumn = (cellIndex: number): TColumn => { + if (props.header) { + const cols = Header.leafColumns(props.header.columns); if (cols) { return cols[cellIndex]; @@ -25,46 +26,41 @@ export class TR extends BaseComponent { } return null; - } + }; - private handleClick(e: JSX.TargetedMouseEvent): void { - if (this.props.messageRow) return; - this.config.eventEmitter.emit('rowClick', e, this.props.row); - } + const handleClick = ( + e: JSX.TargetedMouseEvent, + ): void => { + if (props.messageRow) return; + config.eventEmitter.emit('rowClick', e, props.row); + }; - private getChildren(): ComponentChildren { - if (this.props.children) { - return this.props.children; + const getChildren = (): ComponentChildren => { + if (props.children) { + return props.children; } else { return ( - {this.props.row.cells.map((cell: Cell, i) => { - const column = this.getColumn(i); + {props.row.cells.map((cell: Cell, i) => { + const column = getColumn(i); if (column && column.hidden) return null; return ( - + ); })} ); } - } + }; - render() { - return ( -
-
+
+ - this.processPipelineFn = this.processPipeline.bind(this); - this.props.pipeline.on('updated', this.processPipelineFn); - } - - componentWillUnmount(): void { - this.props.pipeline.off('updated', this.processPipelineFn); - } - - componentDidUpdate( - _: Readonly, - previousState: Readonly, - ): void { - // we can't jump to the Status.Rendered if previous status is not Status.Loaded - if ( - previousState.status != Status.Rendered && - this.state.status == Status.Loaded - ) { - this.setState({ - status: Status.Rendered, - }); - - this.props.config.eventEmitter.emit('ready'); - } - } + - render() { - return ( -
- {this.state.status === Status.Loading && ( -
- )} - - - -
-
- - - - -
-
- - ); - } + ref={props.config.tempRef} + id="gridjs-temp" + className={className('temp')} + /> + + + ); } diff --git a/src/view/plugin/sort/sort.tsx b/src/view/plugin/sort/sort.tsx index 11fd393c..487305eb 100644 --- a/src/view/plugin/sort/sort.tsx +++ b/src/view/plugin/sort/sort.tsx @@ -1,6 +1,5 @@ import { JSX } from 'preact'; -import { BaseComponent, BaseProps } from '../../base'; import { classJoin, className } from '../../../util/className'; import { ProcessorType } from '../../../pipeline/processor'; import NativeSort from '../../../pipeline/sort/native'; @@ -8,6 +7,9 @@ import { SortStore, SortStoreState } from './store'; import { Comparator, TCell, TColumnSort } from '../../../types'; import { SortActions } from './actions'; import ServerSort from '../../../pipeline/sort/server'; +import { useEffect, useState } from 'preact/hooks'; +import { useConfig } from '../../../hooks/useConfig'; +import { useTranslator } from '../../../i18n/language'; // column specific config export interface SortConfig { @@ -29,78 +31,67 @@ export interface GenericSortConfig { }; } -export interface SortProps extends BaseProps { - // column index - index: number; -} - -interface SortState { - direction: 1 | -1 | 0; -} - -export class Sort extends BaseComponent { - private readonly sortProcessor: NativeSort | ServerSort; - private readonly actions: SortActions; - private readonly store: SortStore; - private readonly updateStateFn: (...args) => void; - private updateSortProcessorFn: (sortedColumns: SortStoreState) => void; - - constructor(props: SortProps & SortConfig, context) { - super(props, context); - - this.actions = new SortActions(this.config.dispatcher); - this.store = new SortStore(this.config.dispatcher); - +export function Sort( + props: { + // column index + index: number; + } & SortConfig, +) { + const config = useConfig(); + const _ = useTranslator(); + const [direction, setDirection] = useState(0); + const actions = new SortActions(config.dispatcher); + const store = new SortStore(config.dispatcher); + let sortProcessor: NativeSort | ServerSort; + let updateStateFn: (...args) => void; + let updateSortProcessorFn: (sortedColumns: SortStoreState) => void; + + useEffect(() => { if (props.enabled) { - this.sortProcessor = this.getOrCreateSortProcessor(); - this.updateStateFn = this.updateState.bind(this); - this.store.on('updated', this.updateStateFn); - this.state = { direction: 0 }; + sortProcessor = getOrCreateSortProcessor(); + updateStateFn = updateState; + store.on('updated', updateStateFn); } - } - componentWillUnmount(): void { - this.config.pipeline.unregister(this.sortProcessor); + return () => { + config.pipeline.unregister(sortProcessor); - this.store.off('updated', this.updateStateFn); - if (this.updateSortProcessorFn) - this.store.off('updated', this.updateSortProcessorFn); - } + store.off('updated', updateStateFn); + + if (updateSortProcessorFn) { + store.off('updated', updateSortProcessorFn); + } + }; + }, []); /** * Sets the internal state of component */ - private updateState(): void { - const currentColumn = this.store.state.find( - (x) => x.index === this.props.index, - ); + const updateState = () => { + const currentColumn = store.state.find((x) => x.index === props.index); if (!currentColumn) { - this.setState({ - direction: 0, - }); + setDirection(0); } else { - this.setState({ - direction: currentColumn.direction, - }); + setDirection(currentColumn.direction); } - } + }; - private updateSortProcessor(sortedColumns: SortStoreState) { + const updateSortProcessor = (sortedColumns: SortStoreState) => { // updates the Sorting processor - this.sortProcessor.setProps({ + sortProcessor.setProps({ columns: sortedColumns, }); - } + }; - private getOrCreateSortProcessor(): NativeSort { + const getOrCreateSortProcessor = (): NativeSort => { let processorType = ProcessorType.Sort; - if (this.config.sort && typeof this.config.sort.server === 'object') { + if (config.sort && typeof config.sort.server === 'object') { processorType = ProcessorType.ServerSort; } - const processors = this.config.pipeline.getStepsByType(processorType); + const processors = config.pipeline.getStepsByType(processorType); // my assumption is that we only have ONE sorting processor in the // entire pipeline and that's why I'm displaying a warning here @@ -114,65 +105,62 @@ export class Sort extends BaseComponent { // this event listener is here because // we want to subscribe to the sort store only once - this.updateSortProcessorFn = this.updateSortProcessor.bind(this); - this.store.on('updated', this.updateSortProcessorFn); + updateSortProcessorFn = updateSortProcessor.bind(this); + store.on('updated', updateSortProcessorFn); if (processorType === ProcessorType.ServerSort) { processor = new ServerSort({ - columns: this.store.state, - ...this.config.sort.server, + columns: store.state, + ...config.sort.server, }); } else { processor = new NativeSort({ - columns: this.store.state, + columns: store.state, }); } - this.config.pipeline.register(processor); + config.pipeline.register(processor); } return processor; - } + }; - changeDirection(e: JSX.TargetedMouseEvent): void { + const changeDirection = (e: JSX.TargetedMouseEvent) => { e.preventDefault(); e.stopPropagation(); // to sort two or more columns at the same time - this.actions.sortToggle( - this.props.index, - e.shiftKey === true && this.config.sort.multiColumn, - this.props.compare, + actions.sortToggle( + props.index, + e.shiftKey === true && config.sort.multiColumn, + props.compare, ); - } - - render() { - if (!this.props.enabled) { - return null; - } + }; - const direction = this.state.direction; - let sortClassName = 'neutral'; + if (!props.enabled) { + return null; + } - if (direction === 1) { - sortClassName = 'asc'; - } else if (direction === -1) { - sortClassName = 'desc'; - } + let sortClassName = 'neutral'; - return ( -
- - -
- ); - } + return ( + + + +
+ ); } From f00eccb9e21727d2ba36120d76c2f95861ee6fb6 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Thu, 24 Nov 2022 14:44:45 +0000 Subject: [PATCH 09/55] feat: functional HTMLElement --- src/view/htmlElement.tsx | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/view/htmlElement.tsx b/src/view/htmlElement.tsx index bf8196cb..8daa8f25 100644 --- a/src/view/htmlElement.tsx +++ b/src/view/htmlElement.tsx @@ -1,20 +1,10 @@ import { h } from 'preact'; -import { BaseComponent, BaseProps } from './base'; - -export interface HTMLContentProps extends BaseProps { +export function HTMLElement(props: { content: string; parentElement?: string; -} - -export class HTMLElement extends BaseComponent { - static defaultProps = { - parentElement: 'span', - }; - - render() { - return h(this.props.parentElement, { - dangerouslySetInnerHTML: { __html: this.props.content }, - }); - } +}) { + return h(props.parentElement || 'span', { + dangerouslySetInnerHTML: { __html: props.content }, + }); } From e83bf08017889d4c9d485c8dbb820bccc49efd89 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sat, 26 Nov 2022 11:13:05 +0000 Subject: [PATCH 10/55] feat: functional pagination and search --- src/config.ts | 43 +++-- src/plugin.ts | 55 ++----- src/util/className.ts | 14 +- src/view/plugin/pagination.tsx | 252 ++++++++++++------------------ src/view/plugin/search/search.tsx | 147 +++++++---------- src/view/plugin/sort/sort.tsx | 4 +- 6 files changed, 202 insertions(+), 313 deletions(-) diff --git a/src/config.ts b/src/config.ts index 3c7eb329..379c2443 100644 --- a/src/config.ts +++ b/src/config.ts @@ -202,31 +202,24 @@ export class Config { translator: new Translator(userConfig.language), }); - // Search - config.plugin.add({ - id: 'search', - position: PluginPosition.Header, - component: Search, - props: { - enabled: - userConfig.search === true || userConfig.search instanceof Object, - ...(userConfig.search as SearchConfig), - }, - }); - - // Pagination - config.plugin.add({ - id: 'pagination', - position: PluginPosition.Footer, - component: Pagination, - props: { - enabled: - userConfig.pagination === true || - userConfig.pagination instanceof Object, - ...(userConfig.pagination as PaginationConfig), - }, - }); - + if (userConfig.search) { + // Search + config.plugin.add({ + id: 'search', + position: PluginPosition.Header, + component: Search, + }); + } + + if (userConfig.pagination) { + // Pagination + config.plugin.add({ + id: 'pagination', + position: PluginPosition.Footer, + component: Pagination, + }); + } + // Additional plugins if (userConfig.plugins) { userConfig.plugins.forEach((p) => config.plugin.add(p)); diff --git a/src/plugin.ts b/src/plugin.ts index 65caf523..ce7d2e1e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,40 +1,17 @@ -import { Component, ComponentProps, Fragment, h } from 'preact'; +import { Fragment, FunctionComponent, h } from 'preact'; import { useConfig } from './hooks/useConfig'; import log from './util/log'; -/** - * BaseProps for all plugins - */ -export interface PluginBaseProps { - plugin: Plugin; -} - -/** - * BaseComponent for all plugins - */ -export abstract class PluginBaseComponent< - P extends PluginBaseProps = any, - S = unknown, -> extends BaseComponent {} - -export interface PluginBaseComponentCtor< - P extends PluginBaseProps = any, - S = unknown, -> { - new (props: P, context?: any): Component; -} - export enum PluginPosition { Header, Footer, Cell, } -export interface Plugin { +export interface Plugin { id: string; position: PluginPosition; component: T; - props?: Partial>; order?: number; } @@ -45,17 +22,11 @@ export class PluginManager { this.plugins = []; } - get(id: string): Plugin | null { - const plugins = this.plugins.filter((p) => p.id === id); - - if (plugins.length > 0) { - return plugins[0]; - } - - return null; + get(id: string): Plugin | undefined { + return this.plugins.find((p) => p.id === id); } - add(plugin: Plugin): this { + add>(plugin: Plugin): this { if (!plugin.id) { log.error('Plugin ID cannot be empty'); return this; @@ -71,13 +42,16 @@ export class PluginManager { } remove(id: string): this { - this.plugins.splice(this.plugins.indexOf(this.get(id)), 1); + const plugin = this.get(id); + + if (plugin) { + this.plugins.splice(this.plugins.indexOf(plugin), 1); + } + return this; } - list( - position?: PluginPosition, - ): Plugin[] { + list(position?: PluginPosition): Plugin[] { let plugins: Plugin[]; if (position != null || position != undefined) { @@ -86,7 +60,7 @@ export class PluginManager { plugins = this.plugins; } - return plugins.sort((a, b) => a.order - b.order); + return plugins.sort((a, b) => (a.order && b.order ? a.order - b.order : 1)); } } @@ -110,7 +84,6 @@ export function PluginRenderer(props: { {}, h(plugin.component, { plugin: plugin, - ...plugin.props, ...props.props, }), ); @@ -121,7 +94,7 @@ export function PluginRenderer(props: { {}, config.plugin .list(props.position) - .map((p) => h(p.component, { plugin: p, ...p.props, ...props.props })), + .map((p) => h(p.component, { plugin: p, ...props.props })), ); } diff --git a/src/util/className.ts b/src/util/className.ts index f6b0d211..cfa474c5 100644 --- a/src/util/className.ts +++ b/src/util/className.ts @@ -10,13 +10,11 @@ export function className(...args: string[]): string { } export function classJoin( - ...classNames: (string | JSXInternal.SignalLike)[] + ...classNames: (undefined | string | JSXInternal.SignalLike)[] ): string { - return ( - classNames - .filter((x) => x) - .map((x) => x.toString()) - .reduce((className, prev) => `${className || ''} ${prev}`, '') - .trim() || null - ); + return classNames + .map((x) => (x ? x.toString() : '')) + .filter((x) => x) + .reduce((className, prev) => `${className || ''} ${prev}`, '') + .trim(); } diff --git a/src/view/plugin/pagination.tsx b/src/view/plugin/pagination.tsx index c3a40799..ace7334f 100644 --- a/src/view/plugin/pagination.tsx +++ b/src/view/plugin/pagination.tsx @@ -4,16 +4,12 @@ import { classJoin, className } from '../../util/className'; import ServerPaginationLimit from '../../pipeline/limit/serverPagination'; import Tabular from '../../tabular'; import { PipelineProcessor } from '../../pipeline/processor'; -import { PluginBaseComponent, PluginBaseProps } from '../../plugin'; +import { useConfig } from '../../hooks/useConfig'; +import { useEffect, useState } from 'preact/hooks'; +import { useTranslator } from '../../i18n/language'; -interface PaginationState { - page: number; - limit?: number; - total: number; -} export interface PaginationConfig { - enabled: boolean; limit?: number; page?: number; summary?: boolean; @@ -27,154 +23,112 @@ export interface PaginationConfig { }; } -export class Pagination extends PluginBaseComponent< - PluginBaseProps & PaginationConfig, - PaginationState -> { - private processor: PaginationLimit | ServerPaginationLimit; - private onUpdateFn: (processor: PipelineProcessor) => void; - private setTotalFromTabularFn: (tabular: Tabular) => void; - - static defaultProps = { - summary: true, - nextButton: true, - prevButton: true, - buttonsCount: 3, - limit: 10, - resetPageOnUpdate: true, - }; +export function Pagination(props: PaginationConfig) { + let processor: PaginationLimit | ServerPaginationLimit; - constructor(props, context) { - super(props, context); + const [currentPage, setCurrentPage] = useState(props.page || 0); + const [limit, setLimit] = useState(props.limit); + const [total, setTotal] = useState(0); + const config = useConfig(); + const _ = useTranslator(); - this.state = { - limit: props.limit, - page: props.page || 0, - total: 0, - }; + const setTotalFromTabular = (tabular: Tabular) => { + setTotal(tabular.length); } - componentWillMount(): void { - if (this.props.enabled) { - let processor; - - this.setTotalFromTabularFn = this.setTotalFromTabular.bind(this); + if (this.props.server) { + processor = new ServerPaginationLimit({ + limit: limit, + page: currentPage, + url: props.server.url, + body: props.server.body, + }); - if (this.props.server) { - processor = new ServerPaginationLimit({ - limit: this.state.limit, - page: this.state.page, - url: this.props.server.url, - body: this.props.server.body, - }); + config.pipeline.on('afterProcess', setTotalFromTabular); + } else { + processor = new PaginationLimit({ + limit: limit, + page: currentPage, + }); - this.config.pipeline.on('afterProcess', this.setTotalFromTabularFn); - } else { - processor = new PaginationLimit({ - limit: this.state.limit, - page: this.state.page, - }); + // Pagination (all Limit processors) is the last step in the pipeline + // and we assume that at this stage, we have the rows that we care about. + // Let's grab the rows before processing Pagination and set total number of rows + processor.on('beforeProcess', setTotalFromTabular); + } - // Pagination (all Limit processors) is the last step in the pipeline - // and we assume that at this stage, we have the rows that we care about. - // Let's grab the rows before processing Pagination and set total number of rows - processor.on('beforeProcess', this.setTotalFromTabularFn); - } + config.pipeline.register(processor); - this.processor = processor; - this.config.pipeline.register(processor); + // we need to make sure that the state is set + // to the default props when an error happens + config.pipeline.on('error', () => { + setTotal(0); + setCurrentPage(0); + }); - // we need to make sure that the state is set - // to the default props when an error happens - this.config.pipeline.on('error', () => { - this.setState({ - total: 0, - page: 0, - }); - }); - } - } + useEffect(() => { + config.pipeline.on('updated', onUpdate); - private setTotalFromTabular(tabular: Tabular): void { - this.setTotal(tabular.length); - } + return () => { + config.pipeline.unregister(processor); + config.pipeline.off('updated', onUpdate); + }; + }, []); - private onUpdate(processor): void { + const onUpdate = (updatedProcessor) => { // this is to ensure that the current page is set to 0 // when a processor is updated for some reason - if (this.props.resetPageOnUpdate && processor !== this.processor) { - this.setPage(0); + if (props.resetPageOnUpdate && updatedProcessor !== processor) { + setCurrentPage(0); } - } - - componentDidMount(): void { - this.onUpdateFn = this.onUpdate.bind(this); - this.config.pipeline.on('updated', this.onUpdateFn); - } - - componentWillUnmount() { - this.config.pipeline.unregister(this.processor); - this.config.pipeline.off('updated', this.onUpdateFn); - } + }; - private get pages(): number { - return Math.ceil(this.state.total / this.state.limit); - } + const pages = () => Math.ceil(total / limit); - private setPage(page: number): void { - if (page >= this.pages || page < 0 || page === this.state.page) { + const setPage = (page: number) => { + if (page >= pages() || page < 0 || page === currentPage) { return null; } - this.setState({ - page: page, - }); + setCurrentPage(page); - this.processor.setProps({ + processor.setProps({ page: page, }); } - private setTotal(totalRows: number): void { - // to set the correct total number of rows - // when running in-memory operations - this.setState({ - total: totalRows, - }); - } - - renderPages() { - if (this.props.buttonsCount <= 0) { + const renderPages = () => { + if (props.buttonsCount <= 0) { return null; } // how many pagination buttons to render? - const maxCount: number = Math.min(this.pages, this.props.buttonsCount); + const maxCount: number = Math.min(pages(), props.buttonsCount); - let pagePivot = Math.min(this.state.page, Math.floor(maxCount / 2)); - if (this.state.page + Math.floor(maxCount / 2) >= this.pages) { - pagePivot = maxCount - (this.pages - this.state.page); + let pagePivot = Math.min(currentPage, Math.floor(maxCount / 2)); + if (currentPage + Math.floor(maxCount / 2) >= pages()) { + pagePivot = maxCount - (pages() - currentPage); } return ( - {this.pages > maxCount && this.state.page - pagePivot > 0 && ( + {pages() > maxCount && currentPage - pagePivot > 0 && ( ))} - {this.pages > maxCount && this.pages > this.state.page + pagePivot + 1 && ( + {pages() > maxCount && pages() > currentPage + pagePivot + 1 && ( )} @@ -232,7 +186,7 @@ export class Pagination extends PluginBaseComponent< ); } - renderSummary() { + const renderSummary = () => { return ( {this.props.summary && this.state.total > 0 && ( @@ -268,56 +222,52 @@ export class Pagination extends PluginBaseComponent< ); } - render() { - if (!this.props.enabled) return null; - return (
- {this.renderSummary()} + {renderSummary()}
- {this.props.prevButton && ( + {props.prevButton && ( )} - {this.renderPages()} + {renderPages()} - {this.props.nextButton && ( + {props.nextButton && ( )}
); - } } diff --git a/src/view/plugin/search/search.tsx b/src/view/plugin/search/search.tsx index 8bf6f3d2..31fb37e3 100644 --- a/src/view/plugin/search/search.tsx +++ b/src/view/plugin/search/search.tsx @@ -5,11 +5,12 @@ import { SearchActions } from './actions'; import ServerGlobalSearchFilter from '../../../pipeline/filter/serverGlobalSearch'; import { debounce } from '../../../util/debounce'; import { TCell } from '../../../types'; -import { PluginBaseComponent, PluginBaseProps } from '../../../plugin'; +import { useConfig } from '../../../hooks/useConfig'; +import { useEffect } from 'preact/hooks'; +import { useTranslator } from '../../../i18n/language'; export interface SearchConfig { keyword?: string; - enabled?: boolean; ignoreHiddenColumns?: boolean; debounceTimeout?: number; selector?: (cell: TCell, rowIndex: number, cellIndex: number) => string; @@ -19,102 +20,74 @@ export interface SearchConfig { }; } -export class Search extends PluginBaseComponent< - SearchConfig & PluginBaseProps -> { - private readonly searchProcessor: - | GlobalSearchFilter - | ServerGlobalSearchFilter; - private readonly actions: SearchActions; - private readonly store: SearchStore; - private readonly storeUpdatedFn: (...args) => void; - - static defaultProps = { - debounceTimeout: 250, - }; - - constructor(props, context) { - super(props, context); - - this.actions = new SearchActions(this.config.dispatcher); - this.store = new SearchStore(this.config.dispatcher); - const { enabled, keyword } = props; - - if (enabled) { - // initial search - if (keyword) this.actions.search(keyword); +export function Search(props: SearchConfig) { + let searchProcessor: GlobalSearchFilter | ServerGlobalSearchFilter; + const config = useConfig(); + const actions = new SearchActions(config.dispatcher); + const store = new SearchStore(config.dispatcher); + const _ = useTranslator(); + + if (props.server) { + searchProcessor = new ServerGlobalSearchFilter({ + keyword: props.keyword, + url: props.server.url, + body: props.server.body, + }); + } else { + searchProcessor = new GlobalSearchFilter({ + keyword: props.keyword, + columns: config.header && config.header.columns, + ignoreHiddenColumns: + props.ignoreHiddenColumns || props.ignoreHiddenColumns === undefined, + selector: props.selector, + }); + } - this.storeUpdatedFn = this.storeUpdated.bind(this); - this.store.on('updated', this.storeUpdatedFn); + useEffect(() => { + const { keyword } = props; - let searchProcessor; - if (props.server) { - searchProcessor = new ServerGlobalSearchFilter({ - keyword: props.keyword, - url: props.server.url, - body: props.server.body, - }); - } else { - searchProcessor = new GlobalSearchFilter({ - keyword: props.keyword, - columns: this.config.header && this.config.header.columns, - ignoreHiddenColumns: - props.ignoreHiddenColumns || - props.ignoreHiddenColumns === undefined, - selector: props.selector, - }); - } + // initial search + if (keyword) actions.search(keyword); - this.searchProcessor = searchProcessor; + store.on('updated', storeUpdated); - // adds a new processor to the pipeline - this.config.pipeline.register(searchProcessor); - } - } + // adds a new processor to the pipeline + config.pipeline.register(searchProcessor); - componentWillUnmount(): void { - this.config.pipeline.unregister(this.searchProcessor); - this.store.off('updated', this.storeUpdatedFn); - } + return () => { + config.pipeline.unregister(searchProcessor); + store.off('updated', storeUpdated); + }; + }, []); - private storeUpdated(state: SearchStoreState): void { + const storeUpdated = (state: SearchStoreState) => { // updates the processor state - this.searchProcessor.setProps({ + searchProcessor.setProps({ keyword: state.keyword, }); - } + }; - private onChange(event): void { + const onChange = (event) => { const keyword = event.target.value; - this.actions.search(keyword); - } - - render() { - if (!this.props.enabled) return null; - - let onInput = this.onChange.bind(this); - - // add debounce to input only if it's a server-side search - if (this.searchProcessor instanceof ServerGlobalSearchFilter) { - onInput = debounce(onInput, this.props.debounceTimeout); - } + actions.search(keyword); + }; - return ( -
- -
- ); + let onInput = onChange; + // add debounce to input only if it's a server-side search + if (searchProcessor instanceof ServerGlobalSearchFilter) { + onInput = debounce(onInput, props.debounceTimeout || 250); } + + return ( +
+ +
+ ); } diff --git a/src/view/plugin/sort/sort.tsx b/src/view/plugin/sort/sort.tsx index 487305eb..582ccd47 100644 --- a/src/view/plugin/sort/sort.tsx +++ b/src/view/plugin/sort/sort.tsx @@ -1,4 +1,4 @@ -import { JSX } from 'preact'; +import { JSX, RefObject } from 'preact'; import { classJoin, className } from '../../../util/className'; import { ProcessorType } from '../../../pipeline/processor'; @@ -35,6 +35,7 @@ export function Sort( props: { // column index index: number; + ref: RefObject; } & SortConfig, ) { const config = useConfig(); @@ -151,6 +152,7 @@ export function Sort( return ( - )} +
+ {props.prevButton && ( + + )} - {renderPages()} + {renderPages()} - {props.nextButton && ( - - )} -
+ {props.nextButton && ( + + )}
- ); + + ); } diff --git a/src/view/plugin/resize/resize.tsx b/src/view/plugin/resize/resize.tsx index 65c7d493..a6062172 100644 --- a/src/view/plugin/resize/resize.tsx +++ b/src/view/plugin/resize/resize.tsx @@ -1,4 +1,4 @@ -import { RefObject } from 'preact'; +import { h, RefObject } from 'preact'; import { classJoin, className } from '../../../util/className'; import { TColumn } from '../../../types'; import { TH } from '../../table/th'; diff --git a/src/view/plugin/search/search.tsx b/src/view/plugin/search/search.tsx index 31fb37e3..2f605da5 100644 --- a/src/view/plugin/search/search.tsx +++ b/src/view/plugin/search/search.tsx @@ -1,3 +1,4 @@ +import { h } from 'preact'; import GlobalSearchFilter from '../../../pipeline/filter/globalSearch'; import { classJoin, className } from '../../../util/className'; import { SearchStore, SearchStoreState } from './store'; @@ -86,7 +87,7 @@ export function Search(props: SearchConfig) { aria-label={_('search.placeholder')} onInput={onInput} className={classJoin(className('input'), className('search', 'input'))} - value={store.state.keyword || ""} + value={store.state.keyword || ''} /> ); diff --git a/src/view/plugin/sort/sort.tsx b/src/view/plugin/sort/sort.tsx index 582ccd47..45f782ae 100644 --- a/src/view/plugin/sort/sort.tsx +++ b/src/view/plugin/sort/sort.tsx @@ -1,4 +1,4 @@ -import { JSX, RefObject } from 'preact'; +import { h, JSX, RefObject } from 'preact'; import { classJoin, className } from '../../../util/className'; import { ProcessorType } from '../../../pipeline/processor'; diff --git a/src/view/table/messageRow.tsx b/src/view/table/messageRow.tsx index e5332991..0f2e3841 100644 --- a/src/view/table/messageRow.tsx +++ b/src/view/table/messageRow.tsx @@ -1,3 +1,4 @@ +import { h } from 'preact'; import Cell from '../../cell'; import { classJoin, className } from '../../util/className'; import { TR } from './tr'; diff --git a/src/view/table/shadow.tsx b/src/view/table/shadow.tsx index e1b335e0..7a714152 100644 --- a/src/view/table/shadow.tsx +++ b/src/view/table/shadow.tsx @@ -1,4 +1,4 @@ -import { RefObject } from 'preact'; +import { h, RefObject } from 'preact'; import { className } from '../../util/className'; import { useEffect } from 'preact/hooks'; @@ -32,10 +32,7 @@ export function ShadowTable(props: { tableRef?: RefObject }) { return null; } - -export function getShadowTableWidths( - tableElement: HTMLTableElement -): { +export function getShadowTableWidths(tableElement: HTMLTableElement): { [columnId: string]: { minWidth: number; width: number }; } { const tableClassName = tableElement.className; @@ -77,4 +74,4 @@ export function getShadowTableWidths( }, obj); return obj; -}; +} diff --git a/src/view/table/table.tsx b/src/view/table/table.tsx index deba921b..3b4ab9b2 100644 --- a/src/view/table/table.tsx +++ b/src/view/table/table.tsx @@ -5,7 +5,7 @@ import Header from '../../header'; import { classJoin, className } from '../../util/className'; import { Status } from '../../types'; import { useConfig } from '../../hooks/useConfig'; -import { RefObject } from 'preact'; +import { h, RefObject } from 'preact'; export function Table(props: { ref: RefObject; diff --git a/src/view/table/tbody.tsx b/src/view/table/tbody.tsx index 5a75d665..4a55bfbf 100644 --- a/src/view/table/tbody.tsx +++ b/src/view/table/tbody.tsx @@ -1,3 +1,4 @@ +import { h } from 'preact'; import Row from '../../row'; import { TR } from './tr'; import Tabular from '../../tabular'; diff --git a/src/view/table/td.tsx b/src/view/table/td.tsx index baebcff0..7464c47c 100644 --- a/src/view/table/td.tsx +++ b/src/view/table/td.tsx @@ -1,4 +1,4 @@ -import { ComponentChild, JSX } from 'preact'; +import { h, ComponentChild, JSX } from 'preact'; import Cell from '../../cell'; import { classJoin, className } from '../../util/className'; diff --git a/src/view/table/th.tsx b/src/view/table/th.tsx index 7a26d6c4..800f8229 100644 --- a/src/view/table/th.tsx +++ b/src/view/table/th.tsx @@ -1,4 +1,4 @@ -import { ComponentChild, JSX } from 'preact'; +import { h, ComponentChild, JSX } from 'preact'; import { classJoin, className } from '../../util/className'; import { CSSDeclaration, TColumn } from '../../types'; diff --git a/src/view/table/thead.tsx b/src/view/table/thead.tsx index eb4e4b38..6987baad 100644 --- a/src/view/table/thead.tsx +++ b/src/view/table/thead.tsx @@ -1,3 +1,4 @@ +import { h } from 'preact'; import { TR } from './tr'; import { TH } from './th'; import { classJoin, className } from '../../util/className'; diff --git a/src/view/table/tr.tsx b/src/view/table/tr.tsx index 2c596a4a..7954a207 100644 --- a/src/view/table/tr.tsx +++ b/src/view/table/tr.tsx @@ -1,4 +1,4 @@ -import { JSX, Fragment, ComponentChildren } from 'preact'; +import { h, JSX, Fragment, ComponentChildren } from 'preact'; import Row from '../../row'; import Cell from '../../cell'; diff --git a/tests/jest/util/className.test.ts b/tests/jest/util/className.test.ts index 7179b030..83841de6 100644 --- a/tests/jest/util/className.test.ts +++ b/tests/jest/util/className.test.ts @@ -14,12 +14,12 @@ describe('className', () => { expect(classJoin('boo', 'foo', 'bar')).toBe('boo foo bar'); }); - it('should return null when inputs are null and undefined', () => { - expect(classJoin(null, undefined, null)).toBe(null); + it('should return empty string when inputs are null and undefined', () => { + expect(classJoin(null, undefined, null)).toBe(''); }); - it('should return null when inputs are null', () => { - expect(classJoin(null, null)).toBe(null); + it('should return empty string when inputs are null', () => { + expect(classJoin(null, null)).toBe(''); }); }); diff --git a/tests/jest/view/__snapshots__/container.test.tsx.snap b/tests/jest/view/__snapshots__/container.test.tsx.snap index 02dceb9b..5aad01ac 100644 --- a/tests/jest/view/__snapshots__/container.test.tsx.snap +++ b/tests/jest/view/__snapshots__/container.test.tsx.snap @@ -4,16 +4,30 @@ exports[`Container component should attach classNames 1`] = `"
123
abc
Showing 1 to 2 of 2 results
"`; +exports[`Container component should remove the EventEmitter listeners 1`] = `"
boo123
foo456
bar789
"`; + exports[`Container component should render a container with array of objects with object columns 1`] = `"
Name
Phone Number
boo123
foo456
bar789
"`; exports[`Container component should render a container with array of objects with string columns 1`] = `"
Name
Phone Number
boo123
foo456
bar789
"`; +exports[`Container component should render a container with array of objects with string columns 2`] = `"
boo123
foo456
bar789
"`; + exports[`Container component should render a container with array of objects without columns input 1`] = `"
name
phoneNumber
boo123
foo456
bar789
"`; exports[`Container component should render a container with error 1`] = `"
An error happened while fetching the data
"`; exports[`Container component should render a container with null array 1`] = `"
No matching records found
"`; +exports[`Container component should render a container with null array 2`] = `"
123
abc
"`; + +exports[`Container component should render a container with null array 3`] = `"
123
abc
"`; + +exports[`Container component should render a container with null array 4`] = `"
123
abc
"`; + +exports[`Container component should render a container with null array 5`] = `"
c1
c2
c3
123
abc
"`; + +exports[`Container component should render a container with null array 6`] = `"
An error happened while fetching the data
"`; + exports[`Container component should render a container with searchable table 1`] = `"
123
abc
"`; exports[`Container component should render a container with sortable and paginated table 1`] = `"
c1
c2
c3
123
abc
Showing 1 to 2 of 2 results
"`; diff --git a/tests/jest/view/container.test.tsx b/tests/jest/view/container.test.tsx index 6e0db457..2303d17d 100644 --- a/tests/jest/view/container.test.tsx +++ b/tests/jest/view/container.test.tsx @@ -40,9 +40,10 @@ describe('Container component', () => { />, ); - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); - expect(container.state('status')).toBe(3); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + expect(container.state('status')).toBe(3); + }, 0); }); it('should attach styles', async () => { @@ -80,13 +81,14 @@ describe('Container component', () => { />, ); - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); it('should attach classNames', async () => { config.update({ - search: true , + search: true, pagination: true, columns: [ { @@ -131,8 +133,9 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); }); @@ -149,8 +152,9 @@ describe('Container component', () => { height={config.height} />, ); - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); it('should render a container with sortable and paginated table', async () => { @@ -190,8 +194,10 @@ describe('Container component', () => { height={config.height} />, ); - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); it('should render a container with error', async () => { @@ -220,8 +226,9 @@ describe('Container component', () => { />, ); - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); it('should not violate accessibility test', async () => { @@ -243,9 +250,10 @@ describe('Container component', () => { ); container.update(); - await container.instance().componentDidMount(); - expect(await axe(container.html())).toHaveNoViolations(); + setTimeout(async () => { + expect(await axe(container.html())).toHaveNoViolations(); + }, 0); }); it('should render a container with null array', async () => { @@ -276,8 +284,9 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); }); @@ -301,8 +310,9 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); }); @@ -327,8 +337,9 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); }); @@ -362,8 +373,9 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); - expect(container.html()).toMatchSnapshot(); + setTimeout(() => { + expect(container.html()).toMatchSnapshot(); + }, 0); }); }); @@ -388,9 +400,11 @@ describe('Container component', () => { const mockOff = jest.spyOn(EventEmitter.prototype, 'off'); return flushPromises().then(async () => { - await container.instance().componentDidMount(); container.unmount(); - expect(mockOff.mock.calls.length).toBe(mockOn.mock.calls.length); + + setTimeout(() => { + expect(mockOff.mock.calls.length).toBe(mockOn.mock.calls.length); + }, 0); }); }); @@ -423,11 +437,12 @@ describe('Container component', () => { ); return flushPromises().then(async () => { - await container.instance().componentDidMount(); container.unmount(); - expect(mockUnregister.mock.calls.length).toBe( - mockRegister.mock.calls.length, - ); + setTimeout(() => { + expect(mockUnregister.mock.calls.length).toBe( + mockRegister.mock.calls.length, + ); + }, 0); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 7516230e..ff54beb5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,9 @@ "allowSyntheticDefaultImports": true, "allowJs": true, "importHelpers": true, - "jsx": "react-jsx", - "jsxImportSource": "preact", + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", "alwaysStrict": true, "sourceMap": true, "forceConsistentCasingInFileNames": true, From 4a1e4c16d4ff387d2c062dd1b4370c395be10d3d Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Wed, 30 Nov 2022 09:05:51 +0000 Subject: [PATCH 13/55] fix: pagination and removing sortRef --- src/plugin.ts | 2 +- src/view/plugin/pagination.tsx | 119 ++++++++++++++++----------------- src/view/plugin/sort/sort.tsx | 4 +- src/view/table/th.tsx | 7 +- 4 files changed, 64 insertions(+), 68 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index ce7d2e1e..e690b4f9 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -32,7 +32,7 @@ export class PluginManager { return this; } - if (this.get(plugin.id) !== null) { + if (this.get(plugin.id)) { log.error(`Duplicate plugin ID: ${plugin.id}`); return this; } diff --git a/src/view/plugin/pagination.tsx b/src/view/plugin/pagination.tsx index d7d2f4e1..0df8b5fd 100644 --- a/src/view/plugin/pagination.tsx +++ b/src/view/plugin/pagination.tsx @@ -2,9 +2,8 @@ import { h, Fragment } from 'preact'; import PaginationLimit from '../../pipeline/limit/pagination'; import { classJoin, className } from '../../util/className'; import ServerPaginationLimit from '../../pipeline/limit/serverPagination'; -import Tabular from '../../tabular'; import { useConfig } from '../../hooks/useConfig'; -import { useEffect, useState } from 'preact/hooks'; +import { useEffect, useRef, useState } from 'preact/hooks'; import { useTranslator } from '../../i18n/language'; export interface PaginationConfig { @@ -21,62 +20,60 @@ export interface PaginationConfig { }; } -export function Pagination(props: PaginationConfig) { - props.summary = props.summary || true; - props.nextButton = props.nextButton || true; - props.prevButton = props.prevButton || true; - props.buttonsCount = props.buttonsCount || 3; - props.limit = props.limit || 10; - props.page = props.page || 0; - props.resetPageOnUpdate = props.resetPageOnUpdate || true; - - let processor: PaginationLimit | ServerPaginationLimit; +export function Pagination() { + const config = useConfig(); + const { + server, + summary = true, + nextButton = true, + prevButton = true, + buttonsCount = 3, + limit = 10, + page = 0, + resetPageOnUpdate = true + } = config.pagination; - const [currentPage, setCurrentPage] = useState(props.page); - const [limit] = useState(props.limit); + const processor = useRef(null); + const [currentPage, setCurrentPage] = useState(page); const [total, setTotal] = useState(0); - const config = useConfig(); const _ = useTranslator(); - const setTotalFromTabular = (tabular: Tabular) => { - setTotal(tabular.length); - }; - - if (props.server) { - processor = new ServerPaginationLimit({ - limit: limit, - page: currentPage, - url: props.server.url, - body: props.server.body, - }); - - config.pipeline.on('afterProcess', setTotalFromTabular); - } else { - processor = new PaginationLimit({ - limit: limit, - page: currentPage, - }); - - // Pagination (all Limit processors) is the last step in the pipeline - // and we assume that at this stage, we have the rows that we care about. - // Let's grab the rows before processing Pagination and set total number of rows - processor.on('beforeProcess', setTotalFromTabular); - } - - config.pipeline.register(processor); + useEffect(() => { + if (server) { + processor.current = new ServerPaginationLimit({ + limit: limit, + page: currentPage, + url: server.url, + body: server.body, + }); + } else { + processor.current = new PaginationLimit({ + limit: limit, + page: currentPage, + }); + } - // we need to make sure that the state is set - // to the default props when an error happens - config.pipeline.on('error', () => { - setTotal(0); - setCurrentPage(0); - }); + if (processor.current instanceof ServerPaginationLimit) { + config.pipeline.on('afterProcess', (tabular) => setTotal(tabular.length)); + } else if (processor.current instanceof PaginationLimit) { + // Pagination (all Limit processors) is the last step in the pipeline + // and we assume that at this stage, we have the rows that we care about. + // Let's grab the rows before processing Pagination and set total number of rows + processor.current.on('beforeProcess', (tabular) => setTotal(tabular.length)); + } - useEffect(() => { config.pipeline.on('updated', onUpdate); + config.pipeline.register(processor.current); + + // we need to make sure that the state is set + // to the default props when an error happens + config.pipeline.on('error', () => { + setTotal(0); + setCurrentPage(0); + }); return () => { - config.pipeline.unregister(processor); + config.pipeline.unregister(processor.current); config.pipeline.off('updated', onUpdate); }; }, []); @@ -84,7 +81,7 @@ export function Pagination(props: PaginationConfig) { const onUpdate = (updatedProcessor) => { // this is to ensure that the current page is set to 0 // when a processor is updated for some reason - if (props.resetPageOnUpdate && updatedProcessor !== processor) { + if (resetPageOnUpdate && updatedProcessor !== processor.current) { setCurrentPage(0); } }; @@ -98,18 +95,18 @@ export function Pagination(props: PaginationConfig) { setCurrentPage(page); - processor.setProps({ + processor.current.setProps({ page: page, }); }; const renderPages = () => { - if (props.buttonsCount <= 0) { + if (buttonsCount <= 0) { return null; } // how many pagination buttons to render? - const maxCount: number = Math.min(pages(), props.buttonsCount); + const maxCount: number = Math.min(pages(), buttonsCount); let pagePivot = Math.min(currentPage, Math.floor(maxCount / 2)); if (currentPage + Math.floor(maxCount / 2) >= pages()) { @@ -123,7 +120,7 @@ export function Pagination(props: PaginationConfig) {
"`; +exports[`Container component should attach classNames 1`] = `""`; -exports[`Container component should attach styles 1`] = `"
123
abc
Showing 1 to 2 of 2 results
"`; +exports[`Container component should attach styles 1`] = `""`; -exports[`Container component should remove the EventEmitter listeners 1`] = `"
boo123
foo456
bar789
"`; +exports[`Container component should render a container with array of objects with object columns 1`] = `""`; -exports[`Container component should render a container with array of objects with object columns 1`] = `"
Name
Phone Number
boo123
foo456
bar789
"`; +exports[`Container component should render a container with array of objects with string columns 1`] = `""`; -exports[`Container component should render a container with array of objects with string columns 1`] = `"
Name
Phone Number
boo123
foo456
bar789
"`; +exports[`Container component should render a container with array of objects without columns input 1`] = `""`; -exports[`Container component should render a container with array of objects with string columns 2`] = `"
boo123
foo456
bar789
"`; +exports[`Container component should render a container with error 1`] = `""`; -exports[`Container component should render a container with array of objects without columns input 1`] = `"
name
phoneNumber
boo123
foo456
bar789
"`; +exports[`Container component should render a container with null array 1`] = `""`; -exports[`Container component should render a container with error 1`] = `"
An error happened while fetching the data
"`; +exports[`Container component should render a container with searchable table 1`] = `""`; -exports[`Container component should render a container with null array 1`] = `"
No matching records found
"`; +exports[`Container component should render a container with sortable and paginated table 1`] = `""`; -exports[`Container component should render a container with null array 2`] = `"
123
abc
"`; - -exports[`Container component should render a container with null array 3`] = `"
123
abc
"`; - -exports[`Container component should render a container with null array 4`] = `"
123
abc
"`; - -exports[`Container component should render a container with null array 5`] = `"
c1
c2
c3
123
abc
"`; - -exports[`Container component should render a container with null array 6`] = `"
An error happened while fetching the data
"`; - -exports[`Container component should render a container with searchable table 1`] = `"
123
abc
"`; - -exports[`Container component should render a container with sortable and paginated table 1`] = `"
c1
c2
c3
123
abc
Showing 1 to 2 of 2 results
"`; - -exports[`Container component should render a container with table 1`] = `"
123
abc
"`; +exports[`Container component should render a container with table 1`] = `""`; From 5f717e7664001b729f87d06a21ded2ea23d38b8d Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 12:12:18 +0000 Subject: [PATCH 35/55] chore: search tests --- tests/jest/view/plugin/search/search.test.tsx | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/tests/jest/view/plugin/search/search.test.tsx b/tests/jest/view/plugin/search/search.test.tsx index 494da50e..afda8b4d 100644 --- a/tests/jest/view/plugin/search/search.test.tsx +++ b/tests/jest/view/plugin/search/search.test.tsx @@ -1,12 +1,8 @@ import { h } from 'preact'; +import { act } from 'preact/test-utils'; import { mount } from 'enzyme'; import { Config, ConfigContext } from '../../../../../src/config'; -import { EventEmitter } from '../../../../../src/util/eventEmitter'; -import { GridEvents } from '../../../../../src/events'; -import PipelineUtils from '../../../../../src/pipeline/pipelineUtils'; -import { Translator } from '../../../../../src/i18n/language'; import { Search } from '../../../../../src/view/plugin/search/search'; -import Header from '../../../../../src/header'; import * as SearchActions from '../../../../../src/view/plugin/search/actions'; import { flushPromises } from '../../../testUtil'; @@ -14,12 +10,8 @@ describe('Search plugin', () => { let config: Config; beforeEach(() => { - config = new Config(); - config.autoWidth = true; - config.eventEmitter = new EventEmitter(); - config.translator = new Translator(); - config.pipeline = PipelineUtils.createFromConfig(config); - config.header = Header.fromUserConfig({ + config = new Config().update({ + data: [['a', 'b', 'c']], columns: ['Name', 'Phone Number'], }); }); @@ -31,9 +23,15 @@ describe('Search plugin', () => { it('should render the search box', async () => { const mock = jest.spyOn(SearchActions, 'SearchKeyword'); + config.update({ + search: { + keyword: 'boo', + }, + }); + const search = mount( - + , ); @@ -44,6 +42,10 @@ describe('Search plugin', () => { it('should not call search if keyword is undefined', async () => { const mock = jest.spyOn(SearchActions, 'SearchKeyword'); + config.update({ + search: true, + }); + mount( @@ -56,36 +58,44 @@ describe('Search plugin', () => { it('should call search action after input change', async () => { const mock = jest.spyOn(SearchActions, 'SearchKeyword'); + config.update({ + search: true, + }); + const wrapper = mount( , ); - // https://github.com/preactjs/enzyme-adapter-preact-pure/issues/45 const input = wrapper.find('input'); - //console.log('INPUT', input.getDOMNode()) - //input.getDOMNode().value = '123'; - input.simulate('input', { target: { value: '1234' } }); - - return flushPromises().then(() => { - setTimeout(() => { - expect(mock).toBeCalledWith('123'); - }, 0); + const onInput = input.props().onInput; + + act(() => { + const htmlInputElement = document.createElement('input'); + htmlInputElement.value = '123'; + onInput({ target: htmlInputElement }); }); + + wrapper.update(); + + await flushPromises(); + await flushPromises(); + + expect(mock).toBeCalledWith('123'); }); it('should add config.className.search', async () => { + config.update({ + search: true, + className: { + search: 'test-search-class-name', + }, + }); + const search = mount( - - + + , ); From ed9d6e378e68881856bd57864e0300b897a98251 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 12:26:30 +0000 Subject: [PATCH 36/55] chore: pagination tests --- .../__snapshots__/pagination.test.tsx.snap | 6 +- tests/jest/view/plugin/pagination.test.tsx | 91 ++++++++++--------- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/tests/jest/view/plugin/__snapshots__/pagination.test.tsx.snap b/tests/jest/view/plugin/__snapshots__/pagination.test.tsx.snap index f953b040..16a2f546 100644 --- a/tests/jest/view/plugin/__snapshots__/pagination.test.tsx.snap +++ b/tests/jest/view/plugin/__snapshots__/pagination.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Pagination plugin should render the pagination with no records 1`] = `"
"`; +exports[`Pagination plugin should render the pagination with no records 1`] = `"
"`; -exports[`Pagination plugin should render the pagination with one page 1`] = `"
Showing 1 to 3 of 3 results
"`; +exports[`Pagination plugin should render the pagination with one page 1`] = `"
Showing 1 to 3 of 3 results
"`; -exports[`Pagination plugin should render the pagination with three page 1`] = `"
Showing 1 to 1 of 3 results
"`; +exports[`Pagination plugin should render the pagination with three page 1`] = `"
Showing 1 to 1 of 3 results
"`; diff --git a/tests/jest/view/plugin/pagination.test.tsx b/tests/jest/view/plugin/pagination.test.tsx index 9e4e7bb2..bad00f06 100644 --- a/tests/jest/view/plugin/pagination.test.tsx +++ b/tests/jest/view/plugin/pagination.test.tsx @@ -1,22 +1,14 @@ import { mount } from 'enzyme'; -import { createContext } from 'preact'; -import { Config } from '../../../../src/config'; -import { Plugin, PluginPosition } from '../../../../src/plugin'; +import { h } from 'preact'; +import { Config, ConfigContext } from '../../../../src/config'; import { Pagination } from '../../../../src/view/plugin/pagination'; -import Header from '../../../../src/header'; describe('Pagination plugin', () => { let config: Config; - const configContext = createContext(null); - const plugin: Plugin = { - id: 'mypagination', - position: PluginPosition.Footer, - component: {}, - }; beforeEach(() => { - config = Config.fromUserConfig({ - header: Header.fromColumns(['a', 'b', 'c']), + config = new Config().update({ + columns: ['a', 'b', 'c'], data: [ [1, 2, 3], [4, 5, 6], @@ -30,20 +22,15 @@ describe('Pagination plugin', () => { }); it('should render the pagination with no records', async () => { - config = Config.fromUserConfig({ - header: Header.fromColumns(['a', 'b', 'c']), + config.update({ data: [], + pagination: true, }); const pagination = mount( - - - , + + + , ); await config.pipeline.process(); @@ -51,46 +38,60 @@ describe('Pagination plugin', () => { }); it('should render the pagination with one page', async () => { + config.update({ + pagination: { + limit: 3, + }, + }); + const pagination = mount( - - - , + + + , ); - await config.pipeline.process(); + expect(pagination.html()).toMatchSnapshot(); }); it('should render the pagination with three page', async () => { + config.update({ + pagination: { + limit: 1, + }, + }); + const pagination = mount( - - - , + + + , ); - await config.pipeline.process(); pagination.update(); + await config.pipeline.process(); expect(pagination.html()).toMatchSnapshot(); }); it('should add config.className.pagination', async () => { + config.update({ + pagination: { + limit: 1, + }, + className: { + pagination: 'my-pagination-class', + paginationButton: 'my-button', + paginationButtonNext: 'my-next-button', + paginationButtonPrev: 'my-prev-button', + paginationSummary: 'my-page-summary', + paginationButtonCurrent: 'my-current-button', + }, + }); + const pagination = mount( - - - , + + + , ); await config.pipeline.process(); From 24add15a11f5868ce9b2462241ff99f2b83b1cd4 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 12:40:31 +0000 Subject: [PATCH 37/55] chore: container and grid tests --- tests/jest/grid.test.ts | 74 ++++++++++++------------------ tests/jest/view/container.test.tsx | 2 - 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/tests/jest/grid.test.ts b/tests/jest/grid.test.ts index 03a5bce6..440b0a10 100644 --- a/tests/jest/grid.test.ts +++ b/tests/jest/grid.test.ts @@ -1,9 +1,38 @@ import Grid from '../../src/grid'; +import * as Actions from '../../src/view/actions'; import MemoryStorage from '../../src/storage/memory'; import { mount } from 'enzyme'; import { flushPromises } from './testUtil'; describe('Grid class', () => { + afterEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + it('should trigger the events in the correct order', async () => { + const grid = new Grid({ + columns: ['a', 'b', 'c'], + data: [[1, 2, 3]], + }); + + const setLoadingDataMock = jest.spyOn(Actions, 'SetLoadingData'); + const setDataMock = jest.spyOn(Actions, 'SetData'); + const setStatusToRenderedMock = jest.spyOn(Actions, 'SetStatusToRendered'); + + mount(grid.createElement()); + + await flushPromises(); + await flushPromises(); + + expect(setLoadingDataMock).toHaveBeenCalledBefore(setDataMock); + expect(setDataMock).toHaveBeenCalledBefore(setStatusToRenderedMock); + + expect(setLoadingDataMock).toBeCalledTimes(1); + expect(setDataMock).toBeCalledTimes(1); + expect(setStatusToRenderedMock).toBeCalledTimes(1); + }); + it('should raise an exception with empty config', () => { expect(() => { new Grid({}).render(document.createElement('div')); @@ -49,49 +78,4 @@ describe('Grid class', () => { expect(grid.config.data).toStrictEqual(config1.data); expect(grid.config.width).toStrictEqual(config2.width); }); - - it('should trigger the load and beforeLoad events', async () => { - const grid = new Grid({ - data: [[1, 2, 3]], - }); - - const loadMock = jest.fn(); - const beforeLoadMock = jest.fn(); - grid.on('load', loadMock); - grid.on('beforeLoad', beforeLoadMock); - - const container = mount(grid.createElement()); - - await container.instance().componentDidMount(); - expect(loadMock).toBeCalled(); - expect(beforeLoadMock).toBeCalled(); - }); - - it('should trigger the events in the correct order', async () => { - const grid = new Grid({ - columns: ['a', 'b', 'c'], - data: [[1, 2, 3]], - }); - - const loadMock = jest.fn(); - const beforeLoadMock = jest.fn(); - const readyMock = jest.fn(); - - grid.on('load', loadMock); - grid.on('beforeLoad', beforeLoadMock); - grid.on('ready', readyMock); - - const container = mount(grid.createElement()); - - return flushPromises().then(async () => { - await container.instance().componentDidMount(); - - expect(beforeLoadMock).toHaveBeenCalledBefore(loadMock); - expect(loadMock).toHaveBeenCalledBefore(readyMock); - - expect(beforeLoadMock).toBeCalledTimes(2); - expect(loadMock).toBeCalledTimes(2); - expect(readyMock).toBeCalledTimes(2); - }); - }); }); diff --git a/tests/jest/view/container.test.tsx b/tests/jest/view/container.test.tsx index 025e74d8..417c86b1 100644 --- a/tests/jest/view/container.test.tsx +++ b/tests/jest/view/container.test.tsx @@ -3,13 +3,11 @@ import { mount } from 'enzyme'; import { axe, toHaveNoViolations } from 'jest-axe'; import { Config, ConfigContext } from '../../../src/config'; import { Container } from '../../../src/view/container'; -//import StorageUtils from '../../../src/storage/storageUtils'; import { PipelineProcessor, ProcessorType, } from '../../../src/pipeline/processor'; import { flushPromises } from '../testUtil'; -//import PipelineUtils from '../../../src/pipeline/pipelineUtils'; import { EventEmitter } from '../../../src/util/eventEmitter'; import { GridEvents } from '../../../src/events'; import { PluginManager } from '../../../src/plugin'; From a9c7dd4ab898a7b0fabd18dc251be6cf7959f305 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 12:55:00 +0000 Subject: [PATCH 38/55] chore: plugin manager tests --- tests/jest/__snapshots__/plugin.test.tsx.snap | 2 +- tests/jest/plugin.test.tsx | 48 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/jest/__snapshots__/plugin.test.tsx.snap b/tests/jest/__snapshots__/plugin.test.tsx.snap index 2220c630..d0aaf294 100644 --- a/tests/jest/__snapshots__/plugin.test.tsx.snap +++ b/tests/jest/__snapshots__/plugin.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Plugin should render the plugins 1`] = `"dummyheaderdummyfooter"`; +exports[`Plugin should render the plugins 1`] = `"dummyplugindummyplugin"`; diff --git a/tests/jest/plugin.test.tsx b/tests/jest/plugin.test.tsx index bbee999b..e9bdc2ca 100644 --- a/tests/jest/plugin.test.tsx +++ b/tests/jest/plugin.test.tsx @@ -1,23 +1,23 @@ +import { h } from 'preact'; import { - PluginBaseComponent, - PluginBaseProps, PluginManager, PluginPosition, PluginRenderer, } from '../../src/plugin'; -import { createContext, h } from 'preact'; import { mount } from 'enzyme'; -import { Config } from '../../src/config'; +import { Config, ConfigContext } from '../../src/config'; +import { useConfig } from '../../src/hooks/useConfig'; -describe('Plugin', () => { - interface DummyPluginProps extends PluginBaseProps { - text?: string; - } +interface DummyConfig extends Config { + dummy: { + text: string; + }; +} - class DummyPlugin extends PluginBaseComponent { - render() { - return h('b', {}, this.props.text || 'hello!'); - } +describe('Plugin', () => { + function DummyPlugin() { + const config = useConfig() as T; + return h('b', {}, config.dummy.text || 'hello!'); } it('should add and remove plugins', () => { @@ -28,13 +28,13 @@ describe('Plugin', () => { manager.add({ id: 'dummy', position: PluginPosition.Header, - component: DummyPlugin.prototype, + component: DummyPlugin, }); manager.add({ id: 'dummy2', position: PluginPosition.Header, - component: DummyPlugin.prototype, + component: DummyPlugin, }); expect(manager.list()).toHaveLength(2); @@ -137,48 +137,48 @@ describe('Plugin', () => { expect(plugin.component).toBe(component); expect(plugin.position).toBe(PluginPosition.Header); - expect(manager.get('doesnexist')).toBeNull(); + expect(manager.get('doesnexist')).toBeUndefined(); }); it('should render the plugins', async () => { - const configContext = createContext(null); - const config = Config.fromUserConfig({ + const config = new Config().update({ data: [[1, 2, 3]], - }); + }) as DummyConfig; + + config.dummy = { + text: 'dummyplugin', + }; config.plugin.add({ id: 'dummyheader', position: PluginPosition.Header, component: DummyPlugin, - props: { text: 'dummyheader' }, }); config.plugin.add({ id: 'dummyfooter', position: PluginPosition.Footer, component: DummyPlugin, - props: { text: 'dummyfooter' }, }); const renderer = mount( - + - , + , ); expect(renderer.html()).toMatchSnapshot(); }); it('should create a userConfig with custom plugin', () => { - const config = Config.fromUserConfig({ + const config = new Config().update({ data: [[1, 2, 3]], plugins: [ { id: 'dummyheader', position: PluginPosition.Header, component: DummyPlugin, - props: { text: 'dummyheader' }, }, ], }); From 2040b9414b4d0cc1f499d8b7693dd55332ff1cb0 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:50:17 +0000 Subject: [PATCH 39/55] rebase --- l10n/cs_CZ.ts | 41 ++++++++++++++++++++--------------------- l10n/index.ts | 4 +++- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/l10n/cs_CZ.ts b/l10n/cs_CZ.ts index 4d34e9bd..9f1f10a8 100644 --- a/l10n/cs_CZ.ts +++ b/l10n/cs_CZ.ts @@ -1,23 +1,22 @@ export default { - search: { - placeholder: 'Napište klíčové slovo...', - }, - sort: { - sortAsc: 'Seřadit sloupec vzestupně', - sortDesc: 'Seřadit sloupec sestupně', - }, - pagination: { - firstPage: 'První stránka', - previous: 'Předchozí', - next: 'Další', - navigate: (page, pages) => `Stránka ${page} z ${pages}`, - page: (page) => `Stránka ${page}`, - showing: 'Zobrazeno', - of: 'z', - to: 'až', - results: 'výsledků', - }, - loading: 'Načítám...', - noRecordsFound: 'Nebyly nalezeny žádné odpovídající záznamy', - error: 'Při načítání dat došlo k chybě', + search: { + placeholder: 'Napište klíčové slovo...', + }, + sort: { + sortAsc: 'Seřadit sloupec vzestupně', + sortDesc: 'Seřadit sloupec sestupně', + }, + pagination: { + previous: 'Předchozí', + next: 'Další', + navigate: (page, pages) => `Stránka ${page} z ${pages}`, + page: (page) => `Stránka ${page}`, + showing: 'Zobrazeno', + of: 'z', + to: 'až', + results: 'výsledků', + }, + loading: 'Načítám...', + noRecordsFound: 'Nebyly nalezeny žádné odpovídající záznamy', + error: 'Při načítání dat došlo k chybě', }; diff --git a/l10n/index.ts b/l10n/index.ts index af2fbf9d..2fa39eaf 100644 --- a/l10n/index.ts +++ b/l10n/index.ts @@ -14,6 +14,7 @@ import ptBR from './pt_BR'; import faIR from './fa_IR'; import nbNO from './nb_NO'; import uaUA from './ua_UA'; +import csCZ from './cs_CZ'; export { esES, @@ -31,5 +32,6 @@ export { ptBR, faIR, nbNO, - uaUA + uaUA, + csCZ, }; From d8052a26ee0b4e2ad1b21c578c95e65f00d38414 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 14:28:47 +0000 Subject: [PATCH 40/55] chore: event listener tests --- src/pipeline/pipeline.ts | 2 ++ src/view/plugin/search/search.tsx | 2 +- src/view/plugin/sort/sort.tsx | 2 +- tests/jest/view/container.test.tsx | 18 ++++++++++-------- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/pipeline/pipeline.ts b/src/pipeline/pipeline.ts index 705224a3..aa6a6ded 100644 --- a/src/pipeline/pipeline.ts +++ b/src/pipeline/pipeline.ts @@ -73,6 +73,8 @@ class Pipeline extends EventEmitter> { processor: PipelineProcessor, priority: number = null, ): void { + if (!processor) return; + if (processor.type === null) { throw Error('Processor type is not defined'); } diff --git a/src/view/plugin/search/search.tsx b/src/view/plugin/search/search.tsx index 0e73a044..89593af2 100644 --- a/src/view/plugin/search/search.tsx +++ b/src/view/plugin/search/search.tsx @@ -67,7 +67,7 @@ export function Search() { }, [props]); useEffect(() => { - if (processor) config.pipeline.register(processor); + config.pipeline.register(processor); return () => config.pipeline.unregister(processor); }, [config, processor]); diff --git a/src/view/plugin/sort/sort.tsx b/src/view/plugin/sort/sort.tsx index fc3bfece..5b19779c 100644 --- a/src/view/plugin/sort/sort.tsx +++ b/src/view/plugin/sort/sort.tsx @@ -53,7 +53,7 @@ export function Sort( }, []); useEffect(() => { - if (processor) config.pipeline.register(processor); + config.pipeline.register(processor); return () => config.pipeline.unregister(processor); }, [config, processor]); diff --git a/tests/jest/view/container.test.tsx b/tests/jest/view/container.test.tsx index 417c86b1..1ae4c502 100644 --- a/tests/jest/view/container.test.tsx +++ b/tests/jest/view/container.test.tsx @@ -8,9 +8,6 @@ import { ProcessorType, } from '../../../src/pipeline/processor'; import { flushPromises } from '../testUtil'; -import { EventEmitter } from '../../../src/util/eventEmitter'; -import { GridEvents } from '../../../src/events'; -import { PluginManager } from '../../../src/plugin'; import { Status } from '../../../src/types'; import * as TableActions from '../../../src/view/actions'; import Tabular from '../../../src/tabular'; @@ -21,14 +18,16 @@ describe('Container component', () => { let config: Config; beforeEach(() => { - config = Config.fromPartialConfig({ + config = new Config().update({ data: [ [1, 2, 3], ['a', 'b', 'c'], ], - }) as Config; - config.eventEmitter = new EventEmitter(); - config.plugin = new PluginManager(); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); }); it('should render a container with table', async () => { @@ -338,7 +337,7 @@ describe('Container component', () => { }); it('should unregister the processors', async () => { - const config = Config.fromPartialConfig({ + const config = new Config().update({ pagination: true, search: true, sort: true, @@ -375,6 +374,9 @@ describe('Container component', () => { container.unmount(); + await flushPromises(); + await flushPromises(); + await flushPromises(); await flushPromises(); expect(mockUnregister.mock.calls.length).toBe( From 16e8322f4942abf917e13ab89fe41d8253fe0e13 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 14:32:46 +0000 Subject: [PATCH 41/55] chore: event listener instance --- tests/jest/view/container.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/jest/view/container.test.tsx b/tests/jest/view/container.test.tsx index 1ae4c502..fae66f47 100644 --- a/tests/jest/view/container.test.tsx +++ b/tests/jest/view/container.test.tsx @@ -11,6 +11,7 @@ import { flushPromises } from '../testUtil'; import { Status } from '../../../src/types'; import * as TableActions from '../../../src/view/actions'; import Tabular from '../../../src/tabular'; +import { EventEmitter } from '../../../src/util/eventEmitter'; expect.extend(toHaveNoViolations); @@ -314,11 +315,13 @@ describe('Container component', () => { }); it('should remove the EventEmitter listeners', async () => { - config.update({ + const config = new Config().update({ + data: [], search: true, pagination: true, columns: ['Name', 'Phone Number'], sort: true, + eventEmitter: new EventEmitter(), }); const container = mount( From 674735624fac475ee074721eb30d1f08f9e89d36 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 14:55:59 +0000 Subject: [PATCH 42/55] fix: selection plugin configs --- plugins/selection/src/rowSelection/actions.ts | 6 +- .../src/rowSelection/rowSelection.tsx | 1 + .../tests/rowSelection/actions.test.ts | 77 ++++++++----- .../tests/rowSelection/store.test.ts | 80 ------------- plugins/selection/tsconfig.json | 12 ++ plugins/selection/tsconfig.release.json | 6 +- plugins/selection/tsconfig.test.json | 7 ++ tests/dev-server/package-lock.json | 105 ++++++++++++++++-- 8 files changed, 175 insertions(+), 119 deletions(-) delete mode 100644 plugins/selection/tests/rowSelection/store.test.ts create mode 100644 plugins/selection/tsconfig.json create mode 100644 plugins/selection/tsconfig.test.json diff --git a/plugins/selection/src/rowSelection/actions.ts b/plugins/selection/src/rowSelection/actions.ts index 71afe52f..f3e313d3 100644 --- a/plugins/selection/src/rowSelection/actions.ts +++ b/plugins/selection/src/rowSelection/actions.ts @@ -1,11 +1,13 @@ export const CheckRow = (rowId: string) => (state) => { + const rowIds = state.rowSelection?.rowIds || []; + // rowId already exists - if (state.rowSelection.rowIds.indexOf(rowId) > -1) return state; + if (rowIds.indexOf(rowId) > -1) return state; return { ...state, rowSelection: { - rowIds: [rowId, ...state.rowSelection.rowIds], + rowIds: [rowId, ...rowIds], }, }; }; diff --git a/plugins/selection/src/rowSelection/rowSelection.tsx b/plugins/selection/src/rowSelection/rowSelection.tsx index 1934d5d8..e654baec 100644 --- a/plugins/selection/src/rowSelection/rowSelection.tsx +++ b/plugins/selection/src/rowSelection/rowSelection.tsx @@ -1,3 +1,4 @@ +import { h } from 'preact'; import * as actions from './actions'; import { useStore, className, useEffect, useState, useSelector } from 'gridjs'; import { Row } from 'gridjs'; diff --git a/plugins/selection/tests/rowSelection/actions.test.ts b/plugins/selection/tests/rowSelection/actions.test.ts index 80b5263c..8e9a08eb 100644 --- a/plugins/selection/tests/rowSelection/actions.test.ts +++ b/plugins/selection/tests/rowSelection/actions.test.ts @@ -1,43 +1,68 @@ -import { Dispatcher } from 'gridjs'; -import { RowSelectionActions } from '../../src/rowSelection/actions'; +import * as Actions from '../../src/rowSelection/actions'; describe('Actions', () => { - const dispatcher = new Dispatcher(); + it('should trigger CHECK', () => { + const state = Actions.CheckRow('42')({}); - beforeEach(() => { - dispatcher.dispatch = jest.fn(); + expect(state).toStrictEqual({ + rowSelection: { + rowIds: ['42'], + }, + }); }); - it('should trigger CHECK', () => { - const actions = new RowSelectionActions(dispatcher); - actions.check('42'); + it('should trigger CHECK when rowIds already exists', () => { + const state = Actions.CheckRow('42')({ + rowSelection: { + rowIds: ['24'], + }, + }); + + expect(state).toStrictEqual({ + rowSelection: { + rowIds: ['42', '24'], + }, + }); + }); - expect(dispatcher.dispatch).toBeCalledTimes(1); - expect(dispatcher.dispatch).toBeCalledWith({ - payload: { - ROW_ID: '42', + it('should trigger UNCHECK', () => { + const state = Actions.UncheckRow('42')({ + rowSelection: { + rowIds: ['42'], + }, + }); + + expect(state).toStrictEqual({ + rowSelection: { + rowIds: [], }, - type: 'CHECK', }); }); - it('should trigger CHECK twice', () => { - const actions = new RowSelectionActions(dispatcher); - actions.check('1'); - actions.check('2'); - expect(dispatcher.dispatch).toBeCalledTimes(2); + it('should trigger UNCHECK when rowIds is empty', () => { + const state = Actions.UncheckRow('42')({ + rowSelection: { + rowIds: [], + }, + }); + + expect(state).toStrictEqual({ + rowSelection: { + rowIds: [], + }, + }); }); - it('should trigger UNCHECK', () => { - const actions = new RowSelectionActions(dispatcher); - actions.uncheck('24'); + it('should CHECK and UNCHECK', () => { + let state = {}; + state = Actions.CheckRow('42')(state); + state = Actions.CheckRow('24')(state); + state = Actions.UncheckRow('42')(state); - expect(dispatcher.dispatch).toBeCalledTimes(1); - expect(dispatcher.dispatch).toBeCalledWith({ - payload: { - ROW_ID: '24', + expect(state).toStrictEqual({ + rowSelection: { + rowIds: ['24'], }, - type: 'UNCHECK', }); }); }); diff --git a/plugins/selection/tests/rowSelection/store.test.ts b/plugins/selection/tests/rowSelection/store.test.ts deleted file mode 100644 index bf7eb8fc..00000000 --- a/plugins/selection/tests/rowSelection/store.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { RowSelectionStore } from '../../src/rowSelection/store'; -import { Dispatcher } from 'gridjs'; - -describe('Store', () => { - const dispatcher = new Dispatcher(); - - it('should handle check actions', () => { - const store = new RowSelectionStore(dispatcher); - store.handle('CHECK', { - ROW_ID: 1, - }); - - expect(store.state.rowIds).toHaveLength(1); - expect(store.state.rowIds[0]).toBe(1); - }); - - it('should handle uncheck', () => { - const store = new RowSelectionStore(dispatcher); - - store.handle('CHECK', { - ROW_ID: 1, - }); - - store.handle('CHECK', { - ROW_ID: 2, - }); - - store.handle('UNCHECK', { - ROW_ID: 1, - }); - - expect(store.state.rowIds).toHaveLength(1); - expect(store.state.rowIds[0]).toBe(2); - }); - - it('should handle uncheck with empty store', () => { - const store = new RowSelectionStore(dispatcher); - - store.handle('UNCHECK', { - ROW_ID: 1, - }); - - expect(store.state.rowIds).toHaveLength(0); - }); - - it('should not uncheck wrong items', () => { - const store = new RowSelectionStore(dispatcher); - - store.handle('CHECK', { - ROW_ID: '1', - }); - - store.handle('CHECK', { - ROW_ID: '2', - }); - - store.handle('UNCHECK', { - ROW_ID: '3', - }); - - expect(store.state.rowIds).toHaveLength(2); - expect(store.state.rowIds).toContain('1'); - expect(store.state.rowIds).toContain('2'); - }); - - it('should not check the same item twice', () => { - const store = new RowSelectionStore(dispatcher); - - store.handle('CHECK', { - ROW_ID: 1, - }); - - store.handle('CHECK', { - ROW_ID: 1, - }); - - expect(store.state.rowIds).toHaveLength(1); - expect(store.state.rowIds[0]).toBe(1); - }); -}); diff --git a/plugins/selection/tsconfig.json b/plugins/selection/tsconfig.json new file mode 100644 index 00000000..a0cc8dcf --- /dev/null +++ b/plugins/selection/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "removeComments": true + }, + "include": [ + "index.ts", + "src/**/*", + "tests/**/*" + ] +} diff --git a/plugins/selection/tsconfig.release.json b/plugins/selection/tsconfig.release.json index a0cc8dcf..f65dfe8d 100644 --- a/plugins/selection/tsconfig.release.json +++ b/plugins/selection/tsconfig.release.json @@ -1,12 +1,14 @@ { - "extends": "../../tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "rootDir": ".", "removeComments": true }, "include": [ "index.ts", - "src/**/*", + "src/**/*" + ], + "exclude": [ "tests/**/*" ] } diff --git a/plugins/selection/tsconfig.test.json b/plugins/selection/tsconfig.test.json new file mode 100644 index 00000000..f5515d65 --- /dev/null +++ b/plugins/selection/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest"], + "esModuleInterop": true + } +} diff --git a/tests/dev-server/package-lock.json b/tests/dev-server/package-lock.json index fc7e9813..e8d5365d 100644 --- a/tests/dev-server/package-lock.json +++ b/tests/dev-server/package-lock.json @@ -18,6 +18,56 @@ "sirv-cli": "^1.0.3" } }, + "../..": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "preact": "^10.11.3" + }, + "devDependencies": { + "@types/enzyme": "^3.10.12", + "@types/jest": "^29.2.4", + "@types/jest-axe": "^3.5.5", + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "^5.47.0", + "@typescript-eslint/parser": "^5.47.0", + "autoprefixer": "^10.4.8", + "axe-core": "^4.4.2", + "check-export-map": "^1.1.1", + "cssnano": "^5.0.5", + "cypress": "^8.1.0", + "cypress-visual-regression": "^1.5.7", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^4.0.1", + "eslint": "^8.18.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-jest": "~26.8.7", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.3.1", + "jest-axe": "^6.0.0", + "jest-extended": "^3.2.0", + "jsdom": "^19.0.0", + "jsdom-global": "^3.0.2", + "lerna-changelog": "^2.1.0", + "microbundle": "^0.15.1", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.16", + "postcss-cli": "^9.1.0", + "postcss-nested": "^5.0.6", + "postcss-scss": "^4.0.4", + "postcss-sort-media-queries": "^4.1.0", + "prettier": "~2.7.1", + "rimraf": "~3.0.2", + "sass": "^1.54.5", + "source-map-loader": "^4.0.0", + "start-server-and-test": "^1.12.3", + "ts-jest": "^29.0.3", + "ts-loader": "^9.4.2", + "tslib": "^2.4.1", + "tsutils": "^3.21.0", + "typescript": "^4.9.4" + } + }, "node_modules/@babel/code-frame": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", @@ -6911,12 +6961,8 @@ "dev": true }, "node_modules/gridjs": { - "version": "6.0.0", - "resolved": "file:../..", - "license": "MIT", - "dependencies": { - "preact": "^10.11.3" - } + "resolved": "../..", + "link": true }, "node_modules/gzip-size": { "version": "6.0.0", @@ -24001,9 +24047,50 @@ "dev": true }, "gridjs": { - "version": "6.0.0", - "requires": { - "preact": "^10.11.3" + "version": "file:../..", + "requires": { + "@types/enzyme": "^3.10.12", + "@types/jest": "^29.2.4", + "@types/jest-axe": "^3.5.5", + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "^5.47.0", + "@typescript-eslint/parser": "^5.47.0", + "autoprefixer": "^10.4.8", + "axe-core": "^4.4.2", + "check-export-map": "^1.1.1", + "cssnano": "^5.0.5", + "cypress": "^8.1.0", + "cypress-visual-regression": "^1.5.7", + "enzyme": "^3.11.0", + "enzyme-adapter-preact-pure": "^4.0.1", + "eslint": "^8.18.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-jest": "~26.8.7", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.3.1", + "jest-axe": "^6.0.0", + "jest-extended": "^3.2.0", + "jsdom": "^19.0.0", + "jsdom-global": "^3.0.2", + "lerna-changelog": "^2.1.0", + "microbundle": "^0.15.1", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.16", + "postcss-cli": "^9.1.0", + "postcss-nested": "^5.0.6", + "postcss-scss": "^4.0.4", + "postcss-sort-media-queries": "^4.1.0", + "preact": "^10.11.3", + "prettier": "~2.7.1", + "rimraf": "~3.0.2", + "sass": "^1.54.5", + "source-map-loader": "^4.0.0", + "start-server-and-test": "^1.12.3", + "ts-jest": "^29.0.3", + "ts-loader": "^9.4.2", + "tslib": "^2.4.1", + "tsutils": "^3.21.0", + "typescript": "^4.9.4" } }, "gzip-size": { From aa536b461661c4f070695570c97266f0a3dd6763 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:00:55 +0000 Subject: [PATCH 43/55] fix: eslint config --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index e8888c4d..209937d1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,7 @@ }, "parser": "@typescript-eslint/parser", "parserOptions": { - "project": ["tsconfig.json", "*/tsconfig.release.json", "plugins/*/tsconfig.release.json", "tests/cypress/tsconfig.json"], + "project": ["tsconfig.json", "plugins/*/tsconfig.json", "tests/cypress/tsconfig.json"], "sourceType": "module" }, "plugins": [ From b305c514a635cb2f69874581f4e9d8a5e6f690ec Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:05:56 +0000 Subject: [PATCH 44/55] fix: eslint config --- .eslintrc.json | 2 +- l10n/tsconfig.json | 12 ++++++++++++ l10n/tsconfig.release.json | 8 ++++---- l10n/tsconfig.test.json | 7 +++++++ 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 l10n/tsconfig.json create mode 100644 l10n/tsconfig.test.json diff --git a/.eslintrc.json b/.eslintrc.json index 209937d1..328e2dab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,7 @@ }, "parser": "@typescript-eslint/parser", "parserOptions": { - "project": ["tsconfig.json", "plugins/*/tsconfig.json", "tests/cypress/tsconfig.json"], + "project": ["tsconfig.json", "*/tsconfig.json", "plugins/*/tsconfig.json", "tests/cypress/tsconfig.json"], "sourceType": "module" }, "plugins": [ diff --git a/l10n/tsconfig.json b/l10n/tsconfig.json new file mode 100644 index 00000000..24e030b1 --- /dev/null +++ b/l10n/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "removeComments": true + }, + "include": [ + "index.ts", + "src/**/*", + "tests/**/*" + ] +} diff --git a/l10n/tsconfig.release.json b/l10n/tsconfig.release.json index bbb0cb84..f65dfe8d 100644 --- a/l10n/tsconfig.release.json +++ b/l10n/tsconfig.release.json @@ -1,14 +1,14 @@ { - "extends": "../tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "rootDir": ".", "removeComments": true }, "include": [ - "index.ts" + "index.ts", + "src/**/*" ], "exclude": [ - "tests/**/*", - "src/**/*" + "tests/**/*" ] } diff --git a/l10n/tsconfig.test.json b/l10n/tsconfig.test.json new file mode 100644 index 00000000..f5515d65 --- /dev/null +++ b/l10n/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest"], + "esModuleInterop": true + } +} From ac577c2f5057852819bdc1a7082fb27b887602c2 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:08:42 +0000 Subject: [PATCH 45/55] chore: remove node 12 --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 60a11ed6..d7f4c7c6 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [12.x, 14.x, 16.x] + node-version: [14.x, 16.x] steps: - uses: actions/checkout@v2 From da0c99dc7f736825b78d7cac8849f9780e548695 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:12:21 +0000 Subject: [PATCH 46/55] chore: fix search tests --- tests/jest/view/plugin/search/search.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/jest/view/plugin/search/search.test.tsx b/tests/jest/view/plugin/search/search.test.tsx index afda8b4d..35ebd9ca 100644 --- a/tests/jest/view/plugin/search/search.test.tsx +++ b/tests/jest/view/plugin/search/search.test.tsx @@ -79,6 +79,7 @@ describe('Search plugin', () => { wrapper.update(); + await flushPromises(); await flushPromises(); await flushPromises(); From 9882fdf56c442a6ac61f42f56fbf59040918fb91 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:15:56 +0000 Subject: [PATCH 47/55] chore: adding node 18 --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d7f4c7c6..9bd0fa72 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x] + node-version: [14.x, 16.x, 18.x] steps: - uses: actions/checkout@v2 From 36f1cfcdfe4ec7ac657a748ee7953fa6cf81f47e Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:20:44 +0000 Subject: [PATCH 48/55] chore: remove node 18 --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9bd0fa72..d7f4c7c6 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x, 18.x] + node-version: [14.x, 16.x] steps: - uses: actions/checkout@v2 From efb414234bf20da839c3435fda9d4e63e2ad3771 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 15:44:10 +0000 Subject: [PATCH 49/55] chore: search input tests --- tests/jest/view/plugin/search/search.test.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/jest/view/plugin/search/search.test.tsx b/tests/jest/view/plugin/search/search.test.tsx index 35ebd9ca..dc722357 100644 --- a/tests/jest/view/plugin/search/search.test.tsx +++ b/tests/jest/view/plugin/search/search.test.tsx @@ -71,7 +71,7 @@ describe('Search plugin', () => { const input = wrapper.find('input'); const onInput = input.props().onInput; - act(() => { + await act(() => { const htmlInputElement = document.createElement('input'); htmlInputElement.value = '123'; onInput({ target: htmlInputElement }); @@ -79,11 +79,15 @@ describe('Search plugin', () => { wrapper.update(); - await flushPromises(); - await flushPromises(); await flushPromises(); - expect(mock).toBeCalledWith('123'); + return new Promise((resolve) => { + // TODO: can we fix this and remove the setTimeout? + setTimeout(() => { + expect(mock).toBeCalledWith('123'); + resolve(); + }, 100); + }); }); it('should add config.className.search', async () => { From 86d67b1fe3aba4f2758efc1687c80c256365e274 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Sun, 1 Jan 2023 16:06:22 +0000 Subject: [PATCH 50/55] tests: selection actions tests --- plugins/selection/src/rowSelection/actions.ts | 7 ++++--- .../tests/rowSelection/actions.test.ts | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/plugins/selection/src/rowSelection/actions.ts b/plugins/selection/src/rowSelection/actions.ts index f3e313d3..de8b3944 100644 --- a/plugins/selection/src/rowSelection/actions.ts +++ b/plugins/selection/src/rowSelection/actions.ts @@ -13,12 +13,13 @@ export const CheckRow = (rowId: string) => (state) => { }; export const UncheckRow = (rowId: string) => (state) => { - const index = state.rowSelection.rowIds.indexOf(rowId); + const rowIds = state.rowSelection?.rowIds || []; + const index = rowIds.indexOf(rowId); // rowId doesn't exist - if (index === -1) state; + if (index === -1) return state; - const cloned = [...state.rowSelection.rowIds]; + const cloned = [...rowIds]; cloned.splice(index, 1); return { diff --git a/plugins/selection/tests/rowSelection/actions.test.ts b/plugins/selection/tests/rowSelection/actions.test.ts index 8e9a08eb..9d3d7b85 100644 --- a/plugins/selection/tests/rowSelection/actions.test.ts +++ b/plugins/selection/tests/rowSelection/actions.test.ts @@ -39,6 +39,26 @@ describe('Actions', () => { }); }); + it('should UNCHECK the correct item', () => { + const state = Actions.UncheckRow('42')({ + rowSelection: { + rowIds: ['22', '11'], + }, + }); + + expect(state).toStrictEqual({ + rowSelection: { + rowIds: ['22', '11'], + }, + }); + }); + + it('should UNCHECK when rowIds is null', () => { + const state = Actions.UncheckRow('42')({}); + + expect(state).toStrictEqual({}); + }); + it('should trigger UNCHECK when rowIds is empty', () => { const state = Actions.UncheckRow('42')({ rowSelection: { From 4194741898e9fa787ace607ce0bf9ec5c856ea44 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Tue, 3 Jan 2023 09:35:20 +0000 Subject: [PATCH 51/55] fix: Store type --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index fbc6bbc0..c7903519 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,7 +22,7 @@ export const ConfigContext = createContext(null); export interface Config { // a reference to the current Grid.js instance instance: Grid; - store: Store; + store: Store; eventEmitter: EventEmitter; plugin: PluginManager; /** container element that is used to mount the Grid.js to */ From 516c0fcf76d4bb0b7410ee8b215e00a42841c4e4 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Tue, 3 Jan 2023 09:38:20 +0000 Subject: [PATCH 52/55] chore: code coverage job --- .github/workflows/coverage.yaml | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index de1d2270..965e706b 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -1,21 +1,21 @@ -name: Code Coverage - +name: 'coverage' on: - pull_request: - branches: - - master - - main - + pull_request: + branches: + - master + - main jobs: - coverage: - runs-on: ubuntu-latest - env: - CI_JOB_NUMBER: 1 - steps: - - uses: actions/checkout@v2 - - run: npm install - - run: npm run install:plugins - - uses: artiomtr/jest-coverage-report-action@v1.3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - test_script: npm run test:jest + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: npm install + - run: npm run install:plugins + - uses: ArtiomTr/jest-coverage-report-action@v2 + id: coverage + with: + output: report-markdown + test-script: npm run test:jest + - uses: marocchino/sticky-pull-request-comment@v2 + with: + message: ${{ steps.coverage.outputs.report }} From 0a9f57ffbca3b4a94b8e4929622be21b26cc1daf Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Tue, 3 Jan 2023 10:39:50 +0000 Subject: [PATCH 53/55] fix: Store type --- src/config.ts | 6 +++--- src/state/store.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index c7903519..71e07a43 100644 --- a/src/config.ts +++ b/src/config.ts @@ -22,7 +22,7 @@ export const ConfigContext = createContext(null); export interface Config { // a reference to the current Grid.js instance instance: Grid; - store: Store; + store: Store; eventEmitter: EventEmitter; plugin: PluginManager; /** container element that is used to mount the Grid.js to */ @@ -117,7 +117,7 @@ export class Config { return this; } - static defaultConfig(): Config { + static defaultConfig(): Partial { return { store: new Store({ status: Status.Init, @@ -131,7 +131,7 @@ export class Config { autoWidth: true, style: {}, className: {}, - } as Config; + }; } static fromPartialConfig(partialConfig: Partial): Partial { diff --git a/src/state/store.ts b/src/state/store.ts index a10d87d0..cceacf94 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,4 +1,4 @@ -export class Store { +export class Store> { private state: S; private listeners: (() => void)[] = []; private isDispatching = false; From daeaaa4f5ba38d7120e361aca5f079786f1809ab Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Tue, 10 Jan 2023 20:54:22 +0000 Subject: [PATCH 54/55] Update src/header.ts Co-authored-by: abitbetterthanyesterday --- src/header.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/header.ts b/src/header.ts index ba195c33..41c59736 100644 --- a/src/header.ts +++ b/src/header.ts @@ -62,7 +62,7 @@ class Header extends Base { // let's create a shadow table with the first 10 rows of the data // and let the browser to render the table with table-layout: auto // no padding, margin or border to get the minimum space required - // to render columns. One the table is rendered and widths are known, + // to render columns. Once the table is rendered and widths are known, // we unmount the shadow table from the DOM and set the correct width render( h(ShadowTable, { From 95d8564fc469c74b278e28d194edb2ce2b508db9 Mon Sep 17 00:00:00 2001 From: Afshin Mehrabani Date: Tue, 10 Jan 2023 21:10:47 +0000 Subject: [PATCH 55/55] fix: remove the th setTimeout --- src/view/table/th.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/view/table/th.tsx b/src/view/table/th.tsx index f3317a05..7372c0f2 100644 --- a/src/view/table/th.tsx +++ b/src/view/table/th.tsx @@ -23,18 +23,16 @@ export function TH( const { dispatch } = useStore(); useEffect(() => { - setTimeout(() => { - // sets the `top` style if the current TH is fixed - if (config.fixedHeader && thRef.current) { - const offsetTop = thRef.current.offsetTop; + // sets the `top` style if the current TH is fixed + if (config.fixedHeader && thRef.current) { + const offsetTop = thRef.current.offsetTop; - if (typeof offsetTop === 'number') { - setStyle({ - top: offsetTop, - }); - } + if (typeof offsetTop === 'number') { + setStyle({ + top: offsetTop, + }); } - }, 0); + } }, [thRef]); const isSortable = (): boolean => props.column.sort != undefined;