From d46c590efe441ec2d98b10427ebb813d2899f559 Mon Sep 17 00:00:00 2001 From: Jian Zhang <53095992+Jian-Zhang08@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:30:45 -0500 Subject: [PATCH] fix: prevent Gantt crash when zoomScale is configured without scales _sortScales accessed timelineHeader.scales.length without checking that scales was defined. When only zoomScale is configured (and the zoom scale manager has not populated scales yet), timelineHeader.scales is undefined, so opening the Gantt threw. Guard the access with timelineHeader?.scales. Closes #5159 --- ...59-gantt-sort-scales_2026-06-26-12-00.json | 11 +++++++ .../__tests__/gantt-sort-scales.test.ts | 31 +++++++++++++++++++ packages/vtable-gantt/src/Gantt.ts | 5 ++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 common/changes/@visactor/vtable-gantt/fix-issue-5159-gantt-sort-scales_2026-06-26-12-00.json create mode 100644 packages/vtable-gantt/__tests__/gantt-sort-scales.test.ts diff --git a/common/changes/@visactor/vtable-gantt/fix-issue-5159-gantt-sort-scales_2026-06-26-12-00.json b/common/changes/@visactor/vtable-gantt/fix-issue-5159-gantt-sort-scales_2026-06-26-12-00.json new file mode 100644 index 000000000..f7c9a9e25 --- /dev/null +++ b/common/changes/@visactor/vtable-gantt/fix-issue-5159-gantt-sort-scales_2026-06-26-12-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable-gantt", + "comment": "Fix a crash in the Gantt component when `zoomScale` is configured without `scales`; `_sortScales` accessed `timelineHeader.scales.length` without a guard (GitHub #5159)", + "type": "patch" + } + ], + "packageName": "@visactor/vtable-gantt", + "email": "53095992+Jian-Zhang08@users.noreply.github.com" +} diff --git a/packages/vtable-gantt/__tests__/gantt-sort-scales.test.ts b/packages/vtable-gantt/__tests__/gantt-sort-scales.test.ts new file mode 100644 index 000000000..86258461b --- /dev/null +++ b/packages/vtable-gantt/__tests__/gantt-sort-scales.test.ts @@ -0,0 +1,31 @@ +// @ts-nocheck + +global.__VERSION__ = 'none'; + +import { Gantt } from '../src/index'; + +describe('Gantt._sortScales', () => { + // Regression test for https://github.com/VisActor/VTable/issues/5159 + // When only `zoomScale` is configured (so `timelineHeader.scales` is still + // undefined), `_sortScales` used to crash on `timelineScales.length`. + test('does not throw when timelineHeader.scales is undefined', () => { + const context = { + options: { timelineHeader: {} }, + parsedOptions: {} + }; + + expect(() => Gantt.prototype._sortScales.call(context)).not.toThrow(); + }); + + test('still sorts the configured scales', () => { + const context = { + options: { timelineHeader: { scales: [{ unit: 'day' }, { unit: 'month' }] } }, + parsedOptions: {} + }; + + Gantt.prototype._sortScales.call(context); + + expect(context.parsedOptions.sortedTimelineScales.map(scale => scale.unit)).toEqual(['month', 'day']); + expect(context.parsedOptions.reverseSortedTimelineScales.map(scale => scale.unit)).toEqual(['day', 'month']); + }); +}); diff --git a/packages/vtable-gantt/src/Gantt.ts b/packages/vtable-gantt/src/Gantt.ts index 4084a9713..5492fd58f 100644 --- a/packages/vtable-gantt/src/Gantt.ts +++ b/packages/vtable-gantt/src/Gantt.ts @@ -689,7 +689,10 @@ export class Gantt extends EventTarget { _sortScales() { const { timelineHeader } = this.options; - if (timelineHeader) { + // `scales` can be undefined when only `zoomScale` is configured (the zoom + // scale manager may not have populated it yet), so guard against it instead + // of crashing on `timelineScales.length`. + if (timelineHeader?.scales) { const timelineScales = timelineHeader.scales; const sortOrder = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second']; if (timelineScales.length === 1) {