From 7291c03bba62e93a95b52c3d6ce0c99eeb4449e8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 12:41:36 -0800 Subject: [PATCH 01/11] More style tweaks --- .../data-explorer/cellFormatter.css | 4 ++ .../data-explorer/cellFormatter.tsx | 21 ++++------ .../data-explorer/mainPanel.css | 3 ++ .../data-explorer/reactSlickGrid.css | 42 +++++++++++-------- .../data-explorer/reactSlickGridFilterBox.tsx | 3 +- .../data-explorer/sliceControl.css | 25 ++++------- .../data-explorer/sliceControl.tsx | 28 +++++++++---- 7 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/datascience-ui/data-explorer/cellFormatter.css b/src/datascience-ui/data-explorer/cellFormatter.css index f60b3fb5cde..7f1dd164548 100644 --- a/src/datascience-ui/data-explorer/cellFormatter.css +++ b/src/datascience-ui/data-explorer/cellFormatter.css @@ -11,3 +11,7 @@ text-overflow: ellipsis; overflow: hidden; } + +.index-column-formatter { + text-align: right; +} \ No newline at end of file diff --git a/src/datascience-ui/data-explorer/cellFormatter.tsx b/src/datascience-ui/data-explorer/cellFormatter.tsx index c7cc75a48cf..601ad7bedaa 100644 --- a/src/datascience-ui/data-explorer/cellFormatter.tsx +++ b/src/datascience-ui/data-explorer/cellFormatter.tsx @@ -24,9 +24,6 @@ class CellFormatter extends React.Component { // eslint-disable-next-line @typescript-eslint/no-explicit-any const columnType = (this.props.columnDef as any).type; switch (columnType) { - case ColumnType.Bool: - return this.renderBool(this.props.value as boolean); - case ColumnType.Number: return this.renderNumber(this.props.value as number); @@ -34,7 +31,7 @@ class CellFormatter extends React.Component { break; } } - // Otherwise an unknown type or a string + // Otherwise an unknown type, boolean, or a string const val = this.props.value?.toString() ?? ''; return (
@@ -43,18 +40,16 @@ class CellFormatter extends React.Component { ); } - private renderBool(value: boolean) { - return ( -
- {value.toString()} -
- ); - } - private renderNumber(value: number) { let val = generateDisplayValue(value); + const isIndexColumn = this.props.columnDef.id === '0'; + return ( -
+
{val}
); diff --git a/src/datascience-ui/data-explorer/mainPanel.css b/src/datascience-ui/data-explorer/mainPanel.css index 8c3ea88f219..12aefd52d0a 100644 --- a/src/datascience-ui/data-explorer/mainPanel.css +++ b/src/datascience-ui/data-explorer/mainPanel.css @@ -16,6 +16,9 @@ padding-left: 16px; overflow: auto; white-space: nowrap; + border-bottom-color: var(--vscode-editor-inactiveSelectionBackground); + border-bottom-style: solid; + border-bottom-width: 1px; } .breadcrumb { diff --git a/src/datascience-ui/data-explorer/reactSlickGrid.css b/src/datascience-ui/data-explorer/reactSlickGrid.css index 421fc7deca7..c5e7273c6da 100644 --- a/src/datascience-ui/data-explorer/reactSlickGrid.css +++ b/src/datascience-ui/data-explorer/reactSlickGrid.css @@ -4,9 +4,7 @@ } .react-grid-container { - border-color: var(--vscode-editor-inactiveSelectionBackground); - border-style: solid; - border-width: 1px; + border: none; } .react-grid-measure { @@ -20,16 +18,14 @@ color: var(--vscode-editor-foreground); text-align: left; font-weight: bold; - border-right-color: var(--vscode-editor-inactiveSelectionBackground); + border-right: 1px solid transparent; } .react-grid-cell { padding: 4px; background-color: var(--vscode-editor-background); color: var(--vscode-editor-foreground); - border-bottom-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-style: solid; + border-right: 1px solid transparent; box-sizing: border-box; } @@ -41,9 +37,7 @@ /* Some overrides necessary to get the colors we want */ .slick-headerrow-column { background-color: var(--vscode-menu-background); - border-right-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-style: solid; - border-bottom-color: var(--vscode-menu-background); + border-right: 1px solid transparent; } .slick-headerrow-column.ui-state-default { @@ -55,7 +49,7 @@ .slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default { - border-right-color: var(--vscode-editor-inactiveSelectionBackground); + border-right: 1px solid transparent; display: flex; } @@ -76,21 +70,31 @@ } .react-grid-header-cell:hover { - background-color: var(--vscode-editor-inactiveSelectionBackground); + background-color: var(--override-selection-background, var(--vscode-list-hoverBackground)); } .react-grid-header-cell > .slick-sort-indicator-asc::before { background: none; - font: normal normal normal 16px/1 codicon; + font: normal normal normal 10px/1 codicon; content: '\eaa1'; /* VS Code arrow-up codicon */ align-items: center; + text-align: right; + display: flex; + padding: 3px; + color: var(--vscode-settings-textInputForeground); + opacity: 0.4; } .react-grid-header-cell > .slick-sort-indicator-desc::before { background: none; - font: normal normal normal 16px/1 codicon; + font: normal normal normal 10px/1 codicon; content: '\ea9a'; /* VS Code arrow-down codicon */ align-items: center; + text-align: right; + display: flex; + padding: 3px; + color: var(--vscode-settings-textInputForeground); + opacity: 0.4; } .slick-row:hover > .react-grid-cell { @@ -116,6 +120,10 @@ input.editor-text { font-size: var(--vscode-editor-font-size); } +.slick-cell { + border: 1px solid var(--vscode-editor-lineHighlightBorder); +} + .slick-cell.editable { border-color: none; border-style: none; @@ -125,9 +133,6 @@ input.editor-text { } .control-container { - border-bottom-color: var(--vscode-editor-inactiveSelectionBackground); - border-bottom-style: solid; - border-bottom-width: 1px; padding: 6px; display: flex; justify-content: start; @@ -136,6 +141,9 @@ input.editor-text { .codicon-button { cursor: pointer; + padding-left: 3px; + padding-top: 3px; + color: var(--vscode-settings-textInputForeground); } .header-cell-button { diff --git a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx index d441da213a9..6f5656c9be8 100644 --- a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx +++ b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx @@ -13,7 +13,8 @@ const filterIcon: IIconProps = { root: { fontSize: 'var(--vscode-font-size)', width: 'var(--vscode-font-size)', - color: 'var(--vscode-settings-textInputForeground)' + color: 'var(--vscode-settings-textInputForeground)', + opacity: 0.4 } } }; diff --git a/src/datascience-ui/data-explorer/sliceControl.css b/src/datascience-ui/data-explorer/sliceControl.css index 2eb378ab0e2..97e4ff7740f 100644 --- a/src/datascience-ui/data-explorer/sliceControl.css +++ b/src/datascience-ui/data-explorer/sliceControl.css @@ -1,6 +1,6 @@ .slice-data { - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); + background-color: var(--vscode-settings-textInputBackground); + color: var(--vscode-settings-textInputForeground) !important; padding: 0px 4px; /* input does not inherit font from body */ font-family: var(--vscode-font-family); @@ -27,10 +27,12 @@ .slice-summary { display: flex; flex-direction: row; + cursor: pointer; } .slice-summary-detail { padding-bottom: 2px; + padding-left: 2px; } .current-slice { @@ -39,13 +41,14 @@ padding-right: 5px; padding-top: 0px; background-color: var(--vscode-input-background); + border-radius: 2px; } .slice-form { align-self: center; flex-direction: column; justify-content: space-between; - padding: 4px; + padding: 4px 14px; } .slice-control-row { @@ -78,7 +81,7 @@ details[open] > summary::before { } .submit-slice-button { - color: var(--vscode-button-foreground); + color: var(--vscode-button-foreground) !important; background-color: var(--vscode-button-background); border: none; margin-left: 10px; @@ -88,16 +91,11 @@ details[open] > summary::before { font-weight: var(--vscode-font-weight); font-size: var(--vscode-font-size); max-width: fit-content; -} - -.slice-enablement-checkbox { - margin-right: 6px; - width: 20px; + cursor: pointer; } .slice-enablement-checkbox-container { padding-top: 4px; - padding-bottom: 4px; } :focus { @@ -105,13 +103,6 @@ details[open] > summary::before { } /* Overrides for Fluent UI controls */ -[class*='text'] { - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); - font-weight: var(--vscode-font-weight); - font-size: var(--vscode-font-size); -} - [class*='ms-Dropdown-label'] { color: var(--vscode-editor-foreground); font-family: var(--vscode-font-family); diff --git a/src/datascience-ui/data-explorer/sliceControl.tsx b/src/datascience-ui/data-explorer/sliceControl.tsx index 0f57e1fbbf6..39633ff4c9f 100644 --- a/src/datascience-ui/data-explorer/sliceControl.tsx +++ b/src/datascience-ui/data-explorer/sliceControl.tsx @@ -1,4 +1,4 @@ -import { Dropdown, IDropdownOption, ResponsiveMode } from '@fluentui/react'; +import { Checkbox, Dropdown, IDropdownOption, ResponsiveMode } from '@fluentui/react'; import * as React from 'react'; import { IGetSliceRequest } from '../../client/datascience/data-viewing/types'; import { getLocString } from '../react-common/locReactSide'; @@ -13,6 +13,21 @@ import { import './sliceControl.css'; +const checkboxStyles = { + checkbox: { + color: 'var(--vscode-checkbox-foreground)', + backgroundColor: 'var(--vscode-checkbox-background) !important', + border: 'var(--vscode-checkbox-border)' + }, + text: { + fontFamily: 'var(--vscode-font-family)', + fontWeight: 'var(--vscode-font-weight)', + fontSize: 'var(--vscode-font-size)', + color: 'var(--vscode-editor-foreground) !important', + paddingLeft: 2, + } +}; + // These styles are passed to the FluentUI dropdown controls const styleOverrides = { color: 'var(--vscode-dropdown-foreground)', @@ -20,6 +35,7 @@ const styleOverrides = { fontFamily: 'var(--vscode-font-family)', fontWeight: 'var(--vscode-font-weight)', fontSize: 'var(--vscode-font-size)', + border: 'var(--vscode-dropdown-border)', ':focus': { color: 'var(--vscode-dropdown-foreground)' }, @@ -108,16 +124,12 @@ export class SliceControl extends React.Component
- -
From 6f305bbe8c176050bd77c9ed838ab1a424d9c378 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 12:55:37 -0800 Subject: [PATCH 02/11] Prettier --- src/datascience-ui/data-explorer/sliceControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datascience-ui/data-explorer/sliceControl.tsx b/src/datascience-ui/data-explorer/sliceControl.tsx index 39633ff4c9f..3bee0612e46 100644 --- a/src/datascience-ui/data-explorer/sliceControl.tsx +++ b/src/datascience-ui/data-explorer/sliceControl.tsx @@ -24,7 +24,7 @@ const checkboxStyles = { fontWeight: 'var(--vscode-font-weight)', fontSize: 'var(--vscode-font-size)', color: 'var(--vscode-editor-foreground) !important', - paddingLeft: 2, + paddingLeft: 2 } }; From c61d60b8c3eae852a492b3e5ab9e3144a5f75f08 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 13:32:10 -0800 Subject: [PATCH 03/11] Fix filter box icon opacity --- .../data-explorer/reactSlickGridFilterBox.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx index 6f5656c9be8..aba9e32a0fd 100644 --- a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx +++ b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx @@ -14,11 +14,19 @@ const filterIcon: IIconProps = { fontSize: 'var(--vscode-font-size)', width: 'var(--vscode-font-size)', color: 'var(--vscode-settings-textInputForeground)', - opacity: 0.4 } } }; +const styles = { + iconContainer: { + opacity: 0.4, + ':active': { + opacity: 0 + } + } +} + interface IFilterProps { column: Slick.Column; fontSize: number; @@ -40,6 +48,7 @@ export class ReactSlickGridFilterBox extends React.Component { tabIndex={0} ariaLabel={this.props.column.name} className="filter-box" + styles={styles} value={this.props.filter} /> ); From 6b2979b5f56199fc15542ea07245af6b2926ee7d Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 13:37:25 -0800 Subject: [PATCH 04/11] Prettier --- src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx index aba9e32a0fd..ecbe2e6a52d 100644 --- a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx +++ b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx @@ -13,7 +13,7 @@ const filterIcon: IIconProps = { root: { fontSize: 'var(--vscode-font-size)', width: 'var(--vscode-font-size)', - color: 'var(--vscode-settings-textInputForeground)', + color: 'var(--vscode-settings-textInputForeground)' } } }; @@ -25,7 +25,7 @@ const styles = { opacity: 0 } } -} +}; interface IFilterProps { column: Slick.Column; From 472e34490d04951813d7f072620450b19b90b536 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 21:22:53 -0800 Subject: [PATCH 05/11] Add slice data tests --- .../datascience/jupyter/kernelVariables.ts | 4 - .../dataviewer.functional.test.tsx | 350 +++++++++++++++++- 2 files changed, 346 insertions(+), 8 deletions(-) diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts index d4e02b7adcc..912e193aad2 100644 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ b/src/client/datascience/jupyter/kernelVariables.ts @@ -164,10 +164,6 @@ export class KernelVariables implements IJupyterVariables { // Import the data frame script directory if we haven't already await this.importDataFrameScripts(notebook); - if (targetVariable.rowCount) { - end = Math.min(end, targetVariable.rowCount); - } - let expression = targetVariable.name; if (sliceExpression) { expression = `${targetVariable.name}${sliceExpression}`; diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index b60fc1ca78f..988bd1ec371 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -32,6 +32,12 @@ import { noop, sleep } from '../core'; import { DataScienceIocContainer } from './dataScienceIocContainer'; import { takeSnapshot, writeDiffSnapshot } from './helpers'; import { IMountedWebView } from './mountedWebView'; +import { SliceControl } from '../../datascience-ui/data-explorer/sliceControl'; +import { Dropdown } from '@fluentui/react'; + +interface ISliceControlTestInterface { + toggleEnablement: () => void; +} // import { asyncDump } from '../common/asyncDump'; suite('DataScience DataViewer tests', () => { @@ -95,7 +101,7 @@ suite('DataScience DataViewer tests', () => { delete (global as any).ascquireVsCodeApi; }); - function createJupyterVariable(variable: string, type: string): IJupyterVariable { + function createJupyterVariable(variable: string, type: string, shape: string): IJupyterVariable { return { name: variable, value: '', @@ -103,7 +109,7 @@ suite('DataScience DataViewer tests', () => { type, size: 0, truncated: true, - shape: '', + shape, count: 0 }; } @@ -118,8 +124,12 @@ suite('DataScience DataViewer tests', () => { return dataViewerFactory.create(dataProvider, title); } - async function createJupyterVariableDataViewer(variable: string, type: string): Promise { - const jupyterVariable: IJupyterVariable = createJupyterVariable(variable, type); + async function createJupyterVariableDataViewer( + variable: string, + type: string, + shape: string = '' + ): Promise { + const jupyterVariable: IJupyterVariable = createJupyterVariable(variable, type, shape); const jupyterVariableDataProvider: IDataViewerDataProvider = await createJupyterVariableDataProvider( jupyterVariable ); @@ -168,6 +178,16 @@ suite('DataScience DataViewer tests', () => { }); } + function findSliceControlPanel(wrapper: ReactWrapper, React.Component>) { + const sliceControlWrapper = wrapper.find(SliceControl); + sliceControlWrapper.update(); + assert.ok(sliceControlWrapper && sliceControlWrapper.length > 0, 'Slice control not found'); + // sliceControlWrapper.simulate('click'); + // await sleep(1000); + // sliceControlWrapper.update(); + return sliceControlWrapper; + } + function findGrid(wrapper: ReactWrapper, React.Component>) { const mainPanelWrapper = wrapper.find(MainPanel); assert.ok(mainPanelWrapper && mainPanelWrapper.length > 0, 'Grid not found to sort on'); @@ -515,6 +535,328 @@ suite('DataScience DataViewer tests', () => { verifyRows(wrapper.wrapper, [0, 4, 5, 6]); }); + suite('Data viewer slice data', async () => { + function verifyReadonlyIndicator( + wrapper: ReactWrapper, React.Component>, + currentSlice: string + ) { + const sliceControl = wrapper.find(SliceControl); + const html = sliceControl.html(); + const root = parse(html) as any; + wrapper.render(); + const cells = root.querySelectorAll('.current-slice') as HTMLSpanElement[]; + assert.ok(cells.length === 1, 'No readonly indicator found'); + assert.ok(cells[0].innerHTML === currentSlice, 'Readonly indicator contents did not match'); + } + + function verifyDropdowns(wrapper: ReactWrapper, React.Component>, rows: (string | number)[]) { + const sliceControl = wrapper.find(SliceControl); + const html = sliceControl.html(); + const root = parse(html) as any; + const cells = root.querySelectorAll('.ms-Dropdown-title'); + assert.ok(cells.length >= rows.length, 'Not enough cells found'); + // Cells should be an array that matches up to the values we expect. + for (let i = 0; i < rows.length; i += 1) { + // Span should have our value (based on the CellFormatter's output) + const span = cells[i] as HTMLSpanElement; + assert.ok(span, `Span ${i} not found`); + const val = rows[i].toString(); + assert.equal(span.innerHTML, val, `Row ${i} not matching. ${span.innerHTML} !== ${val}`); + } + } + + function toggleCheckbox(wrapper: ReactWrapper, React.Component>) { + const sliceControl = findSliceControlPanel(wrapper); + // Enable slicing by toggling checkbox + const instance = (sliceControl.instance() as any) as ISliceControlTestInterface; + instance.toggleEnablement(); // simulate('click') doesn't suffice: https://github.com/facebook/react/issues/4950#issuecomment-255408709 + wrapper.render(); + } + + function verifyControlsDisabled( + wrapper: ReactWrapper, React.Component>, + expectedNumberOfDropdowns: number, + initialReadonlyIndicator: string + ) { + // Open the slice panel + findSliceControlPanel(wrapper); + // Verify that all controls are initially disabled + let input = wrapper.find('.slice-data'); + const html = input.html(); + assert.ok(html.includes('disabled'), 'Input field was not initially disabled'); + const dropdowns = wrapper.find(Dropdown); + assert.ok(dropdowns.length === expectedNumberOfDropdowns, 'Unexpected number of dropdowns found'); + // Verify no readonly indicator as we're not slicing yet + assert.throws( + () => verifyReadonlyIndicator(wrapper, initialReadonlyIndicator), + 'Readonly indicator rendered when not slicing' + ); + } + + function editInputValue(wrapper: IMountedWebView, slice: string) { + const inputElement = wrapper.wrapper.find('.slice-data').getDOMNode() as HTMLInputElement; + inputElement.value = slice; + wrapper.wrapper.find('.slice-data').simulate('change'); + } + + async function applySliceAndVerifyReadonlyIndicator(wrapper: IMountedWebView, slice: string) { + // Apply a slice to input box + const gotSlice = getCompletedPromise(wrapper); + editInputValue(wrapper, slice); + wrapper.wrapper.find('form').first().simulate('submit'); + await gotSlice; + // Ensure readonly indicator updates after slicing + verifyReadonlyIndicator(wrapper.wrapper, slice); + } + + runMountedTest('Slice 2D', async (wrapper) => { + const code = `import torch +import numpy as np +arr = np.arange(9).reshape(3,3) +foo = torch.tensor(arr)`; + + // Create data viewer + await injectCode(code); + const gotAllRows = getCompletedPromise(wrapper); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3)'); + assert.ok(dv, 'DataViewer not created'); + await gotAllRows; + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyControlsDisabled(wrapper.wrapper, 2, ''); + + // Apply a slice via input box and verify that dropdowns update + toggleCheckbox(wrapper.wrapper); + await applySliceAndVerifyReadonlyIndicator(wrapper, '[1, :]'); + verifyRows(wrapper.wrapper, [0, 3, 1, 4, 2, 5]); + verifyDropdowns(wrapper.wrapper, [0, 1]); // Axis 0, index 1 + + // Apply a slice with no corresponding dropdown + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:2, :]'); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); + verifyDropdowns(wrapper.wrapper, ['', '']); // Dropdowns should be unset + + // Uncheck slice checkbox and verify original contents are restored + const disableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await disableSlicing; + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyControlsDisabled(wrapper.wrapper, 2, ''); + + // Recheck slice checkbox and verify slice expression is restored + const reenableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await reenableSlicing; + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); + + // Enter an invalid slice expression and verify error message is rendered + editInputValue(wrapper, '[:]'); + assert.ok( + wrapper.wrapper.find('.error-message').length === 1, + 'No error message rendered for invalid slice' + ); + }); + + runMountedTest('Slice 3D', async (wrapper) => { + const code = `import torch +import numpy as np +arr = np.arange(27).reshape(3,3,3) +foo = torch.tensor(arr)`; + // Create data viewer + await injectCode(code); + const gotAllRows = getCompletedPromise(wrapper); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3, 3)'); + assert.ok(dv, 'DataViewer not created'); + await gotAllRows; + verifyRows(wrapper.wrapper, [ + 0, + '[0, 1, 2]', + '[3, 4, 5]', + '[6, 7, 8]', + 1, + '[9, 10, 11]', + '[12, 13, 14]', + '[15, 16, 17]', + 2, + '[18, 19, 20]', + '[21, 22, 23]', + '[24, 25, 26]' + ]); + verifyControlsDisabled(wrapper.wrapper, 2, ''); + + // Toggle on slicing. Slice should immediately be applied + const enableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await enableSlicing; + verifyReadonlyIndicator(wrapper.wrapper, '[0, :, :]'); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + + // Apply a slice via input box and verify that dropdowns update + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, :, 2]'); + verifyRows(wrapper.wrapper, [0, 2, 5, 8, 1, 11, 14, 17, 2, 20, 23, 26]); + verifyDropdowns(wrapper.wrapper, [2, 2]); // Axis 2, index 2 + + // Apply a slice with no corresponding dropdown + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, :1, :]'); + verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[9, 10, 11]', 2, '[18, 19, 20]']); + verifyDropdowns(wrapper.wrapper, ['', '']); // Dropdowns should be unset + + // Uncheck slice checkbox and verify original contents are restored + const disableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await disableSlicing; + verifyRows(wrapper.wrapper, [ + 0, + '[0, 1, 2]', + '[3, 4, 5]', + '[6, 7, 8]', + 1, + '[9, 10, 11]', + '[12, 13, 14]', + '[15, 16, 17]', + 2, + '[18, 19, 20]', + '[21, 22, 23]', + '[24, 25, 26]' + ]); + verifyControlsDisabled(wrapper.wrapper, 2, ''); + + // Recheck slice checkbox and verify slice expression is restored + const reenableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await reenableSlicing; + verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[9, 10, 11]', 2, '[18, 19, 20]']); + verifyReadonlyIndicator(wrapper.wrapper, '[:, :1, :]'); + + // Enter an invalid slice expression and verify error message is rendered + editInputValue(wrapper, '[:]'); + assert.ok( + wrapper.wrapper.find('.error-message').length === 1, + 'No error message rendered for invalid slice' + ); + }); + + runMountedTest('Slice 4D', async (wrapper) => { + const code = `import torch +import numpy as np +arr = np.arange(81).reshape(3,3,3,3) +foo = torch.tensor(arr)`; + // Create data viewer + await injectCode(code); + const gotAllRows = getCompletedPromise(wrapper); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3, 3, 3)'); + assert.ok(dv, 'DataViewer not created'); + await gotAllRows; + verifyRows(wrapper.wrapper, [ + 0, + `[[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]]`, + '[[9, 10, 11],\n [12, 13, 14],\n [15, 16, 17]]', + '[[18, 19, 20],\n [21, 22, 23],\n [24, 25, 26]]', + 1, + '[[27, 28, 29],\n [30, 31, 32],\n [33, 34, 35]]', + '[[36, 37, 38],\n [39, 40, 41],\n [42, 43, 44]]', + '[[45, 46, 47],\n [48, 49, 50],\n [51, 52, 53]]', + 2, + '[[54, 55, 56],\n [57, 58, 59],\n [60, 61, 62]]', + '[[63, 64, 65],\n [66, 67, 68],\n [69, 70, 71]]', + '[[72, 73, 74],\n [75, 76, 77],\n [78, 79, 80]]' + ]); + verifyControlsDisabled(wrapper.wrapper, 4, ''); + + // Toggle on slicing. Slice should immediately be applied + const enableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await enableSlicing; + verifyReadonlyIndicator(wrapper.wrapper, '[0, 0, :, :]'); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + + // Apply a slice via input box and verify that dropdowns update + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, 0, :, 2]'); + verifyRows(wrapper.wrapper, [0, 2, 5, 8, 1, 29, 32, 35, 2, 56, 59, 62]); + verifyDropdowns(wrapper.wrapper, [1, 0, 3, 2]); // Axis 1 index 0, axis 3 index 2 + + // Apply a slice with no corresponding dropdown + await applySliceAndVerifyReadonlyIndicator(wrapper, '[1, 2, 0, :]'); + verifyRows(wrapper.wrapper, [0, 45, 1, 46, 2, 47]); + verifyDropdowns(wrapper.wrapper, ['', '', '', '']); // Dropdowns should be unset + + // Uncheck slice checkbox and verify original contents are restored + const disableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await disableSlicing; + verifyRows(wrapper.wrapper, [ + 0, + `[[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]]`, + '[[9, 10, 11],\n [12, 13, 14],\n [15, 16, 17]]', + '[[18, 19, 20],\n [21, 22, 23],\n [24, 25, 26]]', + 1, + '[[27, 28, 29],\n [30, 31, 32],\n [33, 34, 35]]', + '[[36, 37, 38],\n [39, 40, 41],\n [42, 43, 44]]', + '[[45, 46, 47],\n [48, 49, 50],\n [51, 52, 53]]', + 2, + '[[54, 55, 56],\n [57, 58, 59],\n [60, 61, 62]]', + '[[63, 64, 65],\n [66, 67, 68],\n [69, 70, 71]]', + '[[72, 73, 74],\n [75, 76, 77],\n [78, 79, 80]]' + ]); + verifyControlsDisabled(wrapper.wrapper, 4, ''); + + // Recheck slice checkbox and verify slice expression is restored + const reenableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await reenableSlicing; + verifyRows(wrapper.wrapper, [0, 45, 1, 46, 2, 47]); + verifyReadonlyIndicator(wrapper.wrapper, '[1, 2, 0, :]'); + + // Enter an invalid slice expression and verify error message is rendered + editInputValue(wrapper, '[:]'); + assert.ok( + wrapper.wrapper.find('.error-message').length === 1, + 'No error message rendered for invalid slice' + ); + }); + + runMountedTest('Refresh with slice applied', async (wrapper) => { + // Same shape, old slice is still valid, ensure update in place + const code = `import torch +foo = torch.tensor([[[0, 1, 2], [3, 4, 5]]])`; + // Create data viewer + const notebook = await injectCode(code); + const gotAllRows = getCompletedPromise(wrapper); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(1, 2, 3)'); + assert.ok(dv, 'DataViewer not created'); + await gotAllRows; + + // Toggle on slicing. Slice should immediately be applied + const enableSlicing = getCompletedPromise(wrapper); + toggleCheckbox(wrapper.wrapper); + await enableSlicing; + verifyReadonlyIndicator(wrapper.wrapper, '[0, :, :]'); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); + + // Apply a slice via input box and verify that dropdowns update + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, 1, :]'); + verifyRows(wrapper.wrapper, [0, 3, 4, 5]); + verifyDropdowns(wrapper.wrapper, [1, 1]); // Axis 1 index 1 + + // New variable value but same shape. Ensure slice updates in-place + await executeCode('foo = torch.tensor([[[6, 7, 8], [9, 10, 11]]])', notebook!); + const refreshPromise = getCompletedPromise(wrapper); + await dv.refreshData(); + await refreshPromise; + verifyReadonlyIndicator(wrapper.wrapper, '[:, 1, :]'); + verifyRows(wrapper.wrapper, [0, 9, 10, 11]); + + // New variable shape invalidates old slice + await executeCode('foo = torch.tensor([[[0, 1]], [[2, 3]]])', notebook!); + // Ensure data updates + const invalidateSlicePromise = getCompletedPromise(wrapper); + await dv.refreshData(); + await invalidateSlicePromise; + // Preselected slice is applied + verifyReadonlyIndicator(wrapper.wrapper, '[0, :, :]'); + verifyRows(wrapper.wrapper, [0, 0, 1]); + }); + }); + // https://github.com/microsoft/vscode-jupyter/issues/4706 // Disabled for now. Root cause is that pd.replace isn't recursive over objects in DataFrames, // so our current inf/nan handling does not work for DataFrames whose cells are Series, ndarray, or list From 07080893c393af3836131baa3e80369ceda1034a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 21:54:13 -0800 Subject: [PATCH 06/11] Cleanup --- src/test/datascience/dataviewer.functional.test.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index 988bd1ec371..3ff501d0e83 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -182,9 +182,6 @@ suite('DataScience DataViewer tests', () => { const sliceControlWrapper = wrapper.find(SliceControl); sliceControlWrapper.update(); assert.ok(sliceControlWrapper && sliceControlWrapper.length > 0, 'Slice control not found'); - // sliceControlWrapper.simulate('click'); - // await sleep(1000); - // sliceControlWrapper.update(); return sliceControlWrapper; } From b50180b8491c4291a1964883f0d15d5afcfc5229 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 6 Mar 2021 23:23:50 -0800 Subject: [PATCH 07/11] Fix row chunk bug --- .../dataframes/vscodeDataFrame.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py b/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py index 3210cc61585..9b6800900b5 100644 --- a/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py +++ b/pythonFiles/vscode_datascience_helpers/dataframes/vscodeDataFrame.py @@ -91,14 +91,14 @@ def _VSCODE_convertTensorToDataFrame(tensor, start=None, end=None): def _VSCODE_convertToDataFrame(df, start=None, end=None): vartype = type(df) if isinstance(df, list): - df = _VSCODE_pd.DataFrame(df) + df = _VSCODE_pd.DataFrame(df).iloc[start:end] elif isinstance(df, _VSCODE_pd.Series): - df = _VSCODE_pd.Series.to_frame(df) + df = _VSCODE_pd.Series.to_frame(df).iloc[start:end] elif isinstance(df, dict): df = _VSCODE_pd.Series(df) - df = _VSCODE_pd.Series.to_frame(df) + df = _VSCODE_pd.Series.to_frame(df).iloc[start:end] elif hasattr(df, "toPandas"): - df = df.toPandas() + df = df.toPandas().iloc[start:end] elif ( hasattr(vartype, "__name__") and vartype.__name__ in _VSCODE_allowedTensorTypes ): @@ -109,7 +109,7 @@ def _VSCODE_convertToDataFrame(df, start=None, end=None): """Disabling bandit warning for try, except, pass. We want to swallow all exceptions here to not crash on variable fetching""" try: - temp = _VSCODE_pd.DataFrame(df) + temp = _VSCODE_pd.DataFrame(df).iloc[start:end] df = temp except: # nosec pass From b92a73f6d74acd1c67c80bd2a9dede516a1a1aeb Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sun, 7 Mar 2021 12:44:26 -0800 Subject: [PATCH 08/11] More robust test cases, test dropdowns, fix fluentui icon errors --- src/datascience-ui/data-explorer/index.tsx | 2 - src/datascience-ui/plot/mainPanel.tsx | 3 + .../dataviewer.functional.test.tsx | 176 ++++++++++++------ 3 files changed, 120 insertions(+), 61 deletions(-) diff --git a/src/datascience-ui/data-explorer/index.tsx b/src/datascience-ui/data-explorer/index.tsx index fc3b639bd68..7c2a3b53cc5 100644 --- a/src/datascience-ui/data-explorer/index.tsx +++ b/src/datascience-ui/data-explorer/index.tsx @@ -11,7 +11,6 @@ import '../common/index.css'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { initializeIcons } from '@fluentui/react'; import { IVsCodeApi } from '../react-common/postOffice'; import { detectBaseTheme } from '../react-common/themeDetector'; @@ -21,7 +20,6 @@ import { MainPanel } from './mainPanel'; export declare function acquireVsCodeApi(): IVsCodeApi; const baseTheme = detectBaseTheme(); -initializeIcons(); /* eslint-disable */ ReactDOM.render( diff --git a/src/datascience-ui/plot/mainPanel.tsx b/src/datascience-ui/plot/mainPanel.tsx index 0adbe702cae..ee1bdf9b674 100644 --- a/src/datascience-ui/plot/mainPanel.tsx +++ b/src/datascience-ui/plot/mainPanel.tsx @@ -21,6 +21,9 @@ import { SvgViewer } from '../react-common/svgViewer'; import { TestSvg } from './testSvg'; import { Toolbar } from './toolbar'; +import { initializeIcons } from '@fluentui/react'; +initializeIcons(); + // Our css has to come after in order to override body styles export interface IMainPanelProps { skipDefault?: boolean; diff --git a/src/test/datascience/dataviewer.functional.test.tsx b/src/test/datascience/dataviewer.functional.test.tsx index 3ff501d0e83..fbe181ad3fe 100644 --- a/src/test/datascience/dataviewer.functional.test.tsx +++ b/src/test/datascience/dataviewer.functional.test.tsx @@ -37,6 +37,7 @@ import { Dropdown } from '@fluentui/react'; interface ISliceControlTestInterface { toggleEnablement: () => void; + applyDropdownsToInputBox: () => void; } // import { asyncDump } from '../common/asyncDump'; @@ -178,13 +179,6 @@ suite('DataScience DataViewer tests', () => { }); } - function findSliceControlPanel(wrapper: ReactWrapper, React.Component>) { - const sliceControlWrapper = wrapper.find(SliceControl); - sliceControlWrapper.update(); - assert.ok(sliceControlWrapper && sliceControlWrapper.length > 0, 'Slice control not found'); - return sliceControlWrapper; - } - function findGrid(wrapper: ReactWrapper, React.Component>) { const mainPanelWrapper = wrapper.find(MainPanel); assert.ok(mainPanelWrapper && mainPanelWrapper.length > 0, 'Grid not found to sort on'); @@ -533,6 +527,13 @@ suite('DataScience DataViewer tests', () => { }); suite('Data viewer slice data', async () => { + function findSliceControlPanel(wrapper: ReactWrapper, React.Component>) { + const sliceControlWrapper = wrapper.find(SliceControl); + sliceControlWrapper.update(); + assert.ok(sliceControlWrapper && sliceControlWrapper.length > 0, 'Slice control not found'); + return sliceControlWrapper; + } + function verifyReadonlyIndicator( wrapper: ReactWrapper, React.Component>, currentSlice: string @@ -551,14 +552,14 @@ suite('DataScience DataViewer tests', () => { const html = sliceControl.html(); const root = parse(html) as any; const cells = root.querySelectorAll('.ms-Dropdown-title'); - assert.ok(cells.length >= rows.length, 'Not enough cells found'); - // Cells should be an array that matches up to the values we expect. + assert.ok(cells.length >= rows.length, 'Not enough dropdowns found'); + // Now verify the list of dropdowns have the expected values for (let i = 0; i < rows.length; i += 1) { - // Span should have our value (based on the CellFormatter's output) + // Span reflects the dropdown's current selection const span = cells[i] as HTMLSpanElement; assert.ok(span, `Span ${i} not found`); const val = rows[i].toString(); - assert.equal(span.innerHTML, val, `Row ${i} not matching. ${span.innerHTML} !== ${val}`); + assert.equal(span.innerHTML, val, `Dropdown ${i} selection not matching. ${span.innerHTML} !== ${val}`); } } @@ -606,19 +607,38 @@ suite('DataScience DataViewer tests', () => { verifyReadonlyIndicator(wrapper.wrapper, slice); } + async function changeDropdown( + wrapper: IMountedWebView, + dropdownType: 'Axis' | 'Index', + dropdownRow: number, + newValue: number | string + ) { + const gotSlice = getCompletedPromise(wrapper); + const sliceControl = findSliceControlPanel(wrapper.wrapper); + // Do a setstate because we don't have direct access to the dropdown selection change handler + const newState = { [`selected${dropdownType}${dropdownRow}`]: newValue }; + sliceControl.setState(newState); + const instance = (sliceControl.instance() as any) as ISliceControlTestInterface; + // This is what gets called in the dropdown change handler. Manually call it because + // simulating a change event on the Dropdown node doesn't seem to do anything + instance.applyDropdownsToInputBox(); + wrapper.wrapper.render(); + await gotSlice; + } + runMountedTest('Slice 2D', async (wrapper) => { const code = `import torch import numpy as np -arr = np.arange(9).reshape(3,3) +arr = np.arange(6).reshape(2, 3) foo = torch.tensor(arr)`; // Create data viewer await injectCode(code); const gotAllRows = getCompletedPromise(wrapper); - const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3)'); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(2, 3)'); assert.ok(dv, 'DataViewer not created'); await gotAllRows; - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); verifyControlsDisabled(wrapper.wrapper, 2, ''); // Apply a slice via input box and verify that dropdowns update @@ -627,23 +647,33 @@ foo = torch.tensor(arr)`; verifyRows(wrapper.wrapper, [0, 3, 1, 4, 2, 5]); verifyDropdowns(wrapper.wrapper, [0, 1]); // Axis 0, index 1 + // Change the dropdowns and verify that the slice expression updates + await changeDropdown(wrapper, 'Axis', 0, 1); + verifyReadonlyIndicator(wrapper.wrapper, '[:, 1]'); + verifyDropdowns(wrapper.wrapper, [1, 1]); + verifyRows(wrapper.wrapper, [0, 1, 1, 4]); + assert.ok( + (wrapper.wrapper.find('.slice-data').getDOMNode() as HTMLInputElement).value === '[:, 1]', + 'Input box did not update to match slice' + ); + // Apply a slice with no corresponding dropdown - await applySliceAndVerifyReadonlyIndicator(wrapper, '[:2, :]'); - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, :2]'); + verifyRows(wrapper.wrapper, [0, 0, 1, 1, 3, 4]); verifyDropdowns(wrapper.wrapper, ['', '']); // Dropdowns should be unset // Uncheck slice checkbox and verify original contents are restored const disableSlicing = getCompletedPromise(wrapper); toggleCheckbox(wrapper.wrapper); await disableSlicing; - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); verifyControlsDisabled(wrapper.wrapper, 2, ''); // Recheck slice checkbox and verify slice expression is restored const reenableSlicing = getCompletedPromise(wrapper); toggleCheckbox(wrapper.wrapper); await reenableSlicing; - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5]); + verifyRows(wrapper.wrapper, [0, 0, 1, 1, 3, 4]); // Enter an invalid slice expression and verify error message is rendered editInputValue(wrapper, '[:]'); @@ -656,12 +686,12 @@ foo = torch.tensor(arr)`; runMountedTest('Slice 3D', async (wrapper) => { const code = `import torch import numpy as np -arr = np.arange(27).reshape(3,3,3) +arr = np.arange(24).reshape(2,4,3) foo = torch.tensor(arr)`; // Create data viewer await injectCode(code); const gotAllRows = getCompletedPromise(wrapper); - const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3, 3)'); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(2, 4, 3)'); assert.ok(dv, 'DataViewer not created'); await gotAllRows; verifyRows(wrapper.wrapper, [ @@ -669,14 +699,12 @@ foo = torch.tensor(arr)`; '[0, 1, 2]', '[3, 4, 5]', '[6, 7, 8]', - 1, '[9, 10, 11]', + 1, '[12, 13, 14]', '[15, 16, 17]', - 2, '[18, 19, 20]', - '[21, 22, 23]', - '[24, 25, 26]' + '[21, 22, 23]' ]); verifyControlsDisabled(wrapper.wrapper, 2, ''); @@ -685,16 +713,26 @@ foo = torch.tensor(arr)`; toggleCheckbox(wrapper.wrapper); await enableSlicing; verifyReadonlyIndicator(wrapper.wrapper, '[0, :, :]'); - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8, 3, 9, 10, 11]); + + // Change the dropdowns and verify that the slice expression updates + await changeDropdown(wrapper, 'Axis', 0, 1); + verifyReadonlyIndicator(wrapper.wrapper, '[:, 0, :]'); + verifyDropdowns(wrapper.wrapper, [1, 0]); + verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 12, 13, 14]); + assert.ok( + (wrapper.wrapper.find('.slice-data').getDOMNode() as HTMLInputElement).value === '[:, 0, :]', + 'Input box did not update to match slice' + ); // Apply a slice via input box and verify that dropdowns update await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, :, 2]'); - verifyRows(wrapper.wrapper, [0, 2, 5, 8, 1, 11, 14, 17, 2, 20, 23, 26]); + verifyRows(wrapper.wrapper, [0, 2, 5, 8, 11, 1, 14, 17, 20, 23]); verifyDropdowns(wrapper.wrapper, [2, 2]); // Axis 2, index 2 // Apply a slice with no corresponding dropdown await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, :1, :]'); - verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[9, 10, 11]', 2, '[18, 19, 20]']); + verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[12, 13, 14]']); verifyDropdowns(wrapper.wrapper, ['', '']); // Dropdowns should be unset // Uncheck slice checkbox and verify original contents are restored @@ -706,14 +744,12 @@ foo = torch.tensor(arr)`; '[0, 1, 2]', '[3, 4, 5]', '[6, 7, 8]', - 1, '[9, 10, 11]', + 1, '[12, 13, 14]', '[15, 16, 17]', - 2, '[18, 19, 20]', - '[21, 22, 23]', - '[24, 25, 26]' + '[21, 22, 23]' ]); verifyControlsDisabled(wrapper.wrapper, 2, ''); @@ -721,7 +757,7 @@ foo = torch.tensor(arr)`; const reenableSlicing = getCompletedPromise(wrapper); toggleCheckbox(wrapper.wrapper); await reenableSlicing; - verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[9, 10, 11]', 2, '[18, 19, 20]']); + verifyRows(wrapper.wrapper, [0, '[0, 1, 2]', 1, '[12, 13, 14]']); verifyReadonlyIndicator(wrapper.wrapper, '[:, :1, :]'); // Enter an invalid slice expression and verify error message is rendered @@ -735,27 +771,33 @@ foo = torch.tensor(arr)`; runMountedTest('Slice 4D', async (wrapper) => { const code = `import torch import numpy as np -arr = np.arange(81).reshape(3,3,3,3) +arr = np.arange(30).reshape(3, 5, 1, 2) foo = torch.tensor(arr)`; // Create data viewer await injectCode(code); const gotAllRows = getCompletedPromise(wrapper); - const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 3, 3, 3)'); + const dv = await createJupyterVariableDataViewer('foo', 'Tensor', '(3, 5, 1, 2)'); assert.ok(dv, 'DataViewer not created'); await gotAllRows; verifyRows(wrapper.wrapper, [ 0, - `[[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]]`, - '[[9, 10, 11],\n [12, 13, 14],\n [15, 16, 17]]', - '[[18, 19, 20],\n [21, 22, 23],\n [24, 25, 26]]', + '[[0, 1]]', + `[[2, 3]]`, + '[[4, 5]]', + '[[6, 7]]', + '[[8, 9]]', 1, - '[[27, 28, 29],\n [30, 31, 32],\n [33, 34, 35]]', - '[[36, 37, 38],\n [39, 40, 41],\n [42, 43, 44]]', - '[[45, 46, 47],\n [48, 49, 50],\n [51, 52, 53]]', + '[[10, 11]]', + '[[12, 13]]', + '[[14, 15]]', + '[[16, 17]]', + '[[18, 19]]', 2, - '[[54, 55, 56],\n [57, 58, 59],\n [60, 61, 62]]', - '[[63, 64, 65],\n [66, 67, 68],\n [69, 70, 71]]', - '[[72, 73, 74],\n [75, 76, 77],\n [78, 79, 80]]' + '[[20, 21]]', + '[[22, 23]]', + '[[24, 25]]', + '[[26, 27]]', + '[[28, 29]]' ]); verifyControlsDisabled(wrapper.wrapper, 4, ''); @@ -764,16 +806,26 @@ foo = torch.tensor(arr)`; toggleCheckbox(wrapper.wrapper); await enableSlicing; verifyReadonlyIndicator(wrapper.wrapper, '[0, 0, :, :]'); - verifyRows(wrapper.wrapper, [0, 0, 1, 2, 1, 3, 4, 5, 2, 6, 7, 8]); + verifyRows(wrapper.wrapper, [0, 0, 1]); + + // Change the dropdowns and verify that the slice expression updates + await changeDropdown(wrapper, 'Index', 1, 2); + verifyReadonlyIndicator(wrapper.wrapper, '[0, 2, :, :]'); + verifyDropdowns(wrapper.wrapper, [0, 0, 1, 2]); + verifyRows(wrapper.wrapper, [0, 4, 5]); + assert.ok( + (wrapper.wrapper.find('.slice-data').getDOMNode() as HTMLInputElement).value === '[0, 2, :, :]', + 'Input box did not update to match slice' + ); // Apply a slice via input box and verify that dropdowns update - await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, 0, :, 2]'); - verifyRows(wrapper.wrapper, [0, 2, 5, 8, 1, 29, 32, 35, 2, 56, 59, 62]); - verifyDropdowns(wrapper.wrapper, [1, 0, 3, 2]); // Axis 1 index 0, axis 3 index 2 + await applySliceAndVerifyReadonlyIndicator(wrapper, '[:, 4, :, 1]'); + verifyRows(wrapper.wrapper, [0, 9, 1, 19, 2, 29]); + verifyDropdowns(wrapper.wrapper, [1, 4, 3, 1]); // Axis 1 index 4, axis 3 index 1 // Apply a slice with no corresponding dropdown await applySliceAndVerifyReadonlyIndicator(wrapper, '[1, 2, 0, :]'); - verifyRows(wrapper.wrapper, [0, 45, 1, 46, 2, 47]); + verifyRows(wrapper.wrapper, [0, 14, 1, 15]); verifyDropdowns(wrapper.wrapper, ['', '', '', '']); // Dropdowns should be unset // Uncheck slice checkbox and verify original contents are restored @@ -782,17 +834,23 @@ foo = torch.tensor(arr)`; await disableSlicing; verifyRows(wrapper.wrapper, [ 0, - `[[0, 1, 2],\n [3, 4, 5],\n [6, 7, 8]]`, - '[[9, 10, 11],\n [12, 13, 14],\n [15, 16, 17]]', - '[[18, 19, 20],\n [21, 22, 23],\n [24, 25, 26]]', + '[[0, 1]]', + `[[2, 3]]`, + '[[4, 5]]', + '[[6, 7]]', + '[[8, 9]]', 1, - '[[27, 28, 29],\n [30, 31, 32],\n [33, 34, 35]]', - '[[36, 37, 38],\n [39, 40, 41],\n [42, 43, 44]]', - '[[45, 46, 47],\n [48, 49, 50],\n [51, 52, 53]]', + '[[10, 11]]', + '[[12, 13]]', + '[[14, 15]]', + '[[16, 17]]', + '[[18, 19]]', 2, - '[[54, 55, 56],\n [57, 58, 59],\n [60, 61, 62]]', - '[[63, 64, 65],\n [66, 67, 68],\n [69, 70, 71]]', - '[[72, 73, 74],\n [75, 76, 77],\n [78, 79, 80]]' + '[[20, 21]]', + '[[22, 23]]', + '[[24, 25]]', + '[[26, 27]]', + '[[28, 29]]' ]); verifyControlsDisabled(wrapper.wrapper, 4, ''); @@ -800,8 +858,8 @@ foo = torch.tensor(arr)`; const reenableSlicing = getCompletedPromise(wrapper); toggleCheckbox(wrapper.wrapper); await reenableSlicing; - verifyRows(wrapper.wrapper, [0, 45, 1, 46, 2, 47]); - verifyReadonlyIndicator(wrapper.wrapper, '[1, 2, 0, :]'); + verifyRows(wrapper.wrapper, [0, 14, 1, 15]); + verifyDropdowns(wrapper.wrapper, ['', '', '', '']); // Dropdowns should be unset // Enter an invalid slice expression and verify error message is rendered editInputValue(wrapper, '[:]'); From 89ca95d44e701937b557166c3c91a3fbb19f7590 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sun, 7 Mar 2021 14:56:21 -0800 Subject: [PATCH 09/11] News entry --- news/3 Code Health/5066.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/3 Code Health/5066.md diff --git a/news/3 Code Health/5066.md b/news/3 Code Health/5066.md new file mode 100644 index 00000000000..d32abe7541a --- /dev/null +++ b/news/3 Code Health/5066.md @@ -0,0 +1 @@ +Add tests for data viewer slice data functionality. \ No newline at end of file From f7cc25a0877324882b888a774893ab74de9b049a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 8 Mar 2021 14:52:28 -0800 Subject: [PATCH 10/11] Add initializeIcons comment --- src/datascience-ui/data-explorer/mainPanel.tsx | 3 +++ src/datascience-ui/plot/mainPanel.tsx | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index ef4dd9dc240..882ae9c681b 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -34,6 +34,9 @@ import '../react-common/seti/seti.less'; import { SliceControl } from './sliceControl'; import { debounce } from 'lodash'; +import { initializeIcons } from '@fluentui/react'; +initializeIcons(); // Register all FluentUI icons being used to prevent developer console errors + const SliceableTypes: Set = new Set(['ndarray', 'Tensor', 'EagerTensor']); // Our css has to come after in order to override body styles diff --git a/src/datascience-ui/plot/mainPanel.tsx b/src/datascience-ui/plot/mainPanel.tsx index ee1bdf9b674..40499f1da18 100644 --- a/src/datascience-ui/plot/mainPanel.tsx +++ b/src/datascience-ui/plot/mainPanel.tsx @@ -21,8 +21,6 @@ import { SvgViewer } from '../react-common/svgViewer'; import { TestSvg } from './testSvg'; import { Toolbar } from './toolbar'; -import { initializeIcons } from '@fluentui/react'; -initializeIcons(); // Our css has to come after in order to override body styles export interface IMainPanelProps { From eae618e90e5a913ff2c207d62ea95c3c3a39a321 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 8 Mar 2021 15:47:25 -0800 Subject: [PATCH 11/11] Oops --- src/datascience-ui/plot/mainPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/datascience-ui/plot/mainPanel.tsx b/src/datascience-ui/plot/mainPanel.tsx index 40499f1da18..0adbe702cae 100644 --- a/src/datascience-ui/plot/mainPanel.tsx +++ b/src/datascience-ui/plot/mainPanel.tsx @@ -21,7 +21,6 @@ import { SvgViewer } from '../react-common/svgViewer'; import { TestSvg } from './testSvg'; import { Toolbar } from './toolbar'; - // Our css has to come after in order to override body styles export interface IMainPanelProps { skipDefault?: boolean;