Compare commits

...
Author SHA1 Message Date
rusackasandClaude Opus 4.8 a8b320f33e fix(plugin-chart-table): apply the fallback comparison-arrow color via inline style
The new regression test's `toHaveStyle` assertion on the arrow span kept
failing in CI (rgb(0, 0, 0) instead of the expected success color) even
after wrapping the render in ThemeProvider. The `css` prop on a plain DOM
element only compiles to an actual style when the build wires up emotion's
JSX pragma (importSource: '@emotion/react'), which webpack.config.js does
but this package's Jest/Babel config does not -- so `<span css={...}>` was
rendering a literal, useless `css="[object Object]"` DOM attribute under
Jest, silently defeating the assertion (and any future one like it).

Switches the arrow's color/margin to a plain inline `style` object, which
works identically under both webpack and Jest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-25 23:10:15 -07:00
rusackasandClaude Opus 4.8 63060e224d test(plugin-chart-table): wrap fallback-arrow-color test in ThemeProvider
The new regression test rendered TableChart without ProviderWrapper, so
useTheme() returned an empty theme and the arrow span's `color` style
resolved to the browser default (black) instead of `colorSuccess`,
failing the assertion regardless of the TableChart.tsx fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-25 19:41:54 -07:00
rusackasandClaude Opus 4.8 6b58b24f1e test(plugin-chart-table): assert fallback arrow color, not just cell background
Addresses review feedback: the row-count-mismatch regression test only
checked the fallback cell's background, which would still pass if the
arrow itself regressed to the wrong comparison color.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-25 16:11:57 -07:00
Evan Rusackas 89f9b4aea2 fix(plugin-chart-table): preserve comparison arrow when a column-specific formatter entry is missing
The per-cell comparison arrow and arrow color are computed twice: once
from the row-level basicColorFormatters, then unconditionally reassigned
from basicColorColumnFormatters when that array is present. Unlike the
sibling backgroundColor assignment (which falls back to the prior value
via `|| backgroundColor`), the arrow and arrowColor reassignments had no
such fallback, so a row missing an entry in basicColorColumnFormatters
lost its arrow entirely and would have flipped its arrow color, even
though a valid value had already been computed from basicColorFormatters.

Falls back to the previously-computed value in both cases, same as
backgroundColor already does.
2026-08-24 16:22:52 -07:00
2 changed files with 50 additions and 20 deletions
@@ -1138,7 +1138,8 @@ export default function TableChart<D extends DataRecord = DataRecord>(
?.backgroundColor || backgroundColor;
arrow =
column.label === comparisonLabels[0]
? basicColorColumnFormatters[row.index]?.[column.key]?.mainArrow
? (basicColorColumnFormatters[row.index]?.[column.key]
?.mainArrow ?? arrow)
: '';
}
const rowSurfaceColor =
@@ -1194,30 +1195,36 @@ export default function TableChart<D extends DataRecord = DataRecord>(
}
`;
let arrowStyles = css`
color: ${
// Plain inline style (rather than the `css` prop) so the arrow's
// color is guaranteed to apply regardless of whether the consuming
// app's build wires up the emotion JSX pragma for the `css` prop --
// notably, this codebase's own Jest/Babel config does not, which
// silently no-ops any `css` prop on a plain DOM element.
let arrowStyles: CSSProperties = {
color:
basicColorFormatters &&
basicColorFormatters[row.index]?.[originKey]?.arrowColor ===
ColorSchemeEnum.Green
? theme.colorSuccess
: theme.colorError
};
margin-right: ${theme.sizeUnit}px;
`;
: theme.colorError,
marginRight: theme.sizeUnit,
};
if (
basicColorColumnFormatters &&
basicColorColumnFormatters?.length > 0
) {
arrowStyles = css`
color: ${
basicColorColumnFormatters[row.index]?.[column.key]
?.arrowColor === ColorSchemeEnum.Green
? theme.colorSuccess
: theme.colorError
const columnArrowColor =
basicColorColumnFormatters[row.index]?.[column.key]?.arrowColor;
if (columnArrowColor) {
arrowStyles = {
color:
columnArrowColor === ColorSchemeEnum.Green
? theme.colorSuccess
: theme.colorError,
marginRight: theme.sizeUnit,
};
margin-right: ${theme.sizeUnit}px;
`;
}
}
const cellProps = {
@@ -1302,12 +1309,12 @@ export default function TableChart<D extends DataRecord = DataRecord>(
className="dt-truncate-cell"
style={columnWidth ? { width: columnWidth } : undefined}
>
{arrow && <span css={arrowStyles}>{arrow}</span>}
{arrow && <span style={arrowStyles}>{arrow}</span>}
{text}
</div>
) : (
<>
{arrow && <span css={arrowStyles}>{arrow}</span>}
{arrow && <span style={arrowStyles}>{arrow}</span>}
{text}
</>
)}
@@ -2111,7 +2111,14 @@ describe('plugin-chart-table', () => {
expect(() =>
render(
<TableChart {...propsWithMissingFormatterEntry} sticky={false} />,
ProviderWrapper({
children: (
<TableChart
{...propsWithMissingFormatterEntry}
sticky={false}
/>
),
}),
),
).not.toThrow();
@@ -2125,8 +2132,24 @@ describe('plugin-chart-table', () => {
'rgba(0, 150, 0, 0.2)',
);
// the row missing a formatter entry still renders its raw value
expect(screen.getAllByTitle('110').length).toBeGreaterThan(0);
// the row missing a formatter entry falls back to the row-level
// comparison arrow instead of losing it: before the fix, this row's
// arrow was silently cleared (and its color, computed the same way,
// would have flipped to the "decrease" color) whenever the
// column-specific lookup for this row was undefined.
const arrowCell = screen
.getAllByTitle('110')
.find(cell => cell.querySelector('span'));
expect(arrowCell).toHaveTextContent('↑110');
expect(getComputedStyle(arrowCell!).background).toContain(
'rgba(0, 150, 0, 0.2)',
);
// the fallback arrow itself must also keep the "increase" color --
// asserting only the cell background would still pass if the arrow's
// own color had regressed to the "decrease" color.
expect(arrowCell!.querySelector('span')).toHaveStyle({
color: supersetTheme.colorSuccess,
});
});
test('preserves client-side search text across temporal table rerenders', async () => {