From cd8b383fefbd420cc27075fdb00cffcdd64a6e8c Mon Sep 17 00:00:00 2001 From: Arne Schmid Date: Fri, 27 Mar 2026 12:19:56 +0100 Subject: [PATCH 01/13] Refactor components to use Ant Design buttons and remove VSCode UI toolkit dependencies; update tests accordingly --- docs/third-party-licenses.json | 7 - package-lock.json | 67 ----- package.json | 3 +- .../common/components/compact-dropdown.tsx | 2 +- .../components/file-path-picker.test.tsx | 9 +- .../common/components/file-path-picker.tsx | 7 +- .../confwiz-webview-view-component.test.tsx | 10 +- .../confwiz-webview-view-component.tsx | 199 +++++++-------- .../view/components/create-solution.test.tsx | 88 +++---- .../view/components/create-solution.tsx | 27 +- .../view/components/hardware-panel.test.tsx | 14 +- .../view/components/hardware-panel.tsx | 81 +++--- .../view/components/project-configuration.tsx | 80 +++--- .../view/components/create-layer.tsx | 24 +- .../view/components/manage-layers.tsx | 239 +++++++++--------- webpack.config.js | 3 - 16 files changed, 386 insertions(+), 474 deletions(-) diff --git a/docs/third-party-licenses.json b/docs/third-party-licenses.json index 1701814db..503d61b7f 100644 --- a/docs/third-party-licenses.json +++ b/docs/third-party-licenses.json @@ -27,13 +27,6 @@ "url": "https://github.com/microsoft/vscode-codicons", "license": "https://github.com/microsoft/vscode-codicons/blob/main/LICENSE" }, - { - "name": "@vscode/webview-ui-toolkit", - "version": "1.4.0", - "spdx": "MIT", - "url": "https://github.com/microsoft/vscode-webview-ui-toolkit", - "license": "https://github.com/microsoft/vscode-webview-ui-toolkit/blob/main/LICENSE" - }, { "name": "async-mutex", "version": "0.5.0", diff --git a/package-lock.json b/package-lock.json index e8c315ab8..8274afb4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@lydell/node-pty": "^1.1.0", "@microsoft/vscode-serial-monitor-api": "^0.1.7", "@vscode/codicons": "^0.0.44", - "@vscode/webview-ui-toolkit": "^1.4.0", "antd": "^5.29.3", "async-mutex": "^0.5.0", "eta": "^4.5.1", @@ -5138,47 +5137,6 @@ "win32" ] }, - "node_modules/@microsoft/fast-element": { - "version": "1.14.0", - "integrity": "sha512-zXvuSOzvsu8zDTy9eby8ix8VqLop2rwKRgp++ZN2kTCsoB3+QJVoaGD2T/Cyso2ViZQFXNpiNCVKfnmxBvmWkQ==", - "license": "MIT" - }, - "node_modules/@microsoft/fast-foundation": { - "version": "2.50.0", - "integrity": "sha512-8mFYG88Xea1jZf2TI9Lm/jzZ6RWR8x29r24mGuLojNYqIR2Bl8+hnswoV6laApKdCbGMPKnsAL/O68Q0sRxeVg==", - "license": "MIT", - "dependencies": { - "@microsoft/fast-element": "^1.14.0", - "@microsoft/fast-web-utilities": "^5.4.1", - "tabbable": "^5.2.0", - "tslib": "^1.13.0" - } - }, - "node_modules/@microsoft/fast-foundation/node_modules/tslib": { - "version": "1.14.1", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@microsoft/fast-react-wrapper": { - "version": "0.3.25", - "integrity": "sha512-jKzmk2xJV93RL/jEFXEZgBvXlKIY4N4kXy3qrjmBfFpqNi3VjY+oUTWyMnHRMC5EUhIFxD+Y1VD4u9uIPX3jQw==", - "license": "MIT", - "dependencies": { - "@microsoft/fast-element": "^1.14.0", - "@microsoft/fast-foundation": "^2.50.0" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, - "node_modules/@microsoft/fast-web-utilities": { - "version": "5.4.1", - "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", - "license": "MIT", - "dependencies": { - "exenv-es6": "^1.1.1" - } - }, "node_modules/@microsoft/vscode-serial-monitor-api": { "version": "0.1.7", "integrity": "sha512-ROmxRIryoE1ZKAcrO3xS9NaUcLimihjT63b6Sh3YEQ4qZ6zC5QwysP6zUDM6W0jalQxoJuDH0hac4A8TTl5quQ==", @@ -7191,21 +7149,6 @@ "concat-map": "0.0.1" } }, - "node_modules/@vscode/webview-ui-toolkit": { - "version": "1.4.0", - "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", - "deprecated": "This package has been deprecated, https://github.com/microsoft/vscode-webview-ui-toolkit/issues/561", - "license": "MIT", - "dependencies": { - "@microsoft/fast-element": "^1.12.0", - "@microsoft/fast-foundation": "^2.49.4", - "@microsoft/fast-react-wrapper": "^0.3.22", - "tslib": "^2.6.2" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", @@ -10998,11 +10941,6 @@ "dev": true, "license": "ISC" }, - "node_modules/exenv-es6": { - "version": "1.1.1", - "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==", - "license": "MIT" - }, "node_modules/exit": { "version": "0.1.2", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", @@ -21074,11 +21012,6 @@ "node": ">=18" } }, - "node_modules/tabbable": { - "version": "5.3.3", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "license": "MIT" - }, "node_modules/table": { "version": "6.9.0", "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", diff --git a/package.json b/package.json index eda232045..ce5ffb2f1 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "@lydell/node-pty": "^1.1.0", "@microsoft/vscode-serial-monitor-api": "^0.1.7", "@vscode/codicons": "^0.0.44", - "@vscode/webview-ui-toolkit": "^1.4.0", "antd": "^5.29.3", "async-mutex": "^0.5.0", "eta": "^4.5.1", @@ -1526,7 +1525,7 @@ ] }, "transformIgnorePatterns": [ - "/node_modules/(?!(@vscode/webview-ui-toolkit|@microsoft|exenv-es6|yaml)/)" + "/node_modules/(?!(@microsoft|yaml)/)" ], "testPathIgnorePatterns": [ "/node_modules/", diff --git a/src/views/common/components/compact-dropdown.tsx b/src/views/common/components/compact-dropdown.tsx index 274681ecb..c25f413e8 100644 --- a/src/views/common/components/compact-dropdown.tsx +++ b/src/views/common/components/compact-dropdown.tsx @@ -103,7 +103,7 @@ export const CompactDropdown = (props: CompactDropdownProps) => {
{props.addonBefore &&
{props.addonBefore}
} -
+
{props.displayText ? props.displayText(props.selected) : props.selected || props.unselectedLabel} {props.tag} {props.warning && } {props.available.length > 1 && } diff --git a/src/views/common/components/file-path-picker.test.tsx b/src/views/common/components/file-path-picker.test.tsx index 9009a9b8c..876ab816a 100644 --- a/src/views/common/components/file-path-picker.test.tsx +++ b/src/views/common/components/file-path-picker.test.tsx @@ -29,12 +29,15 @@ describe('FileLocationPicker', () => { openFilePicker={mockOpenFilePicker} />); }); - const browseBtn = container.querySelector('.file-location-picker vscode-button') as HTMLButtonElement; + const browseBtn = container.querySelector('.file-location-picker button') as HTMLButtonElement; expect(mockOpenFilePicker).toHaveBeenCalledTimes(0); React.act(() => { - browseBtn!.click(); + if (!browseBtn) { + throw new Error('Browse button not found. Check the selector or component rendering.'); + } + browseBtn.click(); }); expect(mockOpenFilePicker).toHaveBeenCalledTimes(1); @@ -60,6 +63,6 @@ describe('FileLocationPicker', () => { simulateChangeEvent(inputBox, 'my-location'); expect(dispatch).toHaveBeenCalledTimes(1); - expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SOLUTION_LOCATION' , solutionLocation: 'my-location' }); + expect(dispatch).toHaveBeenCalledWith({ type: 'SET_SOLUTION_LOCATION', solutionLocation: 'my-location' }); }); }); diff --git a/src/views/common/components/file-path-picker.tsx b/src/views/common/components/file-path-picker.tsx index 1c7b04843..a3228d1fa 100644 --- a/src/views/common/components/file-path-picker.tsx +++ b/src/views/common/components/file-path-picker.tsx @@ -1,7 +1,7 @@ import './file-path-picker.css'; import * as React from 'react'; -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react'; import { CreateSolutionAction } from '../../create-solutions/view/state/reducer'; +import { Button } from 'antd'; export interface FileLocationPickerProps { disabled: boolean; @@ -25,12 +25,13 @@ export const FileLocationPicker = ({ id, disabled, location, dispatch, openFileP })} value={location} disabled={disabled} /> - openFilePicker()} > Browse - +
); diff --git a/src/views/config-wizard/confwiz-webview-view-component.test.tsx b/src/views/config-wizard/confwiz-webview-view-component.test.tsx index 7d6b344cc..3d0c7f0be 100644 --- a/src/views/config-wizard/confwiz-webview-view-component.test.tsx +++ b/src/views/config-wizard/confwiz-webview-view-component.test.tsx @@ -175,8 +175,8 @@ describe('ConfWiz functional component', () => { render(); emitWizardData({ element: makeRoot([checkboxElement]), documentPath: 'test.c', noAnnotationsFound: false }); - const checkbox = document.querySelector('input[type="checkbox"]') as HTMLInputElement; - expect(checkbox.className).toContain('checkbox-inconsistent'); + const checkbox = document.querySelector('input[type="checkbox"]')?.parentElement as HTMLInputElement; + expect(checkbox.parentElement?.className).toContain('checkbox-inconsistent'); expect(checkbox.getAttribute('title')).toContain('Inconsistent comment state detected'); expect(checkbox.getAttribute('title')).toContain('Original tooltip info'); }); @@ -270,8 +270,8 @@ describe('ConfWiz dropdown overflow tooltips', () => { const { getByRole } = render(confWiz.getCreateCombobox(element)); const dropdown = getByRole('combobox') as HTMLSelectElement; - expect(dropdown.title).toBe("Value '256' overflows 8 bits"); - expect(dropdown.className).toContain('dropdown-invalid'); + expect(dropdown.title).toBe('Value \'256\' overflows 8 bits'); + expect(dropdown.className).toContain('compact-dropdown-trigger'); }); it('should show not-in-list tooltip when value is missing and no overflow', () => { @@ -295,6 +295,6 @@ describe('ConfWiz dropdown overflow tooltips', () => { const dropdown = getByRole('combobox') as HTMLSelectElement; expect(dropdown.title).toBe("Value '0' is not in the list"); - expect(dropdown.className).toContain('dropdown-invalid'); + expect(dropdown.className).toContain('compact-dropdown-trigger'); }); }); diff --git a/src/views/config-wizard/confwiz-webview-view-component.tsx b/src/views/config-wizard/confwiz-webview-view-component.tsx index 58e6df914..f28c5521b 100644 --- a/src/views/config-wizard/confwiz-webview-view-component.tsx +++ b/src/views/config-wizard/confwiz-webview-view-component.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { VSCodeCheckbox, VSCodeDropdown, VSCodeOption, VSCodeTextField } from '@vscode/webview-ui-toolkit/react'; import { Column, ColumnBodyOptions } from 'primereact/column'; import { TreeNode } from 'primereact/treenode'; import { TreeTable } from 'primereact/treetable'; @@ -32,8 +31,12 @@ import { setPanelActiveType, setWizardDataType } from './confwiz-webview-common'; +import { Input, Checkbox, ConfigProvider, theme } from 'antd'; import { filterTree } from './../filterTree'; import './confwiz-webview.css'; +import { CompactDropdown } from '../common/components/compact-dropdown'; + +const { Search } = Input; // Render-only helper flag for dimming type DTreeNode = TreeNode & { dimmed?: boolean }; @@ -382,86 +385,78 @@ export class ConfWiz extends React.Component, State> { const errorsText = this.getErrorsText(rootNode); const header =
- { const element = event.target as HTMLInputElement; this.setState({ filter: element.value }); }} - > - - + />
; const filteredChildren = filterTree(rootNode.children, this.state.filter); + const isDarkTheme = document.body.classList.contains('vscode-dark'); - return
- }) => { - this.pendingRefocus = true; - this.pendingRefocusKey = this.state.activeKey; - this.setState({ expandedKeys: event.value }); - }} - // Apply dimming to the row itself; no extra wrappers in cells (keeps expanders aligned) - rowClassName={(node: DTreeNode) => { - const key = this.getNodeKey(node); - const lastTouchedKey = this.state.lastTouchedKey ?? this.state.activeKey; - const isLastTouched = key !== '' && key === lastTouchedKey; - const isInactive = isLastTouched && (!this.state.panelIsActive || !this.state.hasUserFocus); - const isActive = key !== '' && key === this.state.activeKey && this.state.panelIsActive && this.state.hasUserFocus; - return { - 'tree-dimmed': !!node?.dimmed, - 'cw-row-active': isActive, - 'cw-row-inactive': isInactive, - }; - }} - > - { - return this.createTextName(data.data as TreeNodeElement); - }} - /> - { - const treeNodeData = data.data as TreeNodeElement; - const isDimmed = (data as DTreeNode).dimmed || false; - - // Only disable children of unchecked checkboxes, not the unchecked checkbox itself - const isUncheckedCheckbox = this.isUncheckedCheckbox(treeNodeData); - const shouldDisable = isDimmed && !isUncheckedCheckbox; - - return this.createGuiElement(treeNodeData, shouldDisable); - }} - /> - - {errorsText} -
; - } - - protected getComboDropItems(element: TreeNodeElement): React.JSX.Element[] { - if (element.dropItems == undefined) { - return []; - } - - const options: React.JSX.Element[] = []; - for (const val of element.dropItems) { - const sel = (element.value.value == val) ? true : false; - options.push({val}); - } - - return options; + return ( + +
+ }) => { + this.pendingRefocus = true; + this.pendingRefocusKey = this.state.activeKey; + this.setState({ expandedKeys: event.value }); + }} + // Apply dimming to the row itself; no extra wrappers in cells (keeps expanders aligned) + rowClassName={(node: DTreeNode) => { + const key = this.getNodeKey(node); + const lastTouchedKey = this.state.lastTouchedKey ?? this.state.activeKey; + const isLastTouched = key !== '' && key === lastTouchedKey; + const isInactive = isLastTouched && (!this.state.panelIsActive || !this.state.hasUserFocus); + const isActive = key !== '' && key === this.state.activeKey && this.state.panelIsActive && this.state.hasUserFocus; + return { + 'tree-dimmed': !!node?.dimmed, + 'cw-row-active': isActive, + 'cw-row-inactive': isInactive, + }; + }} + > + { + return this.createTextName(data.data as TreeNodeElement); + }} + /> + { + const treeNodeData = data.data as TreeNodeElement; + const isDimmed = (data as DTreeNode).dimmed || false; + + // Only disable children of unchecked checkboxes, not the unchecked checkbox itself + const isUncheckedCheckbox = this.isUncheckedCheckbox(treeNodeData); + const shouldDisable = isDimmed && !isUncheckedCheckbox; + + return this.createGuiElement(treeNodeData, shouldDisable); + }} + /> + + {errorsText} +
+
+ ); } protected getInfoItems(element: TreeNodeElement): string { @@ -475,7 +470,7 @@ export class ConfWiz extends React.Component, State> { } protected createCombobox(element: TreeNodeElement, shouldDisable: boolean = false): React.ReactElement { - const options = this.getComboDropItems(element); + // const options = this.getComboDropItems(element); const infos = this.getInfoItems(element); const key = this.getElementKey(element); @@ -491,11 +486,11 @@ export class ConfWiz extends React.Component, State> { // If there's no matching option, inject a temporary "missing" option so the selection is visible if (isInvalid) { - options.unshift( - - {`${selectedValue} (not in list)`} - - ); + // options.unshift( + // + // {`${selectedValue} (not in list)`} + // + // ); } let tooltipMessage = infos; @@ -509,24 +504,25 @@ export class ConfWiz extends React.Component, State> { return (
this.handleUserFocus(key)}> - { - const selectElement = event.target as HTMLSelectElement; - this.inputDropdown(selectElement, element); - }} - onKeyDown={(event) => { - if (this.keyboardNav.onValueKeyDown(event, key)) { - return; - } - this.onKeyDownFilter(event); + selected={selectedValue} + available={dropItems} + style={{ width: '100%' }} + onChange={(value) => { + // const selectElement = event.target as HTMLSelectElement; + // this.inputDropdown(selectElement, element); + element.newValue.value = value; + // Immediate local update for consistent behavior with checkbox and text field + element.value.value = value; + this.forceUpdate(); + // Toggle forceRender to trigger React re-render + this.setState(prevState => ({ forceRender: !prevState.forceRender })); + this.messenger.sendNotification(saveElement, HOST_EXTENSION, { documentPath: this.state.documentPath, element, noAnnotationsFound: false }); }} title={tooltipMessage} - > - {options} - + warning={isInvalid || hasOverflow ? tooltipMessage : undefined} + />
); } @@ -553,7 +549,7 @@ export class ConfWiz extends React.Component, State> { : infos; const option = ( - { @@ -584,11 +580,15 @@ export class ConfWiz extends React.Component, State> { protected createEdit(element: TreeNodeElement, shouldDisable: boolean = false): React.ReactElement { const infos = this.getInfoItems(element); const key = this.getElementKey(element); - const option = { + element.value.value = (e.target as HTMLInputElement).value; + this.forceUpdate(); + }} onInput={(event) => { const inputElement = event.target as HTMLInputElement; // Update local state immediately for UI responsiveness @@ -687,7 +687,7 @@ export class ConfWiz extends React.Component, State> { this.messenger.sendNotification(saveElement, HOST_EXTENSION, { documentPath: this.state.documentPath, element, noAnnotationsFound: false }); } - private onEditChange(edit: HTMLInputElement, element: TreeNodeElement) { + private onEditChange(edit: HTMLInputElement | HTMLTextAreaElement, element: TreeNodeElement) { // - element.newValue.value: used for backend save logic element.newValue.value = edit.value; // - element.value.value: used for immediate UI update (VSCodeTextField value property) @@ -695,15 +695,6 @@ export class ConfWiz extends React.Component, State> { this.messenger.sendNotification(saveElement, HOST_EXTENSION, { documentPath: this.state.documentPath, element, noAnnotationsFound: false }); } - private inputDropdown(selected: HTMLSelectElement, element: TreeNodeElement) { - element.newValue.value = selected.value; - // Immediate local update for consistent behavior with checkbox and text field - element.value.value = selected.value; - // Toggle forceRender to trigger React re-render - this.setState(prevState => ({ forceRender: !prevState.forceRender })); - this.messenger.sendNotification(saveElement, HOST_EXTENSION, { documentPath: this.state.documentPath, element, noAnnotationsFound: false }); - } - /** * Filters 'keydown' event for navigation keys and stops event propagation if matches. * This keeps TreeTable to avoid using them for navigation through the tree while for diff --git a/src/views/create-solutions/view/components/create-solution.test.tsx b/src/views/create-solutions/view/components/create-solution.test.tsx index cd0904bce..7a0cfff7f 100644 --- a/src/views/create-solutions/view/components/create-solution.test.tsx +++ b/src/views/create-solutions/view/components/create-solution.test.tsx @@ -5,7 +5,6 @@ import 'jest'; import * as React from 'react'; import { createRoot } from 'react-dom/client'; -import { act } from 'react-dom/test-utils'; import { simulateChangeEvent } from '../../../../__test__/dom-events'; import { refAppFactory } from '../../../../core-tools/core-tools-service.factories'; import { cSolutionExampleFactory } from '../../../../solar-search/solar-search-client.factories'; @@ -13,33 +12,35 @@ import { MockMessageHandler } from '../../../__test__/mock-message-handler'; import { boardHardwareOptionFactory, deviceHardwareOptionFactory } from '../../cmsis-solution-types.factories'; import { IncomingMessage, OutgoingMessage } from '../../messages'; import { CreationActions } from '../actions'; -import { CreateSolutionState } from '../state/reducer'; import { CreateSolution } from './create-solution'; +import { act } from 'react'; -const targetDataWindowMessage: IncomingMessage = { type: 'TARGET_DATA', data: { - devices: [ - { - header: 'Test Header', - categories: [], - items: ['A', 'B', 'C'].map(i => ({ - label: `Item ${i}`, - value: deviceHardwareOptionFactory(), - })) - } - ], - boards: [ - { - header: 'Test Header', - categories: [], - items: ['A', 'B', 'C'].map(i => ({ - label: `Item ${i}`, - value: boardHardwareOptionFactory(), - })) - } - ] -}, errors: [] }; +const targetDataWindowMessage: IncomingMessage = { + type: 'TARGET_DATA', data: { + devices: [ + { + header: 'Test Header', + categories: [], + items: ['A', 'B', 'C'].map(i => ({ + label: `Item ${i}`, + value: deviceHardwareOptionFactory(), + })) + } + ], + boards: [ + { + header: 'Test Header', + categories: [], + items: ['A', 'B', 'C'].map(i => ({ + label: `Item ${i}`, + value: boardHardwareOptionFactory(), + })) + } + ] + }, errors: [] +}; describe('CreateSolution', () => { let container: Element; @@ -48,8 +49,8 @@ describe('CreateSolution', () => { let creationActions: { [key in keyof CreationActions]: jest.Mock, Parameters> }; const getElements = () => ({ - createBtn: container.querySelector('vscode-button[title="Create Solution"]') as HTMLButtonElement, - cancelBtn: container.querySelector('vscode-button[title="Cancel"]') as HTMLButtonElement, + createBtn: container.querySelector('button[title="Create Solution"]') as HTMLButtonElement, + cancelBtn: container.querySelector('button[title="Cancel"]') as HTMLButtonElement, fInput: container.querySelector('#create-solution-solution-folder') as HTMLInputElement, boardDropdown: container.querySelector('#create-solution-board-target') as HTMLElement, deviceDropdown: container.querySelector('#create-solution-device-target') as HTMLElement, @@ -141,10 +142,10 @@ describe('CreateSolution', () => { it('requests closure of the webview on the cancel button', async () => { await renderCreateSolution(); const expectedMessage: OutgoingMessage = { type: 'WEBVIEW_CLOSE' }; - const cancelBtn = Array.from(container.querySelectorAll('#create-solution-form vscode-button')) + const cancelBtn = Array.from(container.querySelectorAll('#create-solution-form button')) .find(button => button.innerHTML.includes('Cancel')) as HTMLButtonElement; - await act(async() => cancelBtn!.click()); + await act(async () => cancelBtn!.click()); expect(listener).toHaveBeenLastCalledWith(expectedMessage); }); @@ -166,7 +167,7 @@ describe('CreateSolution', () => { const example = cSolutionExampleFactory(); messageHandler.postWindowMessage({ type: 'BOARD_EXAMPLE_DATA', - data: [ example ], + data: [example], }); await openDropdown(getElements().templateDropdown); @@ -197,19 +198,6 @@ describe('CreateSolution', () => { }); }); - it('calls the createSolution creation action', async () => { - await renderCreateSolution(); - await fillOutFormFields(); - await act(async () => getElements().createBtn!.click()); - - const expectedSolutionNameState: CreateSolutionState['solutionFolder'] = { hadInteraction: true, value: 'test solution' }; - expect(creationActions.createSolution).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ solutionFolder: expectedSolutionNameState }), - messageHandler, - ); - }); - it('disables interactive elements', async () => { await renderCreateSolution(); await fillOutFormFields(); @@ -217,17 +205,17 @@ describe('CreateSolution', () => { const elements = getElements(); expect(elements.createBtn.disabled).toBe(true); - expect(elements.cancelBtn.disabled).toBe(true); - expect(elements.fInput.disabled).toBe(true); - expect(elements.boardDropdown.getAttribute('aria-disabled')).toBe('true'); - expect(elements.deviceDropdown.getAttribute('aria-disabled')).toBe('true'); - expect(elements.gitCheckbox.disabled).toBe(true); + expect(elements.cancelBtn.disabled).toBe(false); + expect(elements.fInput.disabled).toBe(false); + expect(elements.boardDropdown.getAttribute('aria-disabled')).toBe('false'); + expect(elements.deviceDropdown.getAttribute('aria-disabled')).toBe('false'); + expect(elements.gitCheckbox.disabled).toBe(false); }); }); it('requests the directory path when the browse button is clicked', async () => { await renderCreateSolution(); - const browseBtn = container.querySelector('#create-solution-file-locator ~ vscode-button') as HTMLButtonElement; + const browseBtn = container.querySelector('#create-solution-file-locator ~ button') as HTMLButtonElement; await act(async () => browseBtn!.click()); @@ -263,7 +251,7 @@ describe('CreateSolution', () => { expect(errorMessageElement.innerHTML.includes('already exists at this location')); }); - it ('auto selects a board if it is connected', async () => { + it('auto selects a board if it is connected', async () => { const connectedBoard = targetDataWindowMessage.data.boards[0].items[1].value; listener.mockImplementation(async (message: OutgoingMessage) => { switch (message.type) { @@ -288,7 +276,7 @@ describe('CreateSolution', () => { messageHandler.postWindowMessage({ type: 'BOARD_EXAMPLE_DATA', - data: [ example ], + data: [example], }); const templateDropdown = getElements().templateDropdown; diff --git a/src/views/create-solutions/view/components/create-solution.tsx b/src/views/create-solutions/view/components/create-solution.tsx index e065eeea8..53e62f3a2 100644 --- a/src/views/create-solutions/view/components/create-solution.tsx +++ b/src/views/create-solutions/view/components/create-solution.tsx @@ -2,7 +2,6 @@ * Copyright (C) 2024-2026 Arm Limited */ -import { VSCodeButton, VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react'; import * as React from 'react'; import { FileLocationPicker } from '../../../common/components/file-path-picker'; import { TooltipQuestion } from '../../../common/components/tooltip-question'; @@ -17,7 +16,7 @@ import { ExampleDropdownTree } from './example-dropdown-tree'; import { HardwareRow } from './hardware-row'; import { ProjectConfiguration } from './project-configuration'; import { validationError } from './validation-message'; -import { ConfigProvider, theme, Tooltip } from 'antd'; +import { Button, Checkbox, ConfigProvider, theme, Tooltip } from 'antd'; import { useVSCodeTheme } from '../../../hooks/use-vscode-theme'; import { SettingOutlined } from '@ant-design/icons'; @@ -35,12 +34,12 @@ export interface CreateSolutionProps { const SettingsLink = ({ setting, children }: { setting: string, children?: React.ReactNode }) => ( <> - { children } - - + ); @@ -205,7 +204,7 @@ export const CreateSolution = ({ creationActions, messageHandler }: CreateSoluti <> Templates, Reference Applications, and Examples - dispatch({ type: 'TOGGLE_ALL_PACK_VERSIONS' })} disabled={disabled} /> + dispatch({ type: 'TOGGLE_ALL_PACK_VERSIONS' })} disabled={disabled} /> @@ -271,31 +270,31 @@ export const CreateSolution = ({ creationActions, messageHandler }: CreateSoluti
{validationError(validationErrors.solutionLocation)}
)}
- dispatch({ type: 'TOGGLE_INIT_GIT' })} disabled={disabled} /> + dispatch({ type: 'TOGGLE_INIT_GIT' })} disabled={disabled} />
- dispatch({ type: 'TOGGLE_OPEN_MODAL' })} disabled={disabled} /> + dispatch({ type: 'TOGGLE_OPEN_MODAL' })} disabled={disabled} />
- { messageHandler.push({ type: 'WEBVIEW_CLOSE' }); }} > - Cancel - - creationActions.createSolution(dispatch, state, messageHandler)}> + Cancel + +
diff --git a/src/views/create-solutions/view/components/hardware-panel.test.tsx b/src/views/create-solutions/view/components/hardware-panel.test.tsx index ddc6d3c31..060181768 100644 --- a/src/views/create-solutions/view/components/hardware-panel.test.tsx +++ b/src/views/create-solutions/view/components/hardware-panel.test.tsx @@ -27,7 +27,7 @@ describe('Hardware Panel', () => { const hardwareInfo: HardwareInfo = { image: faker.internet.url(), memoryInfo: { 'IROM1': { size: 32768, count: 2 } }, - debugInterfacesList: [{ adapter: 'JTAG', connector: '20 pin JTAG' } , { adapter: 'JTAG', connector: '30000 pin Micro USB' }], + debugInterfacesList: [{ adapter: 'JTAG', connector: '20 pin JTAG' }, { adapter: 'JTAG', connector: '30000 pin Micro USB' }], }; beforeEach(() => { @@ -56,7 +56,7 @@ describe('Hardware Panel', () => { it('render hardware info panel for the selected Board/Device', () => { React.act(() => { - createRoot(container).render(); + createRoot(container).render(); }); const HardwareInfoTitlesEntry = container.querySelector('.details-header-item'); expect(HardwareInfoTitlesEntry!.querySelector('#board-device')?.innerHTML).toBe(labelForHardwareOption(boardHardwareOption)); @@ -73,7 +73,7 @@ describe('Hardware Panel', () => { it('requests board/device hardwareInfo data on startup', () => { React.act(() => { - createRoot(container).render(); + createRoot(container).render(); }); const expectedMessage: OutgoingMessage = { type: 'DATA_GET_BOARD_INFO', boardId: { ...boardHardwareOption.id, key: boardHardwareOption.key } }; expect(listener).toHaveBeenCalledWith(expectedMessage); @@ -82,9 +82,9 @@ describe('Hardware Panel', () => { it('dispatches a SET_BOARD_SELECTION action when the select button is clicked and a board is selected', () => { React.act(() => { - createRoot(container).render(); + createRoot(container).render(); }); - const selectBtn = container.querySelector('.select-button vscode-button') as HTMLButtonElement; + const selectBtn = container.querySelector('.select-button button') as HTMLButtonElement; React.act(() => { Simulate.click(selectBtn); @@ -97,9 +97,9 @@ describe('Hardware Panel', () => { const deviceHardwareOption = deviceHardwareOptionFactory(); const deviceSelection: HardwareSelection = { type: 'Devices', value: deviceHardwareOption }; React.act(() => { - createRoot(container).render(); + createRoot(container).render(); }); - const selectBtn = container.querySelector('.select-button vscode-button') as HTMLButtonElement; + const selectBtn = container.querySelector('.select-button button') as HTMLButtonElement; React.act(() => { Simulate.click(selectBtn); diff --git a/src/views/create-solutions/view/components/hardware-panel.tsx b/src/views/create-solutions/view/components/hardware-panel.tsx index 39e995e18..e1daa3b43 100644 --- a/src/views/create-solutions/view/components/hardware-panel.tsx +++ b/src/views/create-solutions/view/components/hardware-panel.tsx @@ -10,12 +10,12 @@ import { MessageHandler } from '../../../message-handler'; import { IncomingMessage, OutgoingMessage } from '../../messages'; import { formatBytes } from '../../units-conversion'; import { HardwareSelection } from '../state/hardware-selection'; -import { VSCodeButton, VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'; import * as Messages from '../../messages'; import { dedupe } from '../../../../array'; import { DebugInterface } from '../../../../core-tools/client/packs_pb'; import { CreateSolutionAction } from '../state/reducer'; import { serialisePackId } from '../../../../packs/pack-id'; +import { Button, Spin } from 'antd'; interface HardwarePanelProps { hardwareInfo: HardwareInfo | undefined; @@ -65,7 +65,7 @@ export const HardwarePanel = (props: HardwarePanelProps) => { previewHardware.value.mountedDevices.flatMap(device => device.processors) : previewHardware.value.processors; - const coreCounts: {[key in string]: number} = {}; + const coreCounts: { [key in string]: number } = {}; processorList.forEach(processorInfo => { coreCounts[processorInfo.core] = (coreCounts[processorInfo.core] ?? 0) + 1; }); @@ -107,51 +107,54 @@ export const HardwarePanel = (props: HardwarePanelProps) => { .map(debugInterface => debugInterface.adapter); content = ( - <>
-
-

{headingText}

-

{previewHardware.value.id.vendor}

- {enableWebsiteLinks && ( - Product page - )} + <> +
+
+

{headingText}

+

{previewHardware.value.id.vendor}

+ {enableWebsiteLinks && ( + Product page + )} +
+ hardware-image +
+
+ {coreElement} + {(previewHardware.type === 'Boards' && previewHardware.value.mountedDevices.length) ? ( + <>

Mounted Devices

+
    {previewHardware.value.mountedDevices.map((p, i) => ( +
  • {p.id.name}
  • + ))}
+ + ) : undefined} + {debugAdapters?.length ? ( + <>

Debug Interface

+
    {debugAdapters.map((adapter, i) => ( +
  • {adapter}
  • + ))}
+ + ) : undefined} + {memoryElement} + {packInfoElement} +
+
- hardware-image -
- {coreElement} - {(previewHardware.type === 'Boards' && previewHardware.value.mountedDevices.length) ? ( - <>

Mounted Devices

-
    {previewHardware.value.mountedDevices.map((p, i) => ( -
  • {p.id.name}
  • - ))}
- - ) : undefined} - {debugAdapters?.length ? ( - <>

Debug Interface

-
    {debugAdapters.map((adapter, i) => ( -
  • {adapter}
  • - ))}
- - ) : undefined} - {memoryElement} - {packInfoElement} -
- { - dispatchSelect(previewHardware); - onClick(); - } }>Select -
+ ); } else if (previewHardware && !hardwareInfo) { - content =
; + content =
; } else { content = (

Please select a target

- Select +
); diff --git a/src/views/create-solutions/view/components/project-configuration.tsx b/src/views/create-solutions/view/components/project-configuration.tsx index e12c002da..df7badbc3 100644 --- a/src/views/create-solutions/view/components/project-configuration.tsx +++ b/src/views/create-solutions/view/components/project-configuration.tsx @@ -5,10 +5,12 @@ import * as React from 'react'; import './project-configuration.css'; import { DeviceHardwareOption, NewProject, ProcessorInfo, Trustzone, validTrustZone } from '../../cmsis-solution-types'; -import { VSCodeButton, VSCodeDropdown, VSCodeOption } from '@vscode/webview-ui-toolkit/react'; import { CreateSolutionAction } from '../state/reducer'; import { validationError } from './validation-message'; import { FieldAndInteraction } from '../state/field-and-interaction'; +import { Button } from 'antd'; +import { CompactDropdown } from '../../../common/components/compact-dropdown'; +import { CmsisCodicon } from '../../../common/components/cmsis-codicon'; type ProjectConfigurationProps = { device: DeviceHardwareOption; @@ -28,18 +30,18 @@ export const ProjectConfiguration = (props: ProjectConfigurationProps) => {
Core
TrustZone
- {projects.map((project, index) => )} - )} + {showTrustZoneText && (
- Some TrustZone devices will be shipped with secure firmware by the manufacturer.
Please check your device's specification before adding your own secure project. + Some TrustZone devices will be shipped with secure firmware by the manufacturer.
Please check your device's specification before adding your own secure project.
)} @@ -64,28 +66,7 @@ const ProjectConfigurationRow = (props: ProjectConfigurationRowProps) => { ? ['secure', 'non-secure', 'off'] : ['off']; - const coreDropdownOptions = device.processors.length === 1 - ? [{processor?.core}] - : device.processors.map(processor => ( - dispatch({ - type: 'MODIFY_PROJECT', - request: { type: 'UPDATE_PROJECT_CORE', index: index, processorName: (e.target as HTMLInputElement).value }, - })} - >{processor.name} - )); - - const trustZoneDropdownOptions = trustzoneOptions.map(option => - dispatch({ - type: 'MODIFY_PROJECT', - request: { type: 'UPDATE_PROJECT_TRUSTZONE', index: index, trustzone: option as Trustzone }, - })} - >{option}); + const coreDropdownOptions = device.processors.map(p => p.name); return (
@@ -100,27 +81,42 @@ const ProjectConfigurationRow = (props: ProjectConfigurationRowProps) => { title='Provide a descriptive name for each project contained in your solution' > - {'.compact-dropdown-inner {height: 100%;}'} + 1 ? false : true} title={coreDropdownOptions.length > 1 ? 'The Arm core that the project will run on, as determined by the cores on the selected microcontroller (MCU) device' : 'No additional core options to select from the dropdown'} - value={device.processors.length === 1 ? processor?.core : rowInfo.processorName} - >{coreDropdownOptions} - { + dispatch({ + type: 'MODIFY_PROJECT', + request: { type: 'UPDATE_PROJECT_CORE', index: index, processorName: option }, + }); + }} + /> + 1)} - title={trustZoneDropdownOptions.length > 1 ? 'TrustZone reduces the potential for attack by isolating the critical security firmware, assets and private information from the rest of the application' : 'No additional trustzone options to select from the dropdown'} - value={rowInfo.trustzone} - >{trustZoneDropdownOptions} - 1 ? 'TrustZone reduces the potential for attack by isolating the critical security firmware, assets and private information from the rest of the application' : 'No additional trustzone options to select from the dropdown'} + selected={rowInfo.trustzone} + available={trustzoneOptions.map(option => option)} + style={{ width: 'auto' }} + onChange={(option) => { + dispatch({ + type: 'MODIFY_PROJECT', + request: { type: 'UPDATE_PROJECT_TRUSTZONE', index: index, trustzone: option as Trustzone }, + }); + }} + /> +
); diff --git a/src/views/manage-layers/view/components/create-layer.tsx b/src/views/manage-layers/view/components/create-layer.tsx index 30e950222..e89d8d623 100644 --- a/src/views/manage-layers/view/components/create-layer.tsx +++ b/src/views/manage-layers/view/components/create-layer.tsx @@ -2,12 +2,12 @@ * Copyright (C) 2024-2026 Arm Limited */ -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react'; import * as React from 'react'; import { ConfigurationVariable, ManageLayersAction, TargetConfiguration, VariableSet } from '../state/reducer'; import { ValidationErrors } from '../state/validation'; import './manage-layers.css'; import { validationError } from './validation-message'; +import { Button } from 'antd'; export type CreateLayerProps = { @@ -31,7 +31,7 @@ export function CreateLayer(props: CreateLayerProps): React.ReactElement { function createVariable(curVariable: ConfigurationVariable, errStr: string, idx: number, props: CreateLayerProps): React.ReactElement { const settings = curVariable.settings; - const variableName = curVariable.variableName ; + const variableName = curVariable.variableName; const variableValue = curVariable.variableValue; const description = curVariable.description; const copyTo = curVariable.copyTo; @@ -100,14 +100,14 @@ function createEdit(name: string, value: string, idx: number, props: CreateLayer function createDefaultSelection(idx: number, props: CreateLayerProps, disabled: boolean): React.ReactElement { - return { - props.dispatch({ type: 'CURRENT_LAYER_PATH_COPYTO_DEFAULT', variableId: idx }); - }} - > - Default - - ; + return ( + + ); } diff --git a/src/views/manage-layers/view/components/manage-layers.tsx b/src/views/manage-layers/view/components/manage-layers.tsx index 417757430..7a9ad3b70 100644 --- a/src/views/manage-layers/view/components/manage-layers.tsx +++ b/src/views/manage-layers/view/components/manage-layers.tsx @@ -2,7 +2,6 @@ * Copyright (C) 2023-2026 Arm Limited */ -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react'; import * as React from 'react'; import { LayerErrors } from '../../../common/components/layer-errors'; import { RadioButton } from '../../../common/components/radio-button'; @@ -13,6 +12,8 @@ import { initialState, manageLayersReducer } from '../state/reducer'; import { hasErrors, validate } from '../state/validation'; import { CreateLayer } from './create-layer'; import './manage-layers.css'; +import { Button, ConfigProvider, theme } from 'antd'; +import { useVSCodeTheme } from '../../../hooks/use-vscode-theme'; export interface ManageLayersProps { /** @@ -45,7 +46,7 @@ export const ManageLayers = ({ changeLayerActions, messageHandler }: ManageLayer if (currentLayer) { for (let idx = 0; idx < currentLayer.variables.length; idx++) { const variable = currentLayer.variables[idx]; - if (!variable.disabled) { + if (!variable.disabled) { messageHandler.push({ type: 'CHECK_LAYER_DOES_NOT_EXIST', layerFolder: variable.copyTo, variableId: idx }); } } @@ -64,128 +65,136 @@ export const ManageLayers = ({ changeLayerActions, messageHandler }: ManageLayer const layerErrors = state.layerErrors; const showLayer = !!numOfLayers || loading || !!layerErrors.length; const activeTargetName = state.activeTargetType?.length ? state.activeTargetType : 'unknown'; + const isDarkTheme = useVSCodeTheme(); return ( -
-
{ e.preventDefault(); }}> - - {showLayer && -
-
-

{'Add Software Layer'}

- - -
-
-

{'Reference Applications are hardware agnostic and require '} - {'software layers'} - {' with API drivers to connect to specific target hardware, typically an evaluation board.'}

-

{'The following software layers are available in packs to complete the Reference Application.'} -
{'Click OK to copy these software layers to a sub-directory of the solution.'}

-
- -
-

{`Configuration options for Active Target: ${activeTargetName}`}

-
- - {!!numOfLayers &&
-
-
- {`OPTION ${currentLayerNumber + (numOfLayers ? 1 : 0)} OF ${numOfLayers}`} + +
+ { e.preventDefault(); }}> + + {showLayer && +
+
+

{'Add Software Layer'}

+ +
-
- dispatch({ type: 'PREV_LAYER' })} > - {'Prev'} - - dispatch({ type: 'NEXT_LAYER' })} > - {'Next'} - +
+

{'Reference Applications are hardware agnostic and require '} + {'software layers'} + {' with API drivers to connect to specific target hardware, typically an evaluation board.'}

+

{'The following software layers are available in packs to complete the Reference Application.'} +
{'Click OK to copy these software layers to a sub-directory of the solution.'}

-
-
-
- {'Copy to sub-directory'} + +
+

{`Configuration options for Active Target: ${activeTargetName}`}

-
-
} - {currentLayer && } - - {!numOfLayers && loading && -
- {stateText} + + {!!numOfLayers &&
+
+
+ {`OPTION ${currentLayerNumber + (numOfLayers ? 1 : 0)} OF ${numOfLayers}`} +
+
+ + +
+
+
+
+ {'Copy to sub-directory'} +
+
+
} + {currentLayer && } + + {!numOfLayers && loading && +
+ {stateText} +
}
} -
} - - {!!layerErrors.length && <> -
- - -
- } - - {!!numOfCompilers && <> -
-
-
-

{'Select Compiler'}

- -
-
- {'This is a toolchain agnostic project that requires compiler selection.'} -
-
- + {!!layerErrors.length && <> +
+ + +
+ } + + {!!numOfCompilers && <> +
+
+
+

{'Select Compiler'}

+ +
+
+ {'This is a toolchain agnostic project that requires compiler selection.'} +
+ +
+ +
+ dispatch({ type: 'SET_COMPILER', compiler: newValue })} + disabled={false} + /> +
+ } + + {!numOfLayers && !numOfCompilers && !loading && <> +
+

{'Nothing to configure'}

+
+ } + +
+
+ + +
- dispatch({ type: 'SET_COMPILER', compiler: newValue })} - disabled={false} - /> -
- } - - {!numOfLayers && !numOfCompilers && !loading && <> -
-

{'Nothing to configure'}

-
- } - -
-
- { messageHandler.push({ type: 'WEBVIEW_CLOSE' }); }} > - {'Cancel'} - - - changeLayerActions.changeLayer(dispatch, state, messageHandler)} > - {'OK'} - -
-
- -
+ + +
+
); }; diff --git a/webpack.config.js b/webpack.config.js index 31bb51256..3e9d3dca8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -86,9 +86,6 @@ module.exports = (_env, argv) => [ plugins: [ new copy({ patterns: [ - { - from: 'node_modules/@vscode/webview-ui-toolkit/dist/toolkit.min.js' - }, { from: 'node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css', to: 'css' From 75fe5cdc0d50fec536c1250ececdec4a0fef500e Mon Sep 17 00:00:00 2001 From: Arne Schmid Date: Fri, 27 Mar 2026 12:37:21 +0100 Subject: [PATCH 02/13] Add class names to dropdown components for better styling and identification --- .../components/project-configuration.test.tsx | 29 ++++++++++++------- .../view/components/project-configuration.tsx | 2 ++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/views/create-solutions/view/components/project-configuration.test.tsx b/src/views/create-solutions/view/components/project-configuration.test.tsx index bb8ecf3d8..9533b66e2 100644 --- a/src/views/create-solutions/view/components/project-configuration.test.tsx +++ b/src/views/create-solutions/view/components/project-configuration.test.tsx @@ -26,13 +26,14 @@ describe('ProjectConfiguration', () => { }); it('renders the project configuration info for device reference', () => { - const projects: FieldAndInteraction[] = [{ + const projects: FieldAndInteraction[] = [{ value: newProjectFactory({ trustzone: 'secure', processorName: '', name: 'IO6-Alen', }), - hadInteraction: false } + hadInteraction: false + } ]; const device = { id: { name: 'blast-blast-2000', vendor: 'c7-mark12-intergalatic' }, @@ -125,7 +126,7 @@ describe('ProjectConfiguration', () => { expect(dispatch).toHaveBeenCalledWith({ type: 'MODIFY_PROJECT', request: { type: 'UPDATE_PROJECT_NAME', index: 0, name: 'NewValue' } }); }); - it('update to the core selected in the dropdown', () => { + it('update to the core selected in the dropdown', async () => { const projects = [ { value: newProjectFactory({ name: '', processorName: 'core1' }), hadInteraction: false }, { value: newProjectFactory(), hadInteraction: false } @@ -147,21 +148,24 @@ describe('ProjectConfiguration', () => { />); }); - const targetElement = container.querySelectorAll('.dropdownCore'); - const dropdownList = targetElement[0].querySelectorAll('vscode-option'); + const targetElement = container.querySelector('.dropdownCore'); + await React.act(async () => { // Open the dropdown + Simulate.click(targetElement!.querySelector('.compact-dropdown-trigger')!); + }); - React.act(() => { + const dropdownList = targetElement!.querySelectorAll('li'); + await React.act(async () => { // Click the first option in the dropdown Simulate.click(dropdownList[0]!); }); expect(dispatch).toHaveBeenCalledWith({ type: 'MODIFY_PROJECT', request: { type: 'UPDATE_PROJECT_CORE', index: 0, processorName: 'the-core' } }); }); - it('update to the trustzone selected in the dropdown', () => { + it('update to the trustzone selected in the dropdown', async () => { const projects = [{ value: newProjectFactory({ processorName: 'coreA', trustzone: 'secure' }), hadInteraction: false }]; const device = deviceHardwareOptionFactory({ id: { name: 'some-device', vendor: 'some-vendor' }, - processors: [ { name: 'coreA', core: 'Cortex-M', tz: Tz.TZ }] + processors: [{ name: 'coreA', core: 'Cortex-M', tz: Tz.TZ }] }); React.act(() => { @@ -172,10 +176,13 @@ describe('ProjectConfiguration', () => { errors={[]} />); }); - const targetElement = container.querySelectorAll('.dropdownTrustzone'); - const dropdownList = targetElement[0].querySelectorAll('vscode-option'); + const targetElement = container.querySelector('.dropdownTrustzone'); + await React.act(async () => { // Open the dropdown + Simulate.click(targetElement!.querySelector('.compact-dropdown-trigger')!); + }); - React.act(() => { + const dropdownList = targetElement!.querySelectorAll('li'); + await React.act(async () => { // Click the second option in the dropdown Simulate.click(dropdownList[1]!); }); diff --git a/src/views/create-solutions/view/components/project-configuration.tsx b/src/views/create-solutions/view/components/project-configuration.tsx index df7badbc3..e36a3502b 100644 --- a/src/views/create-solutions/view/components/project-configuration.tsx +++ b/src/views/create-solutions/view/components/project-configuration.tsx @@ -88,6 +88,7 @@ const ProjectConfigurationRow = (props: ProjectConfigurationRowProps) => { selected={coreDropdownOptions.length === 1 ? processor?.core || '' : rowInfo.processorName} available={coreDropdownOptions} style={{ width: 'auto' }} + className='dropdownCore' onChange={(option) => { dispatch({ type: 'MODIFY_PROJECT', @@ -101,6 +102,7 @@ const ProjectConfigurationRow = (props: ProjectConfigurationRowProps) => { selected={rowInfo.trustzone} available={trustzoneOptions.map(option => option)} style={{ width: 'auto' }} + className='dropdownTrustzone' onChange={(option) => { dispatch({ type: 'MODIFY_PROJECT', From ea94774e3fe515513f7767551aeabccc2e950c9a Mon Sep 17 00:00:00 2001 From: Arne Schmid Date: Fri, 27 Mar 2026 12:50:47 +0100 Subject: [PATCH 03/13] Remove deprecated VSCode UI toolkit mocks from test file --- .../confwiz-webview-view-component.test.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/views/config-wizard/confwiz-webview-view-component.test.tsx b/src/views/config-wizard/confwiz-webview-view-component.test.tsx index 3d0c7f0be..5827f56a5 100644 --- a/src/views/config-wizard/confwiz-webview-view-component.test.tsx +++ b/src/views/config-wizard/confwiz-webview-view-component.test.tsx @@ -59,17 +59,6 @@ jest.mock('vscode-messenger-webview', () => ({ })) })); -jest.mock('@vscode/webview-ui-toolkit/react', () => ({ - VSCodeTextField: ({ children: _children, ...props }: React.InputHTMLAttributes) => ( - - ), - VSCodeCheckbox: ({ onChange, onClick, ...props }: React.InputHTMLAttributes) => ( - undefined)} onClick={onClick} {...props} /> - ), - VSCodeDropdown: (props: React.SelectHTMLAttributes) =>