diff --git a/documentation/ag-grid-docs/src/components/license-setup/components/LicenseSetup.tsx b/documentation/ag-grid-docs/src/components/license-setup/components/LicenseSetup.tsx index d4022ab3a81..3517027433b 100644 --- a/documentation/ag-grid-docs/src/components/license-setup/components/LicenseSetup.tsx +++ b/documentation/ag-grid-docs/src/components/license-setup/components/LicenseSetup.tsx @@ -81,7 +81,7 @@ export const LicenseSetup: FunctionComponent = ({ library, framework, pat () => getBootstrapSnippet({ framework, - license: (licenseState.chartsNoGridEnterpriseError ? '' : userLicense) || 'your License Key', + license: (licenseState.chartsNoGridEnterpriseError ? '' : userLicense) || 'YOUR_LICENSE_KEY', isIntegratedCharts, }), [framework, licenseState, userLicense, isIntegratedCharts] diff --git a/documentation/ag-grid-docs/src/components/license-setup/utils/templates.ts b/documentation/ag-grid-docs/src/components/license-setup/utils/templates.ts index 36709a0ef75..e1a1d95a8f0 100644 --- a/documentation/ag-grid-docs/src/components/license-setup/utils/templates.ts +++ b/documentation/ag-grid-docs/src/components/license-setup/utils/templates.ts @@ -4,24 +4,27 @@ type TemplateFunction = (data: { license?: string; isIntegratedCharts?: boolean type LicenseTemplate = Record; export const GRID_LICENSE_TEMPLATES: LicenseTemplate = { - react: ({ license, isIntegratedCharts }) => `import React from "react"; -import { render } from "react-dom"; + react: ({ + license, + isIntegratedCharts, + }) => `import { AllEnterpriseModule${isIntegratedCharts ? ', IntegratedChartsModule' : ''} } from "ag-grid-enterprise"; +${isIntegratedCharts ? 'import { AgChartsEnterpriseModule } from "ag-charts-enterprise";\n' : ''}import { AgGridProvider, AgGridReact } from "ag-grid-react"; -import { ModuleRegistry } from "ag-grid-community"; -import { AllEnterpriseModule, LicenseManager${isIntegratedCharts ? ', IntegratedChartsModule' : ''} } from "ag-grid-enterprise"; -${isIntegratedCharts ? 'import { AgChartsEnterpriseModule } from "ag-charts-enterprise";\n' : ''} -import App from "./App"; +const modules = [${isIntegratedCharts ? '\n AllEnterpriseModule,\n IntegratedChartsModule.with(AgChartsEnterpriseModule),\n' : 'AllEnterpriseModule'}]; -ModuleRegistry.registerModules([${isIntegratedCharts ? '\n AllEnterpriseModule,\n IntegratedChartsModule.with(AgChartsEnterpriseModule)\n' : 'AllEnterpriseModule'}]); +function App() { + return ( + + + + ); +} -LicenseManager.setLicenseKey("${license}"); +// For versions <35.1 use the LicenseManager and ModuleRegistry directly -document.addEventListener('DOMContentLoaded', () => { - render( - , - document.querySelector('#app') - ); -}); +// import { LicenseManager, ModuleRegistry } from "ag-grid-enterprise"; +// ModuleRegistry.registerModules([${isIntegratedCharts ? '\n// AllEnterpriseModule,\n// IntegratedChartsModule.with(AgChartsEnterpriseModule)\n// ' : 'AllEnterpriseModule'}]); +// LicenseManager.setLicenseKey("${license}"); `, angular: ({ license, isIntegratedCharts }) => { return `import { ModuleRegistry } from "ag-grid-community"; @@ -31,11 +34,20 @@ ModuleRegistry.registerModules([${isIntegratedCharts ? '\n AllEnterpriseModul LicenseManager.setLicenseKey("${license}"); -// Template - +@Component({ + selector: 'app-root', + standalone: true, + imports: [AgGridAngular], + template: + \`\`, +}) +export class AppComponent { + /* Class implementation */ +} + `; }, javascript: ({ license, isIntegratedCharts }) => { diff --git a/documentation/ag-grid-docs/src/components/module-mappings/ModuleMappings.tsx b/documentation/ag-grid-docs/src/components/module-mappings/ModuleMappings.tsx index c3e056fc1d3..d231358fc83 100644 --- a/documentation/ag-grid-docs/src/components/module-mappings/ModuleMappings.tsx +++ b/documentation/ag-grid-docs/src/components/module-mappings/ModuleMappings.tsx @@ -38,7 +38,7 @@ ModuleRegistry.registerModules([ export const ModuleMappings: FunctionComponent = ({ framework, modules }) => { const gridRef = useRef(null); - const moduleConfig = useModuleConfig(gridRef); + const moduleConfig = useModuleConfig(gridRef, framework); const { selectedDependenciesSnippet, setSelectedModules, bundleOption, rowModelOption } = moduleConfig; const rowData = useMemo(() => { diff --git a/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.test.ts b/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.test.ts index f1496ad0450..5cdd1930820 100644 --- a/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.test.ts +++ b/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.test.ts @@ -1,255 +1,390 @@ import { getModuleMappingsSnippet } from './getModuleMappingsSnippet'; describe('getModuleMappingsSnippet', () => { - test('empty', () => { - const selectedModules = { community: [], enterprise: [] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toEqual(undefined); - }); - - test('community', () => { - const selectedModules = { community: ['ValidationModule'], enterprise: [] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toMatchInlineSnapshot(` - "import { - ModuleRegistry, - ValidationModule, - } from 'ag-grid-community'; - - ModuleRegistry.registerModules([ - ValidationModule, - ]);" - `); - }); - - test('multiple community', () => { - const selectedModules = { community: ['ValidationModule', 'ColumnHoverModule'], enterprise: [] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toMatchInlineSnapshot(` - "import { - ModuleRegistry, - ValidationModule, - ColumnHoverModule, - } from 'ag-grid-community'; - - ModuleRegistry.registerModules([ - ValidationModule, - ColumnHoverModule, - ]);" - `); - }); - - test('enterprise', () => { - const selectedModules = { community: [], enterprise: ['SetFilterModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toMatchInlineSnapshot(` - "import { - ModuleRegistry, - } from 'ag-grid-community'; - import { - SetFilterModule, - } from 'ag-grid-enterprise'; - - ModuleRegistry.registerModules([ - SetFilterModule, - ]);" - `); - }); - - test('multiple community', () => { - const selectedModules = { community: [], enterprise: ['SetFilterModule', 'RowGroupingModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toMatchInlineSnapshot(` - "import { - ModuleRegistry, - } from 'ag-grid-community'; - import { - SetFilterModule, - RowGroupingModule, - } from 'ag-grid-enterprise'; - - ModuleRegistry.registerModules([ - SetFilterModule, - RowGroupingModule, - ]);" - `); - }); - - test('community and enterprise', () => { - const selectedModules = { community: ['AllCommunityModule'], enterprise: ['SetFilterModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules })).toMatchInlineSnapshot(` - "import { - ModuleRegistry, - AllCommunityModule, - } from 'ag-grid-community'; - import { - SetFilterModule, - } from 'ag-grid-enterprise'; - - ModuleRegistry.registerModules([ - AllCommunityModule, - SetFilterModule, - ]);" - `); - }); + describe('default (non-React) framework', () => { + test('empty', () => { + const selectedModules = { community: [], enterprise: [] }; + expect( + getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' }) + ).toEqual(undefined); + }); - describe('charts - sparklines', () => { - test('no bundles', () => { - const selectedModules = { community: [], enterprise: ['SparklinesModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'community', selectedModules })).toMatchInlineSnapshot(` - "import { AgChartsCommunityModule } from 'ag-charts-community'; - import { + test('community', () => { + const selectedModules = { community: ['ValidationModule'], enterprise: [] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' })) + .toMatchInlineSnapshot(` + "import { ModuleRegistry, + ValidationModule, } from 'ag-grid-community'; - import { - SparklinesModule, - } from 'ag-grid-enterprise'; ModuleRegistry.registerModules([ - SparklinesModule.with(AgChartsCommunityModule), + ValidationModule, ]);" `); }); - test('all community', () => { - const selectedModules = { community: ['AllCommunityModule'], enterprise: ['SparklinesModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'community', selectedModules })).toMatchInlineSnapshot(` - "import { AgChartsCommunityModule } from 'ag-charts-community'; - import { + test('multiple community', () => { + const selectedModules = { community: ['ValidationModule', 'ColumnHoverModule'], enterprise: [] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' })) + .toMatchInlineSnapshot(` + "import { ModuleRegistry, - AllCommunityModule, + ValidationModule, + ColumnHoverModule, } from 'ag-grid-community'; - import { - SparklinesModule, - } from 'ag-grid-enterprise'; ModuleRegistry.registerModules([ - AllCommunityModule, - SparklinesModule.with(AgChartsCommunityModule), + ValidationModule, + ColumnHoverModule, ]);" `); }); - }); - describe('charts - integrated charts', () => { - test('no bundles (with enterprise charts)', () => { - const selectedModules = { community: [], enterprise: ['IntegratedChartsModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules })) + test('enterprise', () => { + const selectedModules = { community: [], enterprise: ['SetFilterModule'] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' })) .toMatchInlineSnapshot(` - "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; - import { + "import { ModuleRegistry, } from 'ag-grid-community'; import { - IntegratedChartsModule, + SetFilterModule, } from 'ag-grid-enterprise'; ModuleRegistry.registerModules([ - IntegratedChartsModule.with(AgChartsEnterpriseModule), + SetFilterModule, ]);" `); }); - test('all community (with enterprise charts)', () => { - const selectedModules = { community: ['AllCommunityModule'], enterprise: ['IntegratedChartsModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules })) + test('multiple enterprise', () => { + const selectedModules = { community: [], enterprise: ['SetFilterModule', 'RowGroupingModule'] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' })) .toMatchInlineSnapshot(` - "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; - import { + "import { ModuleRegistry, - AllCommunityModule, } from 'ag-grid-community'; import { - IntegratedChartsModule, + SetFilterModule, + RowGroupingModule, } from 'ag-grid-enterprise'; ModuleRegistry.registerModules([ - AllCommunityModule, - IntegratedChartsModule.with(AgChartsEnterpriseModule), + SetFilterModule, + RowGroupingModule, ]);" `); }); - }); - describe('charts - both sparklines and integrated charts', () => { - test('no bundles (with enterprise charts)', () => { - const selectedModules = { community: [], enterprise: ['SparklinesModule', 'IntegratedChartsModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules })) + test('community and enterprise', () => { + const selectedModules = { community: ['AllCommunityModule'], enterprise: ['SetFilterModule'] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'javascript' })) .toMatchInlineSnapshot(` - "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; - import { + "import { ModuleRegistry, + AllCommunityModule, } from 'ag-grid-community'; import { - SparklinesModule, - IntegratedChartsModule, + SetFilterModule, } from 'ag-grid-enterprise'; ModuleRegistry.registerModules([ - SparklinesModule.with(AgChartsEnterpriseModule), - IntegratedChartsModule.with(AgChartsEnterpriseModule), + AllCommunityModule, + SetFilterModule, ]);" `); }); - test('all community (with enterprise charts)', () => { - const selectedModules = { - community: ['AllCommunityModule'], - enterprise: ['SparklinesModule', 'IntegratedChartsModule'], - }; - expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules })) + describe('charts - sparklines', () => { + test('no bundles', () => { + const selectedModules = { community: [], enterprise: ['SparklinesModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'community', + selectedModules, + framework: 'angular', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsCommunityModule } from 'ag-charts-community'; + import { + ModuleRegistry, + } from 'ag-grid-community'; + import { + SparklinesModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + SparklinesModule.with(AgChartsCommunityModule), + ]);" + `); + }); + + test('all community', () => { + const selectedModules = { community: ['AllCommunityModule'], enterprise: ['SparklinesModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'community', + selectedModules, + framework: 'vue', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsCommunityModule } from 'ag-charts-community'; + import { + ModuleRegistry, + AllCommunityModule, + } from 'ag-grid-community'; + import { + SparklinesModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + AllCommunityModule, + SparklinesModule.with(AgChartsCommunityModule), + ]);" + `); + }); + }); + + describe('charts - integrated charts', () => { + test('no bundles (with enterprise charts)', () => { + const selectedModules = { community: [], enterprise: ['IntegratedChartsModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'enterprise', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; + import { + ModuleRegistry, + } from 'ag-grid-community'; + import { + IntegratedChartsModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + IntegratedChartsModule.with(AgChartsEnterpriseModule), + ]);" + `); + }); + + test('all community (with enterprise charts)', () => { + const selectedModules = { community: ['AllCommunityModule'], enterprise: ['IntegratedChartsModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'enterprise', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; + import { + ModuleRegistry, + AllCommunityModule, + } from 'ag-grid-community'; + import { + IntegratedChartsModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + AllCommunityModule, + IntegratedChartsModule.with(AgChartsEnterpriseModule), + ]);" + `); + }); + }); + + describe('charts - both sparklines and integrated charts', () => { + test('no bundles (with enterprise charts)', () => { + const selectedModules = { community: [], enterprise: ['SparklinesModule', 'IntegratedChartsModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'enterprise', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; + import { + ModuleRegistry, + } from 'ag-grid-community'; + import { + SparklinesModule, + IntegratedChartsModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + SparklinesModule.with(AgChartsEnterpriseModule), + IntegratedChartsModule.with(AgChartsEnterpriseModule), + ]);" + `); + }); + + test('all community (with enterprise charts)', () => { + const selectedModules = { + community: ['AllCommunityModule'], + enterprise: ['SparklinesModule', 'IntegratedChartsModule'], + }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'enterprise', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; + import { + ModuleRegistry, + AllCommunityModule, + } from 'ag-grid-community'; + import { + SparklinesModule, + IntegratedChartsModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + AllCommunityModule, + SparklinesModule.with(AgChartsEnterpriseModule), + IntegratedChartsModule.with(AgChartsEnterpriseModule), + ]);" + `); + }); + }); + + /** + * With `AllEnterpriseModule`, only `chartsImportType` is used to + * determine which charts to import + */ + describe('all enterprise', () => { + test('community charts', () => { + const selectedModules = { community: [], enterprise: ['AllEnterpriseModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'community', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsCommunityModule } from 'ag-charts-community'; + import { + ModuleRegistry, + } from 'ag-grid-community'; + import { + AllEnterpriseModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + AllEnterpriseModule.with(AgChartsCommunityModule), + ]);" + `); + }); + + test('enterprise charts', () => { + const selectedModules = { community: [], enterprise: ['AllEnterpriseModule'] }; + expect( + getModuleMappingsSnippet({ + chartsImportType: 'enterprise', + selectedModules, + framework: 'javascript', + }) + ).toMatchInlineSnapshot(` + "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; + import { + ModuleRegistry, + } from 'ag-grid-community'; + import { + AllEnterpriseModule, + } from 'ag-grid-enterprise'; + + ModuleRegistry.registerModules([ + AllEnterpriseModule.with(AgChartsEnterpriseModule), + ]);" + `); + }); + }); + }); + + describe('react framework', () => { + test('empty', () => { + const selectedModules = { community: [], enterprise: [] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'react' })).toEqual( + undefined + ); + }); + + test('community', () => { + const selectedModules = { community: ['ValidationModule'], enterprise: [] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'react' })) .toMatchInlineSnapshot(` - "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; - import { - ModuleRegistry, - AllCommunityModule, + "import { + ValidationModule, } from 'ag-grid-community'; - import { - SparklinesModule, - IntegratedChartsModule, - } from 'ag-grid-enterprise'; + import { AgGridProvider, AgGridReact } from 'ag-grid-react'; - ModuleRegistry.registerModules([ - AllCommunityModule, - SparklinesModule.with(AgChartsEnterpriseModule), - IntegratedChartsModule.with(AgChartsEnterpriseModule), - ]);" + const modules = [ + ValidationModule, + ]; + + function App() { + return ( + + + + ); + }" `); }); - }); - /** - * With `AllEnterpriseModule`, only `chartsImportType` is used to - * determine which charts to import - */ - describe('all enterprise', () => { - test('community charts', () => { - const selectedModules = { community: [], enterprise: ['AllEnterpriseModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'community', selectedModules })).toMatchInlineSnapshot(` - "import { AgChartsCommunityModule } from 'ag-charts-community'; - import { - ModuleRegistry, + test('community and enterprise', () => { + const selectedModules = { community: ['AllCommunityModule'], enterprise: ['SetFilterModule'] }; + expect(getModuleMappingsSnippet({ chartsImportType: 'none', selectedModules, framework: 'react' })) + .toMatchInlineSnapshot(` + "import { + AllCommunityModule, } from 'ag-grid-community'; import { - AllEnterpriseModule, + SetFilterModule, } from 'ag-grid-enterprise'; + import { AgGridProvider, AgGridReact } from 'ag-grid-react'; - ModuleRegistry.registerModules([ - AllEnterpriseModule.with(AgChartsCommunityModule), - ]);" + const modules = [ + AllCommunityModule, + SetFilterModule, + ]; + + function App() { + return ( + + + + ); + }" `); }); - test('enterprise charts', () => { + test('all enterprise with charts', () => { const selectedModules = { community: [], enterprise: ['AllEnterpriseModule'] }; - expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules })) + expect(getModuleMappingsSnippet({ chartsImportType: 'enterprise', selectedModules, framework: 'react' })) .toMatchInlineSnapshot(` "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; - import { - ModuleRegistry, - } from 'ag-grid-community'; import { AllEnterpriseModule, } from 'ag-grid-enterprise'; + import { AgGridProvider, AgGridReact } from 'ag-grid-react'; - ModuleRegistry.registerModules([ + const modules = [ AllEnterpriseModule.with(AgChartsEnterpriseModule), - ]);" + ]; + + function App() { + return ( + + + + ); + }" `); }); }); diff --git a/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.ts b/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.ts index cdd27046954..1e2d4b28caf 100644 --- a/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.ts +++ b/documentation/ag-grid-docs/src/components/module-mappings/getModuleMappingsSnippet.ts @@ -1,9 +1,12 @@ +import type { Framework } from '@ag-grid-types'; + import { ALL_ENTERPRISE_MODULE, INTEGRATED_CHARTS_MODULE, SPARKLINES_MODULE } from './constants'; export type ChartsImportType = 'enterprise' | 'community' | 'none'; interface Params { chartsImportType: ChartsImportType; selectedModules: SelectedModules; + framework: Framework; } export interface SelectedModules { @@ -13,15 +16,81 @@ export interface SelectedModules { const TAB_SPACING = ' '; -const getCodeSnippet = ({ chartsImportType, selectedModules }: Params) => { +const getModulesWithCharts = ( + modules: string[], + chartsImportType: ChartsImportType +): { name: string; withCharts: boolean }[] => { + return modules.map((name) => { + const isChartsModule = [SPARKLINES_MODULE.moduleName, INTEGRATED_CHARTS_MODULE.moduleName].includes(name); + const isEnterpriseModule = name === ALL_ENTERPRISE_MODULE; + const needsCharts = + (chartsImportType === 'community' || chartsImportType === 'enterprise') && + (isChartsModule || isEnterpriseModule); + + return { name, withCharts: needsCharts }; + }); +}; + +const getChartsModuleName = (chartsImportType: ChartsImportType): string => { + return chartsImportType === 'community' ? 'AgChartsCommunityModule' : 'AgChartsEnterpriseModule'; +}; + +const getReactCodeSnippet = ({ chartsImportType, selectedModules }: Omit) => { const { community, enterprise } = selectedModules; - const communityImportsString = community.length - ? community - .map((name) => { - return `${TAB_SPACING}${name},`; - }) - .join('\n') + const communityImportsString = community.length ? community.map((name) => `${TAB_SPACING}${name},`).join('\n') : ''; + const enterpriseImportsString = enterprise.length + ? enterprise.map((name) => `${TAB_SPACING}${name},`).join('\n') : ''; + const chartsImport = + chartsImportType === 'community' + ? "import { AgChartsCommunityModule } from 'ag-charts-community';" + : chartsImportType === 'enterprise' + ? "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise';" + : ''; + + const allModulesWithCharts = getModulesWithCharts(community.concat(enterprise), chartsImportType); + const chartsModuleName = getChartsModuleName(chartsImportType); + const modulesArrayContent = allModulesWithCharts + .map(({ name, withCharts }) => { + const exportName = withCharts ? `${name}.with(${chartsModuleName})` : name; + return `${TAB_SPACING}${exportName},`; + }) + .join('\n'); + + const communityImport = communityImportsString + ? `import {\n${communityImportsString}\n} from 'ag-grid-community';` + : ''; + const enterpriseImport = enterpriseImportsString + ? `import {\n${enterpriseImportsString}\n} from 'ag-grid-enterprise';` + : ''; + + const imports = [ + chartsImport, + communityImport, + enterpriseImport, + "import { AgGridProvider, AgGridReact } from 'ag-grid-react';", + ] + .filter(Boolean) + .join('\n'); + + return `${imports} + +const modules = [ +${modulesArrayContent} +]; + +function App() { + return ( + + + + ); +}`; +}; + +const getDefaultCodeSnippet = ({ chartsImportType, selectedModules }: Omit) => { + const { community, enterprise } = selectedModules; + const communityImportsString = community.length ? community.map((name) => `${TAB_SPACING}${name},`).join('\n') : ''; const enterpriseImportsString = enterprise.length ? enterprise.map((name) => `${TAB_SPACING}${name},`).join('\n') : ''; @@ -31,24 +100,14 @@ const getCodeSnippet = ({ chartsImportType, selectedModules }: Params) => { : chartsImportType === 'enterprise' ? "import { AgChartsEnterpriseModule } from 'ag-charts-enterprise';" : ''; - const allModules = community - .concat(enterprise) - .map((name) => { - const isChartsModule = [SPARKLINES_MODULE.moduleName, INTEGRATED_CHARTS_MODULE.moduleName].includes(name); - const isEnterpriseModule = name === ALL_ENTERPRISE_MODULE; - const isCommunityCharts = chartsImportType === 'community' && (isChartsModule || isEnterpriseModule); - const isEnterpriseCharts = chartsImportType === 'enterprise' && (isChartsModule || isEnterpriseModule); - - let exportName = name; - if (isCommunityCharts) { - exportName = `${name}.with(AgChartsCommunityModule)`; - } else if (isEnterpriseCharts) { - exportName = `${name}.with(AgChartsEnterpriseModule)`; - } - - return exportName; + + const allModulesWithCharts = getModulesWithCharts(community.concat(enterprise), chartsImportType); + const chartsModuleName = getChartsModuleName(chartsImportType); + const modulesArrayContent = allModulesWithCharts + .map(({ name, withCharts }) => { + const exportName = withCharts ? `${name}.with(${chartsModuleName})` : name; + return `${TAB_SPACING}${exportName},`; }) - .map((name) => `${TAB_SPACING}${name},`) .join('\n'); return `${chartsImport ? `${chartsImport}\n` : ''}import { @@ -62,14 +121,18 @@ ${enterpriseImportsString} } ModuleRegistry.registerModules([ -${allModules} +${modulesArrayContent} ]);`; }; -export function getModuleMappingsSnippet({ chartsImportType, selectedModules }: Params): string | undefined { +export function getModuleMappingsSnippet({ chartsImportType, selectedModules, framework }: Params): string | undefined { if (!selectedModules.community.length && !selectedModules.enterprise.length) { return; } - return getCodeSnippet({ chartsImportType, selectedModules }); + if (framework === 'react') { + return getReactCodeSnippet({ chartsImportType, selectedModules }); + } + + return getDefaultCodeSnippet({ chartsImportType, selectedModules }); } diff --git a/documentation/ag-grid-docs/src/components/module-mappings/useModuleConfig.ts b/documentation/ag-grid-docs/src/components/module-mappings/useModuleConfig.ts index b303e69450e..e2d37636f66 100644 --- a/documentation/ag-grid-docs/src/components/module-mappings/useModuleConfig.ts +++ b/documentation/ag-grid-docs/src/components/module-mappings/useModuleConfig.ts @@ -1,3 +1,4 @@ +import type { Framework } from '@ag-grid-types'; import { throwDevWarning } from '@ag-website-shared/utils/throwDevWarning'; import { type RefObject, useCallback, useMemo, useState } from 'react'; @@ -32,7 +33,7 @@ const getChartsImportType = (chartOptions: ChartOptions): ChartsImportType => { return chartsImport; }; -export function useModuleConfig(gridRef: RefObject) { +export function useModuleConfig(gridRef: RefObject, framework: Framework) { const [rowModelOption, setRowModelOption] = useState('ClientSideRowModelModule'); const [bundleOption, setBundleOption] = useState(''); const [chartOptions, setChartOptions] = useState(DEFAULT_CHART_OPTIONS); @@ -88,8 +89,8 @@ export function useModuleConfig(gridRef: RefObject) { }, [selectedModules, bundleOption, rowModelOption, chartOptions]); const selectedDependenciesSnippet = useMemo(() => { const chartsImportType = getChartsImportType(chartOptions); - return getModuleMappingsSnippet({ chartsImportType, selectedModules: allImportModules }); - }, [allImportModules, chartOptions]); + return getModuleMappingsSnippet({ chartsImportType, selectedModules: allImportModules, framework }); + }, [allImportModules, chartOptions, framework]); const updateRowModelOption = useCallback((moduleName: string) => { setRowModelOption(moduleName); diff --git a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/angular/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/angular/example.spec.ts deleted file mode 100644 index ed79767b830..00000000000 --- a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/angular/example.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils'; - -test.agExample(import.meta, () => { - test.eachFramework('Example', async ({ page }) => { - // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS - await ensureGridReady(page); - await waitForGridContent(page); - await clickAllButtons(page); - // END PLACEHOLDER - }); -}); diff --git a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/reactFunctionalTs/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/reactFunctionalTs/example.spec.ts deleted file mode 100644 index ed79767b830..00000000000 --- a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/reactFunctionalTs/example.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils'; - -test.agExample(import.meta, () => { - test.eachFramework('Example', async ({ page }) => { - // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS - await ensureGridReady(page); - await waitForGridContent(page); - await clickAllButtons(page); - // END PLACEHOLDER - }); -}); diff --git a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/vue3/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/vue3/example.spec.ts deleted file mode 100644 index ed79767b830..00000000000 --- a/documentation/ag-grid-docs/src/content/docs/ai-toolkit/_examples/tool-panel-chat-assistant/provided/vue3/example.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils'; - -test.agExample(import.meta, () => { - test.eachFramework('Example', async ({ page }) => { - // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS - await ensureGridReady(page); - await waitForGridContent(page); - await clickAllButtons(page); - // END PLACEHOLDER - }); -}); diff --git a/documentation/ag-grid-docs/src/content/docs/getting-started/index.mdoc b/documentation/ag-grid-docs/src/content/docs/getting-started/index.mdoc index 8b6ceead8bc..f32df553df3 100644 --- a/documentation/ag-grid-docs/src/content/docs/getting-started/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/getting-started/index.mdoc @@ -161,20 +161,28 @@ const gridOptions = { {% metaTag tags=["aggridreact"] /%} -{% numberHeading number="2" title="Register Modules" level="h3" %} +{% numberHeading number="2" title="Provide Modules" level="h3" %} - Register the `AllCommunityModule` to access all Community features: + Pass the `AllCommunityModule` to the `AgGridProvider` to access all Community features: ```js -import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community'; +import { AllCommunityModule } from 'ag-grid-community'; +import { AgGridProvider } from 'ag-grid-react'; -// Register all Community features -ModuleRegistry.registerModules([AllCommunityModule]); +const modules = [AllCommunityModule]; + +function App() { + return ( + + {/* Your AgGridReact components go here */} + + ); +} ``` {% note %} -To minimize bundle size, only register the modules you want to use. See the [Modules](./modules/) docs for more information. +To minimize bundle size, only provide the modules you want to use. See the [Modules](./modules/) docs for more information. {% /note %} {% /numberHeading %} @@ -214,17 +222,21 @@ const GridExample = () => { {% numberHeading number="5" title="React Data Grid Component" level="h3" %} -Return the `AgGridReact` component, wrapped in a parent container `div` with a fixed height. Set Rows and Columns as `AgGridReact` component attributes: +Return the `AgGridReact` component inside `AgGridProvider`, wrapped in a parent container `div` with a fixed height: ```jsx +const modules = [AllCommunityModule]; + return ( - // Data Grid will fill the size of the parent container -
- -
+ + {/* Data Grid will fill the size of the parent container */} +
+ +
+
) ``` {% /numberHeading %} diff --git a/documentation/ag-grid-docs/src/content/docs/installation/index.mdoc b/documentation/ag-grid-docs/src/content/docs/installation/index.mdoc index a474c119e30..c8ac1792520 100644 --- a/documentation/ag-grid-docs/src/content/docs/installation/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/installation/index.mdoc @@ -87,24 +87,39 @@ You can test AG Grid Enterprise locally without a licence. To test in production ### Registering Modules {% /if %} -Register the `AllCommunityModule` to access all Community features: +{% if isFramework("react") %} +Use `AgGridProvider` to provide modules to all grid instances. Provide `AllCommunityModule` to access all Community features or `AllEnterpriseModule` to access all Community ***and*** Enterprise features: + +```jsx +import { AllCommunityModule } from 'ag-grid-community'; +// import { AllEnterpriseModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +const modules = [AllCommunityModule]; + +function App() { + return ( + + + + ); +} +``` + +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} +Register the `AllCommunityModule` to access all Community features or `AllEnterpriseModule` to access all Community ***and*** Enterprise features: ```js -import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community'; +import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community'; +// import { AllEnterpriseModule } from 'ag-grid-enterprise'; // Register all Community features ModuleRegistry.registerModules([AllCommunityModule]); ``` -Register the `AllEnterpriseBundle` to access all Community ***and*** Enterprise features: - -```js -import { ModuleRegistry } from 'ag-grid-community'; -import { AllEnterpriseModule } from 'ag-grid-enterprise'; - -// Register all Community and Enterprise features -ModuleRegistry.registerModules([AllEnterpriseModule]); -``` +{% /if %} {% note %} To minimize bundle size, only register the modules you want to use. See the [Selecting Modules](./modules/) docs for more information. @@ -119,10 +134,10 @@ To minimize bundle size, only register the modules you want to use. See the [Sel {% /if %} {% if isFramework("react") %} -Import the `AgGridReact` component from the `ag-grid-react` package: +Import `AgGridProvider` and `AgGridReact` from the `ag-grid-react` package: ```js -import { AgGridReact } from 'ag-grid-react'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; ``` {% /if %} diff --git a/documentation/ag-grid-docs/src/content/docs/integrated-charts-installation/index.mdoc b/documentation/ag-grid-docs/src/content/docs/integrated-charts-installation/index.mdoc index c5eaa39ee03..2b07ed1c354 100644 --- a/documentation/ag-grid-docs/src/content/docs/integrated-charts-installation/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/integrated-charts-installation/index.mdoc @@ -5,6 +5,31 @@ enterprise: true This section shows how to install Integrated Charts. +{% if isFramework("react") %} +The Integrated Enterprise Charts module, which includes AG Charts Enterprise, can be provided via `AgGridProvider` as follows: + +```jsx +import { ClientSideRowModelModule } from 'ag-grid-community'; +import { IntegratedChartsModule } from 'ag-grid-enterprise'; +import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +const modules = [ + ClientSideRowModelModule, + IntegratedChartsModule.with(AgChartsEnterpriseModule), +]; + +function App() { + return ( + + + + ); +} +``` +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} The Integrated Enterprise Charts module, which includes AG Charts Enterprise, can be imported as follows: ```ts @@ -18,6 +43,7 @@ ModuleRegistry.registerModules([ IntegratedChartsModule.with(AgChartsEnterpriseModule) ]); ``` +{% /if %} {% note %} AG Charts Community (`AgChartsCommunityModule`) is available from the `ag-charts-community` package. diff --git a/documentation/ag-grid-docs/src/content/docs/modules/index.mdoc b/documentation/ag-grid-docs/src/content/docs/modules/index.mdoc index a8f30a6ad45..dbd6b2e0cf8 100644 --- a/documentation/ag-grid-docs/src/content/docs/modules/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/modules/index.mdoc @@ -19,33 +19,80 @@ Alternatively, if you are not concerned about bundle size and you want access to ## Bundles -The `AllCommunityModule` bundle contains all of the modules available in AG Grid Community. The `AllEnterpriseModule` bundle contains all of the modules available in Community and Enterprise. Registering one of these bundles replicates the behaviour of the package versions of AG Grid prior to version 33: +The grid provides two module bundles for ease of use: + - `AllCommunityModule` bundle contains all of the modules available in AG Grid Community. + - `AllEnterpriseModule` bundle contains `AllCommunityModule` and all modules available in AG Grid Enterprise. + + Registering one of these bundles replicates the behaviour of the package versions of AG Grid prior to version 33: + +{% if isFramework("react") %} +```jsx +import { AllCommunityModule } from 'ag-grid-community'; +// import { AllEnterpriseModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +const modules = [AllCommunityModule]; // or [AllEnterpriseModule] + +// Wrap your application (or the part that uses AG Grid) with AgGridProvider +function App() { + return ( + + + + ); +} +``` +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} ```js -// Import ModuleRegistry and the required module import { ModuleRegistry, - AllCommunityModule, // or AllEnterpriseModule + AllCommunityModule } from 'ag-grid-community'; +// import { AllEnterpriseModule } from 'ag-grid-enterprise'; // Register the module ModuleRegistry.registerModules([ AllCommunityModule, // or AllEnterpriseModule ]); ``` +{% /if %} If you are using [Integrated Charts](./integrated-charts/) or [Sparklines](./sparklines-overview/), then you need to provide the relevant module from AG Charts to `AllEnterpriseModule`, for example: +{% if isFramework("react") %} +```jsx +import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; +import { AllEnterpriseModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +// All Enterprise Features, with Integrated Charts and Sparklines +const modules = [AllEnterpriseModule.with(AgChartsEnterpriseModule)]; + +function App() { + return ( + + + + ); +} +``` +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} ```js import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; import { ModuleRegistry } from 'ag-grid-community'; -import { AllEnterpriseModule,} from 'ag-grid-enterprise'; +import { AllEnterpriseModule } from 'ag-grid-enterprise'; -// All Enterprise Features, with Integrated Charts and Sparklines +// All Enterprise Features, with Integrated Charts and Sparklines ModuleRegistry.registerModules([ AllEnterpriseModule.with(AgChartsEnterpriseModule), ]); ``` +{% /if %} ## Selecting Modules @@ -53,35 +100,92 @@ To work out which modules are required, select the features of the grid that you {% moduleMappings /%} +{% if isFramework("react") %} +{% note %}The code snippet above shows providing modules via `AgGridProvider`. It is also possible to [Provide Modules To Individual Grids](#providing-modules-to-individual-grids) or use the `ModuleRegistry` for global registration.{% /note %} +{% /if %} +{% if isFramework("javascript", "angular", "vue") %} {% note %}The code snippet above shows registering modules globally. It is also possible to [Provide Modules To Individual Grids](#providing-modules-to-individual-grids){% /note %} +{% /if %} ## Development Validations The `ValidationModule` adds helpful console warnings/errors that can help identify bad configuration during development. When using Modules it is recommended to only include it in your development build to ensure your production build is as small as possible. +{% if isFramework("react") %} +```jsx +import { ValidationModule } from 'ag-grid-community'; + +const modules = [ + // Other modules... + // Include ValidationModule only in development + ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []), +]; +``` +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} ```js +import { ModuleRegistry, ValidationModule } from 'ag-grid-community'; + // via process.env.NODE_ENV if(process.env.NODE_ENV !== 'production') { ModuleRegistry.registerModules([ValidationModule]); } ``` +{% /if %} - If using the bundles (`AllCommunityModule`/`AllEnterpriseModule`), the `ValidationModule` is included by default. +If using the bundles (`AllCommunityModule`/`AllEnterpriseModule`), the `ValidationModule` is included by default. ## Registering AG Grid Modules -When including AG Grid in your application via modules it is your responsibility to register the required modules with the grid before it is instantiated. You can either register them globally or pass them individually to each grid instance. +{% if isFramework("react") %} +When including AG Grid in your application the required modules must be registered with the grid before it is instantiated. You can either register them globally, via the `ModuleRegistry`, via the `AgGridProvider` or pass them directly to each grid. -### Providing Modules Globally -You can import and register modules globally, but you need to ensure that this is done before **_any_** grids are instantiated. Any modules registered globally will be available to all grids. +{% /if %} -* Import Modules -* Register Modules -A real-world example might be that we wish to use the `Client-Side Row Model` (the default row model) together with the `CSV`, `Excel` and `Master/Detail` features. +{% if isFramework("javascript", "angular", "vue") %} +When including AG Grid in your application the required modules must be registered with the grid before it is instantiated. You can either register them globally, via the `ModuleRegistry` or pass them directly to each grid. +{% /if %} + +{% if isFramework("react") %} + +### Providing Modules via AgGridProvider + +Since v35.1 use the `AgGridProvider` component to provide modules to all `AgGridReact` components within the scope of the provider. + +As an example to use the `Client-Side Row Model` (the default row model) together with the `CSV`, `Excel` and `Master/Detail` features, you would do the following: + +```jsx +import { ClientSideRowModelModule, CsvExportModule } from 'ag-grid-community'; +import { ExcelExportModule, MasterDetailModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; -We need to register the grid modules we wish to use via the `ModuleRegistry`. +const modules = [ + ClientSideRowModelModule, + CsvExportModule, + ExcelExportModule, + MasterDetailModule, +]; + +function App() { + return ( + + {/* All AgGridReact components within this provider will have access to these modules */} + + + ); +} +``` + +{% /if %} + +### Providing Modules Globally + +You can import and register modules globally, but you need to ensure that this is done before **_any_** grids are instantiated. Any modules registered globally will be available to all grids. + +As an example to use the `Client-Side Row Model` (the default row model) together with the `CSV`, `Excel` and `Master/Detail` features, you would do the following: ```js import { ModuleRegistry, ClientSideRowModelModule, CsvExportModule } from 'ag-grid-community'; @@ -97,7 +201,7 @@ ModuleRegistry.registerModules([ {% if isFramework("react") %} {% note %} -If using server-side rendering, modules need to be registered on the client and not the server. +If using server-side rendering, ensure modules are registered in the client side of your application. {% /note %} {% /if %} @@ -112,7 +216,7 @@ The steps required are: * Import Modules * Pass to Grid -Using the same real-world example from above (the `Client-Side Row Model` together with the `CSV`, `Excel` and `Master/Detail` features), how we register the modules is now different. +Using the same example from above (the `Client-Side Row Model` together with the `CSV`, `Excel` and `Master/Detail` features), how we register the modules is now different. {% if isFramework("javascript") %} We pass the modules to createGrid via the `modules` property of the `params`. @@ -161,35 +265,30 @@ export class GridExample { {% /if %} {% if isFramework("react") %} -We pass the modules to the `modules` prop: -{% /if %} +Use `AgGridProvider` for shared modules and pass grid-specific modules directly to `AgGridReact` via the `modules` prop. Modules from both sources will be combined: -{% if isFramework("react") %} -```js -import React, { useState } from 'react'; -import { createRoot } from 'react-dom/client'; -import { AgGridReact } from 'ag-grid-react'; +```jsx import { ClientSideRowModelModule, CsvExportModule } from 'ag-grid-community'; import { ExcelExportModule, MasterDetailModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; -const GridExample = () => { - // ... rest of component ... +// Shared modules available to all grids +const sharedModules = [ClientSideRowModelModule, CsvExportModule]; + +// Grid-specific modules +const gridSpecificModules = [ExcelExportModule, MasterDetailModule]; +function App() { return ( -
+ -
+ ); -}; +} ``` {% /if %} @@ -218,11 +317,16 @@ data() { ``` {% /if %} -The following example shows how you can configure individual grids using a combination of shared Global registrations as well as individual grid module registration. Note the following: +The following example shows how you can configure individual grids using a combination of shared modules as well as individual grid module registration. Note the following: +{% if isFramework("react") %} +* Shared modules via `AgGridProvider`: `[ClientSideRowModelModule, ColumnMenuModule, ContextMenuModule]`. +{% /if %} +{% if isFramework("javascript", "angular", "vue") %} * Globally registered modules: `[ClientSideRowModelModule, ColumnMenuModule, ContextMenuModule]`. +{% /if %} * Left Grid individually registers: `[ClipboardModule, CsvExportModule, SetFilterModule]` -* Right Grid individually registers: `[ CsvExportModule, ExcelExportModule, NumberFilterModule, TextFilterModule]` +* Right Grid individually registers: `[CsvExportModule, ExcelExportModule, NumberFilterModule, TextFilterModule]` To see the difference in features open the context menu and open the column filter: diff --git a/documentation/ag-grid-docs/src/content/docs/sparklines-installation/index.mdoc b/documentation/ag-grid-docs/src/content/docs/sparklines-installation/index.mdoc index 67a4b6a28b6..8a651305657 100644 --- a/documentation/ag-grid-docs/src/content/docs/sparklines-installation/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/sparklines-installation/index.mdoc @@ -8,6 +8,48 @@ This section shows how to install Sparklines. Sparklines are an enterprise grid feature powered by AG Charts. You can use either AG Charts Community or AG Charts Enterprise, depending on your setup. +{% if isFramework("react") %} +The Sparklines module can be provided via `AgGridProvider` as follows: + +{% tabs %} + {% tabItem id="SparklinesInstallationChartsCommunityReact" label="Using AG Charts Community" %} +```jsx +import { AgChartsCommunityModule } from 'ag-charts-community'; +import { SparklinesModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +const modules = [SparklinesModule.with(AgChartsCommunityModule)]; + +function App() { + return ( + + + + ); +} +``` + {% /tabItem %} + {% tabItem id="SparklinesInstallationChartsEnterpriseReact" label="Using AG Charts Enterprise" %} +```jsx +import { AgChartsEnterpriseModule } from 'ag-charts-enterprise'; +import { SparklinesModule } from 'ag-grid-enterprise'; +import { AgGridProvider, AgGridReact } from 'ag-grid-react'; + +const modules = [SparklinesModule.with(AgChartsEnterpriseModule)]; + +function App() { + return ( + + + + ); +} +``` + {% /tabItem %} +{% /tabs %} +{% /if %} + +{% if isFramework("javascript", "angular", "vue") %} The Sparklines module can be imported as follows: {% tabs %} @@ -18,6 +60,7 @@ The Sparklines module can be imported as follows: {% partial file="./_sparklines_charts_enterprise.mdoc" /%} {% /tabItem %} {% /tabs %} +{% /if %} ## Next Up diff --git a/packages/ag-grid-community/src/clientSideRowModel/deltaSort.ts b/packages/ag-grid-community/src/clientSideRowModel/deltaSort.ts new file mode 100644 index 00000000000..f77af40c765 --- /dev/null +++ b/packages/ag-grid-community/src/clientSideRowModel/deltaSort.ts @@ -0,0 +1,183 @@ +import type { RowNode } from '../entities/rowNode'; +import type { SortOption } from '../interfaces/iSortOption'; +import type { RowNodeSorter } from '../sort/rowNodeSorter'; +import type { ChangedPath } from '../utils/changedPath'; +import type { ChangedRowNodes } from './changedRowNodes'; + +/** + * Minimum number of rows changed to enable delta sort. + * Below this threshold, full sort is much faster due to lower overhead. + */ +const MIN_DELTA_SORT_ROWS = 4; + +/** + * Performs an incremental (delta) sort that avoids re-sorting unchanged rows. + * + * Algorithm outline: + * 1. Handle edge cases: empty input or single element - return early + * 2. Fall back to full sort if no previous sorted result or too few rows + * 3. Classify rows as "touched" (updated, added, or in changed path) vs "untouched" + * 4. If no rows are touched, return previous sorted array (filtering removed nodes if needed) + * 5. Sort only the touched rows using a stable sort with original index as tie-breaker + * 6. If all rows are touched, return the sorted touched rows directly + * 7. Merge the sorted touched rows with untouched rows from previous sort order + * using a two-pointer merge algorithm (similar to merge sort's merge step) + * + * Time complexity: O(t log t + n) where t = touched rows, n = total rows + * This is faster than full sort O(n log n) when t << n + */ +export const doDeltaSort = ( + rowNodeSorter: RowNodeSorter, + rowNode: RowNode, + changedRowNodes: ChangedRowNodes, + changedPath: ChangedPath, + sortOptions: SortOption[] +): RowNode[] => { + const oldSortedRows = rowNode.childrenAfterSort; + const unsortedRows = rowNode.childrenAfterAggFilter; + if (!unsortedRows) { + return oldSortedRows && oldSortedRows.length > 0 ? oldSortedRows : []; + } + + const unsortedRowsLen = unsortedRows.length; + if (unsortedRowsLen <= 1) { + if ( + oldSortedRows?.length === unsortedRowsLen && + (unsortedRowsLen === 0 || oldSortedRows[0] === unsortedRows[0]) + ) { + return oldSortedRows; // Same content, reuse old array + } + return unsortedRows.slice(); // Different content, need new reference + } + + if (!oldSortedRows || unsortedRowsLen <= MIN_DELTA_SORT_ROWS) { + // No previous sort, or just too few elements, do full sort + return rowNodeSorter.doFullSortInPlace(unsortedRows.slice(), sortOptions); + } + + const indexByNode = new Map(); + + // Classify rows as touched or untouched and build an index map. + // Map stores current index with sign encoding: negative = touched, non-negative = untouched. + const { updates, adds } = changedRowNodes; + const touchedRows: RowNode[] = []; + for (let i = 0; i < unsortedRowsLen; ++i) { + const node = unsortedRows[i]; + if (updates.has(node) || adds.has(node) || !changedPath.canSkip(node)) { + indexByNode.set(node, ~i); // Bitwise NOT for touched (negative) + touchedRows.push(node); + } else { + indexByNode.set(node, i); // Non-negative for untouched + } + } + + const touchedRowsLen = touchedRows.length; + if (touchedRowsLen === 0) { + // No touched rows: return oldSortedRows if nothing removed, otherwise filter out removed nodes + return unsortedRowsLen === oldSortedRows.length + ? oldSortedRows // Nothing removed + : filterRemovedNodes(oldSortedRows, indexByNode, touchedRows); + } + + // Sort touched rows with stable tie-breaker based on current index + touchedRows.sort( + (a, b) => rowNodeSorter.compareRowNodes(sortOptions, a, b) || ~indexByNode.get(a)! - ~indexByNode.get(b)! + ); + + if (touchedRowsLen === unsortedRowsLen) { + return touchedRows; // All touched: no merge needed, return sorted touched rows directly. + } + + return mergeDeltaSortedArrays(rowNodeSorter, sortOptions, touchedRows, oldSortedRows, indexByNode, unsortedRowsLen); +}; + +/** + * Merge touched rows with untouched rows from previous sorted order. + * Optimized version: caches untouched index to avoid repeated map lookups in hot loop. + * See https://en.wikipedia.org/wiki/Merge_algorithm + */ +const mergeDeltaSortedArrays = ( + rowNodeSorter: RowNodeSorter, + sortOptions: SortOption[], + touchedRows: RowNode[], + oldSortedRows: RowNode[], + indexByNode: ReadonlyMap, + resultSize: number +): RowNode[] => { + // Result array - size equals total number of rows to output + const result = new Array(resultSize); + + let touchedIdx = 0; + let touchedNode = touchedRows[touchedIdx]; + let untouchedNode: RowNode | undefined; + let untouchedIdx = -1; + let oldIdx = 0; + let resultIdx = 0; + const touchedLength = touchedRows.length; + const oldSortedLength = oldSortedRows.length; + while (true) { + // Advance to next valid untouched node if needed + if (untouchedIdx < 0) { + if (oldIdx >= oldSortedLength) { + break; // No more untouched nodes + } + untouchedNode = oldSortedRows[oldIdx++]; + untouchedIdx = indexByNode.get(untouchedNode) ?? -1; + if (untouchedIdx < 0) { + continue; // Skip touched/removed nodes + } + } + + const orderDelta = + rowNodeSorter.compareRowNodes(sortOptions, touchedNode, untouchedNode!) || + ~indexByNode.get(touchedNode)! - untouchedIdx; + + if (orderDelta < 0) { + result[resultIdx++] = touchedNode; // Touched node comes next + if (++touchedIdx >= touchedLength) { + break; // No more touched nodes + } + touchedNode = touchedRows[touchedIdx]; + } else { + result[resultIdx++] = untouchedNode!; // Untouched node comes next + untouchedIdx = -1; // Will be fetched on next iteration + } + } + + // Copy remaining touched nodes + while (touchedIdx < touchedLength) { + result[resultIdx++] = touchedRows[touchedIdx++]; + } + + // If no pending untouched node, we already searched through remaining nodes + if (untouchedIdx < 0) { + return result; + } + + // Add pending untouched node + result[resultIdx++] = untouchedNode!; + + // Copy remaining untouched nodes + while (oldIdx < oldSortedLength) { + const node = oldSortedRows[oldIdx++]; + if (indexByNode.get(node)! >= 0) { + result[resultIdx++] = node; + } + } + + return result; +}; + +/** Filter out removed nodes from oldSortedRows using preallocated array for performance. */ +const filterRemovedNodes = (rows: RowNode[], map: ReadonlyMap, result: RowNode[]): RowNode[] => { + let count = 0; + result.length = map.size; + for (let i = 0, len = rows.length; i < len; ++i) { + const node = rows[i]; + if (map.has(node)) { + result[count++] = node; + } + } + result.length = count; + return result; +}; diff --git a/packages/ag-grid-community/src/clientSideRowModel/sortStage.ts b/packages/ag-grid-community/src/clientSideRowModel/sortStage.ts index 60450b4611a..66ae30ec030 100644 --- a/packages/ag-grid-community/src/clientSideRowModel/sortStage.ts +++ b/packages/ag-grid-community/src/clientSideRowModel/sortStage.ts @@ -8,9 +8,9 @@ import type { ClientSideRowModelStage } from '../interfaces/iClientSideRowModel' import type { WithoutGridCommon } from '../interfaces/iCommon'; import type { IRowNodeSortStage } from '../interfaces/iRowNodeStage'; import type { SortOption } from '../interfaces/iSortOption'; -import type { RowNodeSorter } from '../sort/rowNodeSorter'; import type { ChangedPath } from '../utils/changedPath'; import type { ChangedRowNodes } from './changedRowNodes'; +import { doDeltaSort } from './deltaSort'; export const updateRowNodeAfterSort = (rowNode: RowNode): void => { const childrenAfterSort = rowNode.childrenAfterSort; @@ -97,7 +97,7 @@ export class SortStage extends BeanStub implements NamedBean, IRowNodeSortStage } else if (!sortOptions.length || skipSortingPivotLeafs) { // if there's no sort to make, skip this step } else if (useDeltaSort && changedRowNodes) { - newChildrenAfterSort = doDeltaSort(rowNodeSorter!, rowNode, changedRowNodes, changedPath, sortOptions); + newChildrenAfterSort = doDeltaSort(rowNodeSorter!, rowNode, changedRowNodes, changedPath!, sortOptions); } else { newChildrenAfterSort = rowNodeSorter!.doFullSortInPlace( rowNode.childrenAfterAggFilter!.slice(), @@ -157,92 +157,6 @@ export class SortStage extends BeanStub implements NamedBean, IRowNodeSortStage } } -interface RowNodeWithIndex { - index: number; - node: RowNode; -} - -const doDeltaSort = ( - rowNodeSorter: RowNodeSorter, - rowNode: RowNode, - changedRowNodes: ChangedRowNodes, - changedPath: ChangedPath | undefined, - sortOptions: SortOption[] -): RowNode[] => { - const unsortedRows = rowNode.childrenAfterAggFilter!; - const oldSortedRows = rowNode.childrenAfterSort; - if (!oldSortedRows) { - return rowNodeSorter.doFullSortInPlace(unsortedRows.slice(), sortOptions); - } - - const touchedRows: RowNodeWithIndex[] = []; - const untouchedRows: RowNodeWithIndex[] = []; - - const { updates, adds } = changedRowNodes; - for (let i = 0, len = unsortedRows.length; i < len; ++i) { - const node = unsortedRows[i]; - if (updates.has(node) || adds.has(node) || (changedPath && !changedPath.canSkip(node))) { - touchedRows.push({ index: i, node }); - } else { - untouchedRows.push({ index: i, node }); - } - } - - touchedRows.sort((a, b) => rowNodeSorter.compareRowNodes(sortOptions, a.node, b.node)); - - return mergeSortedArrays(rowNodeSorter, sortOptions, touchedRows, untouchedRows); -}; - -/** Merge two sorted arrays into each other. See https://en.wikipedia.org/wiki/Merge_algorithm */ -const mergeSortedArrays = ( - rowNodeSorter: RowNodeSorter, - sortOptions: SortOption[], - arr1: RowNodeWithIndex[], - arr2: RowNodeWithIndex[] -): RowNode[] => { - let i = 0; - let j = 0; - const arr1Length = arr1.length; - const arr2Length = arr2.length; - - const result = new Array(arr1Length + arr2Length); - let k = 0; - - // Traverse both arrays, adding them in order - while (i < arr1Length && j < arr2Length) { - const a = arr1[i]; - const b = arr2[j]; - - const c = rowNodeSorter.compareRowNodes(sortOptions, a.node, b.node) || a.index - b.index; - - if (c < 0) { - result[k] = a.node; - ++k; - ++i; - } else { - result[k] = b.node; - ++k; - ++j; - } - } - - // add remaining from arr1 - while (i < arr1Length) { - result[k] = arr1[i].node; - ++k; - ++i; - } - - // add remaining from arr2 - while (j < arr2Length) { - result[k] = arr2[j].node; - ++k; - ++j; - } - - return result; -}; - /** * O(n) merge preserving previous visual order and appending new items in current order. */ diff --git a/packages/ag-grid-community/src/sort/rowNodeSorter.ts b/packages/ag-grid-community/src/sort/rowNodeSorter.ts index ab16f303657..3741e258aa1 100644 --- a/packages/ag-grid-community/src/sort/rowNodeSorter.ts +++ b/packages/ag-grid-community/src/sort/rowNodeSorter.ts @@ -27,12 +27,14 @@ export class RowNodeSorter extends BeanStub implements NamedBean { this.updateOptions.bind(this) ); + const updatePivotModeState = this.updatePivotModeState.bind(this); this.addManagedEventListeners({ - columnPivotModeChanged: this.updatePivotModeState.bind(this), + columnPivotModeChanged: updatePivotModeState, + columnPivotChanged: updatePivotModeState, }); this.updateOptions(); - this.updatePivotModeState(); + updatePivotModeState(); } private updateOptions(): void { diff --git a/testing/behavioural/src/grouping-data/grouping-sorting-delta-sorting.test.ts b/testing/behavioural/src/grouping-data/grouping-sorting-delta-sorting.test.ts new file mode 100644 index 00000000000..8048ad71e54 --- /dev/null +++ b/testing/behavioural/src/grouping-data/grouping-sorting-delta-sorting.test.ts @@ -0,0 +1,1033 @@ +import { ClientSideRowModelModule } from 'ag-grid-community'; +import { RowGroupingModule } from 'ag-grid-enterprise'; + +import { GridRows, TestGridsManager, applyTransactionChecked } from '../test-utils'; + +describe('Grouping delta sorting', () => { + const gridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule, RowGroupingModule], + }); + + beforeEach(() => { + gridsManager.reset(); + }); + + afterEach(() => { + gridsManager.reset(); + }); + + test('delta sorting resorts grouped rows when only part of the data changes', async () => { + const rowData = [ + { id: 'ire-a', country: 'Ireland', athlete: 'Aine', score: 40 }, + { id: 'ire-b', country: 'Ireland', athlete: 'Brigid', score: 30 }, + { id: 'esp-a', country: 'Spain', athlete: 'Carlos', score: 35 }, + { id: 'esp-b', country: 'Spain', athlete: 'Diego', score: 28 }, + { id: 'fra-a', country: 'France', athlete: 'Émilie', score: 32 }, + { id: 'fra-b', country: 'France', athlete: 'François', score: 27 }, + { id: 'ger-a', country: 'Germany', athlete: 'Greta', score: 38 }, + { id: 'ger-b', country: 'Germany', athlete: 'Hans', score: 24 }, + { id: 'ita-a', country: 'Italy', athlete: 'Isabella', score: 36 }, + { id: 'ita-b', country: 'Italy', athlete: 'Leonardo', score: 29 }, + ]; + + const rowById = Object.fromEntries(rowData.map((row) => [row.id, row])) as Record< + string, + (typeof rowData)[number] + >; + + const api = gridsManager.createGrid('groupingDeltaSort', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true, aggFunc: 'sum' }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + animateRows: false, + groupDefaultExpanded: -1, + rowData, + deltaSort: true, + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'desc' }] }); + + await new GridRows(api, 'group delta sort initial').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" score:70 + │ ├── LEAF id:ire-a country:"Ireland" athlete:"Aine" score:40 + │ └── LEAF id:ire-b country:"Ireland" athlete:"Brigid" score:30 + ├─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" score:65 + │ ├── LEAF id:ita-a country:"Italy" athlete:"Isabella" score:36 + │ └── LEAF id:ita-b country:"Italy" athlete:"Leonardo" score:29 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" score:63 + │ ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:35 + │ └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:28 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" score:62 + │ ├── LEAF id:ger-a country:"Germany" athlete:"Greta" score:38 + │ └── LEAF id:ger-b country:"Germany" athlete:"Hans" score:24 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" score:59 + · ├── LEAF id:fra-a country:"France" athlete:"Émilie" score:32 + · └── LEAF id:fra-b country:"France" athlete:"François" score:27 + `); + + const updateRow = (id: string, score: number) => ({ ...rowById[id], score }); + + applyTransactionChecked(api, { + update: [updateRow('esp-a', 80), updateRow('ire-b', 5), updateRow('fra-a', 50)], + }); + + await new GridRows(api, 'group delta sort updated').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" score:108 + │ ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:80 + │ └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:28 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" score:77 + │ ├── LEAF id:fra-a country:"France" athlete:"Émilie" score:50 + │ └── LEAF id:fra-b country:"France" athlete:"François" score:27 + ├─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" score:65 + │ ├── LEAF id:ita-a country:"Italy" athlete:"Isabella" score:36 + │ └── LEAF id:ita-b country:"Italy" athlete:"Leonardo" score:29 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" score:62 + │ ├── LEAF id:ger-a country:"Germany" athlete:"Greta" score:38 + │ └── LEAF id:ger-b country:"Germany" athlete:"Hans" score:24 + └─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" score:45 + · ├── LEAF id:ire-a country:"Ireland" athlete:"Aine" score:40 + · └── LEAF id:ire-b country:"Ireland" athlete:"Brigid" score:5 + `); + }); + + test('delta sort preserves order for untouched grouped rows', async () => { + const api = gridsManager.createGrid('deltaSortGroupedUntouched', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-a', country: 'Ireland', athlete: 'Alice', score: 20 }, + { id: 'ire-b', country: 'Ireland', athlete: 'Bob', score: 10 }, + { id: 'esp-a', country: 'Spain', athlete: 'Carlos', score: 30 }, + { id: 'esp-b', country: 'Spain', athlete: 'Diego', score: 40 }, + { id: 'fra-a', country: 'France', athlete: 'Émilie', score: 25 }, + { id: 'fra-b', country: 'France', athlete: 'François', score: 15 }, + { id: 'ger-a', country: 'Germany', athlete: 'Greta', score: 35 }, + { id: 'ger-b', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ita-a', country: 'Italy', athlete: 'Isabella', score: 18 }, + { id: 'ita-b', country: 'Italy', athlete: 'Leonardo', score: 22 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + await new GridRows(api, 'initial sort').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-b country:"Ireland" athlete:"Bob" score:10 + │ └── LEAF id:ire-a country:"Ireland" athlete:"Alice" score:20 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:30 + │ └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:40 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-b country:"France" athlete:"François" score:15 + │ └── LEAF id:fra-a country:"France" athlete:"Émilie" score:25 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-b country:"Germany" athlete:"Hans" score:28 + │ └── LEAF id:ger-a country:"Germany" athlete:"Greta" score:35 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-a country:"Italy" athlete:"Isabella" score:18 + · └── LEAF id:ita-b country:"Italy" athlete:"Leonardo" score:22 + `); + + applyTransactionChecked(api, { update: [{ id: 'ire-a', country: 'Ireland', athlete: 'Alice', score: 5 }] }); + + await new GridRows(api, 'delta sort single update in group').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-a country:"Ireland" athlete:"Alice" score:5 + │ └── LEAF id:ire-b country:"Ireland" athlete:"Bob" score:10 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:30 + │ └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:40 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-b country:"France" athlete:"François" score:15 + │ └── LEAF id:fra-a country:"France" athlete:"Émilie" score:25 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-b country:"Germany" athlete:"Hans" score:28 + │ └── LEAF id:ger-a country:"Germany" athlete:"Greta" score:35 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-a country:"Italy" athlete:"Isabella" score:18 + · └── LEAF id:ita-b country:"Italy" athlete:"Leonardo" score:22 + `); + }); + + test('delta sort handles adds in grouped data', async () => { + const api = gridsManager.createGrid('deltaSortGroupedAdds', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: '1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: '2', country: 'Ireland', athlete: 'Bob', score: 30 }, + { id: '4', country: 'Spain', athlete: 'Carlos', score: 50 }, + { id: '5', country: 'Spain', athlete: 'Diego', score: 20 }, + { id: '7', country: 'France', athlete: 'Émilie', score: 25 }, + { id: '8', country: 'France', athlete: 'François', score: 35 }, + { id: '10', country: 'Germany', athlete: 'Greta', score: 40 }, + { id: '11', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: '13', country: 'Italy', athlete: 'Isabella', score: 32 }, + { id: '14', country: 'Italy', athlete: 'Leonardo', score: 22 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + add: [ + { id: '3', country: 'Ireland', athlete: 'Charlie', score: 20 }, + { id: '6', country: 'Spain', athlete: 'Elena', score: 40 }, + ], + }); + + await new GridRows(api, 'delta sort adds in groups').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:3 country:"Ireland" athlete:"Charlie" score:20 + │ └── LEAF id:2 country:"Ireland" athlete:"Bob" score:30 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:5 country:"Spain" athlete:"Diego" score:20 + │ ├── LEAF id:6 country:"Spain" athlete:"Elena" score:40 + │ └── LEAF id:4 country:"Spain" athlete:"Carlos" score:50 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:7 country:"France" athlete:"Émilie" score:25 + │ └── LEAF id:8 country:"France" athlete:"François" score:35 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:11 country:"Germany" athlete:"Hans" score:28 + │ └── LEAF id:10 country:"Germany" athlete:"Greta" score:40 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:14 country:"Italy" athlete:"Leonardo" score:22 + · └── LEAF id:13 country:"Italy" athlete:"Isabella" score:32 + `); + }); + + test('delta sort handles removes in grouped data', async () => { + const api = gridsManager.createGrid('deltaSortGroupedRemoves', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 20 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 30 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 40 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Emma', score: 50 }, + { id: 'ire-6', country: 'Ireland', athlete: 'Frank', score: 60 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 15 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 25 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 35 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 45 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 55 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 65 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'ire-2' }, { id: 'esp-2' }, { id: 'fra-3' }, { id: 'ger-4' }, { id: 'ita-5' }], + }); + + await new GridRows(api, 'delta sort removes in groups').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:30 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:40 + │ ├── LEAF id:ire-5 country:"Ireland" athlete:"Emma" score:50 + │ └── LEAF id:ire-6 country:"Ireland" athlete:"Frank" score:60 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:15 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:35 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:45 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:55 + │ └── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:65 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:12 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:22 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:42 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:52 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:62 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:18 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:28 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:38 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:58 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:68 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:14 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:24 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:34 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:44 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:64 + `); + }); + + test('delta sort with equal values in grouped data', async () => { + const api = gridsManager.createGrid('deltaSortGroupedEqualValues', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 10 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 10 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 10 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Emma', score: 10 }, + { id: 'ire-6', country: 'Ireland', athlete: 'Frank', score: 10 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 20 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 20 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 20 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 20 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 20 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 20 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 30 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 30 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 30 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 30 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 30 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 30 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 40 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 40 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 40 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 40 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 40 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 40 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 50 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 50 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 50 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 50 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 50 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 50 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 10 }], + add: [{ id: 'esp-7', country: 'Spain', athlete: 'Zara', score: 20 }], + }); + + await new GridRows(api, 'delta sort equal values in groups').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-2 country:"Ireland" athlete:"Bob" score:10 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:10 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:10 + │ ├── LEAF id:ire-5 country:"Ireland" athlete:"Emma" score:10 + │ └── LEAF id:ire-6 country:"Ireland" athlete:"Frank" score:10 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:20 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:20 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:20 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:20 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:20 + │ ├── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:20 + │ └── LEAF id:esp-7 country:"Spain" athlete:"Zara" score:20 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:30 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:30 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:30 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:30 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:30 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:30 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:40 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:40 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:40 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:40 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:40 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:40 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:50 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:50 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:50 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:50 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:50 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:50 + `); + }); + + test('delta sort with addIndex in grouped data', async () => { + const api = gridsManager.createGrid('deltaSortGroupedAddIndex', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 10 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 10 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 10 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Emma', score: 10 }, + { id: 'ire-6', country: 'Ireland', athlete: 'Frank', score: 10 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 20 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 20 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 20 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 20 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 20 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 20 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 30 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 30 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 30 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 30 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 30 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 30 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 40 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 40 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 40 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 40 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 40 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 40 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 50 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 50 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 50 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 50 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 50 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 50 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + addIndex: 1, + add: [{ id: 'ire-7', country: 'Ireland', athlete: 'Zara', score: 10 }], + }); + + await new GridRows(api, 'delta sort addIndex equal values in group').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-7 country:"Ireland" athlete:"Zara" score:10 + │ ├── LEAF id:ire-2 country:"Ireland" athlete:"Bob" score:10 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:10 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:10 + │ ├── LEAF id:ire-5 country:"Ireland" athlete:"Emma" score:10 + │ └── LEAF id:ire-6 country:"Ireland" athlete:"Frank" score:10 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:20 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:20 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:20 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:20 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:20 + │ └── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:20 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:30 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:30 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:30 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:30 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:30 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:30 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:40 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:40 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:40 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:40 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:40 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:40 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:50 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:50 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:50 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:50 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:50 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:50 + `); + }); + + test('delta sort with multi-level groups', async () => { + const api = gridsManager.createGrid('deltaSortMultiLevelGroups', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country/Year' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', year: 2020, athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', year: 2020, athlete: 'Bob', score: 20 }, + { id: 'ire-3', country: 'Ireland', year: 2020, athlete: 'Charlie', score: 30 }, + { id: 'ire-4', country: 'Ireland', year: 2021, athlete: 'Diana', score: 40 }, + { id: 'ire-5', country: 'Ireland', year: 2021, athlete: 'Emma', score: 50 }, + { id: 'ire-6', country: 'Ireland', year: 2021, athlete: 'Frank', score: 60 }, + { id: 'esp-1', country: 'Spain', year: 2020, athlete: 'Carlos', score: 15 }, + { id: 'esp-2', country: 'Spain', year: 2020, athlete: 'Diego', score: 25 }, + { id: 'esp-3', country: 'Spain', year: 2020, athlete: 'Elena', score: 35 }, + { id: 'esp-4', country: 'Spain', year: 2021, athlete: 'Fernando', score: 45 }, + { id: 'esp-5', country: 'Spain', year: 2021, athlete: 'Gloria', score: 55 }, + { id: 'esp-6', country: 'Spain', year: 2021, athlete: 'Hugo', score: 65 }, + { id: 'fra-1', country: 'France', year: 2020, athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', year: 2020, athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', year: 2020, athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', year: 2021, athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', year: 2021, athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', year: 2021, athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', year: 2020, athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', year: 2020, athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', year: 2020, athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', year: 2021, athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', year: 2021, athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', year: 2021, athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', year: 2020, athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', year: 2020, athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', year: 2020, athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', year: 2021, athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', year: 2021, athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', year: 2021, athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + await new GridRows(api, 'initial multi-level').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:ire-1 country:"Ireland" year:2020 athlete:"Alice" score:10 + │ │ ├── LEAF id:ire-2 country:"Ireland" year:2020 athlete:"Bob" score:20 + │ │ └── LEAF id:ire-3 country:"Ireland" year:2020 athlete:"Charlie" score:30 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:ire-4 country:"Ireland" year:2021 athlete:"Diana" score:40 + │ · ├── LEAF id:ire-5 country:"Ireland" year:2021 athlete:"Emma" score:50 + │ · └── LEAF id:ire-6 country:"Ireland" year:2021 athlete:"Frank" score:60 + ├─┬ filler id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├─┬ LEAF_GROUP id:row-group-country-Spain-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:esp-1 country:"Spain" year:2020 athlete:"Carlos" score:15 + │ │ ├── LEAF id:esp-2 country:"Spain" year:2020 athlete:"Diego" score:25 + │ │ └── LEAF id:esp-3 country:"Spain" year:2020 athlete:"Elena" score:35 + │ └─┬ LEAF_GROUP id:row-group-country-Spain-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:esp-4 country:"Spain" year:2021 athlete:"Fernando" score:45 + │ · ├── LEAF id:esp-5 country:"Spain" year:2021 athlete:"Gloria" score:55 + │ · └── LEAF id:esp-6 country:"Spain" year:2021 athlete:"Hugo" score:65 + ├─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:fra-1 country:"France" year:2020 athlete:"François" score:12 + │ │ ├── LEAF id:fra-2 country:"France" year:2020 athlete:"Émilie" score:22 + │ │ └── LEAF id:fra-3 country:"France" year:2020 athlete:"Gabriel" score:32 + │ └─┬ LEAF_GROUP id:row-group-country-France-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:fra-4 country:"France" year:2021 athlete:"Hélène" score:42 + │ · ├── LEAF id:fra-5 country:"France" year:2021 athlete:"Isabelle" score:52 + │ · └── LEAF id:fra-6 country:"France" year:2021 athlete:"Jacques" score:62 + ├─┬ filler id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├─┬ LEAF_GROUP id:row-group-country-Germany-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:ger-1 country:"Germany" year:2020 athlete:"Greta" score:18 + │ │ ├── LEAF id:ger-2 country:"Germany" year:2020 athlete:"Hans" score:28 + │ │ └── LEAF id:ger-3 country:"Germany" year:2020 athlete:"Ingrid" score:38 + │ └─┬ LEAF_GROUP id:row-group-country-Germany-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:ger-4 country:"Germany" year:2021 athlete:"Jürgen" score:48 + │ · ├── LEAF id:ger-5 country:"Germany" year:2021 athlete:"Klaus" score:58 + │ · └── LEAF id:ger-6 country:"Germany" year:2021 athlete:"Lars" score:68 + └─┬ filler id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├─┬ LEAF_GROUP id:row-group-country-Italy-year-2020 ag-Grid-AutoColumn:2020 + · │ ├── LEAF id:ita-1 country:"Italy" year:2020 athlete:"Isabella" score:14 + · │ ├── LEAF id:ita-2 country:"Italy" year:2020 athlete:"Leonardo" score:24 + · │ └── LEAF id:ita-3 country:"Italy" year:2020 athlete:"Marco" score:34 + · └─┬ LEAF_GROUP id:row-group-country-Italy-year-2021 ag-Grid-AutoColumn:2021 + · · ├── LEAF id:ita-4 country:"Italy" year:2021 athlete:"Natalia" score:44 + · · ├── LEAF id:ita-5 country:"Italy" year:2021 athlete:"Olivia" score:54 + · · └── LEAF id:ita-6 country:"Italy" year:2021 athlete:"Paolo" score:64 + `); + + applyTransactionChecked(api, { + update: [{ id: 'ire-2', country: 'Ireland', year: 2020, athlete: 'Bob', score: 5 }], + add: [{ id: 'ire-7', country: 'Ireland', year: 2020, athlete: 'Zara', score: 15 }], + }); + + await new GridRows(api, 'delta sort multi-level with updates and adds').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:ire-2 country:"Ireland" year:2020 athlete:"Bob" score:5 + │ │ ├── LEAF id:ire-1 country:"Ireland" year:2020 athlete:"Alice" score:10 + │ │ ├── LEAF id:ire-7 country:"Ireland" year:2020 athlete:"Zara" score:15 + │ │ └── LEAF id:ire-3 country:"Ireland" year:2020 athlete:"Charlie" score:30 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:ire-4 country:"Ireland" year:2021 athlete:"Diana" score:40 + │ · ├── LEAF id:ire-5 country:"Ireland" year:2021 athlete:"Emma" score:50 + │ · └── LEAF id:ire-6 country:"Ireland" year:2021 athlete:"Frank" score:60 + ├─┬ filler id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├─┬ LEAF_GROUP id:row-group-country-Spain-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:esp-1 country:"Spain" year:2020 athlete:"Carlos" score:15 + │ │ ├── LEAF id:esp-2 country:"Spain" year:2020 athlete:"Diego" score:25 + │ │ └── LEAF id:esp-3 country:"Spain" year:2020 athlete:"Elena" score:35 + │ └─┬ LEAF_GROUP id:row-group-country-Spain-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:esp-4 country:"Spain" year:2021 athlete:"Fernando" score:45 + │ · ├── LEAF id:esp-5 country:"Spain" year:2021 athlete:"Gloria" score:55 + │ · └── LEAF id:esp-6 country:"Spain" year:2021 athlete:"Hugo" score:65 + ├─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:fra-1 country:"France" year:2020 athlete:"François" score:12 + │ │ ├── LEAF id:fra-2 country:"France" year:2020 athlete:"Émilie" score:22 + │ │ └── LEAF id:fra-3 country:"France" year:2020 athlete:"Gabriel" score:32 + │ └─┬ LEAF_GROUP id:row-group-country-France-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:fra-4 country:"France" year:2021 athlete:"Hélène" score:42 + │ · ├── LEAF id:fra-5 country:"France" year:2021 athlete:"Isabelle" score:52 + │ · └── LEAF id:fra-6 country:"France" year:2021 athlete:"Jacques" score:62 + ├─┬ filler id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├─┬ LEAF_GROUP id:row-group-country-Germany-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├── LEAF id:ger-1 country:"Germany" year:2020 athlete:"Greta" score:18 + │ │ ├── LEAF id:ger-2 country:"Germany" year:2020 athlete:"Hans" score:28 + │ │ └── LEAF id:ger-3 country:"Germany" year:2020 athlete:"Ingrid" score:38 + │ └─┬ LEAF_GROUP id:row-group-country-Germany-year-2021 ag-Grid-AutoColumn:2021 + │ · ├── LEAF id:ger-4 country:"Germany" year:2021 athlete:"Jürgen" score:48 + │ · ├── LEAF id:ger-5 country:"Germany" year:2021 athlete:"Klaus" score:58 + │ · └── LEAF id:ger-6 country:"Germany" year:2021 athlete:"Lars" score:68 + └─┬ filler id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├─┬ LEAF_GROUP id:row-group-country-Italy-year-2020 ag-Grid-AutoColumn:2020 + · │ ├── LEAF id:ita-1 country:"Italy" year:2020 athlete:"Isabella" score:14 + · │ ├── LEAF id:ita-2 country:"Italy" year:2020 athlete:"Leonardo" score:24 + · │ └── LEAF id:ita-3 country:"Italy" year:2020 athlete:"Marco" score:34 + · └─┬ LEAF_GROUP id:row-group-country-Italy-year-2021 ag-Grid-AutoColumn:2021 + · · ├── LEAF id:ita-4 country:"Italy" year:2021 athlete:"Natalia" score:44 + · · ├── LEAF id:ita-5 country:"Italy" year:2021 athlete:"Olivia" score:54 + · · └── LEAF id:ita-6 country:"Italy" year:2021 athlete:"Paolo" score:64 + `); + }); + + test('delta sort with mixed operations in multiple groups', async () => { + const api = gridsManager.createGrid('deltaSortMixedOpsGroups', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 20 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 30 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 40 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Emma', score: 50 }, + { id: 'ire-6', country: 'Ireland', athlete: 'Frank', score: 60 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 15 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 25 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 35 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 45 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 55 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 65 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'ire-2' }, { id: 'fra-6' }], + update: [{ id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 5 }], + add: [ + { id: 'ire-7', country: 'Ireland', athlete: 'George', score: 35 }, + { id: 'esp-7', country: 'Spain', athlete: 'Zara', score: 70 }, + ], + }); + + await new GridRows(api, 'delta sort mixed operations multiple groups').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:5 + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-7 country:"Ireland" athlete:"George" score:35 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:40 + │ ├── LEAF id:ire-5 country:"Ireland" athlete:"Emma" score:50 + │ └── LEAF id:ire-6 country:"Ireland" athlete:"Frank" score:60 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:15 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:25 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:35 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:45 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:55 + │ ├── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:65 + │ └── LEAF id:esp-7 country:"Spain" athlete:"Zara" score:70 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:12 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:22 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:32 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:42 + │ └── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:52 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:18 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:28 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:38 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:48 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:58 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:68 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:14 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:24 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:34 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:44 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:54 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:64 + `); + }); + + test('delta sort short-circuits with no changes in grouped data', async () => { + const api = gridsManager.createGrid('deltaSortGroupedNoChanges', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 20 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 30 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 40 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Emma', score: 50 }, + { id: 'ire-6', country: 'Ireland', athlete: 'Frank', score: 60 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 15 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 25 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 35 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 45 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 55 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 65 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { add: [], remove: [], update: [] }); + + await new GridRows(api, 'delta sort no changes grouped').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-2 country:"Ireland" athlete:"Bob" score:20 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:30 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:40 + │ ├── LEAF id:ire-5 country:"Ireland" athlete:"Emma" score:50 + │ └── LEAF id:ire-6 country:"Ireland" athlete:"Frank" score:60 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:15 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:25 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:35 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:45 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:55 + │ └── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:65 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:12 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:22 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:32 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:42 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:52 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:62 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:18 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:28 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:38 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:48 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:58 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:68 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:14 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:24 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:34 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:44 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:54 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:64 + `); + }); + + test('delta sort grouped with duplicate node IDs', async () => { + const consoleWarnSpy = vitest.spyOn(console, 'warn').mockImplementation(() => {}); + + // Note: Duplicate IDs result in Map key collision - last duplicate wins in indexByNode + // This means sort order for duplicates is undefined and may not be stable + const api = gridsManager.createGrid('deltaSortGroupedDuplicateIds', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 20 }, + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 30 }, + { id: 'ire-1', country: 'Ireland', athlete: 'Diana', score: 40 }, // Duplicate ID + { id: 'ire-4', country: 'Ireland', athlete: 'Emma', score: 50 }, + { id: 'ire-5', country: 'Ireland', athlete: 'Frank', score: 60 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 15 }, + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 25 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 35 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 45 }, + { id: 'esp-5', country: 'Spain', athlete: 'Gloria', score: 55 }, + { id: 'esp-6', country: 'Spain', athlete: 'Hugo', score: 65 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 5 }], + add: [{ id: 'esp-7', country: 'Spain', athlete: 'Zara', score: 70 }], + }); + + // Both duplicates remain in correct sorted order + // Skip DOM validation since duplicate IDs cause mismatches between logical tree and DOM + await new GridRows(api, 'delta sort grouped with duplicate IDs', { checkDom: false }).check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-2 country:"Ireland" athlete:"Bob" score:5 + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:30 + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Diana" score:40 + │ ├── LEAF id:ire-4 country:"Ireland" athlete:"Emma" score:50 + │ └── LEAF id:ire-5 country:"Ireland" athlete:"Frank" score:60 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:15 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:25 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:35 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:45 + │ ├── LEAF id:esp-5 country:"Spain" athlete:"Gloria" score:55 + │ ├── LEAF id:esp-6 country:"Spain" athlete:"Hugo" score:65 + │ └── LEAF id:esp-7 country:"Spain" athlete:"Zara" score:70 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:12 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:22 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:32 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:42 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:52 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:62 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:18 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:28 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:38 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:48 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:58 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:68 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:14 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:24 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:34 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:44 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:54 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:64 + `); + + consoleWarnSpy.mockRestore(); + }); + + test('delta sort grouped with duplicate rowData instances', async () => { + const consoleWarnSpy = vitest.spyOn(console, 'warn').mockImplementation(() => {}); + + const sharedIre = { id: 'ire-shared', country: 'Ireland', athlete: 'Shared', score: 25 }; + const sharedEsp = { id: 'esp-shared', country: 'Spain', athlete: 'Shared', score: 27 }; + const api = gridsManager.createGrid('deltaSortGroupedDuplicateInstances', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'athlete' }, + { field: 'score', sortable: true }, + ], + autoGroupColumnDef: { headerName: 'Country' }, + groupDefaultExpanded: -1, + deltaSort: true, + rowData: [ + { id: 'ire-1', country: 'Ireland', athlete: 'Alice', score: 10 }, + { id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 20 }, + sharedIre, + sharedIre, // Duplicate instance + { id: 'ire-3', country: 'Ireland', athlete: 'Charlie', score: 30 }, + { id: 'ire-4', country: 'Ireland', athlete: 'Diana', score: 40 }, + { id: 'esp-1', country: 'Spain', athlete: 'Carlos', score: 15 }, + sharedEsp, + sharedEsp, // Duplicate instance + { id: 'esp-2', country: 'Spain', athlete: 'Diego', score: 35 }, + { id: 'esp-3', country: 'Spain', athlete: 'Elena', score: 45 }, + { id: 'esp-4', country: 'Spain', athlete: 'Fernando', score: 55 }, + { id: 'fra-1', country: 'France', athlete: 'François', score: 12 }, + { id: 'fra-2', country: 'France', athlete: 'Émilie', score: 22 }, + { id: 'fra-3', country: 'France', athlete: 'Gabriel', score: 32 }, + { id: 'fra-4', country: 'France', athlete: 'Hélène', score: 42 }, + { id: 'fra-5', country: 'France', athlete: 'Isabelle', score: 52 }, + { id: 'fra-6', country: 'France', athlete: 'Jacques', score: 62 }, + { id: 'ger-1', country: 'Germany', athlete: 'Greta', score: 18 }, + { id: 'ger-2', country: 'Germany', athlete: 'Hans', score: 28 }, + { id: 'ger-3', country: 'Germany', athlete: 'Ingrid', score: 38 }, + { id: 'ger-4', country: 'Germany', athlete: 'Jürgen', score: 48 }, + { id: 'ger-5', country: 'Germany', athlete: 'Klaus', score: 58 }, + { id: 'ger-6', country: 'Germany', athlete: 'Lars', score: 68 }, + { id: 'ita-1', country: 'Italy', athlete: 'Isabella', score: 14 }, + { id: 'ita-2', country: 'Italy', athlete: 'Leonardo', score: 24 }, + { id: 'ita-3', country: 'Italy', athlete: 'Marco', score: 34 }, + { id: 'ita-4', country: 'Italy', athlete: 'Natalia', score: 44 }, + { id: 'ita-5', country: 'Italy', athlete: 'Olivia', score: 54 }, + { id: 'ita-6', country: 'Italy', athlete: 'Paolo', score: 64 }, + ], + getRowId: (params) => params.data.id, + }); + + api.applyColumnState({ state: [{ colId: 'score', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'ire-2', country: 'Ireland', athlete: 'Bob', score: 5 }], + add: [{ id: 'esp-5', country: 'Spain', athlete: 'Zara', score: 70 }], + }); + + // Skip DOM validation since duplicate IDs cause mismatches between logical tree and DOM + await new GridRows(api, 'delta sort grouped with duplicate instances', { checkDom: false }).check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:ire-2 country:"Ireland" athlete:"Bob" score:5 + │ ├── LEAF id:ire-1 country:"Ireland" athlete:"Alice" score:10 + │ ├── LEAF id:ire-shared country:"Ireland" athlete:"Shared" score:25 + │ ├── LEAF id:ire-shared country:"Ireland" athlete:"Shared" score:25 + │ ├── LEAF id:ire-3 country:"Ireland" athlete:"Charlie" score:30 + │ └── LEAF id:ire-4 country:"Ireland" athlete:"Diana" score:40 + ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" + │ ├── LEAF id:esp-1 country:"Spain" athlete:"Carlos" score:15 + │ ├── LEAF id:esp-shared country:"Spain" athlete:"Shared" score:27 + │ ├── LEAF id:esp-shared country:"Spain" athlete:"Shared" score:27 + │ ├── LEAF id:esp-2 country:"Spain" athlete:"Diego" score:35 + │ ├── LEAF id:esp-3 country:"Spain" athlete:"Elena" score:45 + │ ├── LEAF id:esp-4 country:"Spain" athlete:"Fernando" score:55 + │ └── LEAF id:esp-5 country:"Spain" athlete:"Zara" score:70 + ├─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + │ ├── LEAF id:fra-1 country:"France" athlete:"François" score:12 + │ ├── LEAF id:fra-2 country:"France" athlete:"Émilie" score:22 + │ ├── LEAF id:fra-3 country:"France" athlete:"Gabriel" score:32 + │ ├── LEAF id:fra-4 country:"France" athlete:"Hélène" score:42 + │ ├── LEAF id:fra-5 country:"France" athlete:"Isabelle" score:52 + │ └── LEAF id:fra-6 country:"France" athlete:"Jacques" score:62 + ├─┬ LEAF_GROUP id:row-group-country-Germany ag-Grid-AutoColumn:"Germany" + │ ├── LEAF id:ger-1 country:"Germany" athlete:"Greta" score:18 + │ ├── LEAF id:ger-2 country:"Germany" athlete:"Hans" score:28 + │ ├── LEAF id:ger-3 country:"Germany" athlete:"Ingrid" score:38 + │ ├── LEAF id:ger-4 country:"Germany" athlete:"Jürgen" score:48 + │ ├── LEAF id:ger-5 country:"Germany" athlete:"Klaus" score:58 + │ └── LEAF id:ger-6 country:"Germany" athlete:"Lars" score:68 + └─┬ LEAF_GROUP id:row-group-country-Italy ag-Grid-AutoColumn:"Italy" + · ├── LEAF id:ita-1 country:"Italy" athlete:"Isabella" score:14 + · ├── LEAF id:ita-2 country:"Italy" athlete:"Leonardo" score:24 + · ├── LEAF id:ita-3 country:"Italy" athlete:"Marco" score:34 + · ├── LEAF id:ita-4 country:"Italy" athlete:"Natalia" score:44 + · ├── LEAF id:ita-5 country:"Italy" athlete:"Olivia" score:54 + · └── LEAF id:ita-6 country:"Italy" athlete:"Paolo" score:64 + `); + + consoleWarnSpy.mockRestore(); + }); +}); diff --git a/testing/behavioural/src/grouping-data/grouping-sorting.test.ts b/testing/behavioural/src/grouping-data/grouping-sorting.test.ts index 8e297778ea2..319470739a4 100644 --- a/testing/behavioural/src/grouping-data/grouping-sorting.test.ts +++ b/testing/behavioural/src/grouping-data/grouping-sorting.test.ts @@ -1,7 +1,7 @@ import { ClientSideRowModelModule } from 'ag-grid-community'; import { RowGroupingModule } from 'ag-grid-enterprise'; -import { GridRows, TestGridsManager, applyTransactionChecked } from '../test-utils'; +import { GridRows, TestGridsManager } from '../test-utils'; describe('ag-grid grouping sorting', () => { const gridsManager = new TestGridsManager({ @@ -345,60 +345,4 @@ describe('ag-grid grouping sorting', () => { · · └── LEAF id:5 country:"Italy" year:2021 athlete:"Luigi Verdi" sport:"Football" gold:5 `); }); - - test('delta sorting resorts grouped rows when only part of the data changes', async () => { - const rowData = [ - { id: 'ire-a', country: 'Ireland', athlete: 'Aine', score: 40 }, - { id: 'ire-b', country: 'Ireland', athlete: 'Brigid', score: 30 }, - { id: 'esp-a', country: 'Spain', athlete: 'Carlos', score: 25 }, - { id: 'esp-b', country: 'Spain', athlete: 'Diego', score: 10 }, - ]; - - const rowById = Object.fromEntries(rowData.map((row) => [row.id, row])) as Record< - string, - (typeof rowData)[number] - >; - - const api = gridsManager.createGrid('groupingDeltaSort', { - columnDefs: [ - { field: 'country', rowGroup: true, hide: true }, - { field: 'athlete' }, - { field: 'score', sortable: true, aggFunc: 'sum' }, - ], - autoGroupColumnDef: { headerName: 'Country' }, - animateRows: false, - groupDefaultExpanded: -1, - rowData, - deltaSort: true, - getRowId: (params) => params.data.id, - }); - - api.applyColumnState({ state: [{ colId: 'score', sort: 'desc' }] }); - - await new GridRows(api, 'group delta sort initial').check(` - ROOT id:ROOT_NODE_ID - ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" score:70 - │ ├── LEAF id:ire-a country:"Ireland" athlete:"Aine" score:40 - │ └── LEAF id:ire-b country:"Ireland" athlete:"Brigid" score:30 - └─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" score:35 - · ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:25 - · └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:10 - `); - - const updateRow = (id: string, score: number) => ({ ...rowById[id], score }); - - applyTransactionChecked(api, { - update: [updateRow('esp-a', 80), updateRow('ire-b', 5)], - }); - - await new GridRows(api, 'group delta sort updated').check(` - ROOT id:ROOT_NODE_ID - ├─┬ LEAF_GROUP id:row-group-country-Spain ag-Grid-AutoColumn:"Spain" score:90 - │ ├── LEAF id:esp-a country:"Spain" athlete:"Carlos" score:80 - │ └── LEAF id:esp-b country:"Spain" athlete:"Diego" score:10 - └─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" score:45 - · ├── LEAF id:ire-a country:"Ireland" athlete:"Aine" score:40 - · └── LEAF id:ire-b country:"Ireland" athlete:"Brigid" score:5 - `); - }); }); diff --git a/testing/behavioural/src/grouping-data/grouping-with-pivot.test.ts b/testing/behavioural/src/grouping-data/grouping-with-pivot.test.ts index bc03a93c598..d160b9ece84 100644 --- a/testing/behavioural/src/grouping-data/grouping-with-pivot.test.ts +++ b/testing/behavioural/src/grouping-data/grouping-with-pivot.test.ts @@ -252,6 +252,10 @@ describe('ag-grid grouping with pivot', () => { { id: 'eu-2021', region: 'Europe', year: 2021, sales: 950 }, { id: 'asia-2020', region: 'Asia', year: 2020, sales: 800 }, { id: 'asia-2021', region: 'Asia', year: 2021, sales: 700 }, + { id: 'sa-2020', region: 'South America', year: 2020, sales: 850 }, + { id: 'sa-2021', region: 'South America', year: 2021, sales: 920 }, + { id: 'africa-2020', region: 'Africa', year: 2020, sales: 750 }, + { id: 'africa-2021', region: 'Africa', year: 2021, sales: 780 }, ]; const rowById = Object.fromEntries(rowData.map((row) => [row.id, row])) as Record< @@ -267,28 +271,31 @@ describe('ag-grid grouping with pivot', () => { }); const gridRowsOptions: GridRowsOptions = { - forcedColumns: ['ag-Grid-AutoColumn', 'pivot_year_2020_sales', 'pivot_year_2021_sales'], printHiddenRows: false, }; await new GridRows(api, 'pivot delta sort initial', gridRowsOptions).check(` - ROOT id:ROOT_NODE_ID pivot_year_2020_sales:2700 pivot_year_2021_sales:2750 + ROOT id:ROOT_NODE_ID pivot_year_2020_sales:4300 pivot_year_2021_sales:4450 ├── LEAF_GROUP collapsed id:"row-group-region-North America" ag-Grid-AutoColumn:"North America" pivot_year_2020_sales:1000 pivot_year_2021_sales:1100 ├── LEAF_GROUP collapsed id:row-group-region-Europe ag-Grid-AutoColumn:"Europe" pivot_year_2020_sales:900 pivot_year_2021_sales:950 + ├── LEAF_GROUP collapsed id:"row-group-region-South America" ag-Grid-AutoColumn:"South America" pivot_year_2020_sales:850 pivot_year_2021_sales:920 + ├── LEAF_GROUP collapsed id:row-group-region-Africa ag-Grid-AutoColumn:"Africa" pivot_year_2020_sales:750 pivot_year_2021_sales:780 └── LEAF_GROUP collapsed id:row-group-region-Asia ag-Grid-AutoColumn:"Asia" pivot_year_2020_sales:800 pivot_year_2021_sales:700 `); const updateRow = (id: string, sales: number) => ({ ...rowById[id], sales }); applyTransactionChecked(api, { - update: [updateRow('eu-2021', 1500), updateRow('na-2021', 600)], + update: [updateRow('eu-2021', 1500), updateRow('na-2021', 600), updateRow('africa-2021', 400)], }); await new GridRows(api, 'pivot delta sort updated', gridRowsOptions).check(` - ROOT id:ROOT_NODE_ID pivot_year_2020_sales:2700 pivot_year_2021_sales:2800 + ROOT id:ROOT_NODE_ID pivot_year_2020_sales:4300 pivot_year_2021_sales:4120 ├── LEAF_GROUP collapsed id:row-group-region-Europe ag-Grid-AutoColumn:"Europe" pivot_year_2020_sales:900 pivot_year_2021_sales:1500 + ├── LEAF_GROUP collapsed id:"row-group-region-South America" ag-Grid-AutoColumn:"South America" pivot_year_2020_sales:850 pivot_year_2021_sales:920 ├── LEAF_GROUP collapsed id:row-group-region-Asia ag-Grid-AutoColumn:"Asia" pivot_year_2020_sales:800 pivot_year_2021_sales:700 - └── LEAF_GROUP collapsed id:"row-group-region-North America" ag-Grid-AutoColumn:"North America" pivot_year_2020_sales:1000 pivot_year_2021_sales:600 + ├── LEAF_GROUP collapsed id:"row-group-region-North America" ag-Grid-AutoColumn:"North America" pivot_year_2020_sales:1000 pivot_year_2021_sales:600 + └── LEAF_GROUP collapsed id:row-group-region-Africa ag-Grid-AutoColumn:"Africa" pivot_year_2020_sales:750 pivot_year_2021_sales:400 `); }); diff --git a/testing/behavioural/src/sorting/delta-sorting.test.ts b/testing/behavioural/src/sorting/delta-sorting.test.ts new file mode 100644 index 00000000000..d4ed15df9fe --- /dev/null +++ b/testing/behavioural/src/sorting/delta-sorting.test.ts @@ -0,0 +1,666 @@ +import { ClientSideRowModelModule } from 'ag-grid-community'; + +import { GridRows, TestGridsManager, applyTransactionChecked } from '../test-utils'; + +describe('Delta Sorting', () => { + const gridMgr = new TestGridsManager({ + modules: [ClientSideRowModelModule], + }); + + test('delta sort keeps order', async () => { + const rowCount = 20; + const baseRowData = Array.from({ length: rowCount }, (_, i) => ({ id: `delta-${i}`, value: i })); + + const api = gridMgr.createGrid('deltaSortThirtyPercent', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseRowData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + const updates = [ + { id: 'delta-1', value: 42 }, + { id: 'delta-4', value: -5 }, + { id: 'delta-7', value: 30 }, + { id: 'delta-10', value: 50 }, + { id: 'delta-13', value: 25 }, + { id: 'delta-16', value: 35 }, + ]; + expect(updates).toHaveLength(Math.floor(rowCount * 0.3)); + + applyTransactionChecked(api, { update: updates }); + + await new GridRows(api, 'delta sort updates 30%').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-4 value:-5 + ├── LEAF id:delta-0 value:0 + ├── LEAF id:delta-2 value:2 + ├── LEAF id:delta-3 value:3 + ├── LEAF id:delta-5 value:5 + ├── LEAF id:delta-6 value:6 + ├── LEAF id:delta-8 value:8 + ├── LEAF id:delta-9 value:9 + ├── LEAF id:delta-11 value:11 + ├── LEAF id:delta-12 value:12 + ├── LEAF id:delta-14 value:14 + ├── LEAF id:delta-15 value:15 + ├── LEAF id:delta-17 value:17 + ├── LEAF id:delta-18 value:18 + ├── LEAF id:delta-19 value:19 + ├── LEAF id:delta-13 value:25 + ├── LEAF id:delta-7 value:30 + ├── LEAF id:delta-16 value:35 + ├── LEAF id:delta-1 value:42 + └── LEAF id:delta-10 value:50 + `); + }); + + test('delta sort preserves prior order for untouched rows', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-${i}`, + value: i % 2 === 0 ? i : i + 10, // Mix values + })); + const api = gridMgr.createGrid('deltaSortUntouchedOrder', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + await new GridRows(api, 'initial sort').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-0 value:0 + ├── LEAF id:delta-2 value:2 + ├── LEAF id:delta-4 value:4 + ├── LEAF id:delta-6 value:6 + ├── LEAF id:delta-8 value:8 + ├── LEAF id:delta-10 value:10 + ├── LEAF id:delta-1 value:11 + ├── LEAF id:delta-12 value:12 + ├── LEAF id:delta-3 value:13 + ├── LEAF id:delta-14 value:14 + ├── LEAF id:delta-5 value:15 + ├── LEAF id:delta-16 value:16 + ├── LEAF id:delta-7 value:17 + ├── LEAF id:delta-18 value:18 + ├── LEAF id:delta-9 value:19 + ├── LEAF id:delta-11 value:21 + ├── LEAF id:delta-13 value:23 + ├── LEAF id:delta-15 value:25 + ├── LEAF id:delta-17 value:27 + └── LEAF id:delta-19 value:29 + `); + + applyTransactionChecked(api, { update: [{ id: 'delta-10', value: -1 }] }); + + await new GridRows(api, 'delta sort single update').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-10 value:-1 + ├── LEAF id:delta-0 value:0 + ├── LEAF id:delta-2 value:2 + ├── LEAF id:delta-4 value:4 + ├── LEAF id:delta-6 value:6 + ├── LEAF id:delta-8 value:8 + ├── LEAF id:delta-1 value:11 + ├── LEAF id:delta-12 value:12 + ├── LEAF id:delta-3 value:13 + ├── LEAF id:delta-14 value:14 + ├── LEAF id:delta-5 value:15 + ├── LEAF id:delta-16 value:16 + ├── LEAF id:delta-7 value:17 + ├── LEAF id:delta-18 value:18 + ├── LEAF id:delta-9 value:19 + ├── LEAF id:delta-11 value:21 + ├── LEAF id:delta-13 value:23 + ├── LEAF id:delta-15 value:25 + ├── LEAF id:delta-17 value:27 + └── LEAF id:delta-19 value:29 + `); + }); + + test('delta sort handles adds and removes', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-${i}`, + value: i * 5, + })); + const api = gridMgr.createGrid('deltaSortAddsRemoves', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'delta-2' }, { id: 'delta-7' }, { id: 'delta-15' }], + update: [{ id: 'delta-3', value: -10 }], + add: [{ id: 'delta-new', value: 22 }], + }); + + await new GridRows(api, 'delta sort adds removes').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-3 value:-10 + ├── LEAF id:delta-0 value:0 + ├── LEAF id:delta-1 value:5 + ├── LEAF id:delta-4 value:20 + ├── LEAF id:delta-new value:22 + ├── LEAF id:delta-5 value:25 + ├── LEAF id:delta-6 value:30 + ├── LEAF id:delta-8 value:40 + ├── LEAF id:delta-9 value:45 + ├── LEAF id:delta-10 value:50 + ├── LEAF id:delta-11 value:55 + ├── LEAF id:delta-12 value:60 + ├── LEAF id:delta-13 value:65 + ├── LEAF id:delta-14 value:70 + ├── LEAF id:delta-16 value:80 + ├── LEAF id:delta-17 value:85 + ├── LEAF id:delta-18 value:90 + └── LEAF id:delta-19 value:95 + `); + }); + + test('delta sort keeps stable order on equal values with new rows', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-e${i}`, + value: 1, + })); + const api = gridMgr.createGrid('deltaSortEqualValues', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'delta-e5', value: 1 }], + add: [{ id: 'delta-e20', value: 1 }], + }); + + await new GridRows(api, 'delta sort equal values').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-e0 value:1 + ├── LEAF id:delta-e1 value:1 + ├── LEAF id:delta-e2 value:1 + ├── LEAF id:delta-e3 value:1 + ├── LEAF id:delta-e4 value:1 + ├── LEAF id:delta-e5 value:1 + ├── LEAF id:delta-e6 value:1 + ├── LEAF id:delta-e7 value:1 + ├── LEAF id:delta-e8 value:1 + ├── LEAF id:delta-e9 value:1 + ├── LEAF id:delta-e10 value:1 + ├── LEAF id:delta-e11 value:1 + ├── LEAF id:delta-e12 value:1 + ├── LEAF id:delta-e13 value:1 + ├── LEAF id:delta-e14 value:1 + ├── LEAF id:delta-e15 value:1 + ├── LEAF id:delta-e16 value:1 + ├── LEAF id:delta-e17 value:1 + ├── LEAF id:delta-e18 value:1 + ├── LEAF id:delta-e19 value:1 + └── LEAF id:delta-e20 value:1 + `); + }); + + test('delta sort keeps stable order for addIndex with equal values', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-si${i}`, + value: 1, + })); + const api = gridMgr.createGrid('deltaSortAddIndexEqualValues', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + addIndex: 10, + add: [{ id: 'delta-si20', value: 1 }], + }); + + await new GridRows(api, 'delta sort addIndex equal values').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-si0 value:1 + ├── LEAF id:delta-si1 value:1 + ├── LEAF id:delta-si2 value:1 + ├── LEAF id:delta-si3 value:1 + ├── LEAF id:delta-si4 value:1 + ├── LEAF id:delta-si5 value:1 + ├── LEAF id:delta-si6 value:1 + ├── LEAF id:delta-si7 value:1 + ├── LEAF id:delta-si8 value:1 + ├── LEAF id:delta-si9 value:1 + ├── LEAF id:delta-si20 value:1 + ├── LEAF id:delta-si10 value:1 + ├── LEAF id:delta-si11 value:1 + ├── LEAF id:delta-si12 value:1 + ├── LEAF id:delta-si13 value:1 + ├── LEAF id:delta-si14 value:1 + ├── LEAF id:delta-si15 value:1 + ├── LEAF id:delta-si16 value:1 + ├── LEAF id:delta-si17 value:1 + ├── LEAF id:delta-si18 value:1 + └── LEAF id:delta-si19 value:1 + `); + }); + + test('delta sort with mixed operations (add, remove, update)', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `mixed-${i}`, + value: i * 10, + })); + const api = gridMgr.createGrid('deltaSortMixedOps', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'mixed-2' }, { id: 'mixed-10' }, { id: 'mixed-15' }], + update: [ + { id: 'mixed-5', value: -5 }, + { id: 'mixed-12', value: 105 }, + ], + add: [ + { id: 'mixed-new1', value: 25 }, + { id: 'mixed-new2', value: 85 }, + ], + }); + + await new GridRows(api, 'delta sort all operations').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:mixed-5 value:-5 + ├── LEAF id:mixed-0 value:0 + ├── LEAF id:mixed-1 value:10 + ├── LEAF id:mixed-new1 value:25 + ├── LEAF id:mixed-3 value:30 + ├── LEAF id:mixed-4 value:40 + ├── LEAF id:mixed-6 value:60 + ├── LEAF id:mixed-7 value:70 + ├── LEAF id:mixed-8 value:80 + ├── LEAF id:mixed-new2 value:85 + ├── LEAF id:mixed-9 value:90 + ├── LEAF id:mixed-12 value:105 + ├── LEAF id:mixed-11 value:110 + ├── LEAF id:mixed-13 value:130 + ├── LEAF id:mixed-14 value:140 + ├── LEAF id:mixed-16 value:160 + ├── LEAF id:mixed-17 value:170 + ├── LEAF id:mixed-18 value:180 + └── LEAF id:mixed-19 value:190 + `); + }); + + test('delta sort with balanced add and remove', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `bal-${i}`, + value: i * 10, + })); + const api = gridMgr.createGrid('deltaSortBalanced', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + // Remove 4 rows, add 4 rows (same count) + applyTransactionChecked(api, { + remove: [{ id: 'bal-2' }, { id: 'bal-8' }, { id: 'bal-15' }, { id: 'bal-18' }], + add: [ + { id: 'bal-new1', value: 25 }, + { id: 'bal-new2', value: 75 }, + { id: 'bal-new3', value: 135 }, + { id: 'bal-new4', value: 165 }, + ], + }); + + await new GridRows(api, 'delta sort balanced add/remove').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:bal-0 value:0 + ├── LEAF id:bal-1 value:10 + ├── LEAF id:bal-new1 value:25 + ├── LEAF id:bal-3 value:30 + ├── LEAF id:bal-4 value:40 + ├── LEAF id:bal-5 value:50 + ├── LEAF id:bal-6 value:60 + ├── LEAF id:bal-7 value:70 + ├── LEAF id:bal-new2 value:75 + ├── LEAF id:bal-9 value:90 + ├── LEAF id:bal-10 value:100 + ├── LEAF id:bal-11 value:110 + ├── LEAF id:bal-12 value:120 + ├── LEAF id:bal-13 value:130 + ├── LEAF id:bal-new3 value:135 + ├── LEAF id:bal-14 value:140 + ├── LEAF id:bal-16 value:160 + ├── LEAF id:bal-new4 value:165 + ├── LEAF id:bal-17 value:170 + └── LEAF id:bal-19 value:190 + `); + }); + + test('delta sort with duplicate node IDs', async () => { + const consoleWarnSpy = vitest.spyOn(console, 'warn').mockImplementation(() => {}); + + // Note: Duplicate IDs result in Map key collision - last duplicate wins in indexByNode + // This means sort order for duplicates is undefined and may not be stable + const api = gridMgr.createGrid('deltaSortDuplicateIds', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: [ + { id: 'dup-1', value: 10 }, + { id: 'dup-2', value: 20 }, + { id: 'dup-3', value: 30 }, + { id: 'dup-4', value: 40 }, + { id: 'dup-5', value: 50 }, + { id: 'dup-6', value: 60 }, + { id: 'dup-7', value: 70 }, + { id: 'dup-8', value: 80 }, + { id: 'dup-9', value: 90 }, + { id: 'dup-1', value: 100 }, // Duplicate ID + { id: 'dup-10', value: 110 }, + { id: 'dup-11', value: 120 }, + { id: 'dup-12', value: 130 }, + { id: 'dup-13', value: 140 }, + { id: 'dup-14', value: 150 }, + { id: 'dup-15', value: 160 }, + { id: 'dup-16', value: 170 }, + ], + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'dup-2', value: 5 }], + add: [{ id: 'dup-17', value: 65 }], + }); + + await new GridRows(api, 'delta sort with duplicate IDs', { checkDom: false }).check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:dup-2 value:5 + ├── LEAF id:dup-1 value:10 + ├── LEAF id:dup-3 value:30 + ├── LEAF id:dup-4 value:40 + ├── LEAF id:dup-5 value:50 + ├── LEAF id:dup-6 value:60 + ├── LEAF id:dup-17 value:65 + ├── LEAF id:dup-7 value:70 + ├── LEAF id:dup-8 value:80 + ├── LEAF id:dup-9 value:90 + ├── LEAF id:dup-1 value:100 + ├── LEAF id:dup-10 value:110 + ├── LEAF id:dup-11 value:120 + ├── LEAF id:dup-12 value:130 + ├── LEAF id:dup-13 value:140 + ├── LEAF id:dup-14 value:150 + ├── LEAF id:dup-15 value:160 + └── LEAF id:dup-16 value:170 + `); + + consoleWarnSpy.mockRestore(); + }); + + test('delta sort with duplicate rowData instances', async () => { + const consoleWarnSpy = vitest.spyOn(console, 'warn').mockImplementation(() => {}); + + const sharedData = { id: 'shared', value: 90 }; + const api = gridMgr.createGrid('deltaSortDuplicateInstances', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: [ + { id: 'dup-inst-1', value: 10 }, + { id: 'dup-inst-2', value: 20 }, + { id: 'dup-inst-3', value: 30 }, + { id: 'dup-inst-4', value: 40 }, + { id: 'dup-inst-5', value: 50 }, + { id: 'dup-inst-6', value: 60 }, + { id: 'dup-inst-7', value: 70 }, + { id: 'dup-inst-8', value: 80 }, + sharedData, + sharedData, // Duplicate instance + { id: 'dup-inst-9', value: 100 }, + { id: 'dup-inst-10', value: 110 }, + { id: 'dup-inst-11', value: 120 }, + { id: 'dup-inst-12', value: 130 }, + { id: 'dup-inst-13', value: 140 }, + { id: 'dup-inst-14', value: 150 }, + ], + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + update: [{ id: 'dup-inst-3', value: 5 }], + add: [{ id: 'dup-inst-15', value: 65 }], + }); + + await new GridRows(api, 'delta sort with duplicate instances', { checkDom: false }).check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:dup-inst-3 value:5 + ├── LEAF id:dup-inst-1 value:10 + ├── LEAF id:dup-inst-2 value:20 + ├── LEAF id:dup-inst-4 value:40 + ├── LEAF id:dup-inst-5 value:50 + ├── LEAF id:dup-inst-6 value:60 + ├── LEAF id:dup-inst-15 value:65 + ├── LEAF id:dup-inst-7 value:70 + ├── LEAF id:dup-inst-8 value:80 + ├── LEAF id:shared value:90 + ├── LEAF id:shared value:90 + ├── LEAF id:dup-inst-9 value:100 + ├── LEAF id:dup-inst-10 value:110 + ├── LEAF id:dup-inst-11 value:120 + ├── LEAF id:dup-inst-12 value:130 + ├── LEAF id:dup-inst-13 value:140 + └── LEAF id:dup-inst-14 value:150 + `); + + consoleWarnSpy.mockRestore(); + }); + + test('delta sort handles removes without updates', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-r${i}`, + value: i, + })); + const api = gridMgr.createGrid('deltaSortRemovesOnly', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'delta-r2' }, { id: 'delta-r7' }, { id: 'delta-r12' }, { id: 'delta-r18' }], + }); + + await new GridRows(api, 'delta sort removes only').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-r0 value:0 + ├── LEAF id:delta-r1 value:1 + ├── LEAF id:delta-r3 value:3 + ├── LEAF id:delta-r4 value:4 + ├── LEAF id:delta-r5 value:5 + ├── LEAF id:delta-r6 value:6 + ├── LEAF id:delta-r8 value:8 + ├── LEAF id:delta-r9 value:9 + ├── LEAF id:delta-r10 value:10 + ├── LEAF id:delta-r11 value:11 + ├── LEAF id:delta-r13 value:13 + ├── LEAF id:delta-r14 value:14 + ├── LEAF id:delta-r15 value:15 + ├── LEAF id:delta-r16 value:16 + ├── LEAF id:delta-r17 value:17 + └── LEAF id:delta-r19 value:19 + `); + }); + + test('delta sort handles adds without updates', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-a${i}`, + value: i * 2, + })); + const api = gridMgr.createGrid('deltaSortAddsOnly', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + add: [ + { id: 'delta-a20', value: 5 }, + { id: 'delta-a21', value: 15 }, + ], + }); + + await new GridRows(api, 'delta sort adds only').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-a0 value:0 + ├── LEAF id:delta-a1 value:2 + ├── LEAF id:delta-a2 value:4 + ├── LEAF id:delta-a20 value:5 + ├── LEAF id:delta-a3 value:6 + ├── LEAF id:delta-a4 value:8 + ├── LEAF id:delta-a5 value:10 + ├── LEAF id:delta-a6 value:12 + ├── LEAF id:delta-a7 value:14 + ├── LEAF id:delta-a21 value:15 + ├── LEAF id:delta-a8 value:16 + ├── LEAF id:delta-a9 value:18 + ├── LEAF id:delta-a10 value:20 + ├── LEAF id:delta-a11 value:22 + ├── LEAF id:delta-a12 value:24 + ├── LEAF id:delta-a13 value:26 + ├── LEAF id:delta-a14 value:28 + ├── LEAF id:delta-a15 value:30 + ├── LEAF id:delta-a16 value:32 + ├── LEAF id:delta-a17 value:34 + ├── LEAF id:delta-a18 value:36 + └── LEAF id:delta-a19 value:38 + `); + }); + + test('delta sort short-circuits when no changes', async () => { + const baseData = Array.from({ length: 20 }, (_, i) => ({ + id: `delta-n${i}`, + value: i + 1, + })); + const api = gridMgr.createGrid('deltaSortNoChanges', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: baseData, + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { add: [], remove: [], update: [] }); + + await new GridRows(api, 'delta sort no changes').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-n0 value:1 + ├── LEAF id:delta-n1 value:2 + ├── LEAF id:delta-n2 value:3 + ├── LEAF id:delta-n3 value:4 + ├── LEAF id:delta-n4 value:5 + ├── LEAF id:delta-n5 value:6 + ├── LEAF id:delta-n6 value:7 + ├── LEAF id:delta-n7 value:8 + ├── LEAF id:delta-n8 value:9 + ├── LEAF id:delta-n9 value:10 + ├── LEAF id:delta-n10 value:11 + ├── LEAF id:delta-n11 value:12 + ├── LEAF id:delta-n12 value:13 + ├── LEAF id:delta-n13 value:14 + ├── LEAF id:delta-n14 value:15 + ├── LEAF id:delta-n15 value:16 + ├── LEAF id:delta-n16 value:17 + ├── LEAF id:delta-n17 value:18 + ├── LEAF id:delta-n18 value:19 + └── LEAF id:delta-n19 value:20 + `); + }); + + test('delta sort handles adds and removes without updates', async () => { + const api = gridMgr.createGrid('deltaSortAddsRemovesNoUpdates', { + columnDefs: [{ field: 'value' }], + deltaSort: true, + rowData: [ + { id: 'delta-ar1', value: 1 }, + { id: 'delta-ar2', value: 2 }, + { id: 'delta-ar3', value: 3 }, + { id: 'delta-ar4', value: 4 }, + { id: 'delta-ar5', value: 5 }, + { id: 'delta-ar6', value: 6 }, + { id: 'delta-ar7', value: 7 }, + { id: 'delta-ar8', value: 8 }, + { id: 'delta-ar9', value: 9 }, + { id: 'delta-ar10', value: 10 }, + { id: 'delta-ar11', value: 11 }, + { id: 'delta-ar12', value: 12 }, + { id: 'delta-ar13', value: 13 }, + { id: 'delta-ar14', value: 14 }, + { id: 'delta-ar15', value: 15 }, + { id: 'delta-ar16', value: 16 }, + { id: 'delta-ar17', value: 17 }, + { id: 'delta-ar18', value: 18 }, + ], + getRowId: (params) => params.data?.id, + }); + + api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); + + applyTransactionChecked(api, { + remove: [{ id: 'delta-ar2' }, { id: 'delta-ar10' }], + add: [ + { id: 'delta-ar19', value: 2.5 }, + { id: 'delta-ar20', value: 10.5 }, + ], + }); + + await new GridRows(api, 'delta sort adds removes no updates').check(` + ROOT id:ROOT_NODE_ID + ├── LEAF id:delta-ar1 value:1 + ├── LEAF id:delta-ar19 value:2.5 + ├── LEAF id:delta-ar3 value:3 + ├── LEAF id:delta-ar4 value:4 + ├── LEAF id:delta-ar5 value:5 + ├── LEAF id:delta-ar6 value:6 + ├── LEAF id:delta-ar7 value:7 + ├── LEAF id:delta-ar8 value:8 + ├── LEAF id:delta-ar9 value:9 + ├── LEAF id:delta-ar20 value:10.5 + ├── LEAF id:delta-ar11 value:11 + ├── LEAF id:delta-ar12 value:12 + ├── LEAF id:delta-ar13 value:13 + ├── LEAF id:delta-ar14 value:14 + ├── LEAF id:delta-ar15 value:15 + ├── LEAF id:delta-ar16 value:16 + ├── LEAF id:delta-ar17 value:17 + └── LEAF id:delta-ar18 value:18 + `); + }); +}); diff --git a/testing/behavioural/src/sorting/sorting.test.ts b/testing/behavioural/src/sorting/sorting.test.ts index 674eb80c472..697995ebbc9 100644 --- a/testing/behavioural/src/sorting/sorting.test.ts +++ b/testing/behavioural/src/sorting/sorting.test.ts @@ -3,7 +3,7 @@ import { userEvent } from '@testing-library/user-event'; import { ClientSideRowModelModule, agTestIdFor, getGridElement, setupAgTestIds } from 'ag-grid-community'; -import { GridRows, TestGridsManager, applyTransactionChecked, asyncSetTimeout } from '../test-utils'; +import { GridRows, TestGridsManager, asyncSetTimeout } from '../test-utils'; describe('Sorting', () => { const gridMgr = new TestGridsManager({ @@ -363,43 +363,6 @@ describe('Sorting', () => { `); }); - test('delta sort keeps order', async () => { - const rowCount = 10; - const baseRowData = Array.from({ length: rowCount }, (_, i) => ({ id: `delta-${i}`, value: i })); - - const api = gridMgr.createGrid('deltaSortThirtyPercent', { - columnDefs: [{ field: 'value' }], - deltaSort: true, - rowData: baseRowData, - getRowId: (params) => params.data?.id, - }); - - api.applyColumnState({ state: [{ colId: 'value', sort: 'asc' }] }); - - const updates = [ - { id: 'delta-1', value: 42 }, - { id: 'delta-4', value: -5 }, - { id: 'delta-7', value: 30 }, - ]; - expect(updates).toHaveLength(Math.floor(rowCount * 0.3)); - - applyTransactionChecked(api, { update: updates }); - - await new GridRows(api, 'delta sort updates 30%').check(` - ROOT id:ROOT_NODE_ID - ├── LEAF id:delta-4 value:-5 - ├── LEAF id:delta-0 value:0 - ├── LEAF id:delta-2 value:2 - ├── LEAF id:delta-3 value:3 - ├── LEAF id:delta-5 value:5 - ├── LEAF id:delta-6 value:6 - ├── LEAF id:delta-8 value:8 - ├── LEAF id:delta-9 value:9 - ├── LEAF id:delta-7 value:30 - └── LEAF id:delta-1 value:42 - `); - }); - test('absolute sort orders by magnitude', async () => { const api = gridMgr.createGrid('absoluteSort', { columnDefs: [{ field: 'amount', sortIndex: 0 }], diff --git a/testing/behavioural/src/tree-data/datapath/stages/tree-filter-sort.test.ts b/testing/behavioural/src/tree-data/datapath/stages/tree-filter-sort.test.ts index e255d19c0c7..e245df3b63a 100644 --- a/testing/behavioural/src/tree-data/datapath/stages/tree-filter-sort.test.ts +++ b/testing/behavioural/src/tree-data/datapath/stages/tree-filter-sort.test.ts @@ -368,9 +368,32 @@ describe('ag-grid tree filter sort', () => { { id: 'home', value: 15, orgHierarchy: ['Home'] }, { id: 'garden', value: 12, orgHierarchy: ['Home', 'Garden'] }, { id: 'kitchen', value: 25, orgHierarchy: ['Home', 'Kitchen'] }, + { id: 'bathroom', value: 8, orgHierarchy: ['Home', 'Bathroom'] }, { id: 'electronics', value: 30, orgHierarchy: ['Electronics'] }, { id: 'phones', value: 5, orgHierarchy: ['Electronics', 'Phones'] }, { id: 'laptops', value: 40, orgHierarchy: ['Electronics', 'Laptops'] }, + { id: 'tablets', value: 20, orgHierarchy: ['Electronics', 'Tablets'] }, + { id: 'cameras', value: 33, orgHierarchy: ['Electronics', 'Cameras'] }, + { id: 'tvs', value: 45, orgHierarchy: ['Electronics', 'TVs'] }, + { id: 'headphones', value: 15, orgHierarchy: ['Electronics', 'Headphones'] }, + { id: 'furniture', value: 18, orgHierarchy: ['Furniture'] }, + { id: 'chairs', value: 10, orgHierarchy: ['Furniture', 'Chairs'] }, + { id: 'tables', value: 35, orgHierarchy: ['Furniture', 'Tables'] }, + { id: 'sofas', value: 22, orgHierarchy: ['Furniture', 'Sofas'] }, + { id: 'clothing', value: 14, orgHierarchy: ['Clothing'] }, + { id: 'shirts', value: 6, orgHierarchy: ['Clothing', 'Shirts'] }, + { id: 'pants', value: 28, orgHierarchy: ['Clothing', 'Pants'] }, + { id: 'jackets', value: 16, orgHierarchy: ['Clothing', 'Jackets'] }, + { id: 'shoes', value: 32, orgHierarchy: ['Clothing', 'Shoes'] }, + { id: 'hats', value: 9, orgHierarchy: ['Clothing', 'Hats'] }, + { id: 'socks', value: 4, orgHierarchy: ['Clothing', 'Socks'] }, + { id: 'books', value: 22, orgHierarchy: ['Books'] }, + { id: 'fiction', value: 11, orgHierarchy: ['Books', 'Fiction'] }, + { id: 'nonfiction', value: 38, orgHierarchy: ['Books', 'Nonfiction'] }, + { id: 'magazines', value: 19, orgHierarchy: ['Books', 'Magazines'] }, + { id: 'comics', value: 13, orgHierarchy: ['Books', 'Comics'] }, + { id: 'textbooks', value: 42, orgHierarchy: ['Books', 'Textbooks'] }, + { id: 'cookbooks', value: 24, orgHierarchy: ['Books', 'Cookbooks'] }, ]; const rowById = Object.fromEntries(rowData.map((row) => [row.id, row])) as Record< @@ -394,28 +417,80 @@ describe('ag-grid tree filter sort', () => { await new GridRows(api, 'tree data delta sort initial').check(` ROOT id:ROOT_NODE_ID + ├─┬ Clothing GROUP id:clothing ag-Grid-AutoColumn:"Clothing" value:14 + │ ├── Socks LEAF id:socks ag-Grid-AutoColumn:"Socks" value:4 + │ ├── Shirts LEAF id:shirts ag-Grid-AutoColumn:"Shirts" value:6 + │ ├── Hats LEAF id:hats ag-Grid-AutoColumn:"Hats" value:9 + │ ├── Jackets LEAF id:jackets ag-Grid-AutoColumn:"Jackets" value:16 + │ ├── Pants LEAF id:pants ag-Grid-AutoColumn:"Pants" value:28 + │ └── Shoes LEAF id:shoes ag-Grid-AutoColumn:"Shoes" value:32 ├─┬ Home GROUP id:home ag-Grid-AutoColumn:"Home" value:15 + │ ├── Bathroom LEAF id:bathroom ag-Grid-AutoColumn:"Bathroom" value:8 │ ├── Garden LEAF id:garden ag-Grid-AutoColumn:"Garden" value:12 │ └── Kitchen LEAF id:kitchen ag-Grid-AutoColumn:"Kitchen" value:25 + ├─┬ Furniture GROUP id:furniture ag-Grid-AutoColumn:"Furniture" value:18 + │ ├── Chairs LEAF id:chairs ag-Grid-AutoColumn:"Chairs" value:10 + │ ├── Sofas LEAF id:sofas ag-Grid-AutoColumn:"Sofas" value:22 + │ └── Tables LEAF id:tables ag-Grid-AutoColumn:"Tables" value:35 + ├─┬ Books GROUP id:books ag-Grid-AutoColumn:"Books" value:22 + │ ├── Fiction LEAF id:fiction ag-Grid-AutoColumn:"Fiction" value:11 + │ ├── Comics LEAF id:comics ag-Grid-AutoColumn:"Comics" value:13 + │ ├── Magazines LEAF id:magazines ag-Grid-AutoColumn:"Magazines" value:19 + │ ├── Cookbooks LEAF id:cookbooks ag-Grid-AutoColumn:"Cookbooks" value:24 + │ ├── Nonfiction LEAF id:nonfiction ag-Grid-AutoColumn:"Nonfiction" value:38 + │ └── Textbooks LEAF id:textbooks ag-Grid-AutoColumn:"Textbooks" value:42 └─┬ Electronics GROUP id:electronics ag-Grid-AutoColumn:"Electronics" value:30 · ├── Phones LEAF id:phones ag-Grid-AutoColumn:"Phones" value:5 - · └── Laptops LEAF id:laptops ag-Grid-AutoColumn:"Laptops" value:40 + · ├── Headphones LEAF id:headphones ag-Grid-AutoColumn:"Headphones" value:15 + · ├── Tablets LEAF id:tablets ag-Grid-AutoColumn:"Tablets" value:20 + · ├── Cameras LEAF id:cameras ag-Grid-AutoColumn:"Cameras" value:33 + · ├── Laptops LEAF id:laptops ag-Grid-AutoColumn:"Laptops" value:40 + · └── TVs LEAF id:tvs ag-Grid-AutoColumn:"TVs" value:45 `); const updateRow = (id: string, value: number) => ({ ...rowById[id], value }); applyTransactionChecked(api, { - update: [updateRow('electronics', 5), updateRow('phones', 60), updateRow('kitchen', 1)], + update: [ + updateRow('electronics', 5), + updateRow('phones', 60), + updateRow('kitchen', 1), + updateRow('books', 3), + updateRow('nonfiction', 2), + ], }); await new GridRows(api, 'tree data delta sort updated').check(` ROOT id:ROOT_NODE_ID + ├─┬ Books GROUP id:books ag-Grid-AutoColumn:"Books" value:3 + │ ├── Nonfiction LEAF id:nonfiction ag-Grid-AutoColumn:"Nonfiction" value:2 + │ ├── Fiction LEAF id:fiction ag-Grid-AutoColumn:"Fiction" value:11 + │ ├── Comics LEAF id:comics ag-Grid-AutoColumn:"Comics" value:13 + │ ├── Magazines LEAF id:magazines ag-Grid-AutoColumn:"Magazines" value:19 + │ ├── Cookbooks LEAF id:cookbooks ag-Grid-AutoColumn:"Cookbooks" value:24 + │ └── Textbooks LEAF id:textbooks ag-Grid-AutoColumn:"Textbooks" value:42 ├─┬ Electronics GROUP id:electronics ag-Grid-AutoColumn:"Electronics" value:5 + │ ├── Headphones LEAF id:headphones ag-Grid-AutoColumn:"Headphones" value:15 + │ ├── Tablets LEAF id:tablets ag-Grid-AutoColumn:"Tablets" value:20 + │ ├── Cameras LEAF id:cameras ag-Grid-AutoColumn:"Cameras" value:33 │ ├── Laptops LEAF id:laptops ag-Grid-AutoColumn:"Laptops" value:40 + │ ├── TVs LEAF id:tvs ag-Grid-AutoColumn:"TVs" value:45 │ └── Phones LEAF id:phones ag-Grid-AutoColumn:"Phones" value:60 - └─┬ Home GROUP id:home ag-Grid-AutoColumn:"Home" value:15 - · ├── Kitchen LEAF id:kitchen ag-Grid-AutoColumn:"Kitchen" value:1 - · └── Garden LEAF id:garden ag-Grid-AutoColumn:"Garden" value:12 + ├─┬ Clothing GROUP id:clothing ag-Grid-AutoColumn:"Clothing" value:14 + │ ├── Socks LEAF id:socks ag-Grid-AutoColumn:"Socks" value:4 + │ ├── Shirts LEAF id:shirts ag-Grid-AutoColumn:"Shirts" value:6 + │ ├── Hats LEAF id:hats ag-Grid-AutoColumn:"Hats" value:9 + │ ├── Jackets LEAF id:jackets ag-Grid-AutoColumn:"Jackets" value:16 + │ ├── Pants LEAF id:pants ag-Grid-AutoColumn:"Pants" value:28 + │ └── Shoes LEAF id:shoes ag-Grid-AutoColumn:"Shoes" value:32 + ├─┬ Home GROUP id:home ag-Grid-AutoColumn:"Home" value:15 + │ ├── Kitchen LEAF id:kitchen ag-Grid-AutoColumn:"Kitchen" value:1 + │ ├── Bathroom LEAF id:bathroom ag-Grid-AutoColumn:"Bathroom" value:8 + │ └── Garden LEAF id:garden ag-Grid-AutoColumn:"Garden" value:12 + └─┬ Furniture GROUP id:furniture ag-Grid-AutoColumn:"Furniture" value:18 + · ├── Chairs LEAF id:chairs ag-Grid-AutoColumn:"Chairs" value:10 + · ├── Sofas LEAF id:sofas ag-Grid-AutoColumn:"Sofas" value:22 + · └── Tables LEAF id:tables ag-Grid-AutoColumn:"Tables" value:35 `); }); }); diff --git a/testing/behavioural/src/tree-data/hierarchical/stages/hierarchical-tree-filter-sort.test.ts b/testing/behavioural/src/tree-data/hierarchical/stages/hierarchical-tree-filter-sort.test.ts index 9fe95a34f2a..82ee2e866f6 100644 --- a/testing/behavioural/src/tree-data/hierarchical/stages/hierarchical-tree-filter-sort.test.ts +++ b/testing/behavioural/src/tree-data/hierarchical/stages/hierarchical-tree-filter-sort.test.ts @@ -489,6 +489,10 @@ describe('ag-grid hierarchical tree filter sort', () => { children: [ { id: 'alpha-design', label: 'Alpha Design', value: 35 }, { id: 'alpha-build', label: 'Alpha Build', value: 20 }, + { id: 'alpha-test', label: 'Alpha Test', value: 50 }, + { id: 'alpha-deploy', label: 'Alpha Deploy', value: 28 }, + { id: 'alpha-monitor', label: 'Alpha Monitor', value: 12 }, + { id: 'alpha-maintain', label: 'Alpha Maintain', value: 42 }, ], }, { @@ -498,6 +502,43 @@ describe('ag-grid hierarchical tree filter sort', () => { children: [ { id: 'beta-design', label: 'Beta Design', value: 5 }, { id: 'beta-build', label: 'Beta Build', value: 18 }, + { id: 'beta-test', label: 'Beta Test', value: 12 }, + { id: 'beta-deploy', label: 'Beta Deploy', value: 22 }, + { id: 'beta-monitor', label: 'Beta Monitor', value: 8 }, + { id: 'beta-maintain', label: 'Beta Maintain', value: 16 }, + ], + }, + { + id: 'gamma', + label: 'Gamma', + value: 25, + children: [ + { id: 'gamma-design', label: 'Gamma Design', value: 8 }, + { id: 'gamma-build', label: 'Gamma Build', value: 30 }, + { id: 'gamma-test', label: 'Gamma Test', value: 22 }, + { id: 'gamma-deploy', label: 'Gamma Deploy', value: 35 }, + { id: 'gamma-monitor', label: 'Gamma Monitor', value: 15 }, + { id: 'gamma-maintain', label: 'Gamma Maintain', value: 25 }, + ], + }, + { + id: 'delta', + label: 'Delta', + value: 32, + children: [ + { id: 'delta-design', label: 'Delta Design', value: 28 }, + { id: 'delta-build', label: 'Delta Build', value: 45 }, + { id: 'delta-test', label: 'Delta Test', value: 15 }, + ], + }, + { + id: 'epsilon', + label: 'Epsilon', + value: 38, + children: [ + { id: 'epsilon-design', label: 'Epsilon Design', value: 42 }, + { id: 'epsilon-build', label: 'Epsilon Build', value: 25 }, + { id: 'epsilon-test', label: 'Epsilon Test', value: 55 }, ], }, ]); @@ -510,6 +551,10 @@ describe('ag-grid hierarchical tree filter sort', () => { children: [ { id: 'alpha-design', label: 'Alpha Design', value: 35 }, { id: 'alpha-build', label: 'Alpha Build', value: 12 }, + { id: 'alpha-test', label: 'Alpha Test', value: 50 }, + { id: 'alpha-deploy', label: 'Alpha Deploy', value: 28 }, + { id: 'alpha-monitor', label: 'Alpha Monitor', value: 10 }, + { id: 'alpha-maintain', label: 'Alpha Maintain', value: 42 }, ], }, { @@ -519,6 +564,43 @@ describe('ag-grid hierarchical tree filter sort', () => { children: [ { id: 'beta-design', label: 'Beta Design', value: 5 }, { id: 'beta-build', label: 'Beta Build', value: 1 }, + { id: 'beta-test', label: 'Beta Test', value: 12 }, + { id: 'beta-deploy', label: 'Beta Deploy', value: 22 }, + { id: 'beta-monitor', label: 'Beta Monitor', value: 8 }, + { id: 'beta-maintain', label: 'Beta Maintain', value: 16 }, + ], + }, + { + id: 'gamma', + label: 'Gamma', + value: 25, + children: [ + { id: 'gamma-design', label: 'Gamma Design', value: 8 }, + { id: 'gamma-build', label: 'Gamma Build', value: 30 }, + { id: 'gamma-test', label: 'Gamma Test', value: 22 }, + { id: 'gamma-deploy', label: 'Gamma Deploy', value: 35 }, + { id: 'gamma-monitor', label: 'Gamma Monitor', value: 15 }, + { id: 'gamma-maintain', label: 'Gamma Maintain', value: 25 }, + ], + }, + { + id: 'delta', + label: 'Delta', + value: 32, + children: [ + { id: 'delta-design', label: 'Delta Design', value: 28 }, + { id: 'delta-build', label: 'Delta Build', value: 45 }, + { id: 'delta-test', label: 'Delta Test', value: 15 }, + ], + }, + { + id: 'epsilon', + label: 'Epsilon', + value: 38, + children: [ + { id: 'epsilon-design', label: 'Epsilon Design', value: 42 }, + { id: 'epsilon-build', label: 'Epsilon Build', value: 25 }, + { id: 'epsilon-test', label: 'Epsilon Test', value: 55 }, ], }, ]); @@ -541,10 +623,33 @@ describe('ag-grid hierarchical tree filter sort', () => { ROOT id:ROOT_NODE_ID ├─┬ beta GROUP id:beta ag-Grid-AutoColumn:"beta" value:15 │ ├── beta-design LEAF id:beta-design ag-Grid-AutoColumn:"beta-design" value:5 - │ └── beta-build LEAF id:beta-build ag-Grid-AutoColumn:"beta-build" value:18 + │ ├── beta-monitor LEAF id:beta-monitor ag-Grid-AutoColumn:"beta-monitor" value:8 + │ ├── beta-test LEAF id:beta-test ag-Grid-AutoColumn:"beta-test" value:12 + │ ├── beta-maintain LEAF id:beta-maintain ag-Grid-AutoColumn:"beta-maintain" value:16 + │ ├── beta-build LEAF id:beta-build ag-Grid-AutoColumn:"beta-build" value:18 + │ └── beta-deploy LEAF id:beta-deploy ag-Grid-AutoColumn:"beta-deploy" value:22 + ├─┬ gamma GROUP id:gamma ag-Grid-AutoColumn:"gamma" value:25 + │ ├── gamma-design LEAF id:gamma-design ag-Grid-AutoColumn:"gamma-design" value:8 + │ ├── gamma-monitor LEAF id:gamma-monitor ag-Grid-AutoColumn:"gamma-monitor" value:15 + │ ├── gamma-test LEAF id:gamma-test ag-Grid-AutoColumn:"gamma-test" value:22 + │ ├── gamma-maintain LEAF id:gamma-maintain ag-Grid-AutoColumn:"gamma-maintain" value:25 + │ ├── gamma-build LEAF id:gamma-build ag-Grid-AutoColumn:"gamma-build" value:30 + │ └── gamma-deploy LEAF id:gamma-deploy ag-Grid-AutoColumn:"gamma-deploy" value:35 + ├─┬ delta GROUP id:delta ag-Grid-AutoColumn:"delta" value:32 + │ ├── delta-test LEAF id:delta-test ag-Grid-AutoColumn:"delta-test" value:15 + │ ├── delta-design LEAF id:delta-design ag-Grid-AutoColumn:"delta-design" value:28 + │ └── delta-build LEAF id:delta-build ag-Grid-AutoColumn:"delta-build" value:45 + ├─┬ epsilon GROUP id:epsilon ag-Grid-AutoColumn:"epsilon" value:38 + │ ├── epsilon-build LEAF id:epsilon-build ag-Grid-AutoColumn:"epsilon-build" value:25 + │ ├── epsilon-design LEAF id:epsilon-design ag-Grid-AutoColumn:"epsilon-design" value:42 + │ └── epsilon-test LEAF id:epsilon-test ag-Grid-AutoColumn:"epsilon-test" value:55 └─┬ alpha GROUP id:alpha ag-Grid-AutoColumn:"alpha" value:40 + · ├── alpha-monitor LEAF id:alpha-monitor ag-Grid-AutoColumn:"alpha-monitor" value:12 · ├── alpha-build LEAF id:alpha-build ag-Grid-AutoColumn:"alpha-build" value:20 - · └── alpha-design LEAF id:alpha-design ag-Grid-AutoColumn:"alpha-design" value:35 + · ├── alpha-deploy LEAF id:alpha-deploy ag-Grid-AutoColumn:"alpha-deploy" value:28 + · ├── alpha-design LEAF id:alpha-design ag-Grid-AutoColumn:"alpha-design" value:35 + · ├── alpha-maintain LEAF id:alpha-maintain ag-Grid-AutoColumn:"alpha-maintain" value:42 + · └── alpha-test LEAF id:alpha-test ag-Grid-AutoColumn:"alpha-test" value:50 `); api.setGridOption('rowData', updatedRowData); @@ -552,11 +657,34 @@ describe('ag-grid hierarchical tree filter sort', () => { await new GridRows(api, 'hierarchical tree data updated order').check(` ROOT id:ROOT_NODE_ID ├─┬ alpha GROUP id:alpha ag-Grid-AutoColumn:"alpha" value:5 + │ ├── alpha-monitor LEAF id:alpha-monitor ag-Grid-AutoColumn:"alpha-monitor" value:10 │ ├── alpha-build LEAF id:alpha-build ag-Grid-AutoColumn:"alpha-build" value:12 - │ └── alpha-design LEAF id:alpha-design ag-Grid-AutoColumn:"alpha-design" value:35 - └─┬ beta GROUP id:beta ag-Grid-AutoColumn:"beta" value:15 - · ├── beta-build LEAF id:beta-build ag-Grid-AutoColumn:"beta-build" value:1 - · └── beta-design LEAF id:beta-design ag-Grid-AutoColumn:"beta-design" value:5 + │ ├── alpha-deploy LEAF id:alpha-deploy ag-Grid-AutoColumn:"alpha-deploy" value:28 + │ ├── alpha-design LEAF id:alpha-design ag-Grid-AutoColumn:"alpha-design" value:35 + │ ├── alpha-maintain LEAF id:alpha-maintain ag-Grid-AutoColumn:"alpha-maintain" value:42 + │ └── alpha-test LEAF id:alpha-test ag-Grid-AutoColumn:"alpha-test" value:50 + ├─┬ beta GROUP id:beta ag-Grid-AutoColumn:"beta" value:15 + │ ├── beta-build LEAF id:beta-build ag-Grid-AutoColumn:"beta-build" value:1 + │ ├── beta-design LEAF id:beta-design ag-Grid-AutoColumn:"beta-design" value:5 + │ ├── beta-monitor LEAF id:beta-monitor ag-Grid-AutoColumn:"beta-monitor" value:8 + │ ├── beta-test LEAF id:beta-test ag-Grid-AutoColumn:"beta-test" value:12 + │ ├── beta-maintain LEAF id:beta-maintain ag-Grid-AutoColumn:"beta-maintain" value:16 + │ └── beta-deploy LEAF id:beta-deploy ag-Grid-AutoColumn:"beta-deploy" value:22 + ├─┬ gamma GROUP id:gamma ag-Grid-AutoColumn:"gamma" value:25 + │ ├── gamma-design LEAF id:gamma-design ag-Grid-AutoColumn:"gamma-design" value:8 + │ ├── gamma-monitor LEAF id:gamma-monitor ag-Grid-AutoColumn:"gamma-monitor" value:15 + │ ├── gamma-test LEAF id:gamma-test ag-Grid-AutoColumn:"gamma-test" value:22 + │ ├── gamma-maintain LEAF id:gamma-maintain ag-Grid-AutoColumn:"gamma-maintain" value:25 + │ ├── gamma-build LEAF id:gamma-build ag-Grid-AutoColumn:"gamma-build" value:30 + │ └── gamma-deploy LEAF id:gamma-deploy ag-Grid-AutoColumn:"gamma-deploy" value:35 + ├─┬ delta GROUP id:delta ag-Grid-AutoColumn:"delta" value:32 + │ ├── delta-test LEAF id:delta-test ag-Grid-AutoColumn:"delta-test" value:15 + │ ├── delta-design LEAF id:delta-design ag-Grid-AutoColumn:"delta-design" value:28 + │ └── delta-build LEAF id:delta-build ag-Grid-AutoColumn:"delta-build" value:45 + └─┬ epsilon GROUP id:epsilon ag-Grid-AutoColumn:"epsilon" value:38 + · ├── epsilon-build LEAF id:epsilon-build ag-Grid-AutoColumn:"epsilon-build" value:25 + · ├── epsilon-design LEAF id:epsilon-design ag-Grid-AutoColumn:"epsilon-design" value:42 + · └── epsilon-test LEAF id:epsilon-test ag-Grid-AutoColumn:"epsilon-test" value:55 `); }); }); diff --git a/testing/behavioural/src/tree-data/parentid/stages/parentid-tree-filter-sort.test.ts b/testing/behavioural/src/tree-data/parentid/stages/parentid-tree-filter-sort.test.ts index e27765c5369..206cfc14301 100644 --- a/testing/behavioural/src/tree-data/parentid/stages/parentid-tree-filter-sort.test.ts +++ b/testing/behavioural/src/tree-data/parentid/stages/parentid-tree-filter-sort.test.ts @@ -567,9 +567,32 @@ describe('ag-grid parentId tree data parentId filter sort', () => { { id: 'north', label: 'North', value: 30 }, { id: 'north-west', parentId: 'north', label: 'North West', value: 25 }, { id: 'north-east', parentId: 'north', label: 'North East', value: 35 }, + { id: 'north-central', parentId: 'north', label: 'North Central', value: 18 }, + { id: 'north-upper', parentId: 'north', label: 'North Upper', value: 42 }, + { id: 'north-lower', parentId: 'north', label: 'North Lower', value: 22 }, + { id: 'north-mid', parentId: 'north', label: 'North Mid', value: 28 }, { id: 'south', label: 'South', value: 10 }, { id: 'south-east', parentId: 'south', label: 'South East', value: 5 }, { id: 'south-west', parentId: 'south', label: 'South West', value: 15 }, + { id: 'south-central', parentId: 'south', label: 'South Central', value: 8 }, + { id: 'south-upper', parentId: 'south', label: 'South Upper', value: 20 }, + { id: 'south-lower', parentId: 'south', label: 'South Lower', value: 12 }, + { id: 'south-mid', parentId: 'south', label: 'South Mid', value: 18 }, + { id: 'east', label: 'East', value: 22 }, + { id: 'east-north', parentId: 'east', label: 'East North', value: 12 }, + { id: 'east-south', parentId: 'east', label: 'East South', value: 28 }, + { id: 'east-central', parentId: 'east', label: 'East Central', value: 20 }, + { id: 'east-upper', parentId: 'east', label: 'East Upper', value: 32 }, + { id: 'east-lower', parentId: 'east', label: 'East Lower', value: 16 }, + { id: 'east-mid', parentId: 'east', label: 'East Mid', value: 24 }, + { id: 'west', label: 'West', value: 28 }, + { id: 'west-north', parentId: 'west', label: 'West North', value: 32 }, + { id: 'west-south', parentId: 'west', label: 'West South', value: 24 }, + { id: 'west-central', parentId: 'west', label: 'West Central', value: 38 }, + { id: 'central', label: 'Central', value: 16 }, + { id: 'central-north', parentId: 'central', label: 'Central North', value: 14 }, + { id: 'central-south', parentId: 'central', label: 'Central South', value: 26 }, + { id: 'central-mid', parentId: 'central', label: 'Central Mid', value: 19 }, ]; const rowById = Object.fromEntries(rowData.map((row) => [row.id, row])) as Record< @@ -595,25 +618,77 @@ describe('ag-grid parentId tree data parentId filter sort', () => { ROOT id:ROOT_NODE_ID ├─┬ south GROUP id:south ag-Grid-AutoColumn:"south" value:10 │ ├── south-east LEAF id:south-east ag-Grid-AutoColumn:"south-east" value:5 - │ └── south-west LEAF id:south-west ag-Grid-AutoColumn:"south-west" value:15 + │ ├── south-central LEAF id:south-central ag-Grid-AutoColumn:"south-central" value:8 + │ ├── south-lower LEAF id:south-lower ag-Grid-AutoColumn:"south-lower" value:12 + │ ├── south-west LEAF id:south-west ag-Grid-AutoColumn:"south-west" value:15 + │ ├── south-mid LEAF id:south-mid ag-Grid-AutoColumn:"south-mid" value:18 + │ └── south-upper LEAF id:south-upper ag-Grid-AutoColumn:"south-upper" value:20 + ├─┬ central GROUP id:central ag-Grid-AutoColumn:"central" value:16 + │ ├── central-north LEAF id:central-north ag-Grid-AutoColumn:"central-north" value:14 + │ ├── central-mid LEAF id:central-mid ag-Grid-AutoColumn:"central-mid" value:19 + │ └── central-south LEAF id:central-south ag-Grid-AutoColumn:"central-south" value:26 + ├─┬ east GROUP id:east ag-Grid-AutoColumn:"east" value:22 + │ ├── east-north LEAF id:east-north ag-Grid-AutoColumn:"east-north" value:12 + │ ├── east-lower LEAF id:east-lower ag-Grid-AutoColumn:"east-lower" value:16 + │ ├── east-central LEAF id:east-central ag-Grid-AutoColumn:"east-central" value:20 + │ ├── east-mid LEAF id:east-mid ag-Grid-AutoColumn:"east-mid" value:24 + │ ├── east-south LEAF id:east-south ag-Grid-AutoColumn:"east-south" value:28 + │ └── east-upper LEAF id:east-upper ag-Grid-AutoColumn:"east-upper" value:32 + ├─┬ west GROUP id:west ag-Grid-AutoColumn:"west" value:28 + │ ├── west-south LEAF id:west-south ag-Grid-AutoColumn:"west-south" value:24 + │ ├── west-north LEAF id:west-north ag-Grid-AutoColumn:"west-north" value:32 + │ └── west-central LEAF id:west-central ag-Grid-AutoColumn:"west-central" value:38 └─┬ north GROUP id:north ag-Grid-AutoColumn:"north" value:30 + · ├── north-central LEAF id:north-central ag-Grid-AutoColumn:"north-central" value:18 + · ├── north-lower LEAF id:north-lower ag-Grid-AutoColumn:"north-lower" value:22 · ├── north-west LEAF id:north-west ag-Grid-AutoColumn:"north-west" value:25 - · └── north-east LEAF id:north-east ag-Grid-AutoColumn:"north-east" value:35 + · ├── north-mid LEAF id:north-mid ag-Grid-AutoColumn:"north-mid" value:28 + · ├── north-east LEAF id:north-east ag-Grid-AutoColumn:"north-east" value:35 + · └── north-upper LEAF id:north-upper ag-Grid-AutoColumn:"north-upper" value:42 `); const updateRow = (id: string, value: number) => ({ ...rowById[id], value }); applyTransactionChecked(api, { - update: [updateRow('south', 40), updateRow('south-east', 45), updateRow('north-east', 1)], + update: [ + updateRow('south', 40), + updateRow('south-east', 45), + updateRow('north-east', 1), + updateRow('east', 6), + updateRow('east-south', 2), + ], }); await new GridRows(api, 'parentId tree data updated order').check(` ROOT id:ROOT_NODE_ID + ├─┬ east GROUP id:east ag-Grid-AutoColumn:"east" value:6 + │ ├── east-south LEAF id:east-south ag-Grid-AutoColumn:"east-south" value:2 + │ ├── east-north LEAF id:east-north ag-Grid-AutoColumn:"east-north" value:12 + │ ├── east-lower LEAF id:east-lower ag-Grid-AutoColumn:"east-lower" value:16 + │ ├── east-central LEAF id:east-central ag-Grid-AutoColumn:"east-central" value:20 + │ ├── east-mid LEAF id:east-mid ag-Grid-AutoColumn:"east-mid" value:24 + │ └── east-upper LEAF id:east-upper ag-Grid-AutoColumn:"east-upper" value:32 + ├─┬ central GROUP id:central ag-Grid-AutoColumn:"central" value:16 + │ ├── central-north LEAF id:central-north ag-Grid-AutoColumn:"central-north" value:14 + │ ├── central-mid LEAF id:central-mid ag-Grid-AutoColumn:"central-mid" value:19 + │ └── central-south LEAF id:central-south ag-Grid-AutoColumn:"central-south" value:26 + ├─┬ west GROUP id:west ag-Grid-AutoColumn:"west" value:28 + │ ├── west-south LEAF id:west-south ag-Grid-AutoColumn:"west-south" value:24 + │ ├── west-north LEAF id:west-north ag-Grid-AutoColumn:"west-north" value:32 + │ └── west-central LEAF id:west-central ag-Grid-AutoColumn:"west-central" value:38 ├─┬ north GROUP id:north ag-Grid-AutoColumn:"north" value:30 │ ├── north-east LEAF id:north-east ag-Grid-AutoColumn:"north-east" value:1 - │ └── north-west LEAF id:north-west ag-Grid-AutoColumn:"north-west" value:25 + │ ├── north-central LEAF id:north-central ag-Grid-AutoColumn:"north-central" value:18 + │ ├── north-lower LEAF id:north-lower ag-Grid-AutoColumn:"north-lower" value:22 + │ ├── north-west LEAF id:north-west ag-Grid-AutoColumn:"north-west" value:25 + │ ├── north-mid LEAF id:north-mid ag-Grid-AutoColumn:"north-mid" value:28 + │ └── north-upper LEAF id:north-upper ag-Grid-AutoColumn:"north-upper" value:42 └─┬ south GROUP id:south ag-Grid-AutoColumn:"south" value:40 + · ├── south-central LEAF id:south-central ag-Grid-AutoColumn:"south-central" value:8 + · ├── south-lower LEAF id:south-lower ag-Grid-AutoColumn:"south-lower" value:12 · ├── south-west LEAF id:south-west ag-Grid-AutoColumn:"south-west" value:15 + · ├── south-mid LEAF id:south-mid ag-Grid-AutoColumn:"south-mid" value:18 + · ├── south-upper LEAF id:south-upper ag-Grid-AutoColumn:"south-upper" value:20 · └── south-east LEAF id:south-east ag-Grid-AutoColumn:"south-east" value:45 `); });