You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/10/21 13:32:05 UTC

(superset) 01/02: test(partition): add failing guard for tooltip right-edge overflow

This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch fix-partition-tooltip-right-edge
in repository https://gitbox.apache.org/repos/asf/superset.git

commit af4d5b3ba42c860849c425efed21e868612e15d7
Author: Joe Li <jo...@preset.io>
AuthorDate: Wed Oct 21 13:22:09 2026 +0000

    test(partition): add failing guard for tooltip right-edge overflow
    
    Partition.ts positions the tooltip at cursorX + 15 without checking the
    chart width, so hovering near the right edge pushes it outside the chart
    where it is clipped. Add a jsdom test that drives the real mousemove
    handler and asserts the tooltip stays within the chart width.
    
    Also allow d3-hierarchy (ESM-only) through jest's babel transform so
    Partition.ts can be imported by tests.
---
 superset-frontend/jest.config.js                   |   2 +-
 .../plugin-chart-partition/test/Partition.test.ts  | 120 +++++++++++++++++++++
 2 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js
index 1ddc9590c23..298e3950135 100644
--- a/superset-frontend/jest.config.js
+++ b/superset-frontend/jest.config.js
@@ -112,7 +112,7 @@ export default {
     // @deck.gl/maplibre@9.4.0 ships ESM-only (no dist/*.cjs), unlike every
     // other @deck.gl/* package - allowed through so babel-jest can convert
     // its import/export syntax (see the moduleNameMapper entry above).
-    'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format|selection)|internmap|@mapbox/tiny-sdf|@deck.gl/maplibre|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|@x0k/.*|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|parse5|dom-s [...]
+    'node_modules/(?!@ant-design/(colors|fast-color)|@formatjs/.*|d3-(array|interpolate|color|time|scale|time-format|format|selection|hierarchy)|internmap|@mapbox/tiny-sdf|@deck.gl/maplibre|remark-gfm|(?!@ngrx|(?!deck.gl)|d3-scale)|markdown-table|micromark-*.|decode-named-character-reference|character-entities|mdast-util-*.|unist-util-*.|ccount|escape-string-regexp|nanoid|uuid|@rjsf/*.|@x0k/.*|echarts|zrender|fetch-mock|pretty-ms|parse-ms|ol|@babel/runtime|@emotion|cheerio|cheerio/lib|pa [...]
   ],
   transform: {
     '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
diff --git a/superset-frontend/plugins/plugin-chart-partition/test/Partition.test.ts b/superset-frontend/plugins/plugin-chart-partition/test/Partition.test.ts
new file mode 100644
index 00000000000..3f97d8cf592
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-partition/test/Partition.test.ts
@@ -0,0 +1,120 @@
+/**
+ * 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 Partition from '../src/Partition';
+
+const CHART_WIDTH = 400;
+const CHART_HEIGHT = 300;
+const TOOLTIP_WIDTH = 120;
+const TOOLTIP_OFFSET = 15;
+
+const props = {
+  width: CHART_WIDTH,
+  height: CHART_HEIGHT,
+  data: [
+    {
+      name: 'sum__num',
+      val: 100,
+      children: [
+        { name: 'boy', val: 60 },
+        { name: 'girl', val: 40 },
+      ],
+    },
+  ],
+  colorScheme: 'supersetColors',
+  dateTimeFormat: '%Y-%m-%d',
+  equalDateSize: true,
+  levels: ['gender'],
+  metrics: ['sum__num'],
+  numberFormat: ',d',
+  partitionLimit: 0,
+  partitionThreshold: 0,
+  timeSeriesOption: 'not_time',
+  useLogScale: false,
+  useRichTooltip: false,
+  sliceId: 1,
+};
+
+let container: HTMLDivElement;
+let rectSpy: jest.SpyInstance;
+let offsetWidthSpy: jest.SpyInstance;
+
+beforeEach(() => {
+  container = document.createElement('div');
+  document.body.appendChild(container);
+  // jsdom performs no layout: give the container a fixed box so the
+  // pointer coordinates d3.mouse() derives from clientX are deterministic.
+  rectSpy = jest
+    .spyOn(HTMLElement.prototype, 'getBoundingClientRect')
+    .mockImplementation(function getRect(this: HTMLElement) {
+      const isTooltip = this.classList.contains('partition-tooltip');
+      const width = isTooltip ? TOOLTIP_WIDTH : CHART_WIDTH;
+
+      return {
+        x: 0,
+        y: 0,
+        top: 0,
+        left: 0,
+        right: width,
+        bottom: CHART_HEIGHT,
+        width,
+        height: CHART_HEIGHT,
+        toJSON: () => ({}),
+      } as DOMRect;
+    });
+  offsetWidthSpy = jest
+    .spyOn(HTMLElement.prototype, 'offsetWidth', 'get')
+    .mockImplementation(function getOffsetWidth(this: HTMLElement) {
+      return this.classList.contains('partition-tooltip')
+        ? TOOLTIP_WIDTH
+        : CHART_WIDTH;
+    });
+});
+
+afterEach(() => {
+  rectSpy.mockRestore();
+  offsetWidthSpy.mockRestore();
+  container.remove();
+});
+
+function hoverAt(clientX: number, clientY: number): HTMLElement {
+  Partition(container, props);
+  const node = container.querySelector('svg g rect') as SVGRectElement;
+  node.dispatchEvent(
+    new MouseEvent('mousemove', { bubbles: true, clientX, clientY }),
+  );
+
+  return container.querySelector('.partition-tooltip') as HTMLElement;
+}
+
+test('places the tooltip right of the cursor when there is room', () => {
+  const tooltip = hoverAt(50, 100);
+
+  expect(tooltip.style.left).toBe(`${50 + TOOLTIP_OFFSET}px`);
+  expect(tooltip.style.top).toBe('100px');
+});
+
+test('keeps the tooltip inside the chart when hovering near the right edge', () => {
+  const cursorX = CHART_WIDTH - 10;
+  const tooltip = hoverAt(cursorX, 100);
+
+  const left = parseFloat(tooltip.style.left);
+  expect(left + TOOLTIP_WIDTH).toBeLessThanOrEqual(CHART_WIDTH);
+  // shifted to the left of the cursor rather than overlapping it
+  expect(left + TOOLTIP_WIDTH).toBeLessThanOrEqual(cursorX);
+});