Skip to content

Commit 4a712bd

Browse files
Add React Unit Tests for ColumnSummaryCell Component (#9090)
1 parent ec23bb5 commit 4a712bd

File tree

2 files changed

+214
-5
lines changed

2 files changed

+214
-5
lines changed

src/vs/workbench/services/positronDataExplorer/browser/components/columnSummaryCell.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import React, { useRef, useEffect } from 'react';
1212
// Other dependencies.
1313
import * as nls from '../../../../../nls.js';
1414
import { positronClassNames } from '../../../../../base/common/positronUtilities.js';
15-
import { usePositronDataGridContext } from '../../../../browser/positronDataGrid/positronDataGridContext.js';
1615
import { VectorHistogram } from './vectorHistogram.js';
1716
import { ColumnProfileDate } from './columnProfileDate.js';
1817
import { ColumnProfileNumber } from './columnProfileNumber.js';
@@ -49,9 +48,6 @@ interface ColumnSummaryCellProps {
4948
* @returns The rendered component.
5049
*/
5150
export const ColumnSummaryCell = (props: ColumnSummaryCellProps) => {
52-
// Context hooks.
53-
const context = usePositronDataGridContext();
54-
5551
// Reference hooks.
5652
const dataTypeRef = useRef<HTMLDivElement>(undefined!);
5753

@@ -531,7 +527,7 @@ export const ColumnSummaryCell = (props: ColumnSummaryCellProps) => {
531527
className={positronClassNames(
532528
'cursor-indicator',
533529
{ 'cursor': cursor },
534-
{ 'focused': cursor && context.instance.focused }
530+
{ 'focused': cursor && props.instance.focused }
535531
)}
536532
/>
537533
<div className='basic-info'>
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
3+
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import React from 'react';
7+
import assert from 'assert';
8+
import sinon from 'sinon';
9+
import { createRoot, Root } from 'react-dom/client';
10+
import { mainWindow } from '../../../../../base/browser/window.js';
11+
12+
import { ColumnSummaryCell } from '../../browser/components/columnSummaryCell.js';
13+
import { getColumnSchema } from '../../common/positronDataExplorerMocks.js';
14+
import { ColumnDisplayType, SupportStatus, ColumnProfileType, SupportedFeatures } from '../../../languageRuntime/common/positronDataExplorerComm.js';
15+
import { TableSummaryDataGridInstance } from '../../browser/tableSummaryDataGridInstance.js';
16+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
17+
import { PositronActionBarHoverManager } from '../../../../../platform/positronActionBar/browser/positronActionBarHoverManager.js';
18+
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
19+
20+
/**
21+
* Creates a minimal mock of TableSummaryDataGridInstance
22+
*/
23+
function createMockTableSummaryDataGridInstance(overrides: Partial<TableSummaryDataGridInstance> = {}): TableSummaryDataGridInstance {
24+
// Mock the hover manager
25+
const mockHoverManager: Partial<PositronActionBarHoverManager> = {
26+
showHover: sinon.stub(),
27+
hideHover: sinon.stub()
28+
};
29+
30+
// Mock the configuration service
31+
const mockConfigurationService: Partial<IConfigurationService> = {};
32+
33+
const mockSupportedFeatures: SupportedFeatures = {
34+
get_column_profiles: {
35+
support_status: SupportStatus.Supported,
36+
supported_types: [
37+
{
38+
profile_type: ColumnProfileType.NullCount,
39+
support_status: SupportStatus.Supported
40+
}
41+
]
42+
},
43+
search_schema: {
44+
support_status: SupportStatus.Supported,
45+
supported_types: []
46+
},
47+
set_column_filters: {
48+
support_status: SupportStatus.Supported,
49+
supported_types: []
50+
},
51+
set_row_filters: {
52+
support_status: SupportStatus.Supported,
53+
supported_types: [],
54+
supports_conditions: SupportStatus.Supported
55+
},
56+
set_sort_columns: {
57+
support_status: SupportStatus.Supported
58+
},
59+
export_data_selection: {
60+
support_status: SupportStatus.Supported,
61+
supported_formats: []
62+
},
63+
convert_to_code: {
64+
support_status: SupportStatus.Supported
65+
}
66+
};
67+
68+
const mockTableSummaryDataGridInstance: Partial<TableSummaryDataGridInstance> = {
69+
cursorRowIndex: 0,
70+
focused: false,
71+
72+
// Methods the component calls
73+
getSupportedFeatures: () => mockSupportedFeatures,
74+
getColumnProfileNullPercent: () => undefined,
75+
getColumnProfileNullCount: () => undefined,
76+
getColumnProfileSmallHistogram: () => undefined,
77+
getColumnProfileSmallFrequencyTable: () => undefined,
78+
isColumnExpanded: () => false,
79+
scrollToRow: async () => { },
80+
setCursorRow: () => { },
81+
toggleExpandColumn: async () => { },
82+
83+
// Mock hover manager with basic methods
84+
hoverManager: mockHoverManager as PositronActionBarHoverManager,
85+
// Mock configuration service
86+
configurationService: mockConfigurationService as IConfigurationService,
87+
88+
// Apply any overrides from the test
89+
...overrides
90+
};
91+
92+
return mockTableSummaryDataGridInstance as TableSummaryDataGridInstance;
93+
}
94+
95+
suite('ColumnSummaryCell', () => {
96+
let root: Root;
97+
let container: HTMLElement;
98+
const columnSchema = getColumnSchema('test_column', 0, 'string', ColumnDisplayType.String);
99+
100+
setup(() => {
101+
// Create a container element for React to render into
102+
container = mainWindow.document.createElement('div');
103+
mainWindow.document.body.appendChild(container);
104+
root = createRoot(container);
105+
});
106+
107+
teardown(() => {
108+
// Clean up the React root and container
109+
if (root) {
110+
root.unmount();
111+
}
112+
if (container && container.parentNode) {
113+
container.parentNode.removeChild(container);
114+
}
115+
// Restore spies and stubs
116+
sinon.restore();
117+
});
118+
119+
test('displays 0% when getColumnProfileNullPercent return 0', async () => {
120+
const mockTableSummaryDataGridInstance = createMockTableSummaryDataGridInstance({
121+
getColumnProfileNullPercent: () => 0,
122+
getColumnProfileNullCount: () => 0,
123+
});
124+
125+
root.render(
126+
<ColumnSummaryCell
127+
columnIndex={0}
128+
columnSchema={columnSchema}
129+
instance={mockTableSummaryDataGridInstance}
130+
onDoubleClick={() => { }}
131+
/>
132+
);
133+
134+
// Wait for initial render
135+
await new Promise(resolve => setTimeout(resolve, 0));
136+
137+
const nullPercentElement = container.querySelector('.text-percent');
138+
assert.ok(nullPercentElement, 'Expected to find null percent element');
139+
assert.strictEqual(nullPercentElement.textContent, '0%', 'Expected to find 0% for 0% input');
140+
});
141+
142+
test('displays <1% when getColumnProfileNullPercent returns 0.5', async () => {
143+
const mockTableSummaryDataGridInstance = createMockTableSummaryDataGridInstance({
144+
getColumnProfileNullPercent: () => 0.5,
145+
getColumnProfileNullCount: () => 1,
146+
});
147+
148+
root.render(
149+
<ColumnSummaryCell
150+
columnIndex={0}
151+
columnSchema={columnSchema}
152+
instance={mockTableSummaryDataGridInstance}
153+
onDoubleClick={() => { }}
154+
/>
155+
);
156+
157+
// Wait for initial render
158+
await new Promise(resolve => setTimeout(resolve, 0));
159+
160+
const nullPercentElement = container.querySelector('.text-percent');
161+
assert.ok(nullPercentElement, 'Expected to find null percent element');
162+
assert.strictEqual(nullPercentElement.textContent, '<1%', 'Expected to find <1% for 0.5% input');
163+
});
164+
165+
test('displays 99% when getColumnProfileNullPercent returns 99.9', async () => {
166+
const mockTableSummaryDataGridInstance = createMockTableSummaryDataGridInstance({
167+
getColumnProfileNullPercent: () => 99.9,
168+
getColumnProfileNullCount: () => 999,
169+
});
170+
171+
root.render(
172+
<ColumnSummaryCell
173+
columnIndex={0}
174+
columnSchema={columnSchema}
175+
instance={mockTableSummaryDataGridInstance}
176+
onDoubleClick={() => { }}
177+
/>
178+
);
179+
180+
// Wait for initial render
181+
await new Promise(resolve => setTimeout(resolve, 0));
182+
183+
const nullPercentElement = container.querySelector('.text-percent');
184+
assert.ok(nullPercentElement, 'Expected to find null percent element');
185+
assert.strictEqual(nullPercentElement.textContent, '99%', 'Expected to find 99% for 99.9% input');
186+
});
187+
188+
test('displays 100% when getColumnProfileNullPercent returns 100', async () => {
189+
const mockTableSummaryDataGridInstance = createMockTableSummaryDataGridInstance({
190+
getColumnProfileNullPercent: () => 100,
191+
getColumnProfileNullCount: () => 1000,
192+
});
193+
194+
root.render(
195+
<ColumnSummaryCell
196+
columnIndex={0}
197+
columnSchema={columnSchema}
198+
instance={mockTableSummaryDataGridInstance}
199+
onDoubleClick={() => { }}
200+
/>
201+
);
202+
203+
// Wait for initial render
204+
await new Promise(resolve => setTimeout(resolve, 0));
205+
206+
const nullPercentElement = container.querySelector('.text-percent');
207+
assert.ok(nullPercentElement, 'Expected to find null percent element');
208+
assert.strictEqual(nullPercentElement.textContent, '100%', 'Expected to find 100% for 100% input');
209+
});
210+
211+
// Ensure that all disposables are cleaned up.
212+
ensureNoDisposablesAreLeakedInTestSuite();
213+
});

0 commit comments

Comments
 (0)