• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {Subject} from 'rxjs';
17import type {SerialConnectionEvent, SerialPort, Serial, SerialPortRequestOptions, SerialOptions} from "pigweedjs/types/serial"
18/**
19 * AsyncQueue is a queue that allows values to be dequeued
20 * before they are enqueued, returning a promise that resolves
21 * once the value is available.
22 */
23class AsyncQueue<T> {
24  private queue: T[] = [];
25  private requestQueue: Array<(val: T) => unknown> = [];
26
27  /**
28   * Enqueue val into the queue.
29   * @param {T} val
30   */
31  enqueue(val: T) {
32    const callback = this.requestQueue.shift();
33    if (callback) {
34      callback(val);
35    } else {
36      this.queue.push(val);
37    }
38  }
39
40  /**
41   * Dequeue a value from the queue, returning a promise
42   * if the queue is empty.
43   */
44  async dequeue(): Promise<T> {
45    const val = this.queue.shift();
46    if (val !== undefined) {
47      return val;
48    } else {
49      const queuePromise = new Promise<T>(resolve => {
50        this.requestQueue.push(resolve);
51      });
52      return queuePromise;
53    }
54  }
55}
56
57/**
58 * SerialPortMock is a mock for Chrome's upcoming SerialPort interface.
59 * Since pw_web only depends on a subset of the interface, this mock
60 * only implements that subset.
61 */
62class SerialPortMock implements SerialPort {
63  private deviceData = new AsyncQueue<{
64    data?: Uint8Array;
65    done?: boolean;
66    error?: Error;
67  }>();
68
69  /**
70   * Simulate the device sending data to the browser.
71   * @param {Uint8Array} data
72   */
73  dataFromDevice(data: Uint8Array) {
74    this.deviceData.enqueue({data});
75  }
76
77  /**
78   * Simulate the device closing the connection with the browser.
79   */
80  closeFromDevice() {
81    this.deviceData.enqueue({done: true});
82  }
83
84  /**
85   * Simulate an error in the device's read stream.
86   * @param {Error} error
87   */
88  errorFromDevice(error: Error) {
89    this.deviceData.enqueue({error});
90  }
91
92  /**
93   * An rxjs subject tracking data sent to the (fake) device.
94   */
95  dataToDevice = new Subject<Uint8Array>();
96
97  /**
98   * The ReadableStream of bytes from the device.
99   */
100  readable = new ReadableStream<Uint8Array>({
101    pull: async controller => {
102      const {data, done, error} = await this.deviceData.dequeue();
103      if (done) {
104        controller.close();
105        return;
106      }
107      if (error) {
108        throw error;
109      }
110      if (data) {
111        controller.enqueue(data);
112      }
113    },
114  });
115
116  /**
117   * The WritableStream of bytes to the device.
118   */
119  writable = new WritableStream<Uint8Array>({
120    write: chunk => {
121      this.dataToDevice.next(chunk);
122    },
123  });
124
125  /**
126   * A spy for opening the serial port.
127   */
128  open = jest.fn(async (options?: SerialOptions) => { });
129
130  /**
131   * A spy for closing the serial port.
132   */
133  close = jest.fn(() => { });
134}
135
136export class SerialMock implements Serial {
137  serialPort = new SerialPortMock();
138  dataToDevice = this.serialPort.dataToDevice;
139  dataFromDevice = (data: Uint8Array) => {
140    this.serialPort.dataFromDevice(data);
141  };
142  closeFromDevice = () => {
143    this.serialPort.closeFromDevice();
144  };
145  errorFromDevice = (error: Error) => {
146    this.serialPort.errorFromDevice(error);
147  };
148
149  /**
150   * Request the port from the browser.
151   */
152  async requestPort(options?: SerialPortRequestOptions) {
153    return this.serialPort;
154  }
155
156  // The rest of the methods are unimplemented
157  // and only exist to ensure SerialMock implements Serial
158
159  onconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null {
160    throw new Error('Method not implemented.');
161  }
162
163  ondisconnect(): ((this: this, ev: SerialConnectionEvent) => any) | null {
164    throw new Error('Method not implemented.');
165  }
166
167  getPorts(): Promise<SerialPort[]> {
168    throw new Error('Method not implemented.');
169  }
170
171  addEventListener(
172    type: 'connect' | 'disconnect',
173    listener: (this: this, ev: SerialConnectionEvent) => any,
174    useCapture?: boolean
175  ): void;
176
177  addEventListener(
178    type: string,
179    listener: EventListener | EventListenerObject | null,
180    options?: boolean | AddEventListenerOptions
181  ): void;
182
183  addEventListener(type: any, listener: any, options?: any) {
184    throw new Error('Method not implemented.');
185  }
186
187  removeEventListener(
188    type: 'connect' | 'disconnect',
189    callback: (this: this, ev: SerialConnectionEvent) => any,
190    useCapture?: boolean
191  ): void;
192
193  removeEventListener(
194    type: string,
195    callback: EventListener | EventListenerObject | null,
196    options?: boolean | EventListenerOptions
197  ): void;
198
199  removeEventListener(type: any, callback: any, options?: any) {
200    throw new Error('Method not implemented.');
201  }
202
203  dispatchEvent(event: Event): boolean {
204    throw new Error('Method not implemented.');
205  }
206}
207