From 6d16a7568bb13eab1a085d1f6b6598fff956e69c Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 11:54:30 +0200 Subject: [PATCH 01/12] Added components tests. --- packages/grid-lite-react/tests/Grid.test.tsx | 23 +++ packages/grid-pro-react/tests/Grid.test.tsx | 23 +++ .../tests/createGridTests.tsx | 138 ++++++++++++++++++ .../tests/options/Data.test.tsx | 38 +++++ .../tests/options/Header.test.tsx | 13 ++ .../tests/options/Pagination.test.tsx | 63 ++++++++ 6 files changed, 298 insertions(+) create mode 100644 packages/grid-lite-react/tests/Grid.test.tsx create mode 100644 packages/grid-pro-react/tests/Grid.test.tsx create mode 100644 packages/grid-shared-react/tests/createGridTests.tsx create mode 100644 packages/grid-shared-react/tests/options/Data.test.tsx create mode 100644 packages/grid-shared-react/tests/options/Header.test.tsx create mode 100644 packages/grid-shared-react/tests/options/Pagination.test.tsx diff --git a/packages/grid-lite-react/tests/Grid.test.tsx b/packages/grid-lite-react/tests/Grid.test.tsx new file mode 100644 index 0000000..140e4eb --- /dev/null +++ b/packages/grid-lite-react/tests/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests'; +import { Grid, GridOptions } from '../src/index'; + +createGridTests( + 'Grid Lite', + Grid, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-pro-react/tests/Grid.test.tsx b/packages/grid-pro-react/tests/Grid.test.tsx new file mode 100644 index 0000000..65e49d7 --- /dev/null +++ b/packages/grid-pro-react/tests/Grid.test.tsx @@ -0,0 +1,23 @@ +import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests'; +import { Grid, GridOptions } from '../src/index'; + +createGridTests( + 'Grid Pro', + Grid, + { + dataTable: { + columns: { + name: ['Alice', 'Bob'], + age: [30, 25] + } + } + }, + { + dataTable: { + columns: { + name: ['Charlie', 'Diana'], + age: [40, 35] + } + } + } +); diff --git a/packages/grid-shared-react/tests/createGridTests.tsx b/packages/grid-shared-react/tests/createGridTests.tsx new file mode 100644 index 0000000..acf05db --- /dev/null +++ b/packages/grid-shared-react/tests/createGridTests.tsx @@ -0,0 +1,138 @@ +import { render, waitFor, fireEvent } from '@testing-library/react'; +import { + useRef, + useState, + type ComponentType +} from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { GridProps, GridRefHandle } from '../src/components/BaseGrid'; +import { GridInstance } from '../src/hooks/useGrid'; + +/** + * Creates a standard test suite for a Grid component. + * Use this to avoid duplicating tests between + * grid-lite-react and grid-pro-react. + */ +export function createGridTests( + name: string, + GridComponent: ComponentType>, + testOptions: TOptions, + updatedOptions: TOptions +) { + + describe(name, () => { + it('renders a container div and initializes grid', async () => { + let gridInstance: GridInstance | null = null; + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + const { container } = render( + + ); + + expect(container.firstChild).toBeInstanceOf(HTMLDivElement); + + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + }); + + it('provides grid instance via gridRef prop', async () => { + let gridRef: React.RefObject | null>; + let initialized = false; + + function TestComponent() { + gridRef = useRef>(null); + return ( + { initialized = true; }} + /> + ); + } + + render(); + + await waitFor(() => { + expect(initialized).toBe(true); + expect(gridRef.current?.grid).toBeDefined(); + }); + }); + + it('calls callback when grid is initialized', async () => { + const callback = vi.fn(); + render(); + + await waitFor(() => { + expect(callback).toHaveBeenCalled(); + }); + }); + + it('updates grid when options change', async () => { + let gridInstance: GridInstance | null = null; + + function TestComponent() { + const [opts, setOpts] = useState(testOptions); + + const onGridReady = (grid: GridInstance) => { + gridInstance = grid; + }; + + return ( + <> + + + + ); + } + + const { getByTestId, container } = render(); + + // Wait for initial grid creation + await waitFor(() => { + expect(gridInstance).not.toBeNull(); + }); + + // Trigger options change + fireEvent.click(getByTestId('update-options')); + + // Wait for the grid to update with new data + await waitFor(() => { + const cells = container.querySelectorAll('td[data-value]'); + const values = Array.from(cells).map(c => c.getAttribute('data-value')); + expect(values).toContain('Charlie'); + }); + }); + + it('calls destroy() on unmount', async () => { + let destroySpy: ReturnType | null = null; + + const onGridReady = (grid: GridInstance) => { + destroySpy = vi.spyOn(grid, 'destroy'); + }; + + const { unmount } = render( + + ); + + // Wait for grid to initialize + await waitFor(() => { + expect(destroySpy).not.toBeNull(); + }); + + // Unmount and verify destroy was called + unmount(); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }); + + }); +} diff --git a/packages/grid-shared-react/tests/options/Data.test.tsx b/packages/grid-shared-react/tests/options/Data.test.tsx new file mode 100644 index 0000000..a6f165a --- /dev/null +++ b/packages/grid-shared-react/tests/options/Data.test.tsx @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { Data } from '../../src/components/options/data/Data'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Data', () => { + it('maps columns to options.data.columns', () => { + const columns = { + product: ['Apples', 'Oranges'], + price: [1.2, 2.4] + }; + + expect( + getChildProps( + + ) + ).toEqual({ + data: { + columns, + providerType: 'local', + autogenerateColumns: true + } + }); + }); + + it('maps dataTable to options.data.dataTable', () => { + const dataTable = { id: 'table-1', rows: [] }; + + expect(getChildProps()).toEqual({ + data: { + dataTable + } + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/Header.test.tsx b/packages/grid-shared-react/tests/options/Header.test.tsx new file mode 100644 index 0000000..58fa656 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Header.test.tsx @@ -0,0 +1,13 @@ +import { describe, it, expect } from 'vitest'; +import { Header } from '../../src/components/options/header/Header'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Header', () => { + it('maps header prop to options.header', () => { + const header = ['product', { columnId: 'price', format: '{value} USD' }]; + + expect(getChildProps(
)).toEqual({ + header + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/Pagination.test.tsx b/packages/grid-shared-react/tests/options/Pagination.test.tsx new file mode 100644 index 0000000..1608d8d --- /dev/null +++ b/packages/grid-shared-react/tests/options/Pagination.test.tsx @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest'; +import { Data } from '../../src/components/options/data/Data'; +import { Pagination } from '../../src/components/options/pagination/Pagination'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Pagination', () => { + it('normalizes pagination props into options.pagination', () => { + expect( + getChildProps( + + ) + ).toEqual({ + pagination: { + enabled: false, + page: 2, + pageSize: 25, + align: 'center', + position: 'top', + controls: { + pageInfo: true, + pageSizeSelector: { + enabled: true, + options: [10, 25, 50] + }, + pageButtons: { + enabled: true, + count: 5 + }, + firstLastButtons: true, + previousNextButtons: false + } + } + }); + }); + + it('sets position to top for the first pagination and bottom after other options', () => { + const top = getChildProps( + <> + + + + ); + const bottom = getChildProps( + <> + + + + ); + + expect(top.pagination).toMatchObject({ position: 'top' }); + expect(bottom.pagination).toMatchObject({ position: 'bottom' }); + }); +}); From 3796a7e671809de7fca60418e54079d18fbf5f87 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 12:30:24 +0200 Subject: [PATCH 02/12] Added linter to PR runner. --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c844038..cc0a7ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,9 @@ jobs: - name: Build packages run: pnpm build + - name: Run linter + run: pnpm lint + - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium From aa121c873d307376b62c8d84fcfb9086c2cbe3ee Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Wed, 8 Jul 2026 13:08:31 +0200 Subject: [PATCH 03/12] Rephrased rules in linter. --- eslint.config.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 6be1776..d7605c1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,7 +24,13 @@ export default defineConfig( '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], '@stylistic/eol-last': ['error', 'always'], - '@stylistic/no-trailing-spaces': ['error'] + '@stylistic/no-trailing-spaces': ['error'], + '@stylistic/max-len': ['error', { + code: 80, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true + }] }, }, { From 0e1a9099ff47a032eac09c31db5fa46ce6b4aa31 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:08:57 +0200 Subject: [PATCH 04/12] Added husky precommit action. --- .github/workflows/test.yml | 27 +++++++++++++++++++++++---- .husky/pre-commit | 2 ++ vitest.config.ts | 5 ++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100755 .husky/pre-commit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc0a7ed..3cceffc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,31 @@ -name: Tests +name: CI on: pull_request: jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + + - name: Run linter + run: pnpm lint + test: runs-on: ubuntu-latest @@ -26,9 +48,6 @@ jobs: - name: Build packages run: pnpm build - - name: Run linter - run: pnpm lint - - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..acd310a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +pnpm lint +pnpm test diff --git a/vitest.config.ts b/vitest.config.ts index 8ad4fee..e23f4e9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,10 @@ import { playwright } from '@vitest/browser-playwright'; export default defineConfig({ test: { - include: ['packages/*/src/**/*.test.{ts,tsx}'], + include: [ + 'packages/*/src/**/*.test.{ts,tsx}', + 'packages/*/tests/**/*.test.{ts,tsx}' + ], globals: true, css: true, browser: { From 7b50a33103f9c477ed7f1a22657a586cf537db79 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:12:07 +0200 Subject: [PATCH 05/12] Linted. --- .../src/components/BaseGridOptions.ts | 6 +- .../src/test/createGridTests.tsx | 137 ------------------ .../src/utils/getChildProps.ts | 60 +++++--- .../src/utils/mappers/mapPrefixedProps.ts | 8 +- 4 files changed, 52 insertions(+), 159 deletions(-) delete mode 100644 packages/grid-shared-react/src/test/createGridTests.tsx diff --git a/packages/grid-shared-react/src/components/BaseGridOptions.ts b/packages/grid-shared-react/src/components/BaseGridOptions.ts index 9440edc..aabeb10 100644 --- a/packages/grid-shared-react/src/components/BaseGridOptions.ts +++ b/packages/grid-shared-react/src/components/BaseGridOptions.ts @@ -8,7 +8,8 @@ */ /** - * Metadata attached to declarative option components rendered as BaseGrid children. + * Metadata attached to declarative option components + * rendered as BaseGrid children. */ export interface BaseGridOptions { type: 'Grid_Option'; @@ -25,7 +26,8 @@ export interface BaseGridOptions { } /** - * A React component that maps JSX props to a Grid options path via `_GridReact`. + * A React component that maps JSX props to a Grid options path + * via `_GridReact`. */ export interface BaseGridOptionsComponent { _GridReact: BaseGridOptions; diff --git a/packages/grid-shared-react/src/test/createGridTests.tsx b/packages/grid-shared-react/src/test/createGridTests.tsx deleted file mode 100644 index 1faed12..0000000 --- a/packages/grid-shared-react/src/test/createGridTests.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { render, waitFor, fireEvent } from '@testing-library/react'; -import { - useRef, - useState, - type ComponentType -} from 'react'; -import { describe, it, expect, vi } from 'vitest'; -import { GridProps, GridRefHandle } from '../components/BaseGrid'; -import { GridInstance } from '../hooks/useGrid'; - -/** - * Creates a standard test suite for a Grid component. - * Use this to avoid duplicating tests between grid-lite-react and grid-pro-react. - */ -export function createGridTests( - name: string, - GridComponent: ComponentType>, - testOptions: TOptions, - updatedOptions: TOptions -) { - - describe(name, () => { - it('renders a container div and initializes grid', async () => { - let gridInstance: GridInstance | null = null; - - const onGridReady = (grid: GridInstance) => { - gridInstance = grid; - }; - - const { container } = render( - - ); - - expect(container.firstChild).toBeInstanceOf(HTMLDivElement); - - await waitFor(() => { - expect(gridInstance).not.toBeNull(); - }); - }); - - it('provides grid instance via gridRef prop', async () => { - let gridRef: React.RefObject | null>; - let initialized = false; - - function TestComponent() { - gridRef = useRef>(null); - return ( - { initialized = true; }} - /> - ); - } - - render(); - - await waitFor(() => { - expect(initialized).toBe(true); - expect(gridRef.current?.grid).toBeDefined(); - }); - }); - - it('calls callback when grid is initialized', async () => { - const callback = vi.fn(); - render(); - - await waitFor(() => { - expect(callback).toHaveBeenCalled(); - }); - }); - - it('updates grid when options change', async () => { - let gridInstance: GridInstance | null = null; - - function TestComponent() { - const [opts, setOpts] = useState(testOptions); - - const onGridReady = (grid: GridInstance) => { - gridInstance = grid; - }; - - return ( - <> - - - - ); - } - - const { getByTestId, container } = render(); - - // Wait for initial grid creation - await waitFor(() => { - expect(gridInstance).not.toBeNull(); - }); - - // Trigger options change - fireEvent.click(getByTestId('update-options')); - - // Wait for the grid to update with new data - await waitFor(() => { - const cells = container.querySelectorAll('td[data-value]'); - const values = Array.from(cells).map(c => c.getAttribute('data-value')); - expect(values).toContain('Charlie'); - }); - }); - - it('calls destroy() on unmount', async () => { - let destroySpy: ReturnType | null = null; - - const onGridReady = (grid: GridInstance) => { - destroySpy = vi.spyOn(grid, 'destroy'); - }; - - const { unmount } = render( - - ); - - // Wait for grid to initialize - await waitFor(() => { - expect(destroySpy).not.toBeNull(); - }); - - // Unmount and verify destroy was called - unmount(); - - expect(destroySpy).toHaveBeenCalledTimes(1); - }); - - }); -} diff --git a/packages/grid-shared-react/src/utils/getChildProps.ts b/packages/grid-shared-react/src/utils/getChildProps.ts index cc4880d..bc1c5a6 100644 --- a/packages/grid-shared-react/src/utils/getChildProps.ts +++ b/packages/grid-shared-react/src/utils/getChildProps.ts @@ -59,7 +59,9 @@ function getOptionComponent(type: unknown): BaseGridOptionsComponent | null { return component._GridReact ? type as BaseGridOptionsComponent : null; } -function getChildPropsFromElement(child: ReactElement): Record { +function getChildPropsFromElement( + child: ReactElement +): Record { return (child.props ?? {}) as Record; } @@ -87,7 +89,8 @@ function flattenChildren(childNodes: ReactNode): ReactNode[] { } if (isReactElement(childNodes) && childNodes.type === Fragment) { - return flattenChildren((childNodes.props as { children?: ReactNode }).children); + const fragmentProps = childNodes.props as { children?: ReactNode }; + return flattenChildren(fragmentProps.children); } return [childNodes]; @@ -115,7 +118,15 @@ function getEffectiveMeta( } function parseColumnElement(child: ReactElement): Record { - const { children: _ignored, columnId, id: _cssId, ...props } = getChildPropsFromElement(child); + const { + children, + id, + columnId, + ...props + } = getChildPropsFromElement(child); + void children; + void id; + const options = normalizeColumnOptions(props); // columnId selects the column; Core expects the same value as `id`. @@ -178,7 +189,10 @@ export function getChildProps(children: ReactNode): Record { } } - function handleChild(child: ReactElement, parentMeta?: BaseGridOptions): void { + function handleChild( + child: ReactElement, + parentMeta?: BaseGridOptions + ): void { const component = getOptionComponent(child.type); if (!component) { @@ -206,18 +220,17 @@ export function getChildProps(children: ReactNode): Record { if (meta.gridOption === 'pagination') { const pagination = normalizePaginationOptions(props); - pagination.position = isTopPaginationChild(child, resolvedChildren) ? - 'top' : - 'bottom'; + pagination.position = isTopPaginationChild( + child, + resolvedChildren + ) ? 'top' : 'bottom'; optionsFromChildren.pagination = pagination; return; } if (meta.gridOption === 'header') { - const { header, children: _ignored } = props; - - if (header !== void 0) { - optionsFromChildren.header = header; + if (props.header !== void 0) { + optionsFromChildren.header = props.header; } return; } @@ -226,7 +239,9 @@ export function getChildProps(children: ReactNode): Record { optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {} ); const parentIsArray = Array.isArray(optionParent); - const insertInto = parentIsArray ? {} : optionParent as Record; + const insertInto = parentIsArray + ? {} + : optionParent as Record; if (meta.defaultOptions) { Object.assign(insertInto, meta.defaultOptions); @@ -243,7 +258,10 @@ export function getChildProps(children: ReactNode): Record { } if (parentIsArray) { - (optionsFromChildren[meta.gridOption] as unknown[]).push(insertInto); + const optionItems = optionsFromChildren[ + meta.gridOption + ] as unknown[]; + optionItems.push(insertInto); } } @@ -258,7 +276,8 @@ export function getChildProps(children: ReactNode): Record { /** * When declarative `` components are present, only those columns - * should render unless `data.autogenerateColumns` is set explicitly on ``. + * should render unless `data.autogenerateColumns` is set + * explicitly on ``. */ function applyDeclarativeColumnDefaults( optionsFromChildren: Record @@ -291,7 +310,11 @@ function isTopPaginationChild( return children .slice(0, childIndex) - .every((candidate) => getOptionComponent(candidate.type)?._GridReact.gridOption === 'pagination'); + .every((candidate) => { + const gridOption = getOptionComponent(candidate.type) + ?._GridReact.gridOption; + return gridOption === 'pagination'; + }); } function isOptionElement(child: ReactElement): boolean { @@ -313,9 +336,10 @@ function resolveOptionChild(child: ReactNode): ReactElement | null { return null; } - const rendered = (child.type as (props: Record) => ReactNode)( - getChildPropsFromElement(child) - ); + const renderChild = child.type as ( + props: Record + ) => ReactNode; + const rendered = renderChild(getChildPropsFromElement(child)); if (isReactElement(rendered) && getOptionComponent(rendered.type)) { return rendered; diff --git a/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts index 9fdd271..82418e3 100644 --- a/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts +++ b/packages/grid-shared-react/src/utils/mappers/mapPrefixedProps.ts @@ -27,11 +27,15 @@ export function mapPrefixedProps( ): Record { const result = { ...props }; const groups: Record> = {}; - const prefixes = Object.keys(prefixToGroup).sort((a, b) => b.length - a.length); + const prefixes = Object.keys(prefixToGroup) + .sort((a, b) => b.length - a.length); for (const flatKey of Object.keys(result)) { const prefix = prefixes.find( - (candidate) => flatKey.startsWith(candidate) && flatKey.length > candidate.length + (candidate) => ( + flatKey.startsWith(candidate) + && flatKey.length > candidate.length + ) ); if (!prefix) { From 108a85a5725f2b70d6110a167b80d4501496c6cd Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:13:09 +0200 Subject: [PATCH 06/12] Linted useGrid hook. --- .../grid-shared-react/src/hooks/useGrid.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/grid-shared-react/src/hooks/useGrid.ts b/packages/grid-shared-react/src/hooks/useGrid.ts index c5b752f..f5e1756 100644 --- a/packages/grid-shared-react/src/hooks/useGrid.ts +++ b/packages/grid-shared-react/src/hooks/useGrid.ts @@ -24,7 +24,11 @@ export interface GridInstance { * directly depending on their types. */ export interface GridType { - grid(container: HTMLDivElement, options?: TOptions, async?: boolean): GridInstance | Promise>; + grid( + container: HTMLDivElement, + options?: TOptions, + async?: boolean + ): GridInstance | Promise>; } export interface UseGridOptions { @@ -62,7 +66,8 @@ export function useGrid({ return; } - // StrictMode cleanup runs before re-mount; allow init to complete if re-mounted. + // StrictMode cleanup runs before re-mount; + // allow init to complete if re-mounted. destroyOnInitRef.current = false; // Prevent double initialization @@ -73,21 +78,24 @@ export function useGrid({ const initGrid = async () => { try { - // Use pending options if available (from rapid updates during init) + // Use pending options if available + // (from rapid updates during init) const initOptions = pendingOptionsRef.current ?? options; pendingOptionsRef.current = void 0; const grid = await Grid.grid(container, initOptions, true); if (destroyOnInitRef.current) { - // Component unmounted while we were initializing - destroy immediately + // Component unmounted while initializing - + // destroy immediately grid.destroy(); return; } currGridRef.current = grid; - // Apply any pending options that came in while we were initializing + // Apply pending options that came in + // while we were initializing if (pendingOptionsRef.current !== void 0) { grid.update(pendingOptionsRef.current, true, true); pendingOptionsRef.current = void 0; @@ -122,7 +130,8 @@ export function useGrid({ } if (currGridRef.current) { - // Declarative React options replace the previous snapshot (oneToOne). + // Declarative React options replace the previous + // snapshot (oneToOne). currGridRef.current.update(options, true, true); } else { // Grid still initializing, queue the update From bfc063a898f993f97ba7638d6a56386023a8aaa3 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:33:29 +0200 Subject: [PATCH 07/12] Fixed packages. --- package.json | 71 ++++++++++++++++++++++++++------------------------ pnpm-lock.yaml | 10 +++++++ 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 5d1eb21..0aaabc9 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,39 @@ { - "name": "highcharts-grid-react", - "description": "Monorepo for Highcharts Grid Pro & Lite React libraries.", - "private": true, - "type": "module", - "scripts": { - "test": "vitest run", - "test:watch": "vitest", - "pretest:e2e": "pnpm build", - "test:e2e": "vitest run --config vitest.e2e.config.ts", - "test:all": "pnpm test && pnpm test:e2e", - "check": "pnpm lint && pnpm test", - "build": "pnpm -r --filter './packages/*' run build", - "lint": "pnpm -r --filter './packages/*' run lint", - "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", - "release:preflight": "pnpm check && pnpm build", - "release:prepare": "node scripts/release.js", - "release": "pnpm release:preflight && pnpm publish -r --access public" - }, - "packageManager": "pnpm@10.27.0", - "devDependencies": { - "@eslint/js": "^9.39.1", - "@stylistic/eslint-plugin": "^5.6.1", - "@testing-library/react": "^16.3.1", - "@types/node": "^20.0.0", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "eslint": "^9.39.1", - "globals": "^17.0.0", - "playwright": "^1.57.0", - "rimraf": "^6.1.2", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.0", - "vitest": "^4.0.16" - } + "name": "highcharts-grid-react", + "description": "Monorepo for Highcharts Grid Pro & Lite React libraries.", + "private": true, + "type": "module", + "scripts": { + "test": "vitest run", + "pretest": "pnpm build", + "test:watch": "vitest", + "pretest:e2e": "pnpm build", + "test:e2e": "vitest run --config vitest.e2e.config.ts", + "test:all": "pnpm test && pnpm test:e2e", + "check": "pnpm lint && pnpm test", + "build": "pnpm -r --filter './packages/*' run build", + "lint": "pnpm -r --filter './packages/*' run lint", + "clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules", + "release:preflight": "pnpm check && pnpm build", + "release:prepare": "node scripts/release.js", + "release": "pnpm release:preflight && pnpm publish -r --access public", + "prepare": "husky" + }, + "packageManager": "pnpm@10.27.0", + "devDependencies": { + "@eslint/js": "^9.39.1", + "@stylistic/eslint-plugin": "^5.6.1", + "@testing-library/react": "^16.3.1", + "@types/node": "^20.0.0", + "@vitest/browser": "^4.0.16", + "@vitest/browser-playwright": "^4.0.16", + "eslint": "^9.39.1", + "globals": "^17.0.0", + "husky": "^9.1.7", + "playwright": "^1.57.0", + "rimraf": "^6.1.2", + "typescript": "^5.9.3", + "typescript-eslint": "^8.48.0", + "vitest": "^4.0.16" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff81d18..648ccc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: globals: specifier: ^17.0.0 version: 17.0.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 playwright: specifier: ^1.57.0 version: 1.57.0 @@ -1532,6 +1535,11 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3282,6 +3290,8 @@ snapshots: - supports-color optional: true + husky@9.1.7: {} + ignore@5.3.2: {} ignore@7.0.5: {} From 9dd5e0e26932c813cd2dcbd325c7875c85d59ae0 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:37:26 +0200 Subject: [PATCH 08/12] Linted Grid. --- packages/grid-lite-react/src/Grid.tsx | 3 ++- packages/grid-pro-react/src/Grid.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/grid-lite-react/src/Grid.tsx b/packages/grid-lite-react/src/Grid.tsx index aa47803..44b5643 100644 --- a/packages/grid-lite-react/src/Grid.tsx +++ b/packages/grid-lite-react/src/Grid.tsx @@ -22,7 +22,8 @@ export default function GridLite(props: GridProps) { const { gridRef, children, options, ...gridProps } = props; const childOptions = useMemo(() => getChildProps(children), [children]); const columnKey = useMemo(() => { - const columns = childOptions.columns as Array<{ id?: string }> | undefined; + const columns = childOptions.columns as + Array<{ id?: string }> | undefined; return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); diff --git a/packages/grid-pro-react/src/Grid.tsx b/packages/grid-pro-react/src/Grid.tsx index c236cfd..c9e596f 100644 --- a/packages/grid-pro-react/src/Grid.tsx +++ b/packages/grid-pro-react/src/Grid.tsx @@ -22,7 +22,8 @@ export default function GridPro(props: GridProps) { const { gridRef, children, options, ...gridProps } = props; const childOptions = useMemo(() => getChildProps(children), [children]); const columnKey = useMemo(() => { - const columns = childOptions.columns as Array<{ id?: string }> | undefined; + const columns = childOptions.columns as + Array<{ id?: string }> | undefined; return columns?.map((column) => column.id).join('\0') ?? ''; }, [childOptions]); From 2ade77e12270fb0df96370aac2ec5d219db196dc Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 10:41:59 +0200 Subject: [PATCH 09/12] Cleaned up. --- .../src/__tests__/Grid.test.tsx | 23 ------------------- .../src/__tests__/Grid.test.tsx | 23 ------------------- 2 files changed, 46 deletions(-) delete mode 100644 packages/grid-lite-react/src/__tests__/Grid.test.tsx delete mode 100644 packages/grid-pro-react/src/__tests__/Grid.test.tsx diff --git a/packages/grid-lite-react/src/__tests__/Grid.test.tsx b/packages/grid-lite-react/src/__tests__/Grid.test.tsx deleted file mode 100644 index 770322a..0000000 --- a/packages/grid-lite-react/src/__tests__/Grid.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; -import { Grid, GridOptions } from '../index'; - -createGridTests( - 'Grid Lite', - Grid, - { - dataTable: { - columns: { - name: ['Alice', 'Bob'], - age: [30, 25] - } - } - }, - { - dataTable: { - columns: { - name: ['Charlie', 'Diana'], - age: [40, 35] - } - } - } -); diff --git a/packages/grid-pro-react/src/__tests__/Grid.test.tsx b/packages/grid-pro-react/src/__tests__/Grid.test.tsx deleted file mode 100644 index e44d6d9..0000000 --- a/packages/grid-pro-react/src/__tests__/Grid.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests'; -import { Grid, GridOptions } from '../index'; - -createGridTests( - 'Grid Pro', - Grid, - { - dataTable: { - columns: { - name: ['Alice', 'Bob'], - age: [30, 25] - } - } - }, - { - dataTable: { - columns: { - name: ['Charlie', 'Diana'], - age: [40, 35] - } - } - } -); From 8bcf49e4931bc8b0e1fbaab87da5f7c396a818ec Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 12:13:11 +0200 Subject: [PATCH 10/12] Added Caption test. --- .../tests/options/Caption.test.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Caption.test.tsx diff --git a/packages/grid-shared-react/tests/options/Caption.test.tsx b/packages/grid-shared-react/tests/options/Caption.test.tsx new file mode 100644 index 0000000..832944e --- /dev/null +++ b/packages/grid-shared-react/tests/options/Caption.test.tsx @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { Caption } from '../../src/components/options/caption/Caption'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Caption', () => { + it('maps caption props and children into options.caption', () => { + expect( + getChildProps( + + Sales table + + ) + ).toEqual({ + caption: { + className: 'grid-caption', + htmlTag: 'h2', + text: 'Sales table' + } + }); + }); +}); From 41c7bf5629752f86a201695ed47c01193432f342 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Thu, 9 Jul 2026 14:12:04 +0200 Subject: [PATCH 11/12] Added Description test. --- .../tests/options/Description.test.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Description.test.tsx diff --git a/packages/grid-shared-react/tests/options/Description.test.tsx b/packages/grid-shared-react/tests/options/Description.test.tsx new file mode 100644 index 0000000..145a800 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Description.test.tsx @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; +import { Description } from '../../src/components/options/description/Description'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Description', () => { + it('maps description props and children into options.description', () => { + expect( + getChildProps( + + Monthly sales overview + + ) + ).toEqual({ + description: { + className: 'grid-description', + text: 'Monthly sales overview' + } + }); + }); +}); From 0c973162f03b7b9d52d044c1149d6285f6c98ad1 Mon Sep 17 00:00:00 2001 From: Sebastian Bochan Date: Fri, 10 Jul 2026 09:18:34 +0200 Subject: [PATCH 12/12] Added tests for Coolumns and ColumnDefaults. --- .../tests/options/Column.test.tsx | 54 +++++++++++++++++++ .../tests/options/ColumnDefaults.test.tsx | 27 ++++++++++ 2 files changed, 81 insertions(+) create mode 100644 packages/grid-shared-react/tests/options/Column.test.tsx create mode 100644 packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx diff --git a/packages/grid-shared-react/tests/options/Column.test.tsx b/packages/grid-shared-react/tests/options/Column.test.tsx new file mode 100644 index 0000000..f52c073 --- /dev/null +++ b/packages/grid-shared-react/tests/options/Column.test.tsx @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest'; +import { Column } from '../../src/components/options/columns/Column'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('Column', () => { + it('maps column props into options.columns', () => { + expect( + getChildProps( + + ) + ).toEqual({ + columns: [{ + width: 120, + sorting: { + enabled: true, + order: 'asc' + }, + header: { + format: '{value} USD' + }, + id: 'price' + }], + data: { + autogenerateColumns: false + } + }); + }); + + it('maps multiple columns into options.columns array', () => { + expect( + getChildProps( + <> + + + + ) + ).toEqual({ + columns: [ + { width: 200, id: 'product' }, + { width: 120, id: 'price' } + ], + data: { + autogenerateColumns: false + } + }); + }); +}); diff --git a/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx new file mode 100644 index 0000000..1a43555 --- /dev/null +++ b/packages/grid-shared-react/tests/options/ColumnDefaults.test.tsx @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { ColumnDefaults } from '../../src/components/options/columns/ColumnDefaults'; +import { getChildProps } from '../../src/utils/getChildProps'; + +describe('ColumnDefaults', () => { + it('maps column defaults props into options.columnDefaults', () => { + expect( + getChildProps( + + ) + ).toEqual({ + columnDefaults: { + width: 160, + sorting: { + enabled: true + }, + cells: { + format: '{value}' + } + } + }); + }); +});