From fd5e36de0952f682352f6c1bb8e3b30efaebf965 Mon Sep 17 00:00:00 2001 From: aloktomarr Date: Fri, 17 Jul 2026 17:00:54 +0530 Subject: [PATCH] fix(dataZoom): skip redundant reset when only the origin snapshot remains. close #21660 --- src/component/dataZoom/history.ts | 10 ++- .../spec/component/dataZoom/history.test.ts | 89 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/ut/spec/component/dataZoom/history.test.ts diff --git a/src/component/dataZoom/history.ts b/src/component/dataZoom/history.ts index 5423ecf379..c99892e62e 100644 --- a/src/component/dataZoom/history.ts +++ b/src/component/dataZoom/history.ts @@ -72,8 +72,16 @@ export function push(ecModel: GlobalModel, newSnapshot: DataZoomStoreSnapshot) { export function pop(ecModel: GlobalModel) { const storedSnapshots = getStoreSnapshots(ecModel); + + // When only the origin snapshot remains, there is nothing to undo. + // Return an empty snapshot so callers do not dispatch a redundant + // dataZoom action (see #21660). + if (storedSnapshots.length <= 1) { + return {}; + } + const head = storedSnapshots[storedSnapshots.length - 1]; - storedSnapshots.length > 1 && storedSnapshots.pop(); + storedSnapshots.pop(); // Find top for all dataZoom. const snapshot: DataZoomStoreSnapshot = {}; diff --git a/test/ut/spec/component/dataZoom/history.test.ts b/test/ut/spec/component/dataZoom/history.test.ts new file mode 100644 index 0000000000..d5a0f5673e --- /dev/null +++ b/test/ut/spec/component/dataZoom/history.test.ts @@ -0,0 +1,89 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +*/ + +import { push, pop, count, clear } from '../../../../../src/component/dataZoom/history'; +import GlobalModel from '../../../../../src/model/Global'; + +// `history.push` completes the origin range by querying the related dataZoom +// model. Return a stub whose `getPercentRange` yields a fixed range so the +// origin snapshot is populated the same way it is at runtime. +function createMockECModel(): GlobalModel { + return { + queryComponents() { + return [{ + getPercentRange() { + return [0, 100]; + } + }]; + } + } as unknown as GlobalModel; +} + +describe('dataZoom/history', function () { + + it('starts with a single origin snapshot', function () { + const ecModel = createMockECModel(); + expect(count(ecModel)).toBe(1); + }); + + it('pushes and pops snapshots for multiple dataZooms', function () { + const ecModel = createMockECModel(); + + push(ecModel, { dz0: { dataZoomId: 'dz0', start: 10, end: 90 } }); + push(ecModel, { dz1: { dataZoomId: 'dz1', start: 20, end: 80 } }); + expect(count(ecModel)).toBe(3); + + const undoDz1 = pop(ecModel); + expect(undoDz1.dz1).toBeTruthy(); + expect(count(ecModel)).toBe(2); + + const undoDz0 = pop(ecModel); + expect(undoDz0.dz0).toBeTruthy(); + expect(count(ecModel)).toBe(1); + }); + + // Regression test for #21660: after every zoom has been undone only the + // origin snapshot remains, so an extra "back" / zoom-reset must be a no-op + // instead of dispatching a redundant dataZoom action that restores the + // origin range again. + it('returns an empty snapshot once only the origin remains (#21660)', function () { + const ecModel = createMockECModel(); + + push(ecModel, { dz0: { dataZoomId: 'dz0', start: 10, end: 90 } }); + push(ecModel, { dz1: { dataZoomId: 'dz1', start: 20, end: 80 } }); + + pop(ecModel); // undo the dz1 zoom + pop(ecModel); // undo the dz0 zoom + expect(count(ecModel)).toBe(1); + + expect(pop(ecModel)).toEqual({}); + expect(count(ecModel)).toBe(1); + }); + + it('clears the stored snapshots back to the origin', function () { + const ecModel = createMockECModel(); + + push(ecModel, { dz0: { dataZoomId: 'dz0', start: 10, end: 90 } }); + expect(count(ecModel)).toBe(2); + + clear(ecModel); + expect(count(ecModel)).toBe(1); + }); + +});