convert editor service to saga

This commit is contained in:
David Lechner
2020-06-10 21:59:34 -05:00
committed by David Lechner
parent 5c3ec89e3b
commit 17cb5a32d5
5 changed files with 86 additions and 46 deletions
+29 -2
View File
@@ -4,12 +4,39 @@
import { Ace } from 'ace-builds';
import { mock } from 'jest-mock-extended';
import { AsyncSaga } from '../../test';
import { reloadProgram } from '../actions/editor';
import { open, reloadProgram, saveAs } from '../actions/editor';
import editor from './editor';
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;
saga.setState({ editor: { current: mockEditor } });
saga.put(open(data));
expect(mockEditor.setValue).toBeCalled();
await saga.end();
});
test('saveAs', async () => {
const saga = new AsyncSaga(editor);
const mockEditor = mock<Ace.EditSession>();
saga.setState({ editor: { current: mockEditor } });
saga.put(saveAs());
expect(mockEditor.getValue).toBeCalled();
await saga.end();
});
test('reloadProgram', async () => {
const saga = new AsyncSaga(editor);
const mockEditor = mock<Ace.EditSession>();
saga.setState({ editor: { current: mockEditor } });
+51 -2
View File
@@ -2,18 +2,67 @@
// Copyright (c) 2020 The Pybricks Authors
import { Ace } from 'ace-builds';
import FileSaver from 'file-saver';
import { select, takeEvery } from 'redux-saga/effects';
import { EditorActionType, EditorReloadProgramAction } from '../actions/editor';
import {
EditorActionType,
EditorOpenAction,
EditorReloadProgramAction,
EditorSaveAsAction,
} from '../actions/editor';
import { RootState } from '../reducers';
const decoder = new TextDecoder();
function* open(action: EditorOpenAction): Generator {
const editor = (yield select(
(s: RootState) => s.editor.current,
)) as Ace.EditSession | null;
// istanbul ignore next: it is a bug to dispatch this action with no current editor
if (editor === null) {
console.error('open: No current editor');
return;
}
const text = decoder.decode(action.data);
editor.setValue(text);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function* saveAs(action: EditorSaveAsAction): Generator {
const editor = (yield select(
(s: RootState) => s.editor.current,
)) as Ace.EditSession | null;
// istanbul ignore next: it is a bug to dispatch this action with no current editor
if (editor === null) {
console.error('saveAs: No current editor');
return;
}
const data = editor.getValue();
const blob = new Blob([data], { type: 'text/x-python;charset=utf-8' });
FileSaver.saveAs(blob, 'main.py');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function* reloadProgram(_action: EditorReloadProgramAction): Generator {
const editor = (yield select(
(s: RootState) => s.editor.current,
)) as Ace.EditSession;
)) as Ace.EditSession | null;
// istanbul ignore next: it is a bug to dispatch this action with no current editor
if (editor === null) {
console.error('reloadProgram: No current editor');
return;
}
editor.setValue(localStorage.getItem('program') || '');
}
export default function* (): Generator {
yield takeEvery(EditorActionType.Open, open);
yield takeEvery(EditorActionType.SaveAs, saveAs);
yield takeEvery(EditorActionType.ReloadProgram, reloadProgram);
}
-39
View File
@@ -1,39 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020 The Pybricks Authors
import * as FileSaver from 'file-saver';
import { Action, Dispatch } from '../actions';
import { EditorActionType, EditorOpenAction } from '../actions/editor';
import { RootState } from '../reducers';
import { combineServices } from '.';
const decoder = new TextDecoder();
function open(action: Action, _dispatch: Dispatch, state: RootState): void {
if (action.type !== EditorActionType.Open) {
return;
}
// istanbul ignore next: currently, it is a bug if there is no current editor
if (state.editor.current === null) {
console.error('No current editor');
return;
}
const text = decoder.decode((action as EditorOpenAction).data);
state.editor.current.getDocument().setValue(text);
}
function saveAs(action: Action, _dispatch: Dispatch, state: RootState): void {
if (action.type !== EditorActionType.SaveAs) {
return;
}
// istanbul ignore next: currently, it is a bug if there is no current editor
if (state.editor.current === null) {
console.error('No current editor');
return;
}
const data = state.editor.current.getDocument().getValue();
const blob = new Blob([data], { type: 'text/x-python;charset=utf-8' });
FileSaver.saveAs(blob, 'main.py');
}
export default combineServices(open, saveAs);
+1 -2
View File
@@ -5,7 +5,6 @@ import { Middleware } from 'redux';
import { Action, Dispatch } from '../actions';
import { RootState } from '../reducers';
import ble from './ble';
import editor from './editor';
import errorLog from './error-log';
import hub from './hub';
import bootloader from './lwp3-bootloader';
@@ -39,7 +38,7 @@ export function combineServices(...services: Service[]): Service {
};
}
const rootService = combineServices(ble, bootloader, editor, errorLog, hub);
const rootService = combineServices(ble, bootloader, errorLog, hub);
const serviceMiddleware: Middleware = (store) => (next) => (action): unknown => {
runService(rootService, action, store.dispatch, store.getState());
+5 -1
View File
@@ -1,12 +1,16 @@
const Environment = require('jest-environment-jsdom');
/**
* A custom environment to set the TextEncoder
* A custom environment to set TextDecoder/TextEncoder
* Thanks https://stackoverflow.com/a/57713960/1976323
*/
module.exports = class CustomTestEnvironment extends Environment {
async setup() {
await super.setup();
if (typeof TextDecoder === 'undefined') {
const { TextDecoder } = require('util');
this.global.TextDecoder = TextDecoder;
}
if (typeof TextEncoder === 'undefined') {
const { TextEncoder } = require('util');
this.global.TextEncoder = TextEncoder;