diff --git a/apps/spie-ui-e2e/src/e2e/serial-port.cy.ts b/apps/spie-ui-e2e/src/e2e/connect.cy.ts similarity index 92% rename from apps/spie-ui-e2e/src/e2e/serial-port.cy.ts rename to apps/spie-ui-e2e/src/e2e/connect.cy.ts index f503b88..e897dc7 100644 --- a/apps/spie-ui-e2e/src/e2e/serial-port.cy.ts +++ b/apps/spie-ui-e2e/src/e2e/connect.cy.ts @@ -3,16 +3,20 @@ import { mockSerialPortList, } from '../fixtures/mocks/electron-api.mock'; -describe('Serial Port component', () => { +describe('Connect routine', () => { beforeEach(() => { cy.visit('/'); cy.on('window:before:load', (win) => { win.electron = mockElectronAPI(win); }); + + cy.window().should((win) => { + expect(win.onSerialPortEventTrigger).to.be.a('function'); + }); }); - it('should display available serial ports in the dropdown', () => { + it('should show available serial ports in the dropdown', () => { cy.get( 'app-connection-component [placeholder="Select Serial Port"]' ).click(); @@ -112,7 +116,10 @@ describe('Serial Port component', () => { cy.get('app-connection-component ion-button [name="settings-outline"]') .parent() .click(); - cy.get('ion-modal').should('be.visible'); + cy.get('ion-modal ion-toolbar ion-title').should( + 'contain', + 'Advanced Connection Settings' + ); cy.get('ion-modal ion-toolbar ion-button').click(); cy.get('ion-modal').should('not.be.visible'); }); diff --git a/apps/spie-ui-e2e/src/e2e/send.cy.ts b/apps/spie-ui-e2e/src/e2e/send.cy.ts index abad9dc..491e06b 100644 --- a/apps/spie-ui-e2e/src/e2e/send.cy.ts +++ b/apps/spie-ui-e2e/src/e2e/send.cy.ts @@ -3,7 +3,7 @@ import { mockSerialPortList, } from '../fixtures/mocks/electron-api.mock'; -describe('Send component', () => { +describe('Send routine', () => { beforeEach(() => { cy.visit('/'); @@ -11,6 +11,10 @@ describe('Send component', () => { win.electron = mockElectronAPI(win); }); + cy.window().should((win) => { + expect(win.onSerialPortEventTrigger).to.be.a('function'); + }); + cy.connect(mockSerialPortList[0].path, 9600); }); @@ -68,7 +72,10 @@ describe('Send component', () => { cy.get('app-send-component ion-button [name="settings-outline"]') .parent() .click(); - cy.get('ion-modal').should('be.visible'); + cy.get('ion-modal ion-toolbar ion-title').should( + 'contain', + 'Advanced Send Settings' + ); cy.get('ion-modal ion-toolbar ion-button').click(); cy.get('ion-modal').should('not.be.visible'); }); diff --git a/apps/spie-ui-e2e/src/e2e/terminal.cy.ts b/apps/spie-ui-e2e/src/e2e/terminal.cy.ts index ce79d41..4b0a13a 100644 --- a/apps/spie-ui-e2e/src/e2e/terminal.cy.ts +++ b/apps/spie-ui-e2e/src/e2e/terminal.cy.ts @@ -3,7 +3,7 @@ import { mockSerialPortList, } from '../fixtures/mocks/electron-api.mock'; -describe('Terminal component', () => { +describe('Terminal routine', () => { const mockData = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'; @@ -14,12 +14,16 @@ describe('Terminal component', () => { win.electron = mockElectronAPI(win); }); + cy.window().should((win) => { + expect(win.onSerialPortEventTrigger).to.be.a('function'); + }); + cy.connect(mockSerialPortList[0].path, 9600); }); it('should display data on the terminal', () => { cy.window().then((win) => { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -33,7 +37,7 @@ describe('Terminal component', () => { it('should clear the terminal', () => { cy.window().then((win) => { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -49,7 +53,7 @@ describe('Terminal component', () => { it('should auto scroll when data is emitted', () => { cy.window().then((win) => { for (let index = 0; index < 10; index++) { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -67,14 +71,14 @@ describe('Terminal component', () => { it('should clear the terminal with clear event', () => { cy.window().then((win) => { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); }); cy.window().then((win) => { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'clear', }); }); @@ -90,7 +94,10 @@ describe('Terminal component', () => { cy.get('app-terminal-component ion-button [name="settings-outline"]') .parent() .click(); - cy.get('ion-modal').should('be.visible'); + cy.get('ion-modal ion-toolbar ion-title').should( + 'contain', + 'Advanced Terminal Settings' + ); cy.get('ion-modal ion-toolbar ion-button').click(); cy.get('ion-modal').should('not.be.visible'); }); @@ -98,7 +105,7 @@ describe('Terminal component', () => { it('should clear the terminal after changing encoding', () => { cy.window().then((win) => { for (let index = 0; index < 10; index++) { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -122,7 +129,7 @@ describe('Terminal component', () => { it('should clear the terminal after changing show timestamps', () => { cy.window().then((win) => { for (let index = 0; index < 10; index++) { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -161,7 +168,7 @@ describe('Terminal component', () => { cy.window().then((win) => { for (let index = 0; index < 10; index++) { - win.onEventTrigger({ + win.onSerialPortEventTrigger({ type: 'data', data: mockData, }); @@ -173,4 +180,6 @@ describe('Terminal component', () => { expect(currentScrollTop).to.equal(initialScrollTop); }); }); + + // TODO: PAUSE/CONTINUE }); diff --git a/apps/spie-ui-e2e/src/e2e/updater.cy.ts b/apps/spie-ui-e2e/src/e2e/updater.cy.ts new file mode 100644 index 0000000..c250c67 --- /dev/null +++ b/apps/spie-ui-e2e/src/e2e/updater.cy.ts @@ -0,0 +1,266 @@ +import { type AutoUpdaterEvent } from '@spie/types'; + +import { mockElectronAPI } from '../fixtures/mocks/electron-api.mock'; + +describe('Send routine', () => { + beforeEach(() => { + cy.visit('/'); + + cy.on('window:before:load', (win) => { + win.electron = mockElectronAPI(win); + }); + + cy.window().should((win) => { + expect(win.onSerialPortEventTrigger).to.be.a('function'); + expect(win.onAutoUpdaterEventTrigger).to.be.a('function'); + }); + }); + + it('should handle error event ', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'error', + error: new Error('Test error'), + }; + + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-toast') + .should('have.attr', 'color', 'danger') + .then(console.log); + + cy.get('ion-toast') + .shadow() + .find('.toast-header') + .should('contain', 'Error'); + + cy.get('ion-toast') + .shadow() + .find('.toast-content') + .should('contain', mockEvent.error); + }); + + it('should handle checking-for-update event ', () => { + const mockEvent: AutoUpdaterEvent = { type: 'checking-for-update' }; + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-toast') + .shadow() + .find('.toast-header') + .should('contain', 'Checking for Updates'); + }); + + it('should handle update-not-available event', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-not-available', + updateInfo: { + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-toast') + .shadow() + .find('.toast-header') + .should('contain', 'No Updates Available'); + }); + + it('should handle update-available event', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-available', + updateInfo: { + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-alert') + .find('.alert-head') + .should('contain', 'Update Available for Download'); + cy.get('ion-alert') + .find('.alert-message') + .should( + 'contain', + `Version ${mockEvent.updateInfo.version} is ready for download.` + ); + }); + + it('should handle download button click', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-available', + updateInfo: { + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-alert button').contains('Download').click(); + + cy.window().then((win) => { + cy.wrap(win.electron.downloadUpdate).should('have.been.calledOnce'); + }); + }); + + it('should handle update-downloaded event', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-downloaded', + updateDownloadedEvent: { + downloadedFile: '/test/test.exe', + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-alert') + .find('.alert-head') + .should('contain', 'Update Ready to Install'); + cy.get('ion-alert') + .find('.alert-message') + .should( + 'contain', + `Version ${mockEvent.updateDownloadedEvent.version} is ready to install.` + ); + }); + + it('should handle install button click', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-downloaded', + updateDownloadedEvent: { + downloadedFile: '/test/test.exe', + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-alert button').contains('Install').click(); + + cy.window().then((win) => { + cy.wrap(win.electron.installUpdate).should('have.been.calledOnce'); + }); + }); + + it('should handle download-progress event', () => { + const mockUpdateAvailableEvent: AutoUpdaterEvent = { + type: 'update-available', + updateInfo: { + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + const mockDownloadProgressEvent: AutoUpdaterEvent = { + type: 'download-progress', + progressInfo: { + total: 100, + delta: 1, + transferred: 75.5, + percent: 25, + bytesPerSecond: 725, + }, + }; + + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockUpdateAvailableEvent); + win.onAutoUpdaterEventTrigger(mockDownloadProgressEvent); + }); + + cy.get('ion-alert button').contains('Download').click(); + + cy.get('ion-modal ion-toolbar ion-title').should( + 'contain', + 'Download Progress' + ); + cy.get('ion-modal ion-content ion-list') + .contains('Total Size') + .parent() + .find('ion-note') + .should('contain', `${mockDownloadProgressEvent.progressInfo.total}`); + cy.get('ion-modal ion-content ion-list') + .contains('Transferred') + .parent() + .find('ion-note') + .should( + 'contain', + `${mockDownloadProgressEvent.progressInfo.transferred}` + ); + cy.get('ion-modal ion-content ion-list') + .contains('Speed') + .parent() + .find('ion-note') + .should( + 'contain', + `${mockDownloadProgressEvent.progressInfo.bytesPerSecond}` + ); + cy.get('ion-modal ion-content ion-list') + .contains('Progress') + .parent() + .find('ion-note') + .should('contain', `${mockDownloadProgressEvent.progressInfo.percent}`); + }); + + it('should handle update-cancelled event ', () => { + const mockEvent: AutoUpdaterEvent = { + type: 'update-cancelled', + updateInfo: { + version: '1.0.0', + files: [], + path: '/test', + sha512: 'test', + releaseDate: new Date().toISOString(), + }, + }; + cy.window().then((win) => { + win.onAutoUpdaterEventTrigger(mockEvent); + }); + + cy.get('ion-toast') + .should('have.attr', 'color', 'danger') + .then(console.log); + cy.get('ion-toast') + .shadow() + .find('.toast-header') + .should('contain', 'Error'); + cy.get('ion-toast') + .shadow() + .find('.toast-content') + .should('contain', 'Update Cancelled'); + }); +}); diff --git a/apps/spie-ui-e2e/src/e2e/updater.ts b/apps/spie-ui-e2e/src/e2e/updater.ts deleted file mode 100644 index 726cd24..0000000 --- a/apps/spie-ui-e2e/src/e2e/updater.ts +++ /dev/null @@ -1,3 +0,0 @@ -describe('Update', () => { - // TODO: -}); diff --git a/apps/spie-ui-e2e/src/fixtures/mocks/electron-api.mock.ts b/apps/spie-ui-e2e/src/fixtures/mocks/electron-api.mock.ts index c3aa024..6af33c1 100644 --- a/apps/spie-ui-e2e/src/fixtures/mocks/electron-api.mock.ts +++ b/apps/spie-ui-e2e/src/fixtures/mocks/electron-api.mock.ts @@ -1,12 +1,22 @@ -import { type ElectronAPI, type SerialPortEvent } from '@spie/types'; +import { + type AutoUpdaterEvent, + type DataEvent, + type ElectronAPI, + type SerialPortEvent, +} from '@spie/types'; export const mockSerialPortList = [ { path: '/dev/ttyUSB0', manufacturer: 'Manufacturer1' }, { path: '/dev/ttyUSB1', manufacturer: 'Manufacturer2' }, ]; -export function mockElectronAPI(win: any): ElectronAPI { - const listeners: Array<(serialPortEvent: SerialPortEvent) => void> = []; +export function mockElectronAPI(win: Cypress.AUTWindow): ElectronAPI { + const onSerialPortEventListeners: Array< + (serialPortEvent: SerialPortEvent | DataEvent) => void + > = []; + const onAutoUpdaterEventTriggerListeners: Array< + (autoUpdaterEvent: AutoUpdaterEvent) => void + > = []; const electronAPI: ElectronAPI = { platform: '', @@ -14,7 +24,26 @@ export function mockElectronAPI(win: any): ElectronAPI { getVersion: cy.stub(), downloadUpdate: cy.stub(), installUpdate: cy.stub(), - onUpdateEvent: cy.stub(), + onUpdateEvent: cy + .stub() + .callsFake((callback: (autoUpdaterEvent: AutoUpdaterEvent) => void) => { + onAutoUpdaterEventTriggerListeners.push(callback); + + win.onAutoUpdaterEventTrigger = ( + autoUpdaterEvent: AutoUpdaterEvent + ) => { + onAutoUpdaterEventTriggerListeners.forEach((listener) => + listener(autoUpdaterEvent) + ); + }; + + return () => { + const index = onAutoUpdaterEventTriggerListeners.indexOf(callback); + if (index !== -1) { + onAutoUpdaterEventTriggerListeners.splice(index, 1); + } + }; + }), serialPort: { list: cy.stub().resolves(mockSerialPortList), open: cy.stub(), @@ -26,21 +55,26 @@ export function mockElectronAPI(win: any): ElectronAPI { getOpenOptions: cy.stub().resolves(null), onEvent: cy .stub() - .callsFake((callback: (serialPortEvent: SerialPortEvent) => void) => { - listeners.push(callback); - - // Assign the passed `onEventTrigger` function to trigger the events - win.onEventTrigger = (serialPortEvent: SerialPortEvent) => { - listeners.forEach((listener) => listener(serialPortEvent)); - }; - - return () => { - const index = listeners.indexOf(callback); - if (index !== -1) { - listeners.splice(index, 1); - } - }; - }), + .callsFake( + ( + callback: (serialPortEvent: SerialPortEvent | DataEvent) => void + ) => { + onSerialPortEventListeners.push(callback); + + win.onSerialPortEventTrigger = (serialPortEvent) => { + onSerialPortEventListeners.forEach((listener) => + listener(serialPortEvent) + ); + }; + + return () => { + const index = onSerialPortEventListeners.indexOf(callback); + if (index !== -1) { + onSerialPortEventListeners.splice(index, 1); + } + }; + } + ), }, }; diff --git a/apps/spie-ui-e2e/src/support/commands.ts b/apps/spie-ui-e2e/src/support/commands.ts index dda62e0..50a1ae9 100644 --- a/apps/spie-ui-e2e/src/support/commands.ts +++ b/apps/spie-ui-e2e/src/support/commands.ts @@ -1,4 +1,5 @@ import { + type AutoUpdaterEvent, type DataEvent, type ElectronAPI, type SerialPortEvent, @@ -8,7 +9,10 @@ declare global { namespace Cypress { interface Window { electron: ElectronAPI; - onEventTrigger: (serialPortEvent: SerialPortEvent | DataEvent) => void; + onAutoUpdaterEventTrigger: (autoUpdaterEvent: AutoUpdaterEvent) => void; + onSerialPortEventTrigger: ( + serialPortEvent: SerialPortEvent | DataEvent + ) => void; } // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chainable { @@ -66,7 +70,7 @@ Cypress.Commands.add('connect', (path: string, baudRate: number) => { cy.get('app-connection-component ion-button').contains('Connect').click(); cy.window().then((win) => { - win.onEventTrigger({ type: 'open' }); + win.onSerialPortEventTrigger({ type: 'open' }); }); }); @@ -74,6 +78,6 @@ Cypress.Commands.add('disconnect', () => { cy.get('app-connection-component ion-button').contains('Disconnect').click(); cy.window().then((win) => { - win.onEventTrigger({ type: 'close' }); + win.onSerialPortEventTrigger({ type: 'close' }); }); });