mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
actions: simplify usage of Matchable
This removes use of the pseudo-internal toString() function and also removes extraneous uses of ReturnType<>. Also, a new when() method is added to simplify additional uses.
This commit is contained in:
+40
-5
@@ -12,8 +12,26 @@ type MatchFunction<A extends AnyAction> = (action: AnyAction) => action is A;
|
||||
|
||||
/** The extra members that are attached to a function by createAction(). */
|
||||
type MatchableExtensions<F extends ActionCreationFunction<A>, A extends AnyAction> = {
|
||||
/**
|
||||
* This should not usually be used directly. It allows Matchable action
|
||||
* functions to be passed directly to redux saga effects as an action pattern.
|
||||
*/
|
||||
toString(): ReturnType<F>['type'];
|
||||
/**
|
||||
* Type guard to ensure an action matches this type.
|
||||
*/
|
||||
matches: MatchFunction<ReturnType<F>>;
|
||||
/**
|
||||
* Type guard creation function with addition filtering.
|
||||
*
|
||||
* This is useful for creating a guard function to pass to redux saga
|
||||
* effects.
|
||||
*
|
||||
* @example const action = yield* take(someAction.when((a) => a.property === value));
|
||||
*
|
||||
* @param predicate An predicate to filter actions.
|
||||
*/
|
||||
when(predicate: (action: ReturnType<F>) => boolean): MatchFunction<ReturnType<F>>;
|
||||
};
|
||||
|
||||
/** An action creation function that includes MatchableExtensions. */
|
||||
@@ -26,14 +44,31 @@ type Matchable<F extends ActionCreationFunction<A>, A extends AnyAction> = F &
|
||||
* @param actionCreator The action creation function.
|
||||
* @returns actionCreator with type property and match method added.
|
||||
*/
|
||||
export function createAction<T extends ActionCreationFunction<A>, A extends AnyAction>(
|
||||
actionCreator: T,
|
||||
): Matchable<T, A> {
|
||||
export function createAction<F extends ActionCreationFunction<A>, A extends AnyAction>(
|
||||
actionCreator: F,
|
||||
): Matchable<F, A> {
|
||||
// create a default action so we can get the type string.
|
||||
const type = actionCreator().type;
|
||||
|
||||
return Object.assign(actionCreator, <MatchableExtensions<T, A>>{
|
||||
function matches(action: AnyAction): action is ReturnType<F> {
|
||||
return action.type === type;
|
||||
}
|
||||
|
||||
function when(
|
||||
predicate: (action: ReturnType<F>) => boolean,
|
||||
): MatchFunction<ReturnType<F>> {
|
||||
return (a: AnyAction): a is ReturnType<F> => {
|
||||
if (!matches(a)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return predicate(a);
|
||||
};
|
||||
}
|
||||
|
||||
return Object.assign(actionCreator, <MatchableExtensions<F, A>>{
|
||||
toString: () => type,
|
||||
matches: (action) => action.type === type,
|
||||
matches,
|
||||
when,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,9 +57,8 @@ function* encodeRequest(): Generator {
|
||||
}
|
||||
|
||||
const { failedToSend } = yield* race({
|
||||
sent: take<ReturnType<typeof didWriteCommand>>(didWriteCommand),
|
||||
failedToSend:
|
||||
take<ReturnType<typeof didFailToWriteCommand>>(didFailToWriteCommand),
|
||||
sent: take(didWriteCommand),
|
||||
failedToSend: take(didFailToWriteCommand),
|
||||
});
|
||||
|
||||
if (failedToSend) {
|
||||
|
||||
+1
-4
@@ -63,10 +63,7 @@ import { BleConnectionState } from './reducers';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
function handleDisconnect(
|
||||
server: BluetoothRemoteGATTServer,
|
||||
_action: ReturnType<typeof disconnect>,
|
||||
): void {
|
||||
function handleDisconnect(server: BluetoothRemoteGATTServer): void {
|
||||
server.disconnect();
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -2,7 +2,6 @@
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import { AnyAction } from 'redux';
|
||||
import {
|
||||
call,
|
||||
put,
|
||||
@@ -109,14 +108,11 @@ function* handleSetEditSession(action: ReturnType<typeof setEditSession>): Gener
|
||||
|
||||
yield* put(fileStorageReadFile(currentFileName));
|
||||
const { result } = yield* race({
|
||||
result: take<ReturnType<typeof fileStorageDidReadFile>>(
|
||||
(a: AnyAction) =>
|
||||
fileStorageDidReadFile.matches(a) && a.fileName === currentFileName,
|
||||
result: take(
|
||||
fileStorageDidReadFile.when((a) => a.fileName === currentFileName),
|
||||
),
|
||||
error: take<ReturnType<typeof fileStorageDidFailToReadFile>>(
|
||||
(a: AnyAction) =>
|
||||
fileStorageDidFailToReadFile.matches(a) &&
|
||||
a.fileName === currentFileName,
|
||||
error: take(
|
||||
fileStorageDidFailToReadFile.when((a) => a.fileName === currentFileName),
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ it('should dispatch fail action if file does not exist', async () => {
|
||||
saga.put(fileStorageReadFile(testFileName));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toHaveProperty('type', fileStorageDidFailToReadFile.toString());
|
||||
expect(fileStorageDidFailToReadFile.matches(action)).toBeTruthy();
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
+19
-34
@@ -11,6 +11,7 @@ import cityHubZip from '@pybricks/firmware/build/cityhub.zip';
|
||||
import moveHubZip from '@pybricks/firmware/build/movehub.zip';
|
||||
import technicHubZip from '@pybricks/firmware/build/technichub.zip';
|
||||
import { AnyAction } from 'redux';
|
||||
import { ActionPattern } from 'redux-saga/effects';
|
||||
import {
|
||||
SagaGenerator,
|
||||
all,
|
||||
@@ -85,12 +86,8 @@ function* disconnectAndCancel(): SagaGenerator<void> {
|
||||
|
||||
function* waitForDidRequest(id: number): SagaGenerator<ReturnType<typeof didRequest>> {
|
||||
const { requested, failedToRequest } = yield* race({
|
||||
requested: take<ReturnType<typeof didRequest>>(
|
||||
(a: AnyAction) => didRequest.matches(a) && a.id === id,
|
||||
),
|
||||
failedToRequest: take<ReturnType<typeof didFailToRequest>>(
|
||||
(a: AnyAction) => didFailToRequest.matches(a) && a.id === id,
|
||||
),
|
||||
requested: take(didRequest.when((a) => a.id === id)),
|
||||
failedToRequest: take(didFailToRequest.when((a) => a.id === id)),
|
||||
});
|
||||
|
||||
if (failedToRequest) {
|
||||
@@ -108,30 +105,31 @@ function* waitForDidRequest(id: number): SagaGenerator<ReturnType<typeof didRequ
|
||||
/**
|
||||
* Waits for a response action, an error response or timeout, whichever comes
|
||||
* first.
|
||||
* @param type The action type to wait for.
|
||||
* @param pattern The action type to wait for.
|
||||
* @param timeout The timeout in milliseconds.
|
||||
*/
|
||||
function* waitForResponse<T extends AnyAction>(
|
||||
type: string,
|
||||
function* waitForResponse<A extends AnyAction>(
|
||||
pattern: ActionPattern<A>,
|
||||
timeout = 500,
|
||||
): SagaGenerator<T> {
|
||||
): SagaGenerator<A> {
|
||||
const { response, error, disconnected, timedOut } = yield* race({
|
||||
response: take<T>(type),
|
||||
error: take<ReturnType<typeof errorResponse>>(errorResponse),
|
||||
response: take(pattern),
|
||||
error: take(errorResponse),
|
||||
disconnected: take(didDisconnect),
|
||||
timedOut: delay(timeout),
|
||||
});
|
||||
|
||||
if (timedOut) {
|
||||
// istanbul ignore if: this hacks around a hardware/OS issue
|
||||
if (type === errorResponse.toString()) {
|
||||
if (pattern === (errorResponse as unknown)) {
|
||||
// It has been observed that sometimes this response is not received
|
||||
// or gets stuck in the Bluetooth stack until another request is sent.
|
||||
// So, we ignore the timeout and continue. If there really was a
|
||||
// problem, then the next request should fail anyway.
|
||||
console.warn('Timeout waiting for erase response, continuing anyway.');
|
||||
return eraseResponse(Result.OK) as unknown as T;
|
||||
return eraseResponse(Result.OK) as unknown as A;
|
||||
}
|
||||
|
||||
yield* put(didFailToFinish(FailToFinishReasonType.TimedOut));
|
||||
yield* disconnectAndCancel();
|
||||
}
|
||||
@@ -209,8 +207,8 @@ function* loadFirmware(
|
||||
|
||||
yield* put(compile(program, metadata['mpy-cross-options']));
|
||||
const { mpy, mpyFail } = yield* race({
|
||||
mpy: take<ReturnType<typeof didCompile>>(didCompile),
|
||||
mpyFail: take<ReturnType<typeof didFailToCompile>>(didFailToCompile),
|
||||
mpy: take(didCompile),
|
||||
mpyFail: take(didFailToCompile),
|
||||
});
|
||||
|
||||
if (mpyFail) {
|
||||
@@ -310,9 +308,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
const infoAction = yield* put(infoRequest(nextMessageId()));
|
||||
const { info } = yield* all({
|
||||
sent: waitForDidRequest(infoAction.id),
|
||||
info: waitForResponse<ReturnType<typeof infoResponse>>(
|
||||
infoResponse.toString(),
|
||||
),
|
||||
info: waitForResponse(infoResponse),
|
||||
});
|
||||
|
||||
if (deviceId !== undefined && info.hubType !== deviceId) {
|
||||
@@ -353,10 +349,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
);
|
||||
const { erase } = yield* all({
|
||||
sent: waitForDidRequest(eraseAction.id),
|
||||
erase: waitForResponse<ReturnType<typeof eraseResponse>>(
|
||||
eraseResponse.toString(),
|
||||
5000,
|
||||
),
|
||||
erase: waitForResponse(eraseResponse, 5000),
|
||||
});
|
||||
if (erase.result !== Result.OK) {
|
||||
yield* put(
|
||||
@@ -368,9 +361,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
const initAction = yield* put(initRequest(nextMessageId(), firmware.length));
|
||||
const { init } = yield* all({
|
||||
sent: waitForDidRequest(initAction.id),
|
||||
init: waitForResponse<ReturnType<typeof initResponse>>(
|
||||
initResponse.toString(),
|
||||
),
|
||||
init: waitForResponse(initResponse),
|
||||
});
|
||||
if (init.result) {
|
||||
yield* put(
|
||||
@@ -420,10 +411,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
|
||||
const { response } = yield* all({
|
||||
sent: waitForDidRequest(checksumAction.id),
|
||||
response: waitForResponse<ReturnType<typeof checksumResponse>>(
|
||||
checksumResponse.toString(),
|
||||
5000,
|
||||
),
|
||||
response: waitForResponse(checksumResponse, 5000),
|
||||
});
|
||||
|
||||
if (response.checksum !== runningChecksum) {
|
||||
@@ -447,10 +435,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
}
|
||||
}
|
||||
|
||||
const flash = yield* waitForResponse<ReturnType<typeof programResponse>>(
|
||||
programResponse.toString(),
|
||||
5000,
|
||||
);
|
||||
const flash = yield* waitForResponse(programResponse, 5000);
|
||||
|
||||
if (flash.count !== firmware.length) {
|
||||
yield* put(
|
||||
|
||||
+14
-24
@@ -37,51 +37,45 @@ describe('downloadAndRun', () => {
|
||||
|
||||
// first, it tries to compile the program in the current editor
|
||||
const compileAction = await saga.take();
|
||||
expect(compileAction.type).toBe(compile.toString());
|
||||
expect(compile.matches(compileAction)).toBeTruthy();
|
||||
saga.put(didCompile(new Uint8Array(30)));
|
||||
|
||||
// then it notifies that loading has begun
|
||||
const loadingStatusAction = await saga.take();
|
||||
expect(loadingStatusAction.type).toBe(didStartDownload.toString());
|
||||
expect(loadingStatusAction).toEqual(didStartDownload());
|
||||
|
||||
// first message is the length
|
||||
const writeAction = await saga.take();
|
||||
expect(writeAction.type).toBe(write.toString());
|
||||
expect(writeAction).toBeTruthy();
|
||||
expect((writeAction as ReturnType<typeof write>).value.length).toBe(4);
|
||||
saga.put(didWrite((writeAction as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(0));
|
||||
saga.put(checksum(30));
|
||||
|
||||
// then progress is updated
|
||||
const progressAction = await saga.take();
|
||||
expect(progressAction.type).toBe(didProgressDownload.toString());
|
||||
expect(
|
||||
(progressAction as ReturnType<typeof didProgressDownload>).progress,
|
||||
).toBe(0);
|
||||
expect(progressAction).toEqual(didProgressDownload(0));
|
||||
|
||||
// then the first chunk of 20 bytes
|
||||
const writeAction2 = await saga.take();
|
||||
expect(writeAction2.type).toBe(write.toString());
|
||||
expect(write.matches(writeAction2)).toBeTruthy();
|
||||
expect((writeAction2 as ReturnType<typeof write>).value.length).toBe(20);
|
||||
saga.put(didWrite((writeAction2 as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(1));
|
||||
saga.put(checksum(0));
|
||||
|
||||
// then progress is updated
|
||||
const progress2Action = await saga.take();
|
||||
expect(progress2Action.type).toBe(didProgressDownload.toString());
|
||||
expect(
|
||||
(progress2Action as ReturnType<typeof didProgressDownload>).progress,
|
||||
).toBe(20 / 30);
|
||||
expect(progress2Action).toEqual(didProgressDownload(20 / 30));
|
||||
|
||||
// then last chunk
|
||||
const writeAction3 = await saga.take();
|
||||
expect(writeAction3.type).toBe(write.toString());
|
||||
expect(write.matches(writeAction3)).toBeTruthy();
|
||||
expect((writeAction3 as ReturnType<typeof write>).value.length).toBe(10);
|
||||
saga.put(didWrite((writeAction3 as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(2));
|
||||
saga.put(checksum(0));
|
||||
|
||||
// Then a status message saying that we are done
|
||||
const loadedStatusAction = await saga.take();
|
||||
expect(loadedStatusAction.type).toBe(didFinishDownload.toString());
|
||||
expect(loadedStatusAction).toEqual(didFinishDownload());
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -95,7 +89,7 @@ test('repl', async () => {
|
||||
saga.put(repl());
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect(action).toEqual(write(0, new Uint8Array([32, 32, 32, 32])));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -106,13 +100,9 @@ test('stop', async () => {
|
||||
saga.put(stop());
|
||||
|
||||
const pybricksServiceAction = await saga.take();
|
||||
expect(pybricksServiceAction.type).toBe(sendStopUserProgramCommand.toString());
|
||||
expect(pybricksServiceAction).toEqual(sendStopUserProgramCommand(0));
|
||||
|
||||
saga.put(
|
||||
didSendCommand(
|
||||
(pybricksServiceAction as ReturnType<typeof sendStopUserProgramCommand>).id,
|
||||
),
|
||||
);
|
||||
saga.put(didSendCommand(0));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
+7
-16
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
|
||||
import { AnyAction } from 'redux';
|
||||
import {
|
||||
SagaGenerator,
|
||||
actionChannel,
|
||||
@@ -43,12 +42,8 @@ function* waitForWrite(id: number): SagaGenerator<{
|
||||
didFailToWrite: ReturnType<typeof didFailToWrite> | undefined;
|
||||
}> {
|
||||
return yield* race({
|
||||
didWrite: take<ReturnType<typeof didWrite>>(
|
||||
(a: AnyAction) => didWrite.matches(a) && a.id === id,
|
||||
),
|
||||
didFailToWrite: take<ReturnType<typeof didFailToWrite>>(
|
||||
(a: AnyAction) => didFailToWrite.matches(a) && a.id === id,
|
||||
),
|
||||
didWrite: take(didWrite.when((a) => a.id === id)),
|
||||
didFailToWrite: take(didFailToWrite.when((a) => a.id === id)),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,8 +59,8 @@ function* handleDownloadAndRun(): Generator {
|
||||
const script = editor.getValue();
|
||||
yield* put(compile(script, ['-mno-unicode']));
|
||||
const { mpy, mpyFail } = yield* race({
|
||||
mpy: take<ReturnType<typeof didCompile>>(didCompile),
|
||||
mpyFail: take<ReturnType<typeof didFailToCompile>>(didFailToCompile),
|
||||
mpy: take(didCompile),
|
||||
mpyFail: take(didFailToCompile),
|
||||
});
|
||||
|
||||
if (mpyFail) {
|
||||
@@ -82,7 +77,7 @@ function* handleDownloadAndRun(): Generator {
|
||||
console.log(`Downloading ${mpy.data.byteLength} bytes`);
|
||||
}
|
||||
|
||||
const checksumChannel = yield* actionChannel<ReturnType<typeof checksum>>(checksum);
|
||||
const checksumChannel = yield* actionChannel(checksum);
|
||||
|
||||
const nextMessageId = yield* getContext<() => number>('nextMessageId');
|
||||
|
||||
@@ -180,12 +175,8 @@ function* handleStop(): Generator {
|
||||
// REVISIT: may want to disable button while attempting to send command
|
||||
// this would mean didSendStop() and didFailToSendStop() actions here
|
||||
const { failedToSend } = yield* race({
|
||||
sent: take<ReturnType<typeof didSendCommand>>(
|
||||
(a: AnyAction) => didSendCommand.matches(a) && a.id === id,
|
||||
),
|
||||
failedToSend: take<ReturnType<typeof didFailToSendCommand>>(
|
||||
(a: AnyAction) => didFailToSendCommand.matches(a) && a.id === id,
|
||||
),
|
||||
sent: take(didSendCommand.when((a) => a.id === id)),
|
||||
failedToSend: take(didFailToSendCommand.when((a) => a.id === id)),
|
||||
});
|
||||
if (failedToSend) {
|
||||
// TODO: probably want to check error. If hub disconnected, ignore error
|
||||
|
||||
@@ -114,20 +114,24 @@ describe('message encoder', () => {
|
||||
],
|
||||
],
|
||||
])('encode %s request', async (_n, request, expected) => {
|
||||
const messageTypesThatShouldBeCalledWithoutResponse = [
|
||||
eraseRequest.toString(),
|
||||
programRequest.toString(),
|
||||
rebootRequest.toString(),
|
||||
disconnectRequest.toString(),
|
||||
const requestsThatShouldBeCalledWithoutResponse = [
|
||||
eraseRequest,
|
||||
programRequest,
|
||||
rebootRequest,
|
||||
disconnectRequest,
|
||||
];
|
||||
|
||||
const saga = new AsyncSaga(bootloader);
|
||||
saga.put(request);
|
||||
const message = new Uint8Array(expected);
|
||||
const action = await saga.take();
|
||||
|
||||
expect(action).toEqual(
|
||||
send(
|
||||
message,
|
||||
!messageTypesThatShouldBeCalledWithoutResponse.includes(request.type),
|
||||
!requestsThatShouldBeCalledWithoutResponse.find((r) =>
|
||||
r.matches(request),
|
||||
),
|
||||
),
|
||||
);
|
||||
await saga.end();
|
||||
|
||||
@@ -116,8 +116,8 @@ function* encodeRequest(): Generator {
|
||||
}
|
||||
|
||||
const { failedToSend } = yield* race({
|
||||
sent: take<ReturnType<typeof didSend>>(didSend),
|
||||
failedToSend: take<ReturnType<typeof didFailToSend>>(didFailToSend),
|
||||
sent: take(didSend),
|
||||
failedToSend: take(didFailToSend),
|
||||
});
|
||||
|
||||
if (failedToSend) {
|
||||
|
||||
@@ -16,7 +16,7 @@ test('compiler works', async () => {
|
||||
saga.put(compile('print("hello!")', []));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(didCompile.toString());
|
||||
expect(didCompile.matches(action)).toBeTruthy();
|
||||
const { data } = action as ReturnType<typeof didCompile>;
|
||||
expect(data[0]).toBe('M'.charCodeAt(0));
|
||||
expect(data[1]).toBe(5); // ABI version
|
||||
@@ -30,7 +30,7 @@ test('compiler error works', async () => {
|
||||
saga.put(compile('syntax error!', []));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(didFailToCompile.toString());
|
||||
expect(didFailToCompile.matches(action)).toBeTruthy();
|
||||
const { err } = action as ReturnType<typeof didFailToCompile>;
|
||||
expect(err).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
|
||||
+22
-36
@@ -15,6 +15,8 @@ import { createCountFunc } from '../utils/iter';
|
||||
import { receiveData, sendData } from './actions';
|
||||
import terminal from './sagas';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
describe('Data receiver filters out hub status', () => {
|
||||
test('normal message - no status', async () => {
|
||||
const saga = new AsyncSaga(
|
||||
@@ -27,8 +29,7 @@ describe('Data receiver filters out hub status', () => {
|
||||
saga.put(didNotify(new DataView(new Uint8Array([0x20]).buffer)));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(sendData.toString());
|
||||
expect((action as ReturnType<typeof sendData>).value).toBe(' ');
|
||||
expect(action).toEqual(sendData(' '));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -43,8 +44,7 @@ describe('Data receiver filters out hub status', () => {
|
||||
saga.put(didNotify(new DataView(new Uint8Array([0xaa]).buffer)));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(checksum.toString());
|
||||
expect((action as ReturnType<typeof checksum>).checksum).toBe(0xaa);
|
||||
expect(action).toEqual(checksum(0xaa));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -74,8 +74,7 @@ test('Terminal data source responds to send data actions', async () => {
|
||||
});
|
||||
|
||||
describe('Terminal data source responds to receive data actions', () => {
|
||||
// ASCII/UTF-8 encoding of 'test1234'
|
||||
const expected = new Uint8Array([0x74, 0x65, 0x73, 0x74, 0x31, 0x32, 0x33, 0x34]);
|
||||
const expected = encoder.encode('test1234');
|
||||
|
||||
test('basic function works', async () => {
|
||||
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
|
||||
@@ -83,8 +82,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
saga.put(receiveData('test1234'));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect((action as ReturnType<typeof write>).value).toEqual(expected);
|
||||
expect(action).toEqual(write(0, expected));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -100,19 +98,17 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
expect(saga.numPending()).toBe(1);
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect((action as ReturnType<typeof write>).value).toEqual(expected);
|
||||
expect(action).toEqual(write(0, expected));
|
||||
|
||||
// second message is queued until didWrite or didFailToWrite
|
||||
expect(saga.numPending()).toBe(0);
|
||||
|
||||
saga.put(didWrite((action as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(0));
|
||||
|
||||
const action2 = await saga.take();
|
||||
expect(action2.type).toBe(write.toString());
|
||||
expect((action2 as ReturnType<typeof write>).value).toEqual(expected);
|
||||
expect(action2).toEqual(write(1, expected));
|
||||
|
||||
saga.put(didWrite((action2 as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(1));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -128,24 +124,17 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
expect(saga.numPending()).toBe(1);
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect((action as ReturnType<typeof write>).value).toEqual(expected);
|
||||
expect(action).toEqual(write(0, expected));
|
||||
|
||||
// second message is queued until didWrite or didFailToWrite
|
||||
expect(saga.numPending()).toBe(0);
|
||||
|
||||
saga.put(
|
||||
didFailToWrite(
|
||||
(action as ReturnType<typeof write>).id,
|
||||
new Error('test error'),
|
||||
),
|
||||
);
|
||||
saga.put(didFailToWrite(0, new Error('test error')));
|
||||
|
||||
const action2 = await saga.take();
|
||||
expect(action2.type).toBe(write.toString());
|
||||
expect((action2 as ReturnType<typeof write>).value).toEqual(expected);
|
||||
expect(action2).toEqual(write(1, expected));
|
||||
|
||||
saga.put(didWrite((action2 as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(1));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
@@ -157,30 +146,27 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
saga.put(receiveData('test1234'));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect((action as ReturnType<typeof write>).value).toEqual(
|
||||
new Uint8Array([...expected, ...expected]),
|
||||
);
|
||||
expect(action).toEqual(write(0, new Uint8Array([...expected, ...expected])));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
test('long messages are split', async () => {
|
||||
const testData = '012345678901234567890123456789';
|
||||
|
||||
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
|
||||
|
||||
saga.put(receiveData('012345678901234567890123456789'));
|
||||
saga.put(receiveData(testData));
|
||||
|
||||
const action = await saga.take();
|
||||
expect(action.type).toBe(write.toString());
|
||||
expect((action as ReturnType<typeof write>).value.length).toEqual(20);
|
||||
expect(action).toEqual(write(0, encoder.encode(testData.slice(0, 20))));
|
||||
|
||||
saga.put(didWrite((action as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(0));
|
||||
|
||||
const action2 = await saga.take();
|
||||
expect(action2.type).toBe(write.toString());
|
||||
expect((action2 as ReturnType<typeof write>).value.length).toEqual(10);
|
||||
expect(action2).toEqual(write(1, encoder.encode(testData.slice(20, 40))));
|
||||
|
||||
saga.put(didWrite((action2 as ReturnType<typeof write>).id));
|
||||
saga.put(didWrite(1));
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ function* receiveUartData(action: ReturnType<typeof didNotify>): Generator {
|
||||
}
|
||||
|
||||
function* receiveTerminalData(): Generator {
|
||||
const channel = yield* actionChannel<ReturnType<typeof receiveData>>(receiveData);
|
||||
const channel = yield* actionChannel(receiveData);
|
||||
while (true) {
|
||||
// wait for input from terminal
|
||||
const action = yield* take(channel);
|
||||
|
||||
Reference in New Issue
Block a user