1// Copyright 2020 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, jasmine */ 16import {last, take} from 'rxjs/operators'; 17 18import {SerialMock} from './serial_mock'; 19import {DeviceLockedError, WebSerialTransport} from './web_serial_transport'; 20 21describe('WebSerialTransport', () => { 22 let serialMock: SerialMock; 23 beforeEach(() => { 24 serialMock = new SerialMock(); 25 }); 26 27 it('is disconnected before connecting', () => { 28 const transport = new WebSerialTransport(serialMock as Serial); 29 expect(transport.connected.getValue()).toBeFalse(); 30 }); 31 32 it('reports that it has connected', async () => { 33 const transport = new WebSerialTransport(serialMock as Serial); 34 await transport.connect(); 35 expect(serialMock.serialPort.open).toHaveBeenCalled(); 36 expect(transport.connected.getValue()).toBeTrue(); 37 }); 38 39 it('emits chunks as they arrive from the device', async () => { 40 const transport = new WebSerialTransport(serialMock as Serial); 41 await transport.connect(); 42 const data = new Uint8Array([1, 2, 3]); 43 const emitted = transport.chunks.pipe(take(1)).toPromise(); 44 serialMock.dataFromDevice(data); 45 46 expect(await emitted).toEqual(data); 47 expect(transport.connected.getValue()).toBeTrue(); 48 expect(serialMock.serialPort.readable.locked).toBeTrue(); 49 expect(serialMock.serialPort.writable.locked).toBeTrue(); 50 }); 51 52 it('is disconnected when it reaches the final chunk', async () => { 53 const transport = new WebSerialTransport(serialMock as Serial); 54 await transport.connect(); 55 const disconnectPromise = transport.connected 56 .pipe(take(2), last()) 57 .toPromise(); 58 serialMock.closeFromDevice(); 59 60 expect(await disconnectPromise).toBeFalse(); 61 }); 62 63 it('waits for the writer to be ready', async () => { 64 const transport = new WebSerialTransport(serialMock as Serial); 65 await transport.connect(); 66 const data = new Uint8Array([1, 2, 3]); 67 68 const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); 69 70 let writtenData: Uint8Array | undefined = undefined; 71 dataToDevice.then(data => { 72 writtenData = data; 73 }); 74 75 const sendPromise = transport.sendChunk(data); 76 expect(writtenData).toBeUndefined(); 77 await sendPromise; 78 expect(writtenData).toBeDefined(); 79 }); 80 81 it('sends chunks to the device', async () => { 82 const transport = new WebSerialTransport(serialMock as Serial); 83 await transport.connect(); 84 const data = new Uint8Array([1, 2, 3]); 85 86 const dataToDevice = serialMock.dataToDevice.pipe(take(1)).toPromise(); 87 88 await transport.sendChunk(data); 89 expect(await dataToDevice).toEqual(data); 90 }); 91 92 it('throws an error on failing to connect', async () => { 93 const connectError = new Error('Example connection error'); 94 spyOn(serialMock, 'requestPort').and.throwError(connectError); 95 const transport = new WebSerialTransport(serialMock as Serial); 96 await expectAsync(transport.connect()).toBeRejectedWith(connectError); 97 }); 98 99 it("emits connection errors in the 'errors' observable", async () => { 100 const transport = new WebSerialTransport(serialMock as Serial); 101 await transport.connect(); 102 103 const reportedErrorPromise = transport.errors.pipe(take(1)).toPromise(); 104 serialMock.serialPort.errorFromDevice(new Error()); 105 106 expect(await reportedErrorPromise).toEqual(new DeviceLockedError()); 107 }); 108}); 109