// Copyright 2022 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. /* eslint-env browser */ import {last, take} from 'rxjs/operators'; import {SerialMock} from './serial_mock'; import {WebSerialTransport, DeviceLockedError} from './web_serial_transport'; import type {Serial} from "pigweedjs/types/serial" describe('WebSerialTransport', () => { let serialMock: SerialMock; beforeEach(() => { serialMock = new SerialMock(); }); it('is disconnected before connecting', () => { const transport = new WebSerialTransport(serialMock as Serial); expect(transport.connected.getValue()).toBe(false); }); it('reports that it has connected', async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); expect(serialMock.serialPort.open).toHaveBeenCalled(); expect(transport.connected.getValue()).toBe(true); }); it('emits chunks as they arrive from the device', async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); const data = new Uint8Array([1, 2, 3]); const emitted = transport.chunks.pipe(take(1)).toPromise(); serialMock.dataFromDevice(data); expect(await emitted).toEqual(data); expect(transport.connected.getValue()).toBe(true); expect(serialMock.serialPort.readable.locked).toBe(true); expect(serialMock.serialPort.writable.locked).toBe(true); }); it('is disconnected when it reaches the final chunk', async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); const disconnectPromise = transport.connected .pipe(take(2), last()) .toPromise(); serialMock.closeFromDevice(); expect(await disconnectPromise).toBe(false); }); it('waits for the writer to be ready', async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); const data = new Uint8Array([1, 2, 3]); const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); let writtenData: Uint8Array | undefined = undefined; dataToDevice.then(data => { writtenData = data; }); const sendPromise = transport.sendChunk(data); expect(writtenData).toBeUndefined(); await sendPromise; expect(writtenData).toBeDefined(); }); it('sends chunks to the device', async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); const data = new Uint8Array([1, 2, 3]); const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); await transport.sendChunk(data); expect(await dataToDevice).toEqual(data); }); it('throws an error on failing to connect', async () => { const connectError = new Error('Example connection error'); const spy = jest.spyOn(serialMock, 'requestPort').mockImplementation(() => { throw connectError; }); const transport = new WebSerialTransport(serialMock as Serial); await expect(transport.connect()).rejects.toThrow(connectError.message); }); it("emits connection errors in the 'errors' observable", async () => { const transport = new WebSerialTransport(serialMock as Serial); await transport.connect(); const reportedErrorPromise = transport.errors.pipe(take(1)).toPromise(); serialMock.serialPort.errorFromDevice(new Error()); expect(await reportedErrorPromise).toEqual(new DeviceLockedError()); }); });