1// Copyright 2022 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15/* eslint-env browser */ 16import { last, take } from 'rxjs/operators'; 17 18import { SerialMock } from './serial_mock'; 19import { WebSerialTransport, DeviceLockedError } from './web_serial_transport'; 20import type { Serial } from 'pigweedjs/types/serial'; 21 22describe('WebSerialTransport', () => { 23 let serialMock: SerialMock; 24 beforeEach(() => { 25 serialMock = new SerialMock(); 26 }); 27 28 it('is disconnected before connecting', () => { 29 const transport = new WebSerialTransport(serialMock as Serial); 30 expect(transport.connected.getValue()).toBe(false); 31 }); 32 33 it('reports that it has connected', async () => { 34 const transport = new WebSerialTransport(serialMock as Serial); 35 await transport.connect(); 36 expect(serialMock.serialPort.open).toHaveBeenCalled(); 37 expect(transport.connected.getValue()).toBe(true); 38 }); 39 40 it('emits chunks as they arrive from the device', async () => { 41 const transport = new WebSerialTransport(serialMock as Serial); 42 await transport.connect(); 43 const data = new Uint8Array([1, 2, 3]); 44 const emitted = transport.chunks.pipe(take(1)).toPromise(); 45 serialMock.dataFromDevice(data); 46 47 expect(await emitted).toEqual(data); 48 expect(transport.connected.getValue()).toBe(true); 49 expect(serialMock.serialPort.readable.locked).toBe(true); 50 expect(serialMock.serialPort.writable.locked).toBe(true); 51 }); 52 53 it('is disconnected when it reaches the final chunk', async () => { 54 const transport = new WebSerialTransport(serialMock as Serial); 55 await transport.connect(); 56 const disconnectPromise = transport.connected 57 .pipe(take(2), last()) 58 .toPromise(); 59 serialMock.closeFromDevice(); 60 61 expect(await disconnectPromise).toBe(false); 62 }); 63 64 it('waits for the writer to be ready', async () => { 65 const transport = new WebSerialTransport(serialMock as Serial); 66 await transport.connect(); 67 const data = new Uint8Array([1, 2, 3]); 68 69 const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); 70 71 let writtenData: Uint8Array | undefined = undefined; 72 dataToDevice.then((data) => { 73 writtenData = data; 74 }); 75 76 const sendPromise = transport.sendChunk(data); 77 expect(writtenData).toBeUndefined(); 78 await sendPromise; 79 expect(writtenData).toBeDefined(); 80 }); 81 82 it('sends chunks to the device', async () => { 83 const transport = new WebSerialTransport(serialMock as Serial); 84 await transport.connect(); 85 const data = new Uint8Array([1, 2, 3]); 86 87 const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); 88 89 await transport.sendChunk(data); 90 expect(await dataToDevice).toEqual(data); 91 }); 92 93 it('throws an error on failing to connect', async () => { 94 const connectError = new Error('Example connection error'); 95 const spy = jest.spyOn(serialMock, 'requestPort').mockImplementation(() => { 96 throw connectError; 97 }); 98 const transport = new WebSerialTransport(serialMock as Serial); 99 await expect(transport.connect()).rejects.toThrow(connectError.message); 100 }); 101 102 it("emits connection errors in the 'errors' observable", async () => { 103 const transport = new WebSerialTransport(serialMock as Serial); 104 await transport.connect(); 105 106 const reportedErrorPromise = transport.errors.pipe(take(1)).toPromise(); 107 serialMock.serialPort.errorFromDevice(new DeviceLockedError()); 108 109 expect(await reportedErrorPromise).toEqual(new DeviceLockedError()); 110 }); 111}); 112