From 0ddd2fdba783cda69f855233654370a6c22a3e3b Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Tue, 1 Sep 2026 19:31:17 +0800 Subject: [PATCH 1/6] rename methods --- .../grids/data_grid/export/m_export.ts | 2 +- .../grids/data_grid/focus/m_focus.ts | 2 +- .../grouping/m_grouping_collapsed.ts | 12 +- .../data_grid/grouping/m_grouping_expanded.ts | 2 +- .../commands/__tests__/selection.test.ts | 12 +- .../data_controller/data_controller.ts | 14 ++- .../__tests__/custom_loader.test.ts | 8 +- .../data_source_adapter/custom_loader.ts | 93 ++++++++-------- .../m_data_source_adapter.ts | 34 +----- .../grids/grid_core/focus/m_focus.ts | 4 +- .../header_filter/m_header_filter.ts | 2 +- .../js/__internal/grids/grid_core/m_utils.ts | 2 +- .../grids/grid_core/selection/m_selection.ts | 2 +- .../virtual_scrolling_data_controller.ts | 2 +- .../virtual_scrolling/m_virtual_scrolling.ts | 10 +- .../commands/selection.integration.test.ts | 2 +- .../m_data_source_adapter.test.ts | 5 +- .../m_data_source_adapter.ts | 4 +- .../js/__internal/grids/tree_list/m_focus.ts | 4 +- .../dataController.tests.js | 104 +++++++++--------- .../dataSource.tests.js | 22 ++-- 21 files changed, 162 insertions(+), 180 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts index 4a8226c4538c..e94c768bf7e8 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts @@ -584,7 +584,7 @@ export class ExportController extends dataGridCore.ViewController { let summaryCells; when(data).done((data) => { - this._dataController.loadAll(data, skipFilter).done((sourceItems, totalAggregates) => { + this._dataController.loadAllItems(data, skipFilter).done((sourceItems, totalAggregates) => { that._updateGroupValuesWithSummaryByColumn(sourceItems); if (that._hasSummaryGroupFooters()) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts index 3aa4315f72de..1efd98a49f98 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts @@ -108,7 +108,7 @@ const data = (Base: DataControllerBase) => class FocusDataControllerExtender ext return deferred; } - dataSource.load({ + dataSource.customLoader.load({ filter: this._concatWithCombinedFilter(filter), group, }).done((data) => { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts index 72613af77893..8685816024f0 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts @@ -245,7 +245,9 @@ function loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, group loadOptions.take = expandedInfo.take; } - const loadResult = loadOptions.take === 0 ? [] : that._dataSource.loadFromStore(loadOptions); + const loadResult = loadOptions.take === 0 + ? [] + : that._dataSource.customLoader.loadFromStore(loadOptions); when(loadResult).done((data) => { const item = expandedInfo.items[expandedItemIndex]; @@ -296,7 +298,11 @@ function loadLastLevelGroupItems(that, options, expandedInfo, data) { loadOptions.take = expandedInfo.take; } - when(expandedInfo.take === 0 ? [] : that._dataSource.loadFromStore(loadOptions)).done((items) => { + const loadResult = expandedInfo.take === 0 + ? [] + : that._dataSource.customLoader.loadFromStore(loadOptions); + + when(loadResult).done((items) => { if (isPagingLocal) { items = that._dataSource.sortLastLevelGroupItems(items, groups, expandedInfo.paths); items = expandedInfo.skip ? items.slice(expandedInfo.skip) : items; @@ -321,7 +327,7 @@ const loadGroupTotalCount = function (dataSource, options) { skip: 0, take: 1, requireGroupCount: isGrouping, requireTotalCount: !isGrouping, }, options, { group: isGrouping ? options.group : null }); - dataSource.load(loadOptions).done((data, extra) => { + dataSource.customLoader.load(loadOptions).done((data, extra) => { const count = extra && (isGrouping ? extra.groupCount : extra.totalCount); if (!isFinite(count)) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts index 16d17d890684..36c47bf91d1a 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts @@ -16,7 +16,7 @@ const loadTotalCount = function (dataSource, options) { const d = new Deferred(); const loadOptions = extend({ skip: 0, take: 1, requireTotalCount: true }, options); - dataSource.load(loadOptions).done((data, extra) => { + dataSource.customLoader.load(loadOptions).done((data, extra) => { d.resolve(extra && extra.totalCount); }).fail(d.reject.bind(d)); return d; diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/__tests__/selection.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/__tests__/selection.test.ts index 0a70aeee5fce..d314c65181ea 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/__tests__/selection.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/__tests__/selection.test.ts @@ -31,7 +31,7 @@ const createCallbacks = (): { failure: jest.fn((message?: string) => ({ status: 'failure' as const, message: message ?? '' })), }); -// The local "allPages" path calls loadAll(), which defers behind the grid's +// The local "allPages" path calls loadAllItems(), which defers behind the grid's // loading timer. Under fake timers that timer must be advanced while the // command is in flight, otherwise the awaited result never settles. const executeWithTimers = async ( @@ -669,9 +669,9 @@ describe('selectionByIndexesCommand', () => { }); describe('scope "allPages" — local paging', () => { - it('resolves keys via loadAll (no store.load) and selects with preserve=true', async () => { + it('resolves keys via loadAllItems (no store.load) and selects with preserve=true', async () => { const instance = await createGrid(); - const loadAllSpy = jest.spyOn(instance.getController('data'), 'loadAll'); + const loadAllItemsSpy = jest.spyOn(instance.getController('data'), 'loadAllItems'); const loadSpy = jest.spyOn(instance.getDataSource().store(), 'load'); const selectSpy = jest.spyOn(instance, 'selectRows').mockReturnValue(Promise.resolve([]) as never); const callbacks = createCallbacks(); @@ -682,13 +682,13 @@ describe('selectionByIndexesCommand', () => { }), ); - expect(loadAllSpy).toHaveBeenCalled(); + expect(loadAllItemsSpy).toHaveBeenCalled(); expect(loadSpy).not.toHaveBeenCalled(); expect(selectSpy).toHaveBeenCalledWith([1, 3], true); expect(result.status).toBe('success'); }); - it('resolves keys via loadAll and calls deselectRows when deselecting', async () => { + it('resolves keys via loadAllItems and calls deselectRows when deselecting', async () => { const instance = await createGrid(); const deselectSpy = jest.spyOn(instance, 'deselectRows').mockReturnValue(Promise.resolve([]) as never); const callbacks = createCallbacks(); @@ -738,7 +738,7 @@ describe('selectionByIndexesCommand', () => { expect(result.status).toBe('success'); }); - it('indexes within the filtered dataset (combined filter applied via loadAll)', async () => { + it('indexes within the filtered dataset (combined filter applied via loadAllItems)', async () => { const instance = await createGrid({ columns: [ { dataField: 'id', dataType: 'number' }, diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 8f100ff68974..9a5c0e52c0bc 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -1480,7 +1480,10 @@ export class DataController extends modules.Controller { return this._dataSource?.store() as Store | undefined; } - public loadAll(data?: RawItemData[], skipFilter = false): DeferredObj { + public loadAllItems( + data?: RawItemData[], + skipFilter = false, + ): DeferredObj { const d = Deferred(); const dataSource = this._dataSource; @@ -1502,7 +1505,7 @@ export class DataController extends modules.Controller { }; if (data) { - dataSource.customProcessLoadedData(data, { + dataSource.customLoader.processLoadedData(data, { filter: skipFilter ? null : this.getCombinedFilter(), group: dataSource.group(), sort: dataSource.sort(), @@ -1511,8 +1514,7 @@ export class DataController extends modules.Controller { .done(resolveWithProcessedItems) .fail(d.reject as (...args: unknown[]) => void); } else if (!dataSource.isLoading()) { - dataSource.customLoadAll() - // @ts-expect-error badly typed CustomLoadResult + dataSource.customLoader.loadAll() .done(resolveWithProcessedItems) .fail(d.reject as (...args: unknown[]) => void); } else { @@ -1523,7 +1525,7 @@ export class DataController extends modules.Controller { } public async getAllDataRowKeys(): Promise { - const items = await Promise.resolve(this.loadAll(undefined)); + const items = await Promise.resolve(this.loadAllItems(undefined)); return items .filter((item) => item.rowType === 'data') @@ -1634,7 +1636,7 @@ export class DataController extends modules.Controller { } public isCustomLoading(): boolean { - return this._isCustomLoading || !!this._dataSource?.isCustomLoading(); + return this._isCustomLoading || !!this._dataSource?.customLoader.isLoading(); } public beginCustomLoading(messageText?: string): void { diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts index 8c20b316b48b..3bc880fbe99b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts @@ -4,9 +4,9 @@ import { import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; import CustomStore from '@js/data/custom_store'; -import PublicDataSource from '@js/data/data_source'; import { logger } from '@ts/core/utils/m_console'; -import type { DataSource, StoreLoadOptions } from '@ts/data/data_source/types'; +import { DataSource } from '@ts/data/data_source/data_source'; +import type { StoreLoadOptions } from '@ts/data/data_source/types'; import { CustomLoader } from '../custom_loader'; import type { LoadOperation } from '../types'; @@ -43,10 +43,10 @@ const setup = ({ ._customLoadOptions = (): string[] => customLoadOptions; } - const dataSource = new PublicDataSource({ + const dataSource = new DataSource({ store, ...dataSourceOptions, - }) as unknown as DataSource; + }); const getLoadingTimeout = jest.fn(() => loadingTimeout); const customizeStoreLoadOptionsSpy = jest.fn(customizeStoreLoadOptions); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index d634d1e4816b..392652d052c1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -1,13 +1,12 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred, when } from '@js/core/utils/deferred'; -import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; -import type { DataSource, StoreLoadOptions } from '@ts/data/data_source/types'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type { StoreLoadOptions } from '@ts/data/data_source/types'; import { executeTask } from './m_data_source_adapter_utils'; import type { LoadOperation, RawItemData } from './types'; -export type CustomLoadOptions = StoreLoadOptions & { isLoadingAll?: boolean }; +export type CustomStoreLoadOptions = StoreLoadOptions & { isLoadingAll?: boolean }; export type CustomLoadResult = RawItemData[] | LoadOperation['extra']; @@ -40,57 +39,46 @@ export class CustomLoader { return this._isLoadingAll; } - public load(options: CustomLoadOptions): DeferredObj { - const { dataSource } = this; + public load(options: CustomStoreLoadOptions): DeferredObj { const d = Deferred(); - const store = dataSource.store(); - const dataSourceLoadOptions = dataSource.loadOptions(); - const operation: LoadOperation = { - storeLoadOptions: extend({}, options, { langParams: dataSourceLoadOptions?.langParams }), - isCustomLoading: true, - }; - - // @ts-expect-error badly typed Store type - const customLoadOptions: string[] = store._customLoadOptions() ?? []; - - each(customLoadOptions, (_: number, optionName: string) => { - if (!(optionName in operation.storeLoadOptions)) { - operation.storeLoadOptions[optionName] = dataSourceLoadOptions[optionName]; - } - }); + this._isLoading = true; this._isLoadingAll = options.isLoadingAll ?? false; + this.dataSource._scheduleLoadCallbacks(d); - this.scheduleLoadingCallbacks(d); - dataSource._scheduleLoadCallbacks(d); + const operation = this.createLoadOperation(options); this.customizeStoreLoadOptions(operation); executeTask(() => { - if (!dataSource.store()) { + const store = this.dataSource.store(); + + if (!store) { d.reject('canceled'); return; } - when(operation.data ?? this.loadFromStore(operation.storeLoadOptions)) - .done((data: unknown, loadedExtra: unknown) => { - operation.data = data as RawItemData[]; - operation.extra = (loadedExtra ?? {}) as LoadOperation['extra']; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + when(operation.data ?? this.loadFromStore(operation.storeLoadOptions)) + .done((data: RawItemData[], extra?: LoadOperation['extra']) => { + operation.data = data; + operation.extra = extra; this.customizeLoadResult(operation); - // `customizeLoadResult` may have replaced `extra`, so re-read it. - const extra = (operation.extra ?? {}) as { totalCount?: unknown }; - operation.extra = extra as LoadOperation['extra']; + // customizeLoadResult may have replaced extra, so re-read it + let totalCount: number | DeferredObj | undefined = operation.extra?.totalCount; - if (options.requireTotalCount && extra.totalCount === undefined) { - extra.totalCount = store.totalCount(operation.storeLoadOptions); + if (options.requireTotalCount && totalCount === undefined) { + totalCount = store.totalCount(operation.storeLoadOptions); } - when(operation.data, extra.totalCount) - .done((resolvedData: unknown, totalCount: unknown) => { - extra.totalCount = totalCount; - d.resolve(resolvedData, extra); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + when(operation.data, totalCount) + .done((resolvedData: RawItemData[], resolvedTotalCount: number) => { + operation.extra ??= {}; + operation.extra.totalCount = resolvedTotalCount; + d.resolve(resolvedData, operation.extra); }) .fail((e: unknown) => { d.reject(e); }); }) @@ -99,9 +87,10 @@ export class CustomLoader { return d .fail((...args: unknown[]) => { - dataSource._eventsStrategy.fireEvent('loadError', args); + this.dataSource._eventsStrategy.fireEvent('loadError', args); }) .always(() => { + this._isLoading = false; this._isLoadingAll = false; }) .promise() as unknown as DeferredObj; @@ -143,9 +132,9 @@ export class CustomLoader { public loadFromStore(loadOptions: StoreLoadOptions): DeferredObj { const d = Deferred(); - (this.dataSource + this.dataSource .store() - .load(loadOptions) as unknown as DeferredObj) + .load(loadOptions) .done((data: unknown, extra: unknown) => { // A store may resolve with a single `{ data, totalCount }` object // instead of the `(data, extra)` pair the pipeline expects. @@ -162,11 +151,25 @@ export class CustomLoader { return d; } - private scheduleLoadingCallbacks(deferred: DeferredObj): void { - this._isLoading = true; + private createLoadOperation(options: CustomStoreLoadOptions): LoadOperation { + const dataSourceLoadOptions = this.dataSource.loadOptions(); + const storeLoadOptions: CustomStoreLoadOptions = { ...options }; - deferred.always(() => { - this._isLoading = false; - }); + if (dataSourceLoadOptions?.langParams) { + storeLoadOptions.langParams = dataSourceLoadOptions.langParams; + } + + const customLoadOptions: string[] = this.dataSource.store()._customLoadOptions() ?? []; + + for (const optionName of customLoadOptions) { + if (!(optionName in storeLoadOptions)) { + storeLoadOptions[optionName] = dataSourceLoadOptions[optionName]; + } + } + + return { + storeLoadOptions, + isCustomLoading: true, + }; } } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index e25b4bc85757..3eb3a6495f8d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -15,7 +15,6 @@ import type { BeforePushEvent } from '@ts/data/types'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; -import type { CustomLoadOptions, CustomLoadResult } from './custom_loader'; import { CustomLoader } from './custom_loader'; import { calculateOperationTypes, @@ -25,7 +24,7 @@ import { setPageDataToCache, } from './m_data_source_adapter_utils'; import type { - ChangedEvent, LoadOperation, OperationTypes, RawItemData, RemoteOperationsOptions, + ChangedEvent, LoadOperation, OperationTypes, RemoteOperationsOptions, } from './types'; import { normalizeRemoteOperations } from './utils/remoteOperations'; @@ -96,7 +95,7 @@ export default class DataSourceAdapter extends modules.Controller { private changingHandlerProxy!: (e: ChangingEvent) => void; - private customLoader!: CustomLoader; + public customLoader!: CustomLoader; public init(dataSource?: DataSource): void { if (!dataSource) { @@ -855,40 +854,13 @@ export default class DataSourceAdapter extends modules.Controller { return this._hasLastPage || this._dataSource.totalCount() >= 0; } - protected loadFromStore(loadOptions: StoreLoadOptions): DeferredObj { - return this.customLoader.loadFromStore(loadOptions); - } - - public isCustomLoading(): boolean { - return this.customLoader.isLoading(); - } - - protected isCustomLoadingAll(): boolean { - return this.customLoader.isLoadingAll(); - } - /** * @extended: virtual_scrolling */ - public load(options?: CustomLoadOptions): DeferredObj { - if (options) { - return this.customLoader.load(options); - } - + public load(): DeferredObj { return this._dataSource.load() as unknown as DeferredObj; } - public customLoadAll(): DeferredObj { - return this.customLoader.loadAll(); - } - - public customProcessLoadedData( - data: RawItemData[], - loadOptions: StoreLoadOptions, - ): DeferredObj { - return this.customLoader.processLoadedData(data, loadOptions); - } - /** * @extended: virtual_scrolling */ diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index aa3d5fe659dc..350c8cdb78e5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -727,7 +727,7 @@ const focusDataControllerExtender = ( let filter = this._generateFilterByKey(key); - dataSource.load({ + dataSource.customLoader.load({ filter: this._concatWithCombinedFilter(filter), skip: 0, take: 1, @@ -738,7 +738,7 @@ const focusDataControllerExtender = ( } if ((data as unknown[]).length > 0) { filter = this._generateOperationFilterByKey(key, (data as unknown[])[0], useGroup); - dataSource.load({ + dataSource.customLoader.load({ filter: this._concatWithCombinedFilter(filter, groupFilter), skip: 0, take: 1, diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 26fbdf41717b..abce6b99c7a1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -272,7 +272,7 @@ export class HeaderFilterController extends Modules.ViewController { // TODO remove in 16.1 options.dataField = column.dataField || column.name; - dataSource.load(options).done((data) => { + dataSource.customLoader.load(options).done((data) => { const convertUTCDates = remoteGrouping && isUTCFormat(column.serializationFormat) && cutoffLevel > 3; if (convertUTCDates) { data = convertDataFromUTCToLocal(data, column); diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index 69ee0710a5c8..2b5274006631 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -718,7 +718,7 @@ export default { } else { previousSkip = loadOptions.skip; previousTake = loadOptions.take; - dataSource.load({ + dataSource.customLoader.load({ filter, group, take: hasGroupPaging ? loadOptions.take : undefined, diff --git a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts index 1a46a2822b88..3af9873058e0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts @@ -240,7 +240,7 @@ export class SelectionController extends modules.Controller { }, load(options) { // @ts-expect-error - return dataController.dataSource()?.load(options) || new Deferred().resolve([]); + return dataController.dataSource()?.customLoader.load(options) || new Deferred().resolve([]); }, // eslint-disable-next-line plainItems(cached?) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts index a2b752e7aeec..c709bb79dd0f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts @@ -781,7 +781,7 @@ export const virtualScrollingDataControllerExtender = ( this._updateLoadViewportParams(); const loadingItemsStarted = this._loadItems(checkLoading, !viewportIsNotFilled); - const isCustomLoading = this._dataSource?.isCustomLoading(); + const isCustomLoading = this._dataSource?.customLoader.isLoading(); const isLoading = checkLoading && !isCustomLoading && this._isLoading; const needToUpdateItems = !(loadingItemsStarted || isLoading diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts index 094de621b840..6f69082ec3fe 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts @@ -126,7 +126,7 @@ export const dataSourceAdapterExtender = (Base: ModuleType) = return that._dataSource.pageIndex(index); }, isLoading() { - return that._dataSource.isLoading() && !that.isCustomLoading(); + return that._dataSource.isLoading() && !that.customLoader.isLoading(); }, pageCount() { return that.pageCount(); @@ -168,7 +168,7 @@ export const dataSourceAdapterExtender = (Base: ModuleType) = return; } - if (!isVirtualMode(this) || this.isCustomLoadingAll()) { + if (!isVirtualMode(this) || this.customLoader.isLoadingAll()) { this._isLoading = isLoading; super.loadingChangedHandler(isLoading); } @@ -232,9 +232,9 @@ export const dataSourceAdapterExtender = (Base: ModuleType) = return this._virtualScrollController.itemsCount(); } - public load(loadOptions) { - if (this.option(LEGACY_SCROLLING_MODE) === false || loadOptions) { - return super.load(loadOptions); + public load() { + if (this.option(LEGACY_SCROLLING_MODE) === false) { + return super.load(); } return this._virtualScrollController.load(); } diff --git a/packages/devextreme/js/__internal/grids/tree_list/ai_assistant/commands/selection.integration.test.ts b/packages/devextreme/js/__internal/grids/tree_list/ai_assistant/commands/selection.integration.test.ts index 69d6f14ba955..00eda1f2dbef 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/ai_assistant/commands/selection.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/ai_assistant/commands/selection.integration.test.ts @@ -56,7 +56,7 @@ describe('selectionByIndexesCommand on TreeList — "allPages" scope', () => { jest.useRealTimers(); }); - it('resolves keys by walking the node tree (no loadAll pipeline crash)', async () => { + it('resolves keys by walking the node tree (no loadAllItems pipeline crash)', async () => { const instance = await createTreeList(); const selectSpy = jest.spyOn(instance, 'selectRows').mockReturnValue(Promise.resolve([]) as never); const callbacks = createCallbacks(); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts index 7618c21b41b0..a0fb5521d552 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts @@ -91,7 +91,6 @@ describe('TreeList DataSourceAdapter - T1311885 Race Condition', () => { jest.restoreAllMocks(); loadCalls = []; (dataSourceAdapter as any)._loadDataSource = undefined; - (dataSourceAdapter as any).loadFromStore = undefined; mockStore = undefined as any; dataSourceAdapter = undefined as any; }); @@ -120,7 +119,7 @@ describe('TreeList DataSourceAdapter - T1311885 Race Condition', () => { }; // eslint-disable-next-line @typescript-eslint/no-unused-vars - (dataSourceAdapter as any).loadFromStore = jest.fn((loadOptions) => { + dataSourceAdapter.customLoader.loadFromStore = jest.fn((loadOptions) => { // @ts-expect-error const deferred = new Deferred(); @@ -136,7 +135,7 @@ describe('TreeList DataSourceAdapter - T1311885 Race Condition', () => { options, ); - expect((dataSourceAdapter as any).loadFromStore).toHaveBeenCalledTimes(1); + expect(dataSourceAdapter.customLoader.loadFromStore).toHaveBeenCalledTimes(1); expect(firstLoadDeferred).toBeDefined(); (dataSourceAdapter as any)._cachedStoreData = undefined; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index 794e20ebb1bd..78f76ff8d00d 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -464,7 +464,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { const loadBranchItemsDeferred = options.fullData ? new ArrayStore(options.fullData).load(loadOptions) - : this.loadFromStore(loadOptions); + : this.customLoader.loadFromStore(loadOptions); loadBranchItemsDeferred .done((loadedData: any) => { @@ -894,7 +894,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { const loadOptions = that._dataSource._createStoreLoadOptions(); loadOptions.parentIds = keys; - that.load(loadOptions) + that.customLoader.load(loadOptions) .done(() => { if (!childrenOnly) { const childKeys = getChildKeys(that, keys); diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts index 976b33b0d7d0..ac6011b4d483 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts @@ -61,7 +61,7 @@ const data = ( if (node) { d.resolve(node.parent ? node.parent.key : undefined); } else { - dataSource.load({ + dataSource.customLoader.load({ filter: [dataSource.getKeyExpr(), '=', key], }).done((items) => { const parentData = (items as unknown[])[0]; @@ -106,7 +106,7 @@ const data = ( const d = new Deferred(); this.expandAscendants(key).done(() => { - dataSource.load({ + dataSource.customLoader.load({ parentIds: [], }).done((nodes) => { if ((this._dataSource as unknown) !== (dataSource as unknown)) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js index fcd44b902de2..2949d88b957f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js @@ -5607,11 +5607,11 @@ QUnit.module('Virtual scrolling preload', { assert.deepEqual(this.getSelectedRowKeys().length, 100, 'all items are selected'); }); - QUnit.test('New mode. loadAll after scrolling should return all items (T1045649)', function(assert) { + QUnit.test('New mode. loadAllItems after scrolling should return all items (T1045649)', function(assert) { // act this.dataController.setViewportPosition(10); let loadedItems; - this.dataController.loadAll().done(items => { + this.dataController.loadAllItems().done(items => { loadedItems = items; }); @@ -14500,14 +14500,14 @@ QUnit.module('Exporting', { } }, () => { - QUnit.test('loadAll when no dataSource', function(assert) { + QUnit.test('loadAllItems when no dataSource', function(assert) { let allItems; this.setupDataGridModules({}); this.clock.tick(10); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -14519,7 +14519,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems, [], 'all items is empty array'); }); - QUnit.test('loadAll when error on loading', function(assert) { + QUnit.test('loadAllItems when error on loading', function(assert) { const dataErrorOccurredArgs = []; let error; const changedArgs = []; @@ -14542,7 +14542,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().fail(function(e) { + this.dataController.loadAllItems().fail(function(e) { error = e; }); @@ -14558,7 +14558,7 @@ QUnit.module('Exporting', { }); - QUnit.test('loadAll skip paging', function(assert) { + QUnit.test('loadAllItems skip paging', function(assert) { let allItems; let changedCallCount = 0; @@ -14576,7 +14576,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -14598,7 +14598,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems[3].values, [2, 3, 6], 'item 3 values'); }); - QUnit.test('loadAll grouping when remoteOperations disabled', function(assert) { + QUnit.test('loadAllItems grouping when remoteOperations disabled', function(assert) { let allItems; let changedCallCount = 0; @@ -14624,7 +14624,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -14655,7 +14655,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems[3].values, [2], 'item 3 values'); }); - QUnit.test('loadAll grouping when remote filtering/sorting/paging enabled', function(assert) { + QUnit.test('loadAllItems grouping when remote filtering/sorting/paging enabled', function(assert) { let allItems; let changedCallCount = 0; @@ -14680,7 +14680,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -14712,7 +14712,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems[5].values, [2], 'item 5 values'); }); - QUnit.test('loadAll when remote summary enabled', function(assert) { + QUnit.test('loadAllItems when remote summary enabled', function(assert) { let allItems; let allSummary; let changedCallCount = 0; @@ -14752,7 +14752,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().done(function(items, summary) { + this.dataController.loadAllItems().done(function(items, summary) { allItems = items; allSummary = summary; }); @@ -14768,14 +14768,14 @@ QUnit.module('Exporting', { assert.deepEqual(loadArgs[0].take, 3, 'initial load take'); assert.deepEqual(loadArgs[0].totalSummary, [{ selector: 'field2', summaryType: 'sum' }], 'initial load totalSummary'); assert.deepEqual(loadArgs[0].groupSummary, undefined, 'initial load groupSummary is not defined'); - assert.deepEqual(loadArgs[1].skip, undefined, 'loadAll skip'); - assert.deepEqual(loadArgs[1].take, undefined, 'loadAll load take'); - assert.deepEqual(loadArgs[1].totalSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAll totalSummary'); - assert.deepEqual(loadArgs[1].groupSummary, undefined, 'loadAll groupSummary is not defined'); + assert.deepEqual(loadArgs[1].skip, undefined, 'loadAllItems skip'); + assert.deepEqual(loadArgs[1].take, undefined, 'loadAllItems load take'); + assert.deepEqual(loadArgs[1].totalSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAllItems totalSummary'); + assert.deepEqual(loadArgs[1].groupSummary, undefined, 'loadAllItems groupSummary is not defined'); }); // T324247 - QUnit.test('loadAll when remote grouping and summary enabled', function(assert) { + QUnit.test('loadAllItems when remote grouping and summary enabled', function(assert) { let allItems; let allSummary; let changedCallCount = 0; @@ -14831,7 +14831,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll().done(function(items, summary) { + this.dataController.loadAllItems().done(function(items, summary) { allItems = items; allSummary = summary; }); @@ -14852,20 +14852,20 @@ QUnit.module('Exporting', { assert.deepEqual(loadArgs[0].group, [{ selector: 'field1', isExpanded: false, desc: false }], 'initial load group'); assert.deepEqual(loadArgs[0].totalSummary, [{ selector: 'field1', summaryType: 'sum' }], 'initial load totalSummary'); assert.deepEqual(loadArgs[0].groupSummary, [{ selector: 'field2', summaryType: 'sum' }], 'initial load groupSummary'); - assert.deepEqual(loadArgs[1].skip, undefined, 'loadAll skip'); - assert.deepEqual(loadArgs[1].take, undefined, 'loadAll load take'); - assert.deepEqual(loadArgs[1].group, [{ selector: 'field1', isExpanded: false, desc: false }], 'loadAll load group'); - assert.deepEqual(loadArgs[1].totalSummary, [{ selector: 'field1', summaryType: 'sum' }], 'loadAll totalSummary'); - assert.deepEqual(loadArgs[1].groupSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAll groupSummary'); - assert.deepEqual(loadArgs[2].skip, undefined, 'loadAll skip'); - assert.deepEqual(loadArgs[2].take, undefined, 'loadAll load take'); - assert.deepEqual(loadArgs[2].group, null, 'loadAll load group'); - assert.deepEqual(loadArgs[2].totalSummary, [{ selector: 'field1', summaryType: 'sum' }], 'loadAll totalSummary'); - assert.deepEqual(loadArgs[2].groupSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAll groupSummary'); + assert.deepEqual(loadArgs[1].skip, undefined, 'loadAllItems skip'); + assert.deepEqual(loadArgs[1].take, undefined, 'loadAllItems load take'); + assert.deepEqual(loadArgs[1].group, [{ selector: 'field1', isExpanded: false, desc: false }], 'loadAllItems load group'); + assert.deepEqual(loadArgs[1].totalSummary, [{ selector: 'field1', summaryType: 'sum' }], 'loadAllItems totalSummary'); + assert.deepEqual(loadArgs[1].groupSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAllItems groupSummary'); + assert.deepEqual(loadArgs[2].skip, undefined, 'loadAllItems skip'); + assert.deepEqual(loadArgs[2].take, undefined, 'loadAllItems load take'); + assert.deepEqual(loadArgs[2].group, null, 'loadAllItems load group'); + assert.deepEqual(loadArgs[2].totalSummary, [{ selector: 'field1', summaryType: 'sum' }], 'loadAllItems totalSummary'); + assert.deepEqual(loadArgs[2].groupSummary, [{ selector: 'field2', summaryType: 'sum' }], 'loadAllItems groupSummary'); }); // T437259, T433659 - QUnit.test('loadAll by selected items when remote grouping and summary enabled', function(assert) { + QUnit.test('loadAllItems by selected items when remote grouping and summary enabled', function(assert) { let allItems; let allSummary; let changedCallCount = 0; @@ -14923,7 +14923,7 @@ QUnit.module('Exporting', { // act loadArgs = []; - this.dataController.loadAll([{ field1: 1, field2: 2 }]).done(function(items, summary) { + this.dataController.loadAllItems([{ field1: 1, field2: 2 }]).done(function(items, summary) { allItems = items; allSummary = summary; }); @@ -14941,7 +14941,7 @@ QUnit.module('Exporting', { assert.deepEqual(this.dataController.getTotalSummaryValue('field1'), 4); }); - QUnit.test('Skip detail row when loadAll is applied', function(assert) { + QUnit.test('Skip detail row when loadAllItems is applied', function(assert) { let allItems; this.setupDataGridModules({ @@ -14960,7 +14960,7 @@ QUnit.module('Exporting', { // act this.dataController.expandRow(this.array[0]); - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -15000,7 +15000,7 @@ QUnit.module('Exporting', { } }, 13); - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -15026,7 +15026,7 @@ QUnit.module('Exporting', { this.clock.tick(10); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -15050,7 +15050,7 @@ QUnit.module('Exporting', { this.clock.tick(10); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -15062,7 +15062,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems[0].data, { key: 1, items: array }, 'group row data'); }); - QUnit.test('loadAll during data loading', function(assert) { + QUnit.test('loadAllItems during data loading', function(assert) { let isLoadAllFailed; this.setupDataGridModules({ @@ -15074,20 +15074,20 @@ QUnit.module('Exporting', { // act - this.dataController.loadAll().fail(function() { + this.dataController.loadAllItems().fail(function() { isLoadAllFailed = true; }); this.clock.tick(10); // assert - assert.ok(isLoadAllFailed, 'loadAll failed'); + assert.ok(isLoadAllFailed, 'loadAllItems failed'); assert.equal(this.dataController.items().length, 3, 'items count'); assert.ok(!this.dataController.isLoading(), 'no loading'); }); // T713135 - QUnit.test('loadAll during custom loading', function(assert) { + QUnit.test('loadAllItems during custom loading', function(assert) { let allItems; this.setupDataGridModules({ @@ -15101,7 +15101,7 @@ QUnit.module('Exporting', { // act this.dataController.beginCustomLoading('test'); - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); this.dataController.endCustomLoading(); @@ -15113,7 +15113,7 @@ QUnit.module('Exporting', { assert.ok(!this.dataController.isLoading(), 'no loading'); }); - QUnit.test('data loading during loadAll', function(assert) { + QUnit.test('data loading during loadAllItems', function(assert) { let isLoadAllFailed; let allItems; @@ -15129,7 +15129,7 @@ QUnit.module('Exporting', { // act - this.dataController.loadAll().fail(function() { + this.dataController.loadAllItems().fail(function() { isLoadAllFailed = true; }).done(function(data) { allItems = data; @@ -15140,7 +15140,7 @@ QUnit.module('Exporting', { this.clock.tick(10); // assert - assert.ok(!isLoadAllFailed, 'loadAll is not failed'); + assert.ok(!isLoadAllFailed, 'loadAllItems is not failed'); assert.equal(allItems.length, 5, 'items count'); assert.equal(this.dataController.pageIndex(), 1, 'pageIndex'); assert.equal(this.dataController.items().length, 2, 'items count'); @@ -15164,7 +15164,7 @@ QUnit.module('Exporting', { this.clock.tick(10); // act - this.dataController.loadAll().done(function(items) { + this.dataController.loadAllItems().done(function(items) { allItems = items; }); @@ -15194,7 +15194,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll([this.array[1], this.array[3]]).done(function(items) { + this.dataController.loadAllItems([this.array[1], this.array[3]]).done(function(items) { allItems = items; }); @@ -15217,7 +15217,7 @@ QUnit.module('Exporting', { assert.deepEqual(allItems[1].values, [2, 3, 6], 'item 1 values'); }); - QUnit.test('loadAll with data parameter and grouping', function(assert) { + QUnit.test('loadAllItems with data parameter and grouping', function(assert) { let allItems; let changedCallCount = 0; @@ -15243,7 +15243,7 @@ QUnit.module('Exporting', { }); // act - this.dataController.loadAll([this.array[0], this.array[3], this.array[4]]).done(function(items) { + this.dataController.loadAllItems([this.array[0], this.array[3], this.array[4]]).done(function(items) { allItems = items; }); @@ -15284,7 +15284,7 @@ QUnit.module('Exporting', { }); // T595243 - QUnit.test('loadAll with data parameter and filtering', function(assert) { + QUnit.test('loadAllItems with data parameter and filtering', function(assert) { let allItems; this.setupDataGridModules({ @@ -15295,7 +15295,7 @@ QUnit.module('Exporting', { this.clock.tick(10); // act - this.dataController.loadAll([this.array[1], this.array[2], this.array[3]]).done(function(items) { + this.dataController.loadAllItems([this.array[1], this.array[2], this.array[3]]).done(function(items) { allItems = items; }); @@ -15330,7 +15330,7 @@ QUnit.module('Exporting', { } }, 13); - this.dataController.loadAll([this.array[0], this.array[1]]).done(function(items) { + this.dataController.loadAllItems([this.array[0], this.array[1]]).done(function(items) { allItems = items; }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js index d70624fa2cf4..3ef021571eb4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js @@ -6978,7 +6978,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ filter: ['this', '>', 8] }).done(function(data) { customLoadData = data; @@ -7019,7 +7019,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ filter: ['this', '>', '1'], group: 'this', skip: 2, @@ -7061,7 +7061,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ filter: ['this', '>', '5'], group: 'this', skip: 2, @@ -7103,7 +7103,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ group: function(data) { return data % 2; } }).done(function(data) { customLoadData = data; @@ -7141,7 +7141,7 @@ QUnit.module('Custom Load', { let customLoadData = false; this.loadingCount = 0; // act - dataSource.load({ + dataSource.customLoader.load({ filter: ['this', '>', '5'], group: 'this', skip: 2, @@ -7184,7 +7184,7 @@ QUnit.module('Custom Load', { let customLoadData = false; this.loadingCount = 0; // act - dataSource.load({ + dataSource.customLoader.load({ group: 'this', skip: 0, take: 3 @@ -7226,7 +7226,7 @@ QUnit.module('Custom Load', { this.loadingCount = 0; // act - dataSource.load({ + dataSource.customLoader.load({ group: 'this', skip: 2, take: 2 @@ -7268,7 +7268,7 @@ QUnit.module('Custom Load', { let customLoadData = false; this.loadingCount = 0; // act - dataSource.load({ + dataSource.customLoader.load({ group: [{ selector: 'this', groupInterval: 2 }], skip: 0, take: 3 @@ -7296,7 +7296,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ filter: ['this', '>=', '5'], group: 'this', take: 2 @@ -7387,7 +7387,7 @@ QUnit.module('Custom Load', { this.loadingCount = 0; // act - dataSource.load({ + dataSource.customLoader.load({ isLoadingAll: true, filter: ['this', '>', '5'], group: 'this' @@ -7439,7 +7439,7 @@ QUnit.module('Custom Load', { let customLoadData = false; // act - dataSource.load({ + dataSource.customLoader.load({ group: 'this', skip: 2, take: 2 From b935e23c7ee9f4ad77ee92c11189352eb9e0f287 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 2 Sep 2026 15:55:57 +0800 Subject: [PATCH 2/6] fix qunit tests --- .../__tests__/custom_loader.test.ts | 11 +++++++++ .../data_source_adapter/custom_loader.ts | 11 +++++---- .../testing/helpers/gridBaseMocks.js | 24 +++++++++++++++---- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts index 3bc880fbe99b..cc32d7bcb046 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts @@ -92,6 +92,17 @@ describe('load', () => { expect(operation.isCustomLoading).toBe(true); }); + it('drops load options the caller left undefined', () => { + // Selection builds `filter`/`select` unconditionally, so the store must not + // see them when there is nothing to send. + const { customLoader, store } = setup(); + + customLoader.load({ filter: ['id', '=', 1], select: undefined }); + + expect(store.load).toHaveBeenCalledTimes(1); + expect(store.load.mock.calls[0][0]).not.toHaveProperty('select'); + }); + it('fills the store custom load options from the dataSource, without overwriting', () => { const { customLoader, customizeStoreLoadOptions } = setup({ customLoadOptions: ['fromDataSource', 'fromOptions'], diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index 392652d052c1..59c56a38041f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -1,5 +1,6 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred, when } from '@js/core/utils/deferred'; +import { extend } from '@js/core/utils/extend'; import type { DataSource } from '@ts/data/data_source/data_source'; import type { StoreLoadOptions } from '@ts/data/data_source/types'; @@ -153,11 +154,11 @@ export class CustomLoader { private createLoadOperation(options: CustomStoreLoadOptions): LoadOperation { const dataSourceLoadOptions = this.dataSource.loadOptions(); - const storeLoadOptions: CustomStoreLoadOptions = { ...options }; - - if (dataSourceLoadOptions?.langParams) { - storeLoadOptions.langParams = dataSourceLoadOptions.langParams; - } + const storeLoadOptions: CustomStoreLoadOptions = extend( + {}, + options, + { langParams: dataSourceLoadOptions.langParams }, + ); const customLoadOptions: string[] = this.dataSource.store()._customLoadOptions() ?? []; diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index c4bd861e04b8..7288984234fe 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -38,6 +38,10 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo changedArgs: [], dataSource: function() { + const store = function() { + return new ArrayStore(options.items); + }; + return { beginLoading: function() { }, @@ -49,11 +53,23 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo loadOptions: function() { return {}; }, - store: function() { - return new ArrayStore(options.items); + store: store, + load: function(loadOptions) { + return store().load(loadOptions); }, - load: function(options) { - return this.store().load(options); + customLoader: { + load: function(loadOptions) { + return store().load(loadOptions); + }, + loadFromStore: function(loadOptions) { + return store().load(loadOptions); + }, + isLoading: function() { + return false; + }, + isLoadingAll: function() { + return false; + } } }; }, From 47662f89b998bd0a33f8be60d66bf906510293a2 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 2 Sep 2026 17:56:42 +0800 Subject: [PATCH 3/6] improve types --- .../js/__internal/data/data_source/types.ts | 25 ----------- .../grids/data_grid/focus/m_focus.ts | 3 +- .../__tests__/m_grouping_expanded.helpers.ts | 3 +- .../__tests__/m_grouping_expanded.mock.ts | 6 +++ .../__tests__/m_grouping_expanded.test.ts | 10 ++--- .../grouping/m_grouping_collapsed.ts | 43 +++++++++++------- .../data_grid/grouping/m_grouping_core.ts | 5 ++- .../data_grid/grouping/m_grouping_expanded.ts | 11 +++-- .../grids/data_grid/summary/m_summary.ts | 2 +- .../data_controller/data_controller.ts | 21 +++++---- .../grids/grid_core/data_controller/types.ts | 7 --- .../data_source_adapter/custom_loader.ts | 45 ++++++++++++++----- .../m_data_source_adapter.ts | 7 +-- .../grid_core/data_source_adapter/types.ts | 1 + .../utils/__tests__/remoteOperations.test.ts | 6 +-- .../utils/__tests__/store.test.ts | 6 +-- .../utils/remoteOperations.ts | 2 +- .../data_source_adapter/utils/store.ts | 8 ++-- .../grids/grid_core/editing/m_editing.ts | 2 +- .../grids/grid_core/focus/m_focus.ts | 11 ++--- .../header_filter/m_header_filter.ts | 2 +- .../js/__internal/grids/grid_core/m_utils.ts | 9 ++-- .../data_controller/m_data_controller.ts | 6 +-- .../m_data_source_adapter.ts | 15 ++++--- .../js/__internal/grids/tree_list/m_focus.ts | 6 +-- 25 files changed, 139 insertions(+), 123 deletions(-) diff --git a/packages/devextreme/js/__internal/data/data_source/types.ts b/packages/devextreme/js/__internal/data/data_source/types.ts index 87d513b27774..637d47a8499a 100644 --- a/packages/devextreme/js/__internal/data/data_source/types.ts +++ b/packages/devextreme/js/__internal/data/data_source/types.ts @@ -1,8 +1,6 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import type { DataSourceOptionsStub } from '@js/data/data_source'; -import type PublicDataSource from '@js/data/data_source'; import type { StoreChange } from '@js/data/store'; -import type { EventsStrategy } from '@ts/core/m_events_strategy'; import type Store from '@ts/data/abstract_store'; export interface StoreLoadOptions extends Pick< @@ -65,26 +63,3 @@ export type DataSourceEventName = | 'changed' | 'customizeStoreLoadOptions' | 'customizeLoadResult' | 'changing'; - -export interface DataSource extends PublicDataSource { - _eventsStrategy: EventsStrategy; - - _reshapeOnPush: boolean; - - _scheduleLoadCallbacks: (deferred: DeferredObj) => void; - - _createStoreLoadOptions: () => StoreLoadOptions; - - beginLoading: () => void; - - endLoading: () => void; - - loadOptions: () => StoreLoadOptions; - - // eslint-disable-next-line @typescript-eslint/method-signature-style - on(eventName: DataSourceEventName, eventHandler: Function): this; - // eslint-disable-next-line @typescript-eslint/method-signature-style - on(events: { [key in DataSourceEventName]?: Function }): this; - // eslint-disable-next-line @typescript-eslint/method-signature-style - off(eventName: DataSourceEventName, eventHandler?: Function): this; -} diff --git a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts index 1efd98a49f98..7e1df56bef68 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts @@ -111,8 +111,7 @@ const data = (Base: DataControllerBase) => class FocusDataControllerExtender ext dataSource.customLoader.load({ filter: this._concatWithCombinedFilter(filter), group, - }).done((data) => { - // @ts-expect-error badly typed DataSourceAdapter + }).done(({ data }) => { const hasData = isDefined(data) && data.length > 0; if (this._dataSource !== dataSource || !hasData) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.helpers.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.helpers.ts index 53aef1747266..64d02905bd05 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.helpers.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.helpers.ts @@ -1,5 +1,6 @@ import { GroupingHelper, updateGroupOffsets } from '../m_grouping_expanded'; import type { DataItem, GroupInfoData, GroupItemData } from '../types'; +import { createDataSourceAdapterStub } from './m_grouping_expanded.mock'; export interface GroupConfig { key: string; @@ -35,7 +36,7 @@ export class GroupingTestHelper { private readonly savedChildren = new Map(); constructor(groups: GroupConfig[]) { - this.grouping = new GroupingHelper({ option: (): undefined => undefined }); + this.grouping = new GroupingHelper(createDataSourceAdapterStub()); this.groupsByKey = new Map(); this.leafCounts = new Map(); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.mock.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.mock.ts index 58df5a76d866..140ecafbc805 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.mock.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.mock.ts @@ -1,5 +1,11 @@ +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; + import { GroupingHelper } from '../m_grouping_expanded'; +export const createDataSourceAdapterStub = (): DataSourceAdapter => ({ + option: (): undefined => undefined, +} as unknown as DataSourceAdapter); + /** Subclass that exposes the protected handleDataLoading method for testing. */ export class GroupingHelperMock extends GroupingHelper { public testHandleDataLoading(options: unknown): void { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.test.ts index 607021b6216a..e3013ae673d7 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/m_grouping_expanded.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from '@jest/globals'; import type { GroupConfig } from './m_grouping_expanded.helpers'; import { GroupingTestHelper } from './m_grouping_expanded.helpers'; -import { GroupingHelperMock } from './m_grouping_expanded.mock'; +import { createDataSourceAdapterStub, GroupingHelperMock } from './m_grouping_expanded.mock'; // --------------------------------------------------------------------------- // Test data @@ -247,7 +247,7 @@ describe('isPending logic', () => { describe('handleDataLoading: expandCorrection', () => { it('should widen the load window for collapsed groups after the expanding one', () => { - const grouping = new GroupingHelperMock({ option: (): undefined => undefined }); + const grouping = new GroupingHelperMock(createDataSourceAdapterStub()); // Group A was just expanded — isPending + isExpanded, count=100 grouping.addGroupInfo({ @@ -275,7 +275,7 @@ describe('isPending logic', () => { }); it('should NOT widen the load window for previously expanded groups (not pending)', () => { - const grouping = new GroupingHelperMock({ option: (): undefined => undefined }); + const grouping = new GroupingHelperMock(createDataSourceAdapterStub()); // Group A is expanded but NOT pending (normal steady state) grouping.addGroupInfo({ @@ -301,7 +301,7 @@ describe('isPending logic', () => { describe('handleDataLoading: isPending cleanup', () => { it('should delete isPending from an expanded group after processing', () => { - const grouping = new GroupingHelperMock({ option: (): undefined => undefined }); + const grouping = new GroupingHelperMock(createDataSourceAdapterStub()); grouping.addGroupInfo({ offset: 0, count: 100, isExpanded: true, isPending: true, path: ['A'], @@ -317,7 +317,7 @@ describe('isPending logic', () => { }); it('should delete isPending from a collapsed group after processing', () => { - const grouping = new GroupingHelperMock({ option: (): undefined => undefined }); + const grouping = new GroupingHelperMock(createDataSourceAdapterStub()); grouping.addGroupInfo({ offset: 0, count: 10, isExpanded: false, isPending: true, path: ['A'], diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts index 8685816024f0..3aa3bb64a78c 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts @@ -3,6 +3,8 @@ import { Deferred, when } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import errors from '@js/ui/widget/ui.errors'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import dataGridCore from '../m_core'; import { createGroupFilter } from '../m_utils'; @@ -196,7 +198,7 @@ function makeDataDeferred(options) { } } -function loadGroupItems(that, options, loadedGroupCount, expandedInfo, groupLevel, data) { +function loadGroupItems(that: GroupingHelper, options, loadedGroupCount, expandedInfo, groupLevel, data) { if (!options.isCustomLoading) { expandedInfo = {}; @@ -218,7 +220,7 @@ function loadGroupItems(that, options, loadedGroupCount, expandedInfo, groupLeve } } -function loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, groupLevel, data) { +function loadExpandedGroups(that: GroupingHelper, options, expandedInfo, loadedGroupCount, groupLevel, data) { const groups = options.group || []; const currentGroup = groups[groupLevel + 1]; const deferreds: any[] = []; @@ -245,19 +247,20 @@ function loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, group loadOptions.take = expandedInfo.take; } - const loadResult = loadOptions.take === 0 - ? [] + const loadDeferred = loadOptions.take === 0 + ? { data: [] as RawItemData[] } : that._dataSource.customLoader.loadFromStore(loadOptions); - when(loadResult).done((data) => { - const item = expandedInfo.items[expandedItemIndex]; + when(loadDeferred) + .done((loadResult) => { + const item = expandedInfo.items[expandedItemIndex]; - applyContinuationToGroupItem(options, expandedInfo, groupLevel, expandedItemIndex); + applyContinuationToGroupItem(options, expandedInfo, groupLevel, expandedItemIndex); - item.items = data; - }); + item.items = loadResult.data; + }); - deferreds.push(loadResult); + deferreds.push(loadDeferred); }); when.apply(null, deferreds).done(() => { @@ -267,7 +270,7 @@ function loadExpandedGroups(that, options, expandedInfo, loadedGroupCount, group }); } -function loadLastLevelGroupItems(that, options, expandedInfo, data) { +function loadLastLevelGroupItems(that: GroupingHelper, options, expandedInfo, data) { const expandedFilters: any[] = []; const groups = options.group || []; @@ -291,6 +294,7 @@ function loadLastLevelGroupItems(that, options, expandedInfo, data) { filter, }); + // @ts-expect-error badly typed GroupingHelper.dataSource const isPagingLocal = that._dataSource.isLastLevelGroupItemsPagingLocal(); if (!isPagingLocal) { @@ -298,12 +302,15 @@ function loadLastLevelGroupItems(that, options, expandedInfo, data) { loadOptions.take = expandedInfo.take; } - const loadResult = expandedInfo.take === 0 - ? [] + const loadDeferred = expandedInfo.take === 0 + ? { data: [] as RawItemData[] } : that._dataSource.customLoader.loadFromStore(loadOptions); - when(loadResult).done((items) => { + when(loadDeferred).done((loadResult) => { + let items = loadResult.data; + if (isPagingLocal) { + // @ts-expect-error badly typed GroupingHelper.dataSource items = that._dataSource.sortLastLevelGroupItems(items, groups, expandedInfo.paths); items = expandedInfo.skip ? items.slice(expandedInfo.skip) : items; items = expandedInfo.take ? items.slice(0, expandedInfo.take) : items; @@ -319,7 +326,7 @@ function loadLastLevelGroupItems(that, options, expandedInfo, data) { }).fail(options.data.reject); } -const loadGroupTotalCount = function (dataSource, options) { +const loadGroupTotalCount = function (dataSource: DataSourceAdapter, options) { // @ts-expect-error const d = new Deferred(); const isGrouping = !!(options.group && options.group.length); @@ -327,10 +334,10 @@ const loadGroupTotalCount = function (dataSource, options) { skip: 0, take: 1, requireGroupCount: isGrouping, requireTotalCount: !isGrouping, }, options, { group: isGrouping ? options.group : null }); - dataSource.customLoader.load(loadOptions).done((data, extra) => { + dataSource.customLoader.load(loadOptions).done(({ extra }) => { const count = extra && (isGrouping ? extra.groupCount : extra.totalCount); - if (!isFinite(count)) { + if (count && !isFinite(count)) { d.reject(dataErrors.Error(isGrouping ? 'E4022' : 'E4021')); return; } @@ -344,6 +351,7 @@ export class GroupingHelper extends GroupingHelperCore { let totalItemsCount = 0; const totalCount = options.extra && options.extra.totalCount || 0; const groupCount = options.extra && options.extra.groupCount || 0; + // @ts-expect-error badly typedDataSourceAdapter.pageSize const pageSize = this._dataSource.pageSize(); const isVirtualPaging = this._isVirtualPaging(); @@ -375,6 +383,7 @@ export class GroupingHelper extends GroupingHelperCore { private _updatePagingOptions(options, callback?) { const that = this; const isVirtualPaging = that._isVirtualPaging(); + // @ts-expect-error badly typedDataSourceAdapter.pageSize const pageSize = that._dataSource.pageSize(); const skips: any[] = []; const takes: any[] = []; diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_core.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_core.ts index a3ef1c17869a..b6bbbf35efcc 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_core.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_core.ts @@ -1,6 +1,7 @@ import { normalizeSortingInfo } from '@js/common/data/utils'; import $ from '@js/core/renderer'; import { when } from '@js/core/utils/deferred'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import gridCore from '../m_core'; @@ -102,7 +103,7 @@ const calculateItemsCount = function (that, items, groupsCount) { }; export class GroupingHelper { - protected readonly _dataSource: any; + public readonly _dataSource: DataSourceAdapter; private _groupsInfo: any; @@ -110,7 +111,7 @@ export class GroupingHelper { protected _group: any; - constructor(dataSourceAdapter) { + constructor(dataSourceAdapter: DataSourceAdapter) { this._dataSource = dataSourceAdapter; this.reset(); } diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts index 36c47bf91d1a..0612dc756ef9 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts @@ -5,19 +5,20 @@ import { toComparable } from '@js/core/utils/data'; import { Deferred, when } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import dataGridCore from '../m_core'; import { createGroupFilter } from '../m_utils'; import { createOffsetFilter, GroupingHelper as GroupingHelperCore } from './m_grouping_core'; import type { DataItem, GroupInfoData, GroupItemData } from './types'; -const loadTotalCount = function (dataSource, options) { +const loadTotalCount = function (dataSource: DataSourceAdapter, options) { // @ts-expect-error const d = new Deferred(); const loadOptions = extend({ skip: 0, take: 1, requireTotalCount: true }, options); - dataSource.customLoader.load(loadOptions).done((data, extra) => { - d.resolve(extra && extra.totalCount); + dataSource.customLoader.load(loadOptions).done(({ extra }) => { + d.resolve(extra!.totalCount); }).fail(d.reject.bind(d)); return d; }; @@ -377,10 +378,13 @@ export class GroupingHelper extends GroupingHelperCore { private changeRowExpand(path) { const that = this; const dataSource = that._dataSource; + // @ts-expect-error badly typedDataSourceAdapter.beginPageIndex const beginPageIndex = dataSource.beginPageIndex + // @ts-expect-error badly typedDataSourceAdapter.beginPageIndex ? dataSource.beginPageIndex() : dataSource.pageIndex(); const dataSourceItems = dataSource.items(); + // @ts-expect-error badly typedDataSourceAdapter.pageSize const offset = correctSkipLoadOption(that, beginPageIndex * dataSource.pageSize()); const groupInfo = that.findGroupInfo(path); let groupCountQuery; @@ -418,6 +422,7 @@ export class GroupingHelper extends GroupingHelperCore { } that.updateTotalItemsCount(); }).fail(function () { + // @ts-expect-error badly typedDataSourceAdapter._eventsStrategy dataSource._eventsStrategy.fireEvent('loadError', arguments); }); } diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts index 04cdd157df6f..2ee8355b9e2c 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts @@ -6,7 +6,7 @@ import $ from '@js/core/renderer'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { isDefined, isPlainObject } from '@js/core/utils/type'; -import type { DataSource } from '@ts/data/data_source/types'; +import type { DataSource } from '@ts/data/data_source/data_source'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { RemoteOperationsOptions } from '@ts/grids/grid_core/data_source_adapter/types'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 9a5c0e52c0bc..1186c1e43557 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -1,4 +1,3 @@ -import type { Store } from '@js/common/data'; import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; import type { Callback } from '@js/core/utils/callbacks'; @@ -12,7 +11,9 @@ import type { StoreChange } from '@js/data/store'; import errors from '@js/ui/widget/ui.errors'; import { findChanges } from '@ts/core/utils/m_array_compare'; import { fromPromise } from '@ts/core/utils/m_deferred'; -import type { ChangingEvent, DataSource } from '@ts/data/data_source/types'; +import type Store from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type { ChangingEvent } from '@ts/data/data_source/types'; import type { Column, ColumnsChanges } from '@ts/grids/grid_core/columns_controller/types'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { @@ -27,6 +28,7 @@ import type { import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import type { VirtualScrollController } from '@ts/grids/grid_core/virtual_scrolling/m_virtual_scrolling_core'; +import type { CustomLoadResult } from '../data_source_adapter/custom_loader'; import type { BinaryDataFilterExpression, CallbackFlags, @@ -1477,7 +1479,7 @@ export class DataController extends modules.Controller { } public store(): Store | undefined { - return this._dataSource?.store() as Store | undefined; + return this._dataSource?.store(); } public loadAllItems( @@ -1492,16 +1494,14 @@ export class DataController extends modules.Controller { return d; } - const resolveWithProcessedItems = ( - loadedData: RawItemData[], - extra: LoadOperation['extra'], - ): void => { + const resolveWithProcessedItems = (loadResult: CustomLoadResult): void => { const items = this._processItems( - this._beforeProcessItems(loadedData), + this._beforeProcessItems(loadResult.data), { changeType: 'loadingAll' }, ); + // @ts-expect-error DataGrid-only summary leaks into grid_core - d.resolve(items, extra?.summary); + d.resolve(items, loadResult.extra?.summary); }; if (data) { @@ -1510,7 +1510,6 @@ export class DataController extends modules.Controller { group: dataSource.group(), sort: dataSource.sort(), }) - // @ts-expect-error badly typed CustomLoadResult .done(resolveWithProcessedItems) .fail(d.reject as (...args: unknown[]) => void); } else if (!dataSource.isLoading()) { @@ -1525,7 +1524,7 @@ export class DataController extends modules.Controller { } public async getAllDataRowKeys(): Promise { - const items = await Promise.resolve(this.loadAllItems(undefined)); + const items = await Promise.resolve(this.loadAllItems()); return items .filter((item) => item.rowType === 'data') diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts index fb56b2e9dd66..aef85ec9a842 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts @@ -1,7 +1,6 @@ import type { SearchOperation } from '@js/common/data.types'; import type { ScalarFilterValue } from '@js/common/grids'; import type { DeferredObj } from '@js/core/utils/deferred'; -import type { DataSource } from '@ts/data/data_source/types'; import type { Column } from '../columns_controller/types'; import type { ChangedEvent, OperationTypes, RawItemData } from '../data_source_adapter/types'; @@ -142,12 +141,6 @@ export type ItemChange = | { type: 'insert'; index: number; data: ProcessedItem | { type: 'update'; index: number; data: ProcessedItem; oldItem: ProcessedItem } | { type: 'remove'; index: number; oldItem: ProcessedItem }; -/** data source */ - -export interface DataSourceAdapterLike { - _dataSource: DataSource; -} - /** callbacks */ export interface CallbackFlags { diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index 59c56a38041f..46eb81acce06 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -9,7 +9,10 @@ import type { LoadOperation, RawItemData } from './types'; export type CustomStoreLoadOptions = StoreLoadOptions & { isLoadingAll?: boolean }; -export type CustomLoadResult = RawItemData[] | LoadOperation['extra']; +export interface CustomLoadResult { + data: RawItemData[], + extra?: LoadOperation['extra'] +} /** * Loads data through the adapter's `customizeStoreLoadOptions` and @@ -41,7 +44,7 @@ export class CustomLoader { } public load(options: CustomStoreLoadOptions): DeferredObj { - const d = Deferred(); + const d = Deferred(); this._isLoading = true; this._isLoadingAll = options.isLoadingAll ?? false; @@ -55,6 +58,7 @@ export class CustomLoader { const store = this.dataSource.store(); if (!store) { + // @ts-expect-error badly type Deferred.reject d.reject('canceled'); return; } @@ -79,10 +83,15 @@ export class CustomLoader { .done((resolvedData: RawItemData[], resolvedTotalCount: number) => { operation.extra ??= {}; operation.extra.totalCount = resolvedTotalCount; - d.resolve(resolvedData, operation.extra); + d.resolve({ + data: resolvedData, + extra: operation.extra, + }); }) + // @ts-expect-error badly type Deferred.reject .fail((e: unknown) => { d.reject(e); }); }) + // @ts-expect-error badly type Deferred.reject .fail((e: unknown) => { d.reject(e); }); }, this.getLoadingTimeout()); @@ -112,7 +121,7 @@ export class CustomLoader { data: RawItemData[], loadOptions: StoreLoadOptions, ): DeferredObj { - const d = Deferred(); + const d = Deferred(); const operation: LoadOperation = { data, isCustomLoading: true, @@ -123,15 +132,22 @@ export class CustomLoader { this.customizeLoadResult(operation); // customizeLoadResult may have replaced operation.data with deferred - when(operation.data) - .done((loadedData: unknown) => { d.resolve(loadedData, operation.extra); }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + when(operation.data) + .done((loadedData: RawItemData[]) => { + d.resolve({ + data: loadedData, + extra: operation.extra, + }); + }) + // @ts-expect-error badly type Deferred.reject .fail((...args: unknown[]) => { d.reject(...args); }); - return d as unknown as DeferredObj; + return d; } - public loadFromStore(loadOptions: StoreLoadOptions): DeferredObj { - const d = Deferred(); + public loadFromStore(loadOptions: StoreLoadOptions): DeferredObj { + const d = Deferred(); this.dataSource .store() @@ -142,11 +158,18 @@ export class CustomLoader { const result = data as { data?: unknown } | undefined; if (result && !Array.isArray(result) && Array.isArray(result.data)) { - d.resolve(result.data, result); + d.resolve({ + data: result.data, + extra: result as LoadOperation['extra'], + }); } else { - d.resolve(data, extra); + d.resolve({ + data: data as RawItemData[], + extra: extra as LoadOperation['extra'], + }); } }) + // @ts-expect-error badly type Deferred.reject .fail((...args: unknown[]) => { d.reject(...args); }); return d; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index 3eb3a6495f8d..9ea98b847fd5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -10,7 +10,9 @@ import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import type { StoreChange } from '@js/data/store'; -import type { ChangingEvent, DataSource, StoreLoadOptions } from '@ts/data/data_source/types'; +import type Store from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type { ChangingEvent, StoreLoadOptions } from '@ts/data/data_source/types'; import type { BeforePushEvent } from '@ts/data/types'; import modules from '../m_modules'; @@ -202,8 +204,7 @@ export default class DataSourceAdapter extends modules.Controller { return (this._dataSource.requireTotalCount as (...a: unknown[]) => unknown)(value); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public store(): any { + public store(): Store { return this._dataSource.store(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts index bf7d03fe9217..39ffd601d1df 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts @@ -51,6 +51,7 @@ export interface LoadOperation extends Omit { extra?: { totalCount?: number; summary?: unknown[]; + groupCount?: number; }; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/remoteOperations.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/remoteOperations.test.ts index 2bd0148d6ace..4d6d9c788a0a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/remoteOperations.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/remoteOperations.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; -import type { Store } from '@js/data'; -import ArrayStore from '@js/data/array_store'; -import CustomStore from '@js/data/custom_store'; +import type Store from '@ts/data/abstract_store'; +import ArrayStore from '@ts/data/array_store'; +import CustomStore from '@ts/data/custom_store'; import { normalizeRemoteOperations } from '../remoteOperations'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/store.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/store.test.ts index 55392bbe1650..6aa0006f15b6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/store.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/__tests__/store.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; -import type { Store } from '@js/data'; -import ArrayStore from '@js/data/array_store'; -import CustomStore from '@js/data/custom_store'; +import type Store from '@ts/data/abstract_store'; +import ArrayStore from '@ts/data/array_store'; +import CustomStore from '@ts/data/custom_store'; import { isCustomStore, isLocalStore } from '../store'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/remoteOperations.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/remoteOperations.ts index af9acb2fe45e..58c2d7ba8f92 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/remoteOperations.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/remoteOperations.ts @@ -1,5 +1,5 @@ import { isObject } from '@js/core/utils/type'; -import type { Store } from '@js/data'; +import type Store from '@ts/data/abstract_store'; import type { RemoteOperations, RemoteOperationsOptions } from '../types'; import { isCustomStore, isLocalStore } from './store'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/store.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/store.ts index fb36eb51e28f..4e461ad921ab 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/store.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/utils/store.ts @@ -1,9 +1,9 @@ -import { ArrayStore as ArrayStoreClass, CustomStore } from '@js/common/data'; -import type { Store } from '@js/data'; -import type { ArrayStore } from '@ts/data/types'; +import type Store from '@ts/data/abstract_store'; +import ArrayStore from '@ts/data/array_store'; +import CustomStore from '@ts/data/custom_store'; export function isLocalStore(store: Store | undefined): store is ArrayStore { - return store instanceof ArrayStoreClass; + return store instanceof ArrayStore; } export function isCustomStore(store: Store): store is CustomStore { diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts index 06c4dfee73d7..2f29be8d4f02 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts @@ -6,7 +6,6 @@ import pointerEvents from '@js/common/core/events/pointer'; import { removeEvent } from '@js/common/core/events/remove'; import { addNamespace } from '@js/common/core/events/utils/index'; import messageLocalization from '@js/common/core/localization/message'; -import type { Store } from '@js/common/data'; import { createObjectWithChanges } from '@js/common/data/array_utils'; import type { DataChange as EditingDataChange, GridsEditMode } from '@js/common/grids'; import devices from '@js/core/devices'; @@ -28,6 +27,7 @@ import { import { confirm } from '@js/ui/dialog'; import { current, isFluent } from '@js/ui/themes'; import domUtils from '@ts/core/utils/m_dom'; +import type Store from '@ts/data/abstract_store'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import { generateRowValues } from '@ts/grids/grid_core/data_controller/utils/row_values'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index 350c8cdb78e5..23f317f2c4d9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -731,24 +731,25 @@ const focusDataControllerExtender = ( filter: this._concatWithCombinedFilter(filter), skip: 0, take: 1, - }).done((data) => { + }).done(({ data }) => { if (this._dataSource !== dataSource) { deferred.resolve(-1); return; } - if ((data as unknown[]).length > 0) { - filter = this._generateOperationFilterByKey(key, (data as unknown[])[0], useGroup); + if (data.length > 0) { + filter = this._generateOperationFilterByKey(key, data[0], useGroup); + dataSource.customLoader.load({ filter: this._concatWithCombinedFilter(filter, groupFilter), skip: 0, take: 1, requireTotalCount: true, - }).done((_, extra) => { + }).done(({ extra }) => { if (this._dataSource !== dataSource) { deferred.resolve(-1); return; } - deferred.resolve((extra as { totalCount: number }).totalCount); + deferred.resolve(extra!.totalCount); }); } else { deferred.resolve(-1); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index abce6b99c7a1..10d151fa7380 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -272,7 +272,7 @@ export class HeaderFilterController extends Modules.ViewController { // TODO remove in 16.1 options.dataField = column.dataField || column.name; - dataSource.customLoader.load(options).done((data) => { + dataSource.customLoader.load(options).done(({ data }) => { const convertUTCDates = remoteGrouping && isUTCFormat(column.serializationFormat) && cutoffLevel > 3; if (convertUTCDates) { data = convertDataFromUTCToLocal(data, column); diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index 2b5274006631..16006734d8e2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -28,6 +28,7 @@ import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import type { ColumnPoint } from '@ts/grids/grid_core/m_types'; import { AI_COLUMN_NAME } from './ai_column/const'; +import type DataSourceAdapter from './data_source_adapter/m_data_source_adapter'; import { isEqualSelectors, isSelectorEqualWithCallback } from './utils/index'; const BASE_LOAD_PANEL_Z_INDEX = 1000; @@ -677,7 +678,7 @@ export default { return normalizeDataSourceOptions(lookupDataSourceOptions); }, - getWrappedLookupDataSource(column, dataSource, filter) { + getWrappedLookupDataSource(column, dataSource: DataSourceAdapter, filter) { if (!dataSource) { return []; } @@ -723,9 +724,9 @@ export default { group, take: hasGroupPaging ? loadOptions.take : undefined, skip: hasGroupPaging ? loadOptions.skip : undefined, - }).done((items) => { - cachedUniqueRelevantItems = items; - d.resolve(hasGroupPaging ? items : sliceItems(items, loadOptions)); + }).done(({ data }) => { + cachedUniqueRelevantItems = data; + d.resolve(hasGroupPaging ? data : sliceItems(data, loadOptions)); }).fail(d.fail); } diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index 55c9e35e78fa..605a26b735bc 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -88,11 +88,7 @@ export class TreeListDataController extends DataController { } public key() { - const dataSource = this._dataSource; - - if (dataSource) { - return dataSource.getKeyExpr(); - } + return this._dataSource?.getKeyExpr(); } public publicMethods() { diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index 78f76ff8d00d..4ed94cde0ca9 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -9,8 +9,10 @@ import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { isDefined, isFunction } from '@js/core/utils/type'; import errors from '@js/ui/widget/ui.errors'; +import type Store from '@ts/data/abstract_store'; import type { ChangingEvent } from '@ts/data/data_source/types'; import type { BeforePushEvent } from '@ts/data/types'; +import type { CustomLoadResult } from '@ts/grids/grid_core/data_source_adapter/custom_loader'; import DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import { createDataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/provider'; import gridCoreUtils from '@ts/grids/grid_core/m_utils'; @@ -80,7 +82,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { private _createKeyGetter() { const keyExpr = this.getKeyExpr(); - return compileGetter(keyExpr); + return compileGetter(keyExpr as string); } private _createKeySetter() { @@ -90,7 +92,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return keyExpr; } - return compileSetter(keyExpr); + return compileSetter(keyExpr as string); } private createParentIdGetter() { @@ -467,7 +469,9 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { : this.customLoader.loadFromStore(loadOptions); loadBranchItemsDeferred - .done((loadedData: any) => { + .done((loadResult: CustomLoadResult | unknown[]) => { + let loadedData = Array.isArray(loadResult) ? loadResult : loadResult.data; + if (this._isOperationIdOutdated(options.operationId)) { d.reject(); return; @@ -478,6 +482,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { // @ts-expect-error loadedData = query(loadedData).filter(filter).toArray(); } + this._loadParentsOrChildren(concatLoadedData(loadedData), options, needChildren).done(d.resolve).fail(d.reject); } else { d.resolve(data); @@ -513,8 +518,8 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { protected _getKeyInfo() { return { key: () => 'key', - keyOf: (data) => data.key, - }; + keyOf: (data: any) => data.key, + } as Store; } private _processChanges(changes) { diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts index ac6011b4d483..bce1620c83d1 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts @@ -63,8 +63,8 @@ const data = ( } else { dataSource.customLoader.load({ filter: [dataSource.getKeyExpr(), '=', key], - }).done((items) => { - const parentData = (items as unknown[])[0]; + }).done((loadResult) => { + const parentData = loadResult.data[0]; if (parentData) { d.resolve(dataSource.parentKeyOf(parentData)); @@ -108,7 +108,7 @@ const data = ( this.expandAscendants(key).done(() => { dataSource.customLoader.load({ parentIds: [], - }).done((nodes) => { + }).done(({ data: nodes }) => { if ((this._dataSource as unknown) !== (dataSource as unknown)) { d.resolve(-1); return; From cf24b0820ad6b93631a39c2ef4acfcb26c204ad6 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 2 Sep 2026 18:32:07 +0800 Subject: [PATCH 4/6] fix tests --- .../__tests__/custom_loader.test.ts | 33 +++++++++---------- .../data_source_adapter/custom_loader.ts | 14 +++++--- .../m_data_source_adapter.test.ts | 2 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts index cc32d7bcb046..720503d2e1c3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/__tests__/custom_loader.test.ts @@ -3,8 +3,8 @@ import { } from '@jest/globals'; import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; -import CustomStore from '@js/data/custom_store'; import { logger } from '@ts/core/utils/m_console'; +import CustomStore from '@ts/data/custom_store'; import { DataSource } from '@ts/data/data_source/data_source'; import type { StoreLoadOptions } from '@ts/data/data_source/types'; @@ -33,7 +33,6 @@ const setup = ({ }: SetupOptions = {}) => { const load = jest.fn(storeLoad); const totalCount = jest.fn(storeTotalCount); - // @ts-expect-error const store = new CustomStore({ load, totalCount }); if (customLoadOptions.length) { @@ -138,7 +137,7 @@ describe('load', () => { customLoader.load({}).done(done); - expect(done).toHaveBeenCalledWith(data, { totalCount: 42 }); + expect(done).toHaveBeenCalledWith({ data, extra: { totalCount: 42 } }); }); it('skips the store when a customizeStoreLoadOptions handler supplies the data', () => { @@ -151,7 +150,7 @@ describe('load', () => { customLoader.load({}).done(done); expect(store.load).not.toHaveBeenCalled(); - expect(done).toHaveBeenCalledWith(data, expect.anything()); + expect(done).toHaveBeenCalledWith({ data, extra: expect.anything() }); }); it('resolves with the data a customizeLoadResult handler put on the operation', () => { @@ -164,7 +163,7 @@ describe('load', () => { customLoader.load({}).done(done); - expect(done).toHaveBeenCalledWith(transformed, expect.anything()); + expect(done).toHaveBeenCalledWith({ data: transformed, extra: expect.anything() }); }); it('rejects as canceled when the dataSource is disposed before the load runs', () => { @@ -191,7 +190,7 @@ describe('load', () => { customLoader.load({ requireTotalCount: true }).done(done); expect(store.totalCount).toHaveBeenCalledTimes(1); - expect(done).toHaveBeenCalledWith([{ id: 1 }], { totalCount: 17 }); + expect(done).toHaveBeenCalledWith({ data: [{ id: 1 }], extra: { totalCount: 17 } }); }); it('keeps the total count the store already reported', () => { @@ -203,7 +202,7 @@ describe('load', () => { customLoader.load({ requireTotalCount: true }).done(done); expect(store.totalCount).not.toHaveBeenCalled(); - expect(done).toHaveBeenCalledWith([{ id: 1 }], { totalCount: 3 }); + expect(done).toHaveBeenCalledWith({ data: [{ id: 1 }], extra: { totalCount: 3 } }); }); it('resolves a deferred total count before resolving the load', () => { @@ -220,7 +219,7 @@ describe('load', () => { totalCount.resolve(9); expect(d.state()).toBe('resolved'); - expect(done).toHaveBeenCalledWith([{ id: 1 }], { totalCount: 9 }); + expect(done).toHaveBeenCalledWith({ data: [{ id: 1 }], extra: { totalCount: 9 } }); }); it('reports a store failure through the dataSource loadError event', () => { @@ -368,7 +367,7 @@ describe('processLoadedData', () => { expect(operation.isCustomLoading).toBe(true); expect(operation.storeLoadOptions).toEqual({ isLoadingAll: true }); expect(operation.loadOptions).toEqual({ sort: 'id' }); - expect(done).toHaveBeenCalledWith(data, undefined); + expect(done).toHaveBeenCalledWith({ data, extra: undefined }); }); it('resolves with the data and extra the result stage produced', () => { @@ -384,7 +383,7 @@ describe('processLoadedData', () => { customLoader.processLoadedData([{ id: 1 }], {}).done(done); - expect(done).toHaveBeenCalledWith(transformed, { summary }); + expect(done).toHaveBeenCalledWith({ data: transformed, extra: { summary } }); }); it('waits for data the result stage left deferred', () => { @@ -400,7 +399,7 @@ describe('processLoadedData', () => { expect(done).not.toHaveBeenCalled(); deferredData.resolve([{ id: 2 }]); - expect(done).toHaveBeenCalledWith([{ id: 2 }], undefined); + expect(done).toHaveBeenCalledWith({ data: [{ id: 2 }], extra: undefined }); }); it('rejects when the result stage rejects the data', () => { @@ -437,17 +436,17 @@ describe('loadFromStore', () => { customLoader.loadFromStore({}).done(done); - expect(done).toHaveBeenCalledWith(data, extra); + expect(done).toHaveBeenCalledWith({ data, extra }); }); - it('unwraps a single `{ data, totalCount }` object into a data/extra pair', () => { + it('unwraps a single `{ data, totalCount }` object into the data/extra result', () => { const result = { data: [{ id: 1 }], totalCount: 42 }; const { customLoader } = setup({ storeLoad: () => Deferred().resolve(result) }); const done = jest.fn(); customLoader.loadFromStore({}).done(done); - expect(done).toHaveBeenCalledWith(result.data, result); + expect(done).toHaveBeenCalledWith({ data: result.data, extra: result }); }); it('leaves an array result alone even when it has a `data` property', () => { @@ -457,7 +456,7 @@ describe('loadFromStore', () => { customLoader.loadFromStore({}).done(done); - expect(done).toHaveBeenCalledWith(data, undefined); + expect(done).toHaveBeenCalledWith({ data, extra: undefined }); }); it('leaves an object whose `data` is not an array alone', () => { @@ -467,7 +466,7 @@ describe('loadFromStore', () => { customLoader.loadFromStore({}).done(done); - expect(done).toHaveBeenCalledWith(result, undefined); + expect(done).toHaveBeenCalledWith({ data: result, extra: undefined }); }); it('resolves with no data when the store reports none', () => { @@ -476,7 +475,7 @@ describe('loadFromStore', () => { customLoader.loadFromStore({}).done(done); - expect(done).toHaveBeenCalledWith(undefined, undefined); + expect(done).toHaveBeenCalledWith({ data: undefined, extra: undefined }); }); it('rejects with the error the store failed with', () => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index 46eb81acce06..ad662e95b425 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -63,11 +63,15 @@ export class CustomLoader { return; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - when(operation.data ?? this.loadFromStore(operation.storeLoadOptions)) - .done((data: RawItemData[], extra?: LoadOperation['extra']) => { - operation.data = data; - operation.extra = extra; + const loadDeferred = operation.data + ?? this.loadFromStore(operation.storeLoadOptions); + + when(loadDeferred) + .done((result) => { + const loaded: CustomLoadResult = Array.isArray(result) ? { data: result } : result; + + operation.data = loaded.data; + operation.extra = loaded.extra; this.customizeLoadResult(operation); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts index a0fb5521d552..2209b98a012f 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.test.ts @@ -141,7 +141,7 @@ describe('TreeList DataSourceAdapter - T1311885 Race Condition', () => { (dataSourceAdapter as any)._cachedStoreData = undefined; (dataSourceAdapter as any)._lastOperationId = OPERATION_ID.SECOND; - firstLoadDeferred.resolve(parentData); + firstLoadDeferred.resolve({ data: parentData }); await Promise.resolve(); process.off('unhandledRejection', unhandledRejectionHandler); From a502ec9cf744a68fbefb2dd25f0c50a8546301f3 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 2 Sep 2026 18:36:58 +0800 Subject: [PATCH 5/6] fix qunit --- .../data_source_adapter/custom_loader.ts | 6 +++--- .../testing/helpers/gridBaseMocks.js | 20 +++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index ad662e95b425..8a16a21b8cdd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -63,8 +63,7 @@ export class CustomLoader { return; } - const loadDeferred = operation.data - ?? this.loadFromStore(operation.storeLoadOptions); + const loadDeferred = operation.data ?? this.loadFromStore(operation.storeLoadOptions); when(loadDeferred) .done((result) => { @@ -82,9 +81,10 @@ export class CustomLoader { totalCount = store.totalCount(operation.storeLoadOptions); } + // customizeLoadResult may have replaced data, so re-resolve it // eslint-disable-next-line @typescript-eslint/no-explicit-any when(operation.data, totalCount) - .done((resolvedData: RawItemData[], resolvedTotalCount: number) => { + .done((resolvedData: RawItemData[], resolvedTotalCount?: number) => { operation.extra ??= {}; operation.extra.totalCount = resolvedTotalCount; d.resolve({ diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 7288984234fe..05441eea0baa 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -42,6 +42,18 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo return new ArrayStore(options.items); }; + const loadCustomResult = (loadOptions) => { + const d = $.Deferred(); + + store().load(loadOptions) + .done((data, extra) => { + d.resolve({ data: data, extra: extra }); + }) + .fail(d.reject); + + return d; + }; + return { beginLoading: function() { }, @@ -58,12 +70,8 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo return store().load(loadOptions); }, customLoader: { - load: function(loadOptions) { - return store().load(loadOptions); - }, - loadFromStore: function(loadOptions) { - return store().load(loadOptions); - }, + load: loadCustomResult, + loadFromStore: loadCustomResult, isLoading: function() { return false; }, From e6969dd60bc5050e8274ff4238bffbc1207c6e53 Mon Sep 17 00:00:00 2001 From: Eldar Iusupzhanov Date: Wed, 2 Sep 2026 19:27:59 +0800 Subject: [PATCH 6/6] fix qunit tests --- .../grouping/m_grouping_collapsed.ts | 4 ++-- .../data_source_adapter/custom_loader.ts | 18 ++++++++------- .../dataSource.tests.js | 22 +++++++++---------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts index 3aa3bb64a78c..da47fe8c409b 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_collapsed.ts @@ -335,9 +335,9 @@ const loadGroupTotalCount = function (dataSource: DataSourceAdapter, options) { }, options, { group: isGrouping ? options.group : null }); dataSource.customLoader.load(loadOptions).done(({ extra }) => { - const count = extra && (isGrouping ? extra.groupCount : extra.totalCount); + const count: number | undefined = extra && (isGrouping ? extra.groupCount : extra.totalCount); - if (count && !isFinite(count)) { + if (count === undefined || !isFinite(count)) { d.reject(dataErrors.Error(isGrouping ? 'E4022' : 'E4021')); return; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts index 8a16a21b8cdd..6805a43eaddb 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/custom_loader.ts @@ -58,7 +58,7 @@ export class CustomLoader { const store = this.dataSource.store(); if (!store) { - // @ts-expect-error badly type Deferred.reject + // @ts-expect-error badly typed Deferred.reject d.reject('canceled'); return; } @@ -92,10 +92,10 @@ export class CustomLoader { extra: operation.extra, }); }) - // @ts-expect-error badly type Deferred.reject + // @ts-expect-error badly typed Deferred.reject .fail((e: unknown) => { d.reject(e); }); }) - // @ts-expect-error badly type Deferred.reject + // @ts-expect-error badly typed Deferred.reject .fail((e: unknown) => { d.reject(e); }); }, this.getLoadingTimeout()); @@ -144,7 +144,7 @@ export class CustomLoader { extra: operation.extra, }); }) - // @ts-expect-error badly type Deferred.reject + // @ts-expect-error badly typed Deferred.reject .fail((...args: unknown[]) => { d.reject(...args); }); return d; @@ -157,14 +157,16 @@ export class CustomLoader { .store() .load(loadOptions) .done((data: unknown, extra: unknown) => { - // A store may resolve with a single `{ data, totalCount }` object + // A store may resolve with a single `{ data, ...extra }` object // instead of the `(data, extra)` pair the pipeline expects. - const result = data as { data?: unknown } | undefined; + const result = data as { + data?: unknown, + } & LoadOperation['extra'] | undefined; if (result && !Array.isArray(result) && Array.isArray(result.data)) { d.resolve({ data: result.data, - extra: result as LoadOperation['extra'], + extra: result, }); } else { d.resolve({ @@ -173,7 +175,7 @@ export class CustomLoader { }); } }) - // @ts-expect-error badly type Deferred.reject + // @ts-expect-error badly typed Deferred.reject .fail((...args: unknown[]) => { d.reject(...args); }); return d; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js index 3ef021571eb4..d83fa3a157c7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js @@ -6980,7 +6980,7 @@ QUnit.module('Custom Load', { // act dataSource.customLoader.load({ filter: ['this', '>', 8] - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7024,7 +7024,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 2, take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7066,7 +7066,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 2, take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7105,7 +7105,7 @@ QUnit.module('Custom Load', { // act dataSource.customLoader.load({ group: function(data) { return data % 2; } - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7146,7 +7146,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 2, take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7188,7 +7188,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 0, take: 3 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7230,7 +7230,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 2, take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7272,7 +7272,7 @@ QUnit.module('Custom Load', { group: [{ selector: 'this', groupInterval: 2 }], skip: 0, take: 3 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7300,7 +7300,7 @@ QUnit.module('Custom Load', { filter: ['this', '>=', '5'], group: 'this', take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7391,7 +7391,7 @@ QUnit.module('Custom Load', { isLoadingAll: true, filter: ['this', '>', '5'], group: 'this' - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; }); @@ -7443,7 +7443,7 @@ QUnit.module('Custom Load', { group: 'this', skip: 2, take: 2 - }).done(function(data) { + }).done(function({ data }) { customLoadData = data; });