add state initializer in async saga constructor

This commit is contained in:
David Lechner
2021-01-22 15:58:10 -06:00
parent 08fea237c9
commit c1c08e5ced
8 changed files with 195 additions and 132 deletions
+4 -7
View File
@@ -11,11 +11,10 @@ jest.mock('ace-builds');
jest.mock('file-saver');
test('open', async () => {
const saga = new AsyncSaga(editor);
const mockEditor = mock<Ace.EditSession>();
const data = new Uint8Array().buffer;
const saga = new AsyncSaga(editor, { editor: { current: mockEditor } });
saga.updateState({ editor: { current: mockEditor } });
const data = new Uint8Array().buffer;
saga.put(open(data));
expect(mockEditor.setValue).toBeCalled();
@@ -24,10 +23,9 @@ test('open', async () => {
});
test('saveAs', async () => {
const saga = new AsyncSaga(editor);
const mockEditor = mock<Ace.EditSession>();
const saga = new AsyncSaga(editor, { editor: { current: mockEditor } });
saga.updateState({ editor: { current: mockEditor } });
saga.put(saveAs());
expect(mockEditor.getValue).toBeCalled();
@@ -36,10 +34,9 @@ test('saveAs', async () => {
});
test('reloadProgram', async () => {
const saga = new AsyncSaga(editor);
const mockEditor = mock<Ace.EditSession>();
const saga = new AsyncSaga(editor, { editor: { current: mockEditor } });
saga.updateState({ editor: { current: mockEditor } });
saga.put(reloadProgram());
expect(mockEditor.setValue).toHaveBeenCalled();
+120 -79
View File
@@ -10,6 +10,7 @@ import JSZip from 'jszip';
import { AsyncSaga } from '../../test';
import {
FailToFinishReasonType,
HubError,
MetadataProblem,
didFailToFinish,
didFinish,
@@ -72,11 +73,16 @@ describe('flashFirmware', () => {
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -216,11 +222,16 @@ describe('flashFirmware', () => {
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -267,11 +278,16 @@ describe('flashFirmware', () => {
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -333,11 +349,16 @@ describe('flashFirmware', () => {
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -398,11 +419,16 @@ describe('flashFirmware', () => {
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -458,11 +484,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -600,14 +631,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
});
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -648,14 +681,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
});
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -695,11 +730,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -722,10 +762,6 @@ describe('flashFirmware', () => {
// this triggers a failure
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Disconnected },
});
saga.put(didFailToCompile(['test']));
// compiler error should trigger firmware flash failure
@@ -756,11 +792,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -781,10 +822,6 @@ describe('flashFirmware', () => {
}
`);
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Disconnected },
});
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
@@ -818,11 +855,16 @@ describe('flashFirmware', () => {
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.updateState({ settings: { flashCurrentProgram: false } });
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
settings: { flashCurrentProgram: false },
},
{
nextMessageId: createCountFunc(),
},
);
// saga is triggered by this action
@@ -843,10 +885,6 @@ describe('flashFirmware', () => {
}
`);
saga.updateState({
bootloader: { connection: BootloaderConnectionState.Disconnected },
});
const mpySize = 20;
const mpyBinaryData = new Uint8Array(mpySize);
saga.put(didCompile(mpyBinaryData));
@@ -892,12 +930,15 @@ describe('flashFirmware', () => {
getValue: () => 'print("test")',
};
const saga = new AsyncSaga(flashFirmware, { nextMessageId: createCountFunc() });
saga.updateState({
editor: { current: editor },
settings: { flashCurrentProgram: true },
});
const saga = new AsyncSaga(
flashFirmware,
{
bootloader: { connection: BootloaderConnectionState.Disconnected },
editor: { current: editor },
settings: { flashCurrentProgram: true },
},
{ nextMessageId: createCountFunc() },
);
// saga is triggered by this action
+7 -5
View File
@@ -22,10 +22,12 @@ jest.mock('ace-builds');
describe('downloadAndRun', () => {
test('no errors', async () => {
const saga = new AsyncSaga(hub, { nextMessageId: createCountFunc() });
const mockEditor = mock<Ace.EditSession>();
saga.updateState({ editor: { current: mockEditor } });
const saga = new AsyncSaga(
hub,
{ editor: { current: mockEditor } },
{ nextMessageId: createCountFunc() },
);
saga.put(downloadAndRun());
@@ -76,7 +78,7 @@ describe('downloadAndRun', () => {
});
test('repl', async () => {
const saga = new AsyncSaga(hub, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(hub, {}, { nextMessageId: createCountFunc() });
saga.put(repl());
@@ -87,7 +89,7 @@ test('repl', async () => {
});
test('stop', async () => {
const saga = new AsyncSaga(hub, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(hub, {}, { nextMessageId: createCountFunc() });
saga.put(stop());
+3 -6
View File
@@ -16,7 +16,7 @@ afterAll(() => {
describe('fetchLicenses', () => {
test('first call', async () => {
const testLicenseList: LicenseList = [];
const saga = new AsyncSaga(license);
const saga = new AsyncSaga(license, { license: { list: null } });
jest.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify(testLicenseList)),
@@ -24,7 +24,6 @@ describe('fetchLicenses', () => {
// initially, license list starts as null, so fetch is called to get
// the list
saga.updateState({ license: { list: null } });
saga.put(openLicenseDialog());
const action = await saga.take();
@@ -34,7 +33,7 @@ describe('fetchLicenses', () => {
});
test('second call', async () => {
const testLicenseList: LicenseList = [];
const saga = new AsyncSaga(license);
const saga = new AsyncSaga(license, { license: { list: testLicenseList } });
jest.spyOn(globalThis, 'fetch').mockRejectedValue(
'fetch () should not have been called',
@@ -42,7 +41,6 @@ describe('fetchLicenses', () => {
// after we have the list, we don't fetch it again since it will
// always be the same list
saga.updateState({ license: { list: testLicenseList } });
saga.put(openLicenseDialog());
// have to yield to be sure fetch call would have taken place on error
@@ -52,11 +50,10 @@ describe('fetchLicenses', () => {
});
test('failed fetch', async () => {
const failResponse = new Response(undefined, { status: 404 });
const saga = new AsyncSaga(license);
const saga = new AsyncSaga(license, { license: { list: null } });
jest.spyOn(globalThis, 'fetch').mockResolvedValue(failResponse);
saga.updateState({ license: { list: null } });
saga.put(openLicenseDialog());
const action = await saga.take();
+3 -3
View File
@@ -50,7 +50,7 @@ test.each([
clear,
};
const saga = new AsyncSaga(notification, { notification: { toaster } });
const saga = new AsyncSaga(notification, {}, { notification: { toaster } });
saga.put(action);
@@ -78,7 +78,7 @@ test.each([
clear,
};
const saga = new AsyncSaga(notification, { notification: { toaster } });
const saga = new AsyncSaga(notification, {}, { notification: { toaster } });
saga.put(action);
@@ -105,7 +105,7 @@ test.each([[didCompile(new Uint8Array()), MessageId.MpyError]])(
clear,
};
const saga = new AsyncSaga(notification, { notification: { toaster } });
const saga = new AsyncSaga(notification, {}, { notification: { toaster } });
saga.put(action);
+6 -8
View File
@@ -210,7 +210,7 @@ describe('startup', () => {
describe('store settings to local storage', () => {
test('failed storage', async () => {
const saga = new AsyncSaga(settings);
const saga = new AsyncSaga(settings, { settings: { showDocs: false } });
const testError = new Error('local storage is disabled');
@@ -220,7 +220,6 @@ describe('store settings to local storage', () => {
throw testError;
});
saga.updateState({ settings: { showDocs: false } });
saga.put(setBoolean(SettingId.ShowDocs, true));
expect(mockSetItem).toHaveBeenCalled();
@@ -236,7 +235,7 @@ describe('store settings to local storage', () => {
});
test('showDocs', async () => {
const saga = new AsyncSaga(settings);
const saga = new AsyncSaga(settings, { settings: { showDocs: false } });
const mockSetItem = jest
.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem')
@@ -245,7 +244,6 @@ describe('store settings to local storage', () => {
expect(value).toBe('true');
});
saga.updateState({ settings: { showDocs: false } });
saga.put(setBoolean(SettingId.ShowDocs, true));
expect(mockSetItem).toHaveBeenCalled();
@@ -256,7 +254,7 @@ describe('store settings to local storage', () => {
});
test('darkMode', async () => {
const saga = new AsyncSaga(settings);
const saga = new AsyncSaga(settings, { settings: { darkMode: true } });
const mockSetItem = jest
.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem')
@@ -265,7 +263,6 @@ describe('store settings to local storage', () => {
expect(value).toBe('false');
});
saga.updateState({ settings: { darkMode: true } });
saga.put(setBoolean(SettingId.DarkMode, false));
expect(mockSetItem).toHaveBeenCalled();
@@ -276,7 +273,9 @@ describe('store settings to local storage', () => {
});
test('flashCurrentProgram', async () => {
const saga = new AsyncSaga(settings);
const saga = new AsyncSaga(settings, {
settings: { flashCurrentProgram: true },
});
const mockSetItem = jest
.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem')
@@ -285,7 +284,6 @@ describe('store settings to local storage', () => {
expect(value).toBe('false');
});
saga.updateState({ settings: { flashCurrentProgram: true } });
saga.put(setBoolean(SettingId.FlashCurrentProgram, false));
expect(mockSetItem).toHaveBeenCalled();
+46 -22
View File
@@ -30,10 +30,13 @@ import terminal from './terminal';
describe('Data receiver filters out hub status', () => {
test('normal message - no status', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// sending ASCII space character
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(notify(new DataView(new Uint8Array([0x20]).buffer)));
const action = await saga.take();
@@ -44,9 +47,12 @@ describe('Data receiver filters out hub status', () => {
});
test('checksum message', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Loading } },
{ nextMessageId: createCountFunc() },
);
saga.updateState({ hub: { runtime: HubRuntimeState.Loading } });
saga.put(notify(new DataView(new Uint8Array([0xaa]).buffer)));
const action = await saga.take();
@@ -57,10 +63,13 @@ describe('Data receiver filters out hub status', () => {
});
test('idle message', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '>>>> IDLE'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -89,10 +98,13 @@ describe('Data receiver filters out hub status', () => {
});
test('idle message with extra text', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '0>>>> IDLE1'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -133,10 +145,13 @@ describe('Data receiver filters out hub status', () => {
});
test('error message', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '>>>> ERROR'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -166,10 +181,13 @@ describe('Data receiver filters out hub status', () => {
});
test('error message with extra text', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '0>>>> ERROR1'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -211,10 +229,13 @@ describe('Data receiver filters out hub status', () => {
});
test('running message', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '>>>> ERROR'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -246,10 +267,13 @@ describe('Data receiver filters out hub status', () => {
});
test('running message with extra text', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(
terminal,
{ hub: { runtime: HubRuntimeState.Unknown } },
{ nextMessageId: createCountFunc() },
);
// '0>>>> RUNNING1'
saga.updateState({ hub: { runtime: HubRuntimeState.Unknown } });
saga.put(
notify(
new DataView(
@@ -294,7 +318,7 @@ describe('Data receiver filters out hub status', () => {
});
test('Terminal data source responds to send data actions', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(didStart());
const dataSourceAction = await saga.take();
@@ -321,7 +345,7 @@ describe('Terminal data source responds to receive data actions', () => {
const expected = new Uint8Array([0x74, 0x65, 0x73, 0x74, 0x31, 0x32, 0x33, 0x34]);
test('basic function works', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(receiveData('test1234'));
@@ -333,7 +357,7 @@ describe('Terminal data source responds to receive data actions', () => {
});
test('messages are queued until previous has completed', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(receiveData('test1234'));
await delay(50); // without delay, messages are combined
@@ -361,7 +385,7 @@ describe('Terminal data source responds to receive data actions', () => {
});
test('messages are queued until previous has failed', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(receiveData('test1234'));
await delay(50); // without delay, messages are combined
@@ -391,7 +415,7 @@ describe('Terminal data source responds to receive data actions', () => {
});
test('small messages are combined', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(receiveData('test1234'));
saga.put(receiveData('test1234'));
@@ -406,7 +430,7 @@ describe('Terminal data source responds to receive data actions', () => {
});
test('long messages are split', async () => {
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() });
saga.put(receiveData('012345678901234567890123456789'));
+6 -2
View File
@@ -16,11 +16,15 @@ export class AsyncSaga {
private state: RecursivePartial<RootState>;
private task: Task;
public constructor(saga: Saga, context?: Record<string, unknown>) {
public constructor(
saga: Saga,
state: RecursivePartial<RootState> = {},
context?: Record<string, unknown>,
) {
this.channel = stdChannel();
this.dispatches = [];
this.takers = [];
this.state = {};
this.state = state;
this.task = runSaga(
{
channel: this.channel,