diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index 522cfe36c..e1dfb49b3 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -393,7 +393,7 @@ export class BoardsServiceProvider } if (selectedPort && selectedBoard) { - this._boardListHistory[Port.keyOf(selectedPort)] = selectedBoard; + this.updateBoardListHistory(selectedPort, selectedBoard); } if ( previousSelectedPort !== undefined && @@ -438,7 +438,7 @@ export class BoardsServiceProvider const selectedBoard = this._boardsConfig.selectedBoard; const previousSelectedPort = this._boardsConfig.selectedPort; if (selectedPort && selectedBoard) { - this._boardListHistory[Port.keyOf(selectedPort)] = selectedBoard; + this.updateBoardListHistory(selectedPort, selectedBoard); } this._boardsConfig.selectedPort = selectedPort; if (portIdentifierEquals(previousSelectedPort, selectedPort)) { @@ -454,6 +454,26 @@ export class BoardsServiceProvider return true; } + private updateBoardListHistory( + selectedPort: PortIdentifier, + selectedBoard: BoardIdentifier + ): { [portKey: string]: BoardIdentifier | undefined } { + const match = this.boardList.items.find( + (item) => + portIdentifierEquals(item.port, selectedPort) && + item.board && + boardIdentifierEquals(item.board, selectedBoard) + ); + const portKey = Port.keyOf(selectedPort); + // When board `B` is detected on port `P` and saving `B` on `P`, remove the entry instead! + if (match) { + delete this._boardListHistory[portKey]; + } else { + this._boardListHistory[portKey] = selectedBoard; + } + return { [portKey]: match ? undefined : selectedBoard }; + } + get ready(): Promise { return this._ready.promise; } diff --git a/arduino-ide-extension/src/test/browser/board-service-provider.test.ts b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts index 0bee3b12d..748d83078 100644 --- a/arduino-ide-extension/src/test/browser/board-service-provider.test.ts +++ b/arduino-ide-extension/src/test/browser/board-service-provider.test.ts @@ -25,6 +25,15 @@ import { StorageWrapper } from '../../browser/storage-wrapper'; import { BoardsService, Port } from '../../common/protocol/boards-service'; import { NotificationServiceServer } from '../../common/protocol/notification-service'; import { bindCommon } from '../common/common-test-bindings'; +import { + detectedPort, + esp32S3DevModule, + mkr1000, + mkr1000SerialPort, + undiscoveredSerialPort, + uno, + unoSerialPort, +} from '../common/fixtures'; disableJSDOM(); @@ -54,33 +63,74 @@ describe('board-service-provider', () => { }); it('should detect a port change and find selection index', () => { - const board = { name: 'board', fqbn: 'a:b:c' }; - const port: Port = { - protocol: 'serial', - protocolLabel: '', - address: 'COM1', - addressLabel: '', - }; + let boardList = boardsServiceProvider.boardList; const hasUpdated = boardsServiceProvider.updateConfig({ - selectedBoard: board, - selectedPort: port, + selectedBoard: uno, + selectedPort: unoSerialPort, }); expect(hasUpdated).to.be.true; - expect(boardsServiceProvider.boardList.selectedIndex).to.be.equal(-1); + expect(boardList.selectedIndex).to.be.equal(-1); + let selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem).to.be.undefined; // attach board notificationCenter.notifyDetectedPortsDidChange({ detectedPorts: { - [Port.keyOf(port)]: { port, boards: [board] }, + ...detectedPort(unoSerialPort, uno), }, }); + boardList = boardsServiceProvider.boardList; expect(boardsServiceProvider.boardList.selectedIndex).to.be.equal(0); + selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem.board).to.be.deep.equal(uno); + expect(selectedItem.port).to.be.deep.equal(unoSerialPort); // detach board notificationCenter.notifyDetectedPortsDidChange({ detectedPorts: {}, }); + boardList = boardsServiceProvider.boardList; expect(boardsServiceProvider.boardList.selectedIndex).to.be.equal(-1); + selectedItem = boardList.items[boardList.selectedIndex]; + expect(selectedItem).to.be.undefined; + }); + + it('should update the board selection history for the port', () => { + notificationCenter.notifyDetectedPortsDidChange({ + detectedPorts: { + ...detectedPort(undiscoveredSerialPort), + ...detectedPort(unoSerialPort, uno), + ...detectedPort(mkr1000SerialPort, mkr1000), + }, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: esp32S3DevModule, + selectedPort: undiscoveredSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: esp32S3DevModule, + selectedPort: unoSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + [Port.keyOf(unoSerialPort)]: esp32S3DevModule, + }); + + boardsServiceProvider.updateConfig({ + selectedBoard: uno, + selectedPort: unoSerialPort, + }); + + expect(boardsServiceProvider['_boardListHistory']).to.be.deep.equal({ + [Port.keyOf(undiscoveredSerialPort)]: esp32S3DevModule, + }); }); function createContainer(): Container { diff --git a/arduino-ide-extension/src/test/common/board-list.test.ts b/arduino-ide-extension/src/test/common/board-list.test.ts index 339685eb4..d0d9c081d 100644 --- a/arduino-ide-extension/src/test/common/board-list.test.ts +++ b/arduino-ide-extension/src/test/common/board-list.test.ts @@ -7,183 +7,29 @@ import { SelectBoardsConfigActionParams, } from '../../common/protocol/board-list'; import { - BoardIdentifier, - DetectedPort, - DetectedPorts, emptyBoardsConfig, - Port, unconfirmedBoard, } from '../../common/protocol/boards-service'; - -const mkr1000: BoardIdentifier = { - name: 'Arduino MKR1000', - fqbn: 'arduino:samd:mkr1000', -}; -const uno: BoardIdentifier = { - name: 'Arduino Uno', - fqbn: 'arduino:avr:uno', -}; -const arduinoNanoEsp32: BoardIdentifier = { - fqbn: 'arduino:esp32:nano_nora', - name: 'Arduino Nano ESP32', -}; -const esp32NanoEsp32: BoardIdentifier = { - fqbn: 'esp32:esp32:nano_nora', - name: 'Arduino Nano ESP32', -}; -const esp32S3DevModule: BoardIdentifier = { - name: 'ESP32S3 Dev Module', - fqbn: 'esp32:esp32:esp32s3', -}; -const esp32S3Box: BoardIdentifier = { - name: 'ESP32-S3-Box', - fqbn: 'esp32:esp32:esp32s3box', -}; - -const bluetoothSerialPort: Port = { - address: '/dev/cu.Bluetooth-Incoming-Port', - addressLabel: '/dev/cu.Bluetooth-Incoming-Port', - protocol: 'serial', - protocolLabel: 'Serial Port', - properties: {}, - hardwareId: '', -}; -const builtinSerialPort: Port = { - address: '/dev/cu.BLTH', - addressLabel: '/dev/cu.BLTH', - protocol: 'serial', - protocolLabel: 'Serial Port', - properties: {}, - hardwareId: '', -}; -const undiscoveredSerialPort: Port = { - address: '/dev/cu.usbserial-0001', - addressLabel: '/dev/cu.usbserial-0001', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0xEA60', - serialNumber: '0001', - vid: '0x10C4', - }, - hardwareId: '0001', -}; -const mkr1000NetworkPort: Port = { - address: '192.168.0.104', - addressLabel: 'Arduino at 192.168.0.104', - protocol: 'network', - protocolLabel: 'Network Port', - properties: { - '.': 'mkr1000', - auth_upload: 'yes', - board: 'mkr1000', - hostname: 'Arduino.local.', - port: '65280', - ssh_upload: 'no', - tcp_check: 'no', - }, - hardwareId: '', -}; -const undiscoveredUsbToUARTSerialPort: Port = { - address: '/dev/cu.SLAB_USBtoUART', - addressLabel: '/dev/cu.SLAB_USBtoUART', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0xEA60', - serialNumber: '0001', - vid: '0x10C4', - }, - hardwareId: '0001', -}; -const mkr1000SerialPort: Port = { - address: '/dev/cu.usbmodem14301', - addressLabel: '/dev/cu.usbmodem14301', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0x804E', - serialNumber: '94A3397C5150435437202020FF150838', - vid: '0x2341', - }, - hardwareId: '94A3397C5150435437202020FF150838', -}; -const unoSerialPort: Port = { - address: '/dev/cu.usbmodem14201', - addressLabel: '/dev/cu.usbmodem14201', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0x0043', - serialNumber: '75830303934351618212', - vid: '0x2341', - }, - hardwareId: '75830303934351618212', -}; -const nanoEsp32SerialPort: Port = { - address: '/dev/cu.usbmodem3485187BD9882', - addressLabel: '/dev/cu.usbmodem3485187BD9882', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0x0070', - serialNumber: '3485187BD988', - vid: '0x2341', - }, - hardwareId: '3485187BD988', -}; -const nanoEsp32DetectsMultipleEsp32BoardsSerialPort: Port = { - address: 'COM41', - addressLabel: 'COM41', - protocol: 'serial', - protocolLabel: 'Serial Port (USB)', - properties: { - pid: '0x1001', - serialNumber: '', - vid: '0x303A', - }, -}; - -function createPort(address: string, protocol = 'serial'): Port { - return { - address, - addressLabel: `Address label: ${address}`, - protocol, - protocolLabel: `Protocol label: ${protocol}`, - }; -} - -function detectedPort( - port: Port, - ...boards: BoardIdentifier[] -): { [portKey: string]: DetectedPort } { - return { [Port.keyOf(port)]: boards.length ? { port, boards } : { port } }; -} - -function history( - port: Port, - board: BoardIdentifier -): { [portKey: string]: BoardIdentifier } { - return { [Port.keyOf(port)]: board }; -} - -const detectedPorts: DetectedPorts = { - ...detectedPort(builtinSerialPort), - ...detectedPort(bluetoothSerialPort), - ...detectedPort(unoSerialPort, uno), - ...detectedPort(mkr1000SerialPort, mkr1000), - ...detectedPort(mkr1000NetworkPort, mkr1000), - ...detectedPort(undiscoveredSerialPort), - ...detectedPort(undiscoveredUsbToUARTSerialPort), - // multiple discovered on the same port with different board names - ...detectedPort( - nanoEsp32DetectsMultipleEsp32BoardsSerialPort, - esp32S3DevModule, - esp32S3Box - ), - // multiple discovered on the same port with the same board name - ...detectedPort(nanoEsp32SerialPort, arduinoNanoEsp32, esp32NanoEsp32), -}; +import { + arduinoNanoEsp32, + bluetoothSerialPort, + builtinSerialPort, + createPort, + detectedPort, + detectedPorts, + esp32NanoEsp32, + esp32S3Box, + esp32S3DevModule, + history, + mkr1000, + mkr1000SerialPort, + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + nanoEsp32SerialPort, + undiscoveredSerialPort, + undiscoveredUsbToUARTSerialPort, + uno, + unoSerialPort, +} from './fixtures'; describe('board-list', () => { describe('createBoardList', () => { diff --git a/arduino-ide-extension/src/test/common/fixtures.ts b/arduino-ide-extension/src/test/common/fixtures.ts new file mode 100644 index 000000000..294984524 --- /dev/null +++ b/arduino-ide-extension/src/test/common/fixtures.ts @@ -0,0 +1,176 @@ +import { + BoardIdentifier, + DetectedPort, + DetectedPorts, + Port, +} from '../../common/protocol/boards-service'; + +export const mkr1000: BoardIdentifier = { + name: 'Arduino MKR1000', + fqbn: 'arduino:samd:mkr1000', +}; +export const uno: BoardIdentifier = { + name: 'Arduino Uno', + fqbn: 'arduino:avr:uno', +}; +export const arduinoNanoEsp32: BoardIdentifier = { + fqbn: 'arduino:esp32:nano_nora', + name: 'Arduino Nano ESP32', +}; +export const esp32NanoEsp32: BoardIdentifier = { + fqbn: 'esp32:esp32:nano_nora', + name: 'Arduino Nano ESP32', +}; +export const esp32S3DevModule: BoardIdentifier = { + name: 'ESP32S3 Dev Module', + fqbn: 'esp32:esp32:esp32s3', +}; +export const esp32S3Box: BoardIdentifier = { + name: 'ESP32-S3-Box', + fqbn: 'esp32:esp32:esp32s3box', +}; + +export const bluetoothSerialPort: Port = { + address: '/dev/cu.Bluetooth-Incoming-Port', + addressLabel: '/dev/cu.Bluetooth-Incoming-Port', + protocol: 'serial', + protocolLabel: 'Serial Port', + properties: {}, + hardwareId: '', +}; +export const builtinSerialPort: Port = { + address: '/dev/cu.BLTH', + addressLabel: '/dev/cu.BLTH', + protocol: 'serial', + protocolLabel: 'Serial Port', + properties: {}, + hardwareId: '', +}; +export const undiscoveredSerialPort: Port = { + address: '/dev/cu.usbserial-0001', + addressLabel: '/dev/cu.usbserial-0001', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0xEA60', + serialNumber: '0001', + vid: '0x10C4', + }, + hardwareId: '0001', +}; +export const mkr1000NetworkPort: Port = { + address: '192.168.0.104', + addressLabel: 'Arduino at 192.168.0.104', + protocol: 'network', + protocolLabel: 'Network Port', + properties: { + '.': 'mkr1000', + auth_upload: 'yes', + board: 'mkr1000', + hostname: 'Arduino.local.', + port: '65280', + ssh_upload: 'no', + tcp_check: 'no', + }, + hardwareId: '', +}; +export const undiscoveredUsbToUARTSerialPort: Port = { + address: '/dev/cu.SLAB_USBtoUART', + addressLabel: '/dev/cu.SLAB_USBtoUART', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0xEA60', + serialNumber: '0001', + vid: '0x10C4', + }, + hardwareId: '0001', +}; +export const mkr1000SerialPort: Port = { + address: '/dev/cu.usbmodem14301', + addressLabel: '/dev/cu.usbmodem14301', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x804E', + serialNumber: '94A3397C5150435437202020FF150838', + vid: '0x2341', + }, + hardwareId: '94A3397C5150435437202020FF150838', +}; +export const unoSerialPort: Port = { + address: '/dev/cu.usbmodem14201', + addressLabel: '/dev/cu.usbmodem14201', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x0043', + serialNumber: '75830303934351618212', + vid: '0x2341', + }, + hardwareId: '75830303934351618212', +}; +export const nanoEsp32SerialPort: Port = { + address: '/dev/cu.usbmodem3485187BD9882', + addressLabel: '/dev/cu.usbmodem3485187BD9882', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x0070', + serialNumber: '3485187BD988', + vid: '0x2341', + }, + hardwareId: '3485187BD988', +}; +export const nanoEsp32DetectsMultipleEsp32BoardsSerialPort: Port = { + address: 'COM41', + addressLabel: 'COM41', + protocol: 'serial', + protocolLabel: 'Serial Port (USB)', + properties: { + pid: '0x1001', + serialNumber: '', + vid: '0x303A', + }, +}; + +export function createPort(address: string, protocol = 'serial'): Port { + return { + address, + addressLabel: `Address label: ${address}`, + protocol, + protocolLabel: `Protocol label: ${protocol}`, + }; +} + +export function detectedPort( + port: Port, + ...boards: BoardIdentifier[] +): { [portKey: string]: DetectedPort } { + return { [Port.keyOf(port)]: boards.length ? { port, boards } : { port } }; +} + +export function history( + port: Port, + board: BoardIdentifier +): { [portKey: string]: BoardIdentifier } { + return { [Port.keyOf(port)]: board }; +} + +export const detectedPorts: DetectedPorts = { + ...detectedPort(builtinSerialPort), + ...detectedPort(bluetoothSerialPort), + ...detectedPort(unoSerialPort, uno), + ...detectedPort(mkr1000SerialPort, mkr1000), + ...detectedPort(mkr1000NetworkPort, mkr1000), + ...detectedPort(undiscoveredSerialPort), + ...detectedPort(undiscoveredUsbToUARTSerialPort), + // multiple discovered on the same port with different board names + ...detectedPort( + nanoEsp32DetectsMultipleEsp32BoardsSerialPort, + esp32S3DevModule, + esp32S3Box + ), + // multiple discovered on the same port with the same board name + ...detectedPort(nanoEsp32SerialPort, arduinoNanoEsp32, esp32NanoEsp32), +};