) {
+ try {
+ return yield* saga(...args);
+ } catch (error) {
+ handleUncaughtError(error);
+ }
+ };
+}
+
export function safeTakeEvery>(
pattern: P,
worker: (action: A) => any
@@ -95,3 +111,29 @@ export function safeTakeLeading
(pattern, wrappedWorker, ...args);
}
+
+export function selectWorkspace(
+ workspaceLocation: T,
+ func: (state: WorkspaceManagerState[T]) => U
+): Generator;
+export function selectWorkspace(
+ workspaceLocation: T
+): Generator;
+export function* selectWorkspace(
+ workspaceLocation: T,
+ f?: (state: WorkspaceManagerState[T]) => U
+) {
+ const workspace: WorkspaceManagerState[T] = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation]
+ );
+
+ if (f) return f(workspace);
+ return workspace;
+}
+
+export function* selectStoryEnv(storyEnv: string) {
+ const workspace: StoriesEnvState = yield select(
+ (state: OverallState) => state.stories.envs[storyEnv]
+ );
+ return workspace;
+}
diff --git a/src/commons/sagas/SideContentSaga.ts b/src/commons/sagas/SideContentSaga.ts
index 955c2b0173..2f99877591 100644
--- a/src/commons/sagas/SideContentSaga.ts
+++ b/src/commons/sagas/SideContentSaga.ts
@@ -1,4 +1,4 @@
-import { Action } from '@reduxjs/toolkit';
+import type { Action } from '@reduxjs/toolkit';
import { put, take } from 'redux-saga/effects';
import StoriesActions from 'src/features/stories/StoriesActions';
@@ -11,10 +11,10 @@ const isSpawnSideContent = (
): action is ReturnType =>
action.type === SideContentActions.spawnSideContent.type;
-// TODO: Refactor and combine in a future commit
-const sagaActions = { ...SideContentActions, ...WorkspaceActions, ...StoriesActions };
-export const SideContentSaga = combineSagaHandlers(sagaActions, {
- beginAlertSideContent: function* ({ payload: { id, workspaceLocation } }) {
+const SideContentSaga = combineSagaHandlers({
+ [SideContentActions.beginAlertSideContent.type]: function* ({
+ payload: { id, workspaceLocation }
+ }) {
// When a program finishes evaluation, we clear all alerts,
// So we must wait until after and all module tabs have been spawned
// to process any kind of alerts that were raised by non-module side content
@@ -24,7 +24,7 @@ export const SideContentSaga = combineSagaHandlers(sagaActions, {
);
yield put(SideContentActions.endAlertSideContent(id, workspaceLocation));
},
- notifyProgramEvaluated: function* (action) {
+ [WorkspaceActions.notifyProgramEvaluated.type]: function* (action) {
if (!action.payload.workspaceLocation || action.payload.workspaceLocation === 'stories') return;
const debuggerContext = {
@@ -38,7 +38,7 @@ export const SideContentSaga = combineSagaHandlers(sagaActions, {
SideContentActions.spawnSideContent(action.payload.workspaceLocation, debuggerContext)
);
},
- notifyStoriesEvaluated: function* (action) {
+ [StoriesActions.notifyStoriesEvaluated.type]: function* (action) {
yield put(SideContentActions.spawnSideContent(`stories.${action.payload.env}`, action.payload));
}
});
diff --git a/src/commons/sagas/StoriesSaga.ts b/src/commons/sagas/StoriesSaga.ts
index c071186724..dc43f442e3 100644
--- a/src/commons/sagas/StoriesSaga.ts
+++ b/src/commons/sagas/StoriesSaga.ts
@@ -1,4 +1,4 @@
-import { Context } from 'js-slang';
+import type { Context } from 'js-slang';
import { call, put, select } from 'redux-saga/effects';
import StoriesActions from 'src/features/stories/StoriesActions';
import {
@@ -12,10 +12,10 @@ import {
putStoriesUserRole,
updateStory
} from 'src/features/stories/storiesComponents/BackendAccess';
-import { StoryData, StoryListView, StoryView } from 'src/features/stories/StoriesTypes';
+import type { StoryData, StoryListView, StoryView } from 'src/features/stories/StoriesTypes';
import SessionActions from '../application/actions/SessionActions';
-import { OverallState, StoriesRole } from '../application/ApplicationTypes';
+import { type OverallState, StoriesRole } from '../application/ApplicationTypes';
import { Tokens } from '../application/types/SessionTypes';
import { combineSagaHandlers } from '../redux/utils';
import { resetSideContent } from '../sideContent/SideContentActions';
@@ -25,20 +25,19 @@ import { defaultStoryContent } from '../utils/StoriesHelper';
import { selectTokens } from './BackendSaga';
import { evalCodeSaga } from './WorkspaceSaga/helpers/evalCode';
-// TODO: Refactor and combine in a future commit
-const sagaActions = { ...StoriesActions, ...SessionActions };
-const StoriesSaga = combineSagaHandlers(sagaActions, {
- // TODO: This should be using `takeLatest`, not `takeEvery`
- getStoriesList: function* () {
- const tokens: Tokens = yield selectTokens();
- const allStories: StoryListView[] = yield call(async () => {
- const resp = await getStories(tokens);
- return resp ?? [];
- });
+const StoriesSaga = combineSagaHandlers({
+ [StoriesActions.getStoriesList.type]: {
+ takeLatest: function* () {
+ const tokens: Tokens = yield selectTokens();
+ const allStories: StoryListView[] = yield call(async () => {
+ const resp = await getStories(tokens);
+ return resp ?? [];
+ });
- yield put(actions.updateStoriesList(allStories));
+ yield put(actions.updateStoriesList(allStories));
+ }
},
- setCurrentStoryId: function* (action) {
+ [StoriesActions.setCurrentStoryId.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const storyId = action.payload;
if (storyId) {
@@ -53,7 +52,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.setCurrentStory(defaultStory));
}
},
- createStory: function* (action) {
+ [StoriesActions.createStory.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const story = action.payload;
const userId: number | undefined = yield select((state: OverallState) => state.stories.userId);
@@ -79,7 +78,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.getStoriesList());
},
- saveStory: function* (action) {
+ [StoriesActions.saveStory.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const { story, id } = action.payload;
const updatedStory: StoryView | null = yield call(
@@ -99,7 +98,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.getStoriesList());
},
- deleteStory: function* (action) {
+ [StoriesActions.deleteStory.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const storyId = action.payload;
yield call(deleteStory, tokens, storyId);
@@ -107,7 +106,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.getStoriesList());
},
- getStoriesUser: function* () {
+ [StoriesActions.getStoriesUser.type]: function* () {
const tokens: Tokens = yield selectTokens();
const me: {
id: number;
@@ -125,7 +124,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.setCurrentStoriesUser(me.id, me.name));
yield put(actions.setCurrentStoriesGroup(me.groupId, me.groupName, me.role));
},
- evalStory: function* (action) {
+ [StoriesActions.evalStory.type]: function* (action) {
const env = action.payload.env;
const code = action.payload.code;
const execTime: number = yield select(
@@ -143,12 +142,12 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
codeFilePath,
context,
execTime,
- 'stories',
action.type,
+ 'stories',
env
);
},
- fetchAdminPanelStoriesUsers: function* (action) {
+ [StoriesActions.fetchAdminPanelStoriesUsers.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const storiesUsers = yield call(getAdminPanelStoriesUsers, tokens);
@@ -157,7 +156,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield put(actions.setAdminPanelStoriesUsers(storiesUsers));
}
},
- updateStoriesUserRole: function* (action) {
+ [SessionActions.updateStoriesUserRole.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const { userId, role } = action.payload;
@@ -168,7 +167,7 @@ const StoriesSaga = combineSagaHandlers(sagaActions, {
yield call(showSuccessMessage, 'Role updated!');
}
},
- deleteStoriesUserUserGroups: function* (action) {
+ [SessionActions.deleteStoriesUserUserGroups.type]: function* (action) {
const tokens: Tokens = yield selectTokens();
const { userId } = action.payload;
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts b/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts
index 95ec7e6fb7..a93c57b056 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts
@@ -1,4 +1,4 @@
-import { Context } from 'js-slang';
+import type { Context } from 'js-slang';
import { call } from 'redux-saga/effects';
import {
@@ -6,7 +6,7 @@ import {
getDifferenceInMethods,
getStoreExtraMethodsString
} from '../../../utils/JsSlangHelper';
-import { EVAL_SILENT, WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { EVAL_SILENT, type WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
import { evalCodeSaga } from './evalCode';
export function* blockExtraMethods(
@@ -30,8 +30,8 @@ export function* blockExtraMethods(
storeValuesFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
}
@@ -46,7 +46,7 @@ export function* blockExtraMethods(
nullifierFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
}
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts
index a12b6f3100..cb7d034334 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts
@@ -1,28 +1,19 @@
-import { Context } from 'js-slang';
+import type { Context } from 'js-slang';
import { defineSymbol } from 'js-slang/dist/createContext';
-import { Variant } from 'js-slang/dist/types';
import { put, select, take } from 'redux-saga/effects';
import WorkspaceActions from 'src/commons/workspace/WorkspaceActions';
-import { OverallState } from '../../../application/ApplicationTypes';
-import { ExternalLibraryName } from '../../../application/types/ExternalTypes';
+import type { OverallState } from '../../../application/ApplicationTypes';
import { actions } from '../../../utils/ActionsHelper';
-import { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import type { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { selectWorkspace } from '../../SafeEffects';
export function* clearContext(workspaceLocation: WorkspaceLocation, entrypointCode: string) {
- const [chapter, symbols, externalLibraryName, globals, variant]: [
- number,
- string[],
- ExternalLibraryName,
- Array<[string, any]>,
- Variant
- ] = yield select((state: OverallState) => [
- state.workspaces[workspaceLocation].context.chapter,
- state.workspaces[workspaceLocation].context.externalSymbols,
- state.workspaces[workspaceLocation].externalLibrary,
- state.workspaces[workspaceLocation].globals,
- state.workspaces[workspaceLocation].context.variant
- ]);
+ const {
+ context: { chapter, externalSymbols: symbols, variant },
+ externalLibrary: externalLibraryName,
+ globals
+ } = yield* selectWorkspace(workspaceLocation);
const library = {
chapter,
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
index 28217b184e..2348afe6d3 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts
@@ -1,12 +1,12 @@
import { compileAndRun as compileAndRunCCode } from '@sourceacademy/c-slang/ctowasm/dist/index';
import { tokenizer } from 'acorn';
-import { IConduit } from 'conductor/dist/conduit';
-import { Context, interrupt, Result, resume, runFilesInContext } from 'js-slang';
+import type { IConduit } from 'conductor/dist/conduit';
+import { type Context, interrupt, type Result, resume, runFilesInContext } from 'js-slang';
import { ACORN_PARSE_OPTIONS } from 'js-slang/dist/constants';
import { InterruptedError } from 'js-slang/dist/errors/errors';
-import { manualToggleDebugger } from 'js-slang/dist/stdlib/inspector';
-import { Chapter, ErrorSeverity, ErrorType, SourceError, Variant } from 'js-slang/dist/types';
-import { eventChannel, SagaIterator } from 'redux-saga';
+import { Chapter, ErrorSeverity, ErrorType, type SourceError, Variant } from 'js-slang/dist/types';
+import { pick } from 'lodash';
+import { eventChannel, type SagaIterator } from 'redux-saga';
import { call, cancel, cancelled, fork, put, race, select, take } from 'redux-saga/effects';
import * as Sourceror from 'sourceror';
@@ -15,158 +15,46 @@ import { selectFeatureSaga } from '../../../../commons/featureFlags/selectFeatur
import { makeCCompilerConfig, specialCReturnObject } from '../../../../commons/utils/CToWasmHelper';
import { javaRun } from '../../../../commons/utils/JavaHelper';
import { EventType } from '../../../../features/achievement/AchievementTypes';
-import { BrowserHostPlugin } from '../../../../features/conductor/BrowserHostPlugin';
+import type { BrowserHostPlugin } from '../../../../features/conductor/BrowserHostPlugin';
import { createConductor } from '../../../../features/conductor/createConductor';
import { flagConductorEnable } from '../../../../features/conductor/flagConductorEnable';
import { flagConductorEvaluatorUrl } from '../../../../features/conductor/flagConductorEvaluatorUrl';
import StoriesActions from '../../../../features/stories/StoriesActions';
-import { isSchemeLanguage, OverallState } from '../../../application/ApplicationTypes';
+import { isSchemeLanguage, type OverallState } from '../../../application/ApplicationTypes';
import { SideContentType } from '../../../sideContent/SideContentTypes';
import { actions } from '../../../utils/ActionsHelper';
import DisplayBufferService from '../../../utils/DisplayBufferService';
import { showWarningMessage } from '../../../utils/notifications/NotificationsHelper';
import { makeExternalBuiltins as makeSourcerorExternalBuiltins } from '../../../utils/SourcerorHelper';
import WorkspaceActions from '../../../workspace/WorkspaceActions';
-import {
- EVAL_SILENT,
- PlaygroundWorkspaceState,
- SicpWorkspaceState,
- WorkspaceLocation
-} from '../../../workspace/WorkspaceTypes';
+import { EVAL_SILENT, type WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { selectStoryEnv, selectWorkspace } from '../../SafeEffects';
import { dumpDisplayBuffer } from './dumpDisplayBuffer';
import { updateInspector } from './updateInspector';
-export function* evalCodeSaga(
- files: Record,
- entrypointFilePath: string,
+async function wasm_compile_and_run(
+ wasmCode: string,
context: Context,
- execTime: number,
- workspaceLocation: WorkspaceLocation,
- actionType: string,
- storyEnv?: string
-): SagaIterator {
- if (yield call(selectFeatureSaga, flagConductorEnable)) {
- return yield call(
- evalCodeConductorSaga,
- files,
- entrypointFilePath,
+ isRepl: boolean
+): Promise {
+ try {
+ const wasmModule = await Sourceror.compile(wasmCode, context, isRepl);
+ const transcoder = new Sourceror.Transcoder();
+ const returnedValue = await Sourceror.run(
+ wasmModule,
+ Sourceror.makePlatformImports(makeSourcerorExternalBuiltins(context), transcoder),
+ transcoder,
context,
- execTime,
- workspaceLocation,
- actionType,
- storyEnv
+ isRepl
);
+ return { status: 'finished', context, value: returnedValue };
+ } catch (e) {
+ console.log(e);
+ return { status: 'error' };
}
- context.runtime.debuggerOn =
- (actionType === WorkspaceActions.evalEditor.type ||
- actionType === InterpreterActions.debuggerResume.type) &&
- context.chapter > 2;
- const isStoriesBlock = actionType === actions.evalStory.type || workspaceLocation === 'stories';
-
- // Logic for execution of substitution model visualizer
- const correctWorkspace = workspaceLocation === 'playground' || workspaceLocation === 'sicp';
- const substIsActive: boolean = correctWorkspace
- ? yield select(
- (state: OverallState) =>
- (state.workspaces[workspaceLocation] as PlaygroundWorkspaceState | SicpWorkspaceState)
- .usingSubst
- )
- : isStoriesBlock
- ? // Safe to use ! as storyEnv will be defined from above when we call from EVAL_STORY
- yield select((state: OverallState) => state.stories.envs[storyEnv!].usingSubst)
- : false;
- const stepLimit: number = isStoriesBlock
- ? yield select((state: OverallState) => state.stories.envs[storyEnv!].stepLimit)
- : yield select((state: OverallState) => state.workspaces[workspaceLocation].stepLimit);
- const substActiveAndCorrectChapter = context.chapter <= 2 && substIsActive;
- if (substActiveAndCorrectChapter) {
- context.executionMethod = 'interpreter';
- }
-
- const uploadIsActive: boolean = correctWorkspace
- ? yield select(
- (state: OverallState) =>
- (state.workspaces[workspaceLocation] as PlaygroundWorkspaceState | SicpWorkspaceState)
- .usingUpload
- )
- : false;
- const uploads = yield select((state: OverallState) => state.workspaces[workspaceLocation].files);
-
- // For the CSE machine slider
- const cseIsActive: boolean = correctWorkspace
- ? yield select(
- (state: OverallState) =>
- (state.workspaces[workspaceLocation] as PlaygroundWorkspaceState | SicpWorkspaceState)
- .usingCse
- )
- : false;
- const needUpdateCse: boolean = correctWorkspace
- ? yield select(
- (state: OverallState) =>
- (state.workspaces[workspaceLocation] as PlaygroundWorkspaceState | SicpWorkspaceState)
- .updateCse
- )
- : false;
- // When currentStep is -1, the entire code is run from the start.
- const currentStep: number = needUpdateCse
- ? -1
- : correctWorkspace
- ? yield select(
- (state: OverallState) =>
- (state.workspaces[workspaceLocation] as PlaygroundWorkspaceState | SicpWorkspaceState)
- .currentStep
- )
- : -1;
- const cseActiveAndCorrectChapter =
- (isSchemeLanguage(context.chapter) || context.chapter >= 3) && cseIsActive;
- if (cseActiveAndCorrectChapter) {
- context.executionMethod = 'cse-machine';
- }
-
- const isFolderModeEnabled: boolean = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].isFolderModeEnabled
- );
-
- const entrypointCode = files[entrypointFilePath];
-
- function call_variant(variant: Variant) {
- if (variant === Variant.WASM) {
- // Note: WASM does not support multiple file programs.
- return call(
- wasm_compile_and_run,
- entrypointCode,
- context,
- actionType === WorkspaceActions.evalRepl.type
- );
- } else {
- throw new Error('Unknown variant: ' + variant);
- }
- }
- async function wasm_compile_and_run(
- wasmCode: string,
- wasmContext: Context,
- isRepl: boolean
- ): Promise {
- return Sourceror.compile(wasmCode, wasmContext, isRepl)
- .then((wasmModule: WebAssembly.Module) => {
- const transcoder = new Sourceror.Transcoder();
- return Sourceror.run(
- wasmModule,
- Sourceror.makePlatformImports(makeSourcerorExternalBuiltins(wasmContext), transcoder),
- transcoder,
- wasmContext,
- isRepl
- );
- })
- .then(
- (returnedValue: any): Result => ({ status: 'finished', context, value: returnedValue }),
- (e: any): Result => {
- console.log(e);
- return { status: 'error' };
- }
- );
- }
+}
+async function cCompileAndRun(cCode: string, context: Context): Promise {
function reportCCompilationError(errorMessage: string, context: Context) {
context.errors.push({
type: ErrorType.SYNTAX,
@@ -204,64 +92,181 @@ export function* evalCodeSaga(
elaborate: () => ''
});
}
-
- async function cCompileAndRun(cCode: string, context: Context) {
- const cCompilerConfig = await makeCCompilerConfig(cCode, context);
- return await compileAndRunCCode(cCode, cCompilerConfig)
- .then(compilationResult => {
- if (compilationResult.status === 'failure') {
- // report any compilation failure
- reportCCompilationError(
- `Compilation failed with the following error(s):\n\n${compilationResult.errorMessage}`,
- context
- );
- return {
- status: 'error',
- context
- };
- }
- if (compilationResult.warnings.length > 0) {
- return {
- status: 'finished',
- context,
- value: {
- toReplString: () =>
- `Compilation and program execution successful with the following warning(s):\n${compilationResult.warnings.join(
- '\n'
- )}`
- }
- };
- }
- if (specialCReturnObject === null) {
- return {
- status: 'finished',
- context,
- value: { toReplString: () => 'Compilation and program execution successful.' }
- };
+ const cCompilerConfig = await makeCCompilerConfig(cCode, context);
+ try {
+ const compilationResult = await compileAndRunCCode(cCode, cCompilerConfig);
+ if (compilationResult.status === 'failure') {
+ // report any compilation failure
+ reportCCompilationError(
+ `Compilation failed with the following error(s):\n\n${compilationResult.errorMessage}`,
+ context
+ );
+ return {
+ status: 'error',
+ context
+ } as Result;
+ }
+ if (compilationResult.warnings.length > 0) {
+ return {
+ status: 'finished',
+ context,
+ value: {
+ toReplString: () =>
+ `Compilation and program execution successful with the following warning(s):\n${compilationResult.warnings.join(
+ '\n'
+ )}`
}
- return { status: 'finished', context, value: specialCReturnObject };
- })
- .catch((e: any): Result => {
- console.log(e);
- reportCRuntimeError(e.message, context);
- return { status: 'error' };
- });
+ };
+ }
+ if (specialCReturnObject === null) {
+ return {
+ status: 'finished',
+ context,
+ value: { toReplString: () => 'Compilation and program execution successful.' }
+ };
+ }
+ return { status: 'finished', context, value: specialCReturnObject };
+ } catch (e) {
+ console.log(e);
+ reportCRuntimeError(e.message, context);
+ return { status: 'error' };
}
+}
- const isWasm: boolean = context.variant === Variant.WASM;
- const isC: boolean = context.chapter === Chapter.FULL_C;
- const isJava: boolean = context.chapter === Chapter.FULL_JAVA;
+export function* evalCodeSaga(
+ files: Record,
+ entrypointFilePath: string,
+ context: Context,
+ execTime: number,
+ actionType: string,
+ workspaceLocation: WorkspaceLocation,
+ storyEnv?: string
+): SagaIterator {
+ if (yield call(selectFeatureSaga, flagConductorEnable)) {
+ return yield call(
+ evalCodeConductorSaga,
+ files,
+ entrypointFilePath,
+ context,
+ execTime,
+ workspaceLocation,
+ actionType,
+ storyEnv
+ );
+ }
+ context.runtime.debuggerOn =
+ (actionType === WorkspaceActions.evalEditor.type ||
+ actionType === InterpreterActions.debuggerResume.type) &&
+ context.chapter > 2;
+ const isStoriesBlock = actionType === actions.evalStory.type || workspaceLocation === 'stories';
- let lastDebuggerResult = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult
- );
- const isUsingCse = yield select((state: OverallState) => state.workspaces['playground'].usingCse);
+ function* getWorkspaceData() {
+ const workspace = yield* selectWorkspace(workspaceLocation);
+ const commons = pick(workspace, ['isFolderModeEnabled', 'stepLimit']);
+
+ if (workspaceLocation === 'sicp' || workspaceLocation === 'playground') {
+ const { currentStep, updateCse, usingCse, usingSubst } =
+ yield* selectWorkspace(workspaceLocation);
+
+ return {
+ ...commons,
+ currentStep: updateCse ? -1 : currentStep,
+ cseIsActive: usingCse,
+ needUpdateCse: updateCse,
+ substIsActive: usingSubst
+ };
+ }
+
+ if (isStoriesBlock) {
+ const { usingSubst } = yield* selectStoryEnv(storyEnv!);
+ return {
+ ...commons,
+ currentStep: -1,
+ cseIsActive: false,
+ needUpdateCse: false,
+ substIsActive: usingSubst
+ };
+ }
+
+ return {
+ ...commons,
+ currentStep: -1,
+ cseIsActive: false,
+ needUpdateCse: false,
+ substIsActive: false
+ };
+ }
+
+ const entrypointCode = files[entrypointFilePath];
+ // Logic for execution of substitution model visualizer
+ const { currentStep, cseIsActive, isFolderModeEnabled, needUpdateCse, stepLimit, substIsActive } =
+ yield* getWorkspaceData();
+
+ const cseActiveAndCorrectChapter =
+ (isSchemeLanguage(context.chapter) || context.chapter >= Chapter.SOURCE_3) && cseIsActive;
+ if (cseActiveAndCorrectChapter) {
+ context.executionMethod = 'cse-machine';
+ }
+
+ function* getEvalEffect() {
+ if (actionType === InterpreterActions.debuggerResume.type) {
+ const { lastDebuggerResult } = yield* selectWorkspace(workspaceLocation);
+ return call(resume, lastDebuggerResult);
+ }
+
+ if (context.variant === Variant.WASM) {
+ // Note: WASM does not support multiple file programs.
+ return call(
+ wasm_compile_and_run,
+ entrypointCode,
+ context,
+ actionType === WorkspaceActions.evalRepl.type
+ );
+ }
+
+ switch (context.chapter) {
+ case Chapter.FULL_C:
+ return call(cCompileAndRun, entrypointCode, context);
+ case Chapter.FULL_JAVA: {
+ const {
+ usingCse: isUsingCse,
+ usingUpload: uploadIsActive,
+ files: uploads
+ } = yield* selectWorkspace('playground');
+
+ return call(javaRun, entrypointCode, context, currentStep, isUsingCse, {
+ uploadIsActive,
+ uploads
+ });
+ }
+ }
+
+ const substActiveAndCorrectChapter = context.chapter <= Chapter.SOURCE_2 && substIsActive;
+
+ return call(
+ runFilesInContext,
+ isFolderModeEnabled
+ ? files
+ : {
+ [entrypointFilePath]: files[entrypointFilePath]
+ },
+ entrypointFilePath,
+ context,
+ {
+ originalMaxExecTime: execTime,
+ stepLimit: stepLimit,
+ throwInfiniteLoops: true,
+ useSubst: substActiveAndCorrectChapter,
+ envSteps: currentStep,
+ executionMethod: cseActiveAndCorrectChapter ? 'cse-machine' : 'auto'
+ }
+ );
+ }
// Handles `console.log` statements in fullJS
- const detachConsole: () => void =
- context.chapter === Chapter.FULL_JS
- ? DisplayBufferService.attachConsole(workspaceLocation)
- : () => {};
+ if (context.chapter === Chapter.FULL_JS || context.chapter === Chapter.FULL_TS) {
+ yield call([DisplayBufferService, DisplayBufferService.attachConsole], workspaceLocation);
+ }
const {
result,
@@ -272,37 +277,7 @@ export function* evalCodeSaga(
interrupted: any;
paused: any;
} = yield race({
- result:
- actionType === InterpreterActions.debuggerResume.type
- ? call(resume, lastDebuggerResult)
- : isWasm
- ? call_variant(context.variant)
- : isC
- ? call(cCompileAndRun, entrypointCode, context)
- : isJava
- ? call(javaRun, entrypointCode, context, currentStep, isUsingCse, {
- uploadIsActive,
- uploads
- })
- : call(
- runFilesInContext,
- isFolderModeEnabled
- ? files
- : {
- [entrypointFilePath]: files[entrypointFilePath]
- },
- entrypointFilePath,
- context,
- {
- scheduler: 'preemptive',
- originalMaxExecTime: execTime,
- stepLimit: stepLimit,
- throwInfiniteLoops: true,
- useSubst: substActiveAndCorrectChapter,
- envSteps: currentStep
- }
- ),
-
+ result: yield* getEvalEffect(),
/**
* A BEGIN_INTERRUPT_EXECUTION signals the beginning of an interruption,
* i.e the trigger for the interpreter to interrupt execution.
@@ -311,8 +286,6 @@ export function* evalCodeSaga(
paused: take(InterpreterActions.beginDebuggerPause.type)
});
- detachConsole();
-
if (interrupted) {
interrupt(context);
/* Redundancy, added ensure that interruption results in an error. */
@@ -324,7 +297,7 @@ export function* evalCodeSaga(
}
if (paused) {
yield put(actions.endDebuggerPause(workspaceLocation));
- yield put(actions.updateLastDebuggerResult(manualToggleDebugger(context), workspaceLocation));
+ // yield put(actions.updateLastDebuggerResult(manualToggleDebugger(context), workspaceLocation));
yield call(updateInspector, workspaceLocation);
yield call(showWarningMessage, 'Execution paused', 750);
return;
@@ -339,11 +312,11 @@ export function* evalCodeSaga(
yield call(updateInspector, workspaceLocation);
}
- if (
- result.status !== 'suspended' &&
- result.status !== 'finished' &&
- result.status !== 'suspended-cse-eval'
- ) {
+ if (result.status === 'suspended-cse-eval') {
+ yield put(actions.endDebuggerPause(workspaceLocation));
+ yield put(actions.evalInterpreterSuccess('Breakpoint hit!', workspaceLocation));
+ return;
+ } else if (result.status !== 'finished') {
yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv);
if (!isStoriesBlock) {
const specialError = checkSpecialError(context.errors);
@@ -381,10 +354,6 @@ export function* evalCodeSaga(
yield put(actions.addEvent(events));
return;
- } else if (result.status === 'suspended' || result.status === 'suspended-cse-eval') {
- yield put(actions.endDebuggerPause(workspaceLocation));
- yield put(actions.evalInterpreterSuccess('Breakpoint hit!', workspaceLocation));
- return;
}
yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv);
@@ -406,7 +375,7 @@ export function* evalCodeSaga(
}
}
- lastDebuggerResult = yield select(
+ const lastDebuggerResult = yield select(
(state: OverallState) => state.workspaces[workspaceLocation].lastDebuggerResult
);
// For EVAL_EDITOR and EVAL_REPL, we send notification to workspace that a program has been evaluated
@@ -452,9 +421,14 @@ export function* evalCodeSaga(
yield put(actions.updateChangePointSteps(context.runtime.changepointSteps, workspaceLocation));
}
// Stop the home icon from flashing for an error if it is doing so since the evaluation is successful
- if (context.executionMethod === 'cse-machine' || context.executionMethod === 'interpreter') {
- const introIcon = document.getElementById(SideContentType.introduction + '-icon');
- introIcon?.classList.remove('side-content-tab-alert-error');
+ if (context.executionMethod === 'cse-machine') {
+ if (workspaceLocation !== 'stories') {
+ yield put(actions.removeSideContentAlert(SideContentType.introduction, workspaceLocation));
+ } else {
+ yield put(
+ actions.removeSideContentAlert(SideContentType.introduction, `stories.${storyEnv!}`)
+ );
+ }
}
}
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts
index 4e7cb11ca2..1ad74892f8 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts
@@ -1,15 +1,16 @@
-import { FSModule } from 'browserfs/dist/node/core/FS';
+import type { FSModule } from 'browserfs/dist/node/core/FS';
import { call, put, select, StrictEffect } from 'redux-saga/effects';
import WorkspaceActions from 'src/commons/workspace/WorkspaceActions';
import { EventType } from '../../../../features/achievement/AchievementTypes';
-import { DeviceSession } from '../../../../features/remoteExecution/RemoteExecutionTypes';
+import type { DeviceSession } from '../../../../features/remoteExecution/RemoteExecutionTypes';
import { WORKSPACE_BASE_PATHS } from '../../../../pages/fileSystem/createInBrowserFileSystem';
-import { OverallState } from '../../../application/ApplicationTypes';
+import type { OverallState } from '../../../application/ApplicationTypes';
import { retrieveFilesInWorkspaceAsRecord } from '../../../fileSystem/utils';
import { actions } from '../../../utils/ActionsHelper';
import { makeElevatedContext } from '../../../utils/JsSlangHelper';
-import { EditorTabState, EVAL_SILENT, WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { EVAL_SILENT, type WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { selectWorkspace } from '../../SafeEffects';
import { blockExtraMethods } from './blockExtraMethods';
import { clearContext } from './clearContext';
import { evalCodeSaga } from './evalCode';
@@ -18,31 +19,20 @@ import { insertDebuggerStatements } from './insertDebuggerStatements';
export function* evalEditorSaga(
workspaceLocation: WorkspaceLocation
): Generator {
- const [
- prepend,
+ const {
activeEditorTabIndex,
+ programPrependValue: prepend,
editorTabs,
execTime,
- isFolderModeEnabled,
- fileSystem,
- remoteExecutionSession
- ]: [
- string,
- number | null,
- EditorTabState[],
- number,
- boolean,
- FSModule,
- DeviceSession | undefined
- ] = yield select((state: OverallState) => [
- state.workspaces[workspaceLocation].programPrependValue,
- state.workspaces[workspaceLocation].activeEditorTabIndex,
- state.workspaces[workspaceLocation].editorTabs,
- state.workspaces[workspaceLocation].execTime,
- state.workspaces[workspaceLocation].isFolderModeEnabled,
- state.fileSystem.inBrowserFileSystem,
- state.session.remoteExecutionSession
- ]);
+ isFolderModeEnabled
+ } = yield* selectWorkspace(workspaceLocation);
+
+ const [fileSystem, remoteExecutionSession]: [FSModule, DeviceSession | undefined] = yield select(
+ (state: OverallState) => [
+ state.fileSystem.inBrowserFileSystem,
+ state.session.remoteExecutionSession
+ ]
+ );
if (activeEditorTabIndex === null) {
throw new Error('Cannot evaluate program without an entrypoint file.');
@@ -58,7 +48,6 @@ export function* evalEditorSaga(
};
}
const entrypointFilePath = editorTabs[activeEditorTabIndex].filePath ?? defaultFilePath;
-
yield put(actions.addEvent([EventType.RUN_CODE]));
if (remoteExecutionSession && remoteExecutionSession.workspace === workspaceLocation) {
@@ -99,8 +88,8 @@ export function* evalEditorSaga(
prependFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
// Block use of methods from privileged context
yield* blockExtraMethods(elevatedContext, context, execTime, workspaceLocation);
@@ -112,8 +101,8 @@ export function* evalEditorSaga(
entrypointFilePath,
context,
execTime,
- workspaceLocation,
- WorkspaceActions.evalEditor.type
+ WorkspaceActions.evalEditor.type,
+ workspaceLocation
);
}
}
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalTestCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalTestCode.ts
index e7c0cb43fa..146fbd1f07 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/evalTestCode.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/evalTestCode.ts
@@ -1,12 +1,12 @@
-import { Context, interrupt, runInContext } from 'js-slang';
+import { type Context, interrupt, runInContext } from 'js-slang';
import { InterruptedError } from 'js-slang/dist/errors/errors';
import { call, put, race, take } from 'redux-saga/effects';
import InterpreterActions from 'src/commons/application/actions/InterpreterActions';
-import { TestcaseType, TestcaseTypes } from '../../../assessment/AssessmentTypes';
+import { type TestcaseType, TestcaseTypes } from '../../../assessment/AssessmentTypes';
import { actions } from '../../../utils/ActionsHelper';
import { showWarningMessage } from '../../../utils/notifications/NotificationsHelper';
-import { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import type { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
import { dumpDisplayBuffer } from './dumpDisplayBuffer';
export function* evalTestCode(
@@ -20,7 +20,6 @@ export function* evalTestCode(
yield put(actions.resetTestcase(workspaceLocation, index));
const { result, interrupted } = yield race({
result: call(runInContext, code, context, {
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
throwInfiniteLoops: true
}),
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/restoreExtraMethods.ts b/src/commons/sagas/WorkspaceSaga/helpers/restoreExtraMethods.ts
index 377d3c6eaf..12458d6f0b 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/restoreExtraMethods.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/restoreExtraMethods.ts
@@ -1,8 +1,8 @@
-import { Context } from 'js-slang';
+import type { Context } from 'js-slang';
import { call } from 'redux-saga/effects';
import { getDifferenceInMethods, getRestoreExtraMethodsString } from '../../../utils/JsSlangHelper';
-import { EVAL_SILENT, WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { EVAL_SILENT, type WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
import { evalCodeSaga } from './evalCode';
export function* restoreExtraMethods(
@@ -24,7 +24,7 @@ export function* restoreExtraMethods(
restorerFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
}
diff --git a/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts b/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts
index 1a29a369d5..35452c49cd 100644
--- a/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts
+++ b/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts
@@ -1,12 +1,12 @@
-import { Context } from 'js-slang';
+import type { Context } from 'js-slang';
import { random } from 'lodash';
import { call, put, select, StrictEffect } from 'redux-saga/effects';
-import { OverallState } from '../../../application/ApplicationTypes';
-import { TestcaseType } from '../../../assessment/AssessmentTypes';
+import type { OverallState } from '../../../application/ApplicationTypes';
import { actions } from '../../../utils/ActionsHelper';
import { makeElevatedContext } from '../../../utils/JsSlangHelper';
-import { EVAL_SILENT, WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { EVAL_SILENT, type WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
+import { selectWorkspace } from '../../SafeEffects';
import { blockExtraMethods } from './blockExtraMethods';
import { clearContext } from './clearContext';
import { evalCodeSaga } from './evalCode';
@@ -17,22 +17,17 @@ export function* runTestCase(
workspaceLocation: WorkspaceLocation,
index: number
): Generator {
- const [prepend, value, postpend, testcase]: [string, string, string, string] = yield select(
- (state: OverallState) => {
- const prepend = state.workspaces[workspaceLocation].programPrependValue;
- const postpend = state.workspaces[workspaceLocation].programPostpendValue;
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- const value = state.workspaces[workspaceLocation].editorTabs[0].value;
- const testcase = state.workspaces[workspaceLocation].editorTestcases[index].program;
- return [prepend, value, postpend, testcase] as [string, string, string, string];
- }
- );
- const type: TestcaseType = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].editorTestcases[index].type
- );
- const execTime: number = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].execTime
- );
+ const {
+ editorTabs: {
+ [0]: { value }
+ },
+ editorTestcases: {
+ [index]: { program: testcase, type: type }
+ },
+ execTime,
+ programPrependValue: prepend,
+ programPostpendValue: postpend
+ } = yield* selectWorkspace(workspaceLocation);
yield* clearContext(workspaceLocation, value);
@@ -60,8 +55,8 @@ export function* runTestCase(
prependFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
// Block use of methods from privileged context using a randomly generated blocking key
@@ -78,8 +73,8 @@ export function* runTestCase(
valueFilePath,
context,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
// Halt execution if the student's code in the editor results in an error
@@ -103,8 +98,8 @@ export function* runTestCase(
postpendFilePath,
elevatedContext,
execTime,
- workspaceLocation,
- EVAL_SILENT
+ EVAL_SILENT,
+ workspaceLocation
);
yield* blockExtraMethods(elevatedContext, context, execTime, workspaceLocation, blockKey);
}
diff --git a/src/commons/sagas/WorkspaceSaga/index.ts b/src/commons/sagas/WorkspaceSaga/index.ts
index 4ecba4d6f8..0c4002f4c1 100644
--- a/src/commons/sagas/WorkspaceSaga/index.ts
+++ b/src/commons/sagas/WorkspaceSaga/index.ts
@@ -1,5 +1,5 @@
-import { FSModule } from 'browserfs/dist/node/core/FS';
-import { Context, findDeclaration, getNames } from 'js-slang';
+import type { FSModule } from 'browserfs/dist/node/core/FS';
+import { type Context, findDeclaration, getNames } from 'js-slang';
import { Chapter, Variant } from 'js-slang/dist/types';
import Phaser from 'phaser';
import { call, put, select } from 'redux-saga/effects';
@@ -15,11 +15,11 @@ import DataVisualizer from '../../../features/dataVisualizer/dataVisualizer';
import { WORKSPACE_BASE_PATHS } from '../../../pages/fileSystem/createInBrowserFileSystem';
import {
defaultEditorValue,
- OverallState,
+ type OverallState,
styliseSublanguage
} from '../../application/ApplicationTypes';
import { externalLibraries, ExternalLibraryName } from '../../application/types/ExternalTypes';
-import { Library, Testcase } from '../../assessment/AssessmentTypes';
+import type { Library, Testcase } from '../../assessment/AssessmentTypes';
import { Documentation } from '../../documentation/Documentation';
import { writeFileRecursively } from '../../fileSystem/utils';
import { actions } from '../../utils/ActionsHelper';
@@ -34,447 +34,426 @@ import {
showWarningMessage
} from '../../utils/notifications/NotificationsHelper';
import { showFullJSDisclaimer, showFullTSDisclaimer } from '../../utils/WarningDialogHelper';
-import { EditorTabState } from '../../workspace/WorkspaceTypes';
+import { selectWorkspace } from '../SafeEffects';
import { evalCodeSaga } from './helpers/evalCode';
import { evalEditorSaga } from './helpers/evalEditor';
import { runTestCase } from './helpers/runTestCase';
-const WorkspaceSaga = combineSagaHandlers(
- // TODO: Refactor and combine in a future commit
- { ...WorkspaceActions, ...InterpreterActions },
- {
- addHtmlConsoleError: function* (action) {
- // TODO: Do not use if-else logic
- if (!action.payload.storyEnv) {
- yield put(
- actions.handleConsoleLog(action.payload.workspaceLocation, action.payload.errorMsg)
- );
- } else {
- yield put(
- actions.handleStoriesConsoleLog(action.payload.storyEnv, action.payload.errorMsg)
- );
- }
- },
- toggleFolderMode: function* (action) {
- const workspaceLocation = action.payload.workspaceLocation;
- const isFolderModeEnabled: boolean = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].isFolderModeEnabled
- );
- yield put(actions.setFolderMode(workspaceLocation, !isFolderModeEnabled));
- const warningMessage = `Folder mode ${!isFolderModeEnabled ? 'enabled' : 'disabled'}`;
- yield call(showWarningMessage, warningMessage, 750);
- },
- setFolderMode: function* (action): any {
- const workspaceLocation = action.payload.workspaceLocation;
- const isFolderModeEnabled: boolean = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].isFolderModeEnabled
- );
- // Do nothing if Folder mode is enabled.
- if (isFolderModeEnabled) {
- return;
- }
-
- const editorTabs: EditorTabState[] = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].editorTabs
+const WorkspaceSaga = combineSagaHandlers({
+ [WorkspaceActions.addHtmlConsoleError.type]: function* (action) {
+ // TODO: Do not use if-else logic
+ if (!action.payload.storyEnv) {
+ yield put(
+ actions.handleConsoleLog(action.payload.workspaceLocation, action.payload.errorMsg)
);
- // If Folder mode is disabled and there are no open editor tabs, add an editor tab.
- if (editorTabs.length === 0) {
- const defaultFilePath = `${WORKSPACE_BASE_PATHS[workspaceLocation]}/program.js`;
- const fileSystem: FSModule | null = yield select(
- (state: OverallState) => state.fileSystem.inBrowserFileSystem
- );
- // If the file system is not initialised, add an editor tab with the default editor value.
- if (fileSystem === null) {
- yield put(actions.addEditorTab(workspaceLocation, defaultFilePath, defaultEditorValue));
- return;
- }
- const editorValue: string = yield new Promise((resolve, reject) => {
- fileSystem.exists(defaultFilePath, fileExists => {
- if (!fileExists) {
- // If the file does not exist, we need to also create it in the file system.
- writeFileRecursively(fileSystem, defaultFilePath, defaultEditorValue)
- .then(() => resolve(defaultEditorValue))
- .catch(err => reject(err));
- return;
- }
- fileSystem.readFile(defaultFilePath, 'utf-8', (err, fileContents) => {
- if (err) {
- reject(err);
- return;
- }
- if (fileContents === undefined) {
- reject(new Error('File exists but has no contents.'));
- return;
- }
- resolve(fileContents);
- });
- });
- });
- yield put(actions.addEditorTab(workspaceLocation, defaultFilePath, editorValue));
- }
- },
- // Mirror editor updates to the associated file in the filesystem.
- updateEditorValue: function* (action): any {
- const workspaceLocation = action.payload.workspaceLocation;
- const editorTabIndex = action.payload.editorTabIndex;
+ } else {
+ yield put(actions.handleStoriesConsoleLog(action.payload.storyEnv, action.payload.errorMsg));
+ }
+ },
+ [WorkspaceActions.toggleFolderMode.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const isFolderModeEnabled: boolean = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].isFolderModeEnabled
+ );
+ yield put(actions.setFolderMode(workspaceLocation, !isFolderModeEnabled));
+ const warningMessage = `Folder mode ${!isFolderModeEnabled ? 'enabled' : 'disabled'}`;
+ yield call(showWarningMessage, warningMessage, 750);
+ },
+ [WorkspaceActions.setFolderMode.type]: function* (action): any {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const { editorTabs, isFolderModeEnabled } = yield* selectWorkspace(workspaceLocation);
- const filePath: string | undefined = yield select(
- (state: OverallState) =>
- state.workspaces[workspaceLocation].editorTabs[editorTabIndex].filePath
- );
- // If the code does not have an associated file, do nothing.
- if (filePath === undefined) {
- return;
- }
+ // Do nothing if Folder mode is enabled.
+ if (isFolderModeEnabled) {
+ return;
+ }
+ // If Folder mode is disabled and there are no open editor tabs, add an editor tab.
+ if (editorTabs.length === 0) {
+ const defaultFilePath = `${WORKSPACE_BASE_PATHS[workspaceLocation]}/program.js`;
const fileSystem: FSModule | null = yield select(
(state: OverallState) => state.fileSystem.inBrowserFileSystem
);
- // If the file system is not initialised, do nothing.
+ // If the file system is not initialised, add an editor tab with the default editor value.
if (fileSystem === null) {
+ yield put(actions.addEditorTab(workspaceLocation, defaultFilePath, defaultEditorValue));
return;
}
-
- fileSystem.writeFile(filePath, action.payload.newEditorValue, err => {
- if (err) {
- console.error(err);
- }
+ const editorValue: string = yield new Promise((resolve, reject) => {
+ fileSystem.exists(defaultFilePath, fileExists => {
+ if (!fileExists) {
+ // If the file does not exist, we need to also create it in the file system.
+ writeFileRecursively(fileSystem, defaultFilePath, defaultEditorValue)
+ .then(() => resolve(defaultEditorValue))
+ .catch(err => reject(err));
+ return;
+ }
+ fileSystem.readFile(defaultFilePath, 'utf-8', (err, fileContents) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ if (fileContents === undefined) {
+ reject(new Error('File exists but has no contents.'));
+ return;
+ }
+ resolve(fileContents);
+ });
+ });
});
- yield;
- },
- evalEditor: function* (action) {
- const workspaceLocation = action.payload.workspaceLocation;
- yield* evalEditorSaga(workspaceLocation);
- },
- promptAutocomplete: function* (action): any {
- const workspaceLocation = action.payload.workspaceLocation;
+ yield put(actions.addEditorTab(workspaceLocation, defaultFilePath, editorValue));
+ }
+ },
+ // Mirror editor updates to the associated file in the filesystem.
+ [WorkspaceActions.updateEditorValue.type]: function* (action): any {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const editorTabIndex = action.payload.editorTabIndex;
- const context: Context = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].context
- );
+ const filePath: string | undefined = yield select(
+ (state: OverallState) =>
+ state.workspaces[workspaceLocation].editorTabs[editorTabIndex].filePath
+ );
+ // If the code does not have an associated file, do nothing.
+ if (filePath === undefined) {
+ return;
+ }
- const code: string = yield select((state: OverallState) => {
- const prependCode = state.workspaces[workspaceLocation].programPrependValue;
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- const editorCode = state.workspaces[workspaceLocation].editorTabs[0].value;
- return [prependCode, editorCode] as [string, string];
- });
- const [prepend, editorValue] = code;
+ const fileSystem: FSModule | null = yield select(
+ (state: OverallState) => state.fileSystem.inBrowserFileSystem
+ );
+ // If the file system is not initialised, do nothing.
+ if (fileSystem === null) {
+ return;
+ }
- // Deal with prepended code
- let autocompleteCode;
- let prependLength = 0;
- if (!prepend) {
- autocompleteCode = editorValue;
- } else {
- prependLength = prepend.split('\n').length;
- autocompleteCode = prepend + '\n' + editorValue;
+ fileSystem.writeFile(filePath, action.payload.newEditorValue, err => {
+ if (err) {
+ console.error(err);
}
+ });
+ },
+ [WorkspaceActions.evalEditor.type]: ({ payload: { workspaceLocation } }) =>
+ evalEditorSaga(workspaceLocation),
+ [WorkspaceActions.promptAutocomplete.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const {
+ activeEditorTabIndex,
+ editorTabs,
+ context,
+ externalLibrary: extLib,
+ programPrependValue: prepend
+ } = yield* selectWorkspace(workspaceLocation);
- const [editorNames, displaySuggestions] = yield call(
- getNames,
- autocompleteCode,
- action.payload.row + prependLength,
- action.payload.column,
- context
- );
+ const editorValue = editorTabs[activeEditorTabIndex ?? 0].value;
- if (!displaySuggestions) {
- yield call(action.payload.callback);
- return;
- }
+ // Deal with prepended code
+ let autocompleteCode;
+ let prependLength = 0;
+ if (!prepend) {
+ autocompleteCode = editorValue;
+ } else {
+ prependLength = prepend.split('\n').length;
+ autocompleteCode = prepend + '\n' + editorValue;
+ }
- const editorSuggestions = editorNames.map((name: any) => {
- return {
- ...name,
- caption: name.name,
- value: name.name,
- score: name.score ? name.score + 1000 : 1000, // Prioritize suggestions from code
- name: undefined
- };
- });
+ const [editorNames, displaySuggestions]: Awaited> = yield call(
+ getNames,
+ autocompleteCode,
+ action.payload.row + prependLength,
+ action.payload.column,
+ context
+ );
- let chapterName = context.chapter.toString();
- const variant = context.variant ?? Variant.DEFAULT;
- if (variant !== Variant.DEFAULT) {
- chapterName += '_' + variant;
- }
+ if (!displaySuggestions) {
+ yield call(action.payload.callback);
+ return;
+ }
- const builtinSuggestions = Documentation.builtins[chapterName] || [];
+ const editorSuggestions: any[] = editorNames.map(name => {
+ return {
+ ...name,
+ caption: name.name,
+ value: name.name,
+ score: name.score ? name.score + 1000 : 1000, // Prioritize suggestions from code
+ name: undefined
+ };
+ });
- const extLib = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].externalLibrary
- );
+ let chapterName = context.chapter.toString();
+ const variant = context.variant ?? Variant.DEFAULT;
+ if (variant !== Variant.DEFAULT) {
+ chapterName += '_' + variant;
+ }
- const extLibSuggestions = Documentation.externalLibraries[extLib] || [];
+ const builtinSuggestions = Documentation.builtins[chapterName] || [];
+ const extLibSuggestions = Documentation.externalLibraries[extLib] || [];
- yield call(
- action.payload.callback,
- null,
- editorSuggestions.concat(builtinSuggestions, extLibSuggestions)
- );
- },
- toggleEditorAutorun: function* (action): any {
- const workspaceLocation = action.payload.workspaceLocation;
- const isEditorAutorun = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].isEditorAutorun
- );
- yield call(showWarningMessage, 'Autorun ' + (isEditorAutorun ? 'Started' : 'Stopped'), 750);
- },
- evalRepl: function* (action) {
- if (yield call(selectFeatureSaga, flagConductorEnable)) {
- return; // no-op: evalCodeConductorSaga will pick up this action and handle it from there
- }
- const workspaceLocation = action.payload.workspaceLocation;
- const code: string = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].replValue
- );
- const execTime: number = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].execTime
- );
- yield put(actions.beginInterruptExecution(workspaceLocation));
- yield put(actions.clearReplInput(workspaceLocation));
- yield put(actions.sendReplInputToOutput(code, workspaceLocation));
- const context: Context = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].context
- );
- // Reset old context.errors
- context.errors = [];
- const codeFilePath = '/code.js';
- const codeFiles = {
- [codeFilePath]: code
- };
- yield call(
- evalCodeSaga,
- codeFiles,
- codeFilePath,
- context,
- execTime,
- workspaceLocation,
- WorkspaceActions.evalRepl.type
- );
- },
- debuggerResume: function* (action) {
- const workspaceLocation = action.payload.workspaceLocation;
- const code: string = yield select(
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- (state: OverallState) => state.workspaces[workspaceLocation].editorTabs[0].value
- );
- const execTime: number = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].execTime
- );
- yield put(actions.beginInterruptExecution(workspaceLocation));
- /** Clear the context, with the same chapter and externalSymbols as before. */
- yield put(actions.clearReplOutput(workspaceLocation));
- const context: Context = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].context
- );
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
- const codeFilePath = '/code.js';
- const codeFiles = {
- [codeFilePath]: code
- };
- yield call(
- evalCodeSaga,
- codeFiles,
- codeFilePath,
- context,
- execTime,
- workspaceLocation,
- InterpreterActions.debuggerResume.type
- );
- },
- debuggerReset: function* (action) {
- const workspaceLocation = action.payload.workspaceLocation;
- const context: Context = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].context
- );
- yield put(actions.clearReplOutput(workspaceLocation));
+ yield call(
+ action.payload.callback,
+ null,
+ editorSuggestions.concat(builtinSuggestions, extLibSuggestions)
+ );
+ },
+ [WorkspaceActions.toggleEditorAutorun.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const isEditorAutorun = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].isEditorAutorun
+ );
+ yield call(showWarningMessage, 'Autorun ' + (isEditorAutorun ? 'Started' : 'Stopped'), 750);
+ },
+ [WorkspaceActions.evalRepl.type]: function* (action) {
+ if (yield call(selectFeatureSaga, flagConductorEnable)) {
+ return; // no-op: evalCodeConductorSaga will pick up this action and handle it from there
+ }
+ const workspaceLocation = action.payload.workspaceLocation;
+ const { replValue: code, execTime } = yield* selectWorkspace(workspaceLocation);
+
+ yield put(actions.beginInterruptExecution(workspaceLocation));
+ yield put(actions.clearReplInput(workspaceLocation));
+ yield put(actions.sendReplInputToOutput(code, workspaceLocation));
+ const context: Context = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].context
+ );
+ // Reset old context.errors
+ context.errors = [];
+ const codeFilePath = '/code.js';
+ const codeFiles = {
+ [codeFilePath]: code
+ };
+ yield call(
+ evalCodeSaga,
+ codeFiles,
+ codeFilePath,
+ context,
+ execTime,
+ WorkspaceActions.evalRepl.type,
+ workspaceLocation
+ );
+ },
+ [InterpreterActions.debuggerResume.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const code: string = yield select(
// TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
- context.runtime.break = false;
- yield put(actions.updateLastDebuggerResult(undefined, workspaceLocation));
- },
- setEditorHighlightedLines: function* (action): any {
- const newHighlightedLines = action.payload.newHighlightedLines;
- if (newHighlightedLines.length === 0) {
- highlightClean();
- } else {
- try {
- newHighlightedLines.forEach(([startRow, endRow]: [number, number]) => {
- for (let row = startRow; row <= endRow; row++) {
- highlightLine(row);
- }
- });
- } catch (e) {
- // Error most likely caused by trying to highlight the lines of the prelude
- // in CSE Machine. Can be ignored.
+ (state: OverallState) => state.workspaces[workspaceLocation].editorTabs[0].value
+ );
+ const execTime: number = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].execTime
+ );
+ yield put(actions.beginInterruptExecution(workspaceLocation));
+ /** Clear the context, with the same chapter and externalSymbols as before. */
+ yield put(actions.clearReplOutput(workspaceLocation));
+ const context: Context = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].context
+ );
+ // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
+ yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
+ const codeFilePath = '/code.js';
+ const codeFiles = {
+ [codeFilePath]: code
+ };
+ yield call(
+ evalCodeSaga,
+ codeFiles,
+ codeFilePath,
+ context,
+ execTime,
+ InterpreterActions.debuggerResume.type,
+ workspaceLocation
+ );
+ },
+ [InterpreterActions.debuggerReset.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const context: Context = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].context
+ );
+ yield put(actions.clearReplOutput(workspaceLocation));
+ // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
+ yield put(actions.setEditorHighlightedLines(workspaceLocation, 0, []));
+ context.runtime.break = false;
+ yield put(actions.updateLastDebuggerResult(undefined, workspaceLocation));
+ },
+ [WorkspaceActions.setEditorHighlightedLines.type]: function* ({
+ payload: { newHighlightedLines }
+ }) {
+ if (newHighlightedLines.length === 0) {
+ yield call(highlightClean);
+ } else {
+ try {
+ for (const [startRow, endRow] of newHighlightedLines) {
+ for (let row = startRow; row <= endRow; row++) {
+ yield call(highlightLine, row);
+ }
}
+ } catch (e) {
+ // Error most likely caused by trying to highlight the lines of the prelude
+ // in CSE Machine. Can be ignored.
}
- yield;
- },
- setEditorHighlightedLinesControl: function* (action): any {
- const newHighlightedLines = action.payload.newHighlightedLines;
- if (newHighlightedLines.length === 0) {
- yield call(highlightCleanForControl);
- } else {
- try {
- for (const [startRow, endRow] of newHighlightedLines) {
- for (let row = startRow; row <= endRow; row++) {
- yield call(highlightLineForControl, row);
- }
+ }
+ },
+ [WorkspaceActions.setEditorHighlightedLinesControl.type]: function* ({
+ payload: { newHighlightedLines }
+ }) {
+ if (newHighlightedLines.length === 0) {
+ yield call(highlightCleanForControl);
+ } else {
+ try {
+ for (const [startRow, endRow] of newHighlightedLines) {
+ for (let row = startRow; row <= endRow; row++) {
+ yield call(highlightLineForControl, row);
}
- } catch (e) {
- // Error most likely caused by trying to highlight the lines of the prelude
- // in CSE Machine. Can be ignored.
}
+ } catch (e) {
+ // Error most likely caused by trying to highlight the lines of the prelude
+ // in CSE Machine. Can be ignored.
}
- },
- evalTestcase: function* (action) {
- yield put(actions.addEvent([EventType.RUN_TESTCASE]));
- const workspaceLocation = action.payload.workspaceLocation;
- const index = action.payload.testcaseId;
- yield* runTestCase(workspaceLocation, index);
- },
- chapterSelect: function* (action) {
- const { workspaceLocation, chapter: newChapter, variant: newVariant } = action.payload;
- const [oldVariant, oldChapter, symbols, globals, externalLibraryName]: [
- Variant,
- Chapter,
- string[],
- Array<[string, any]>,
- ExternalLibraryName
- ] = yield select((state: OverallState) => [
- state.workspaces[workspaceLocation].context.variant,
- state.workspaces[workspaceLocation].context.chapter,
- state.workspaces[workspaceLocation].context.externalSymbols,
- state.workspaces[workspaceLocation].globals,
- state.workspaces[workspaceLocation].externalLibrary
- ]);
+ }
+ },
+ [WorkspaceActions.evalTestcase.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ if (workspaceLocation === 'stories') return;
- const chapterChanged: boolean = newChapter !== oldChapter || newVariant !== oldVariant;
- const toChangeChapter: boolean =
- newChapter === Chapter.FULL_JS
- ? chapterChanged && (yield call(showFullJSDisclaimer))
- : newChapter === Chapter.FULL_TS
- ? chapterChanged && (yield call(showFullTSDisclaimer))
- : chapterChanged;
+ yield put(actions.addEvent([EventType.RUN_TESTCASE], workspaceLocation));
+ const index = action.payload.testcaseId;
+ yield* runTestCase(workspaceLocation, index);
+ },
+ [WorkspaceActions.chapterSelect.type]: function* (action) {
+ const { workspaceLocation, chapter: newChapter, variant: newVariant } = action.payload;
+ const [oldVariant, oldChapter, symbols, globals, externalLibraryName]: [
+ Variant,
+ Chapter,
+ string[],
+ Array<[string, any]>,
+ ExternalLibraryName
+ ] = yield select((state: OverallState) => [
+ state.workspaces[workspaceLocation].context.variant,
+ state.workspaces[workspaceLocation].context.chapter,
+ state.workspaces[workspaceLocation].context.externalSymbols,
+ state.workspaces[workspaceLocation].globals,
+ state.workspaces[workspaceLocation].externalLibrary
+ ]);
- if (toChangeChapter) {
- const library: Library = {
- chapter: newChapter,
- variant: newVariant,
- external: {
- name: externalLibraryName,
- symbols
- },
- globals
- };
- yield put(actions.beginClearContext(workspaceLocation, library, false));
- yield put(actions.clearReplOutput(workspaceLocation));
- yield put(actions.debuggerReset(workspaceLocation));
- if (workspaceLocation !== 'stories') yield put(actions.resetSideContent(workspaceLocation));
- yield call(
- showSuccessMessage,
- `Switched to ${styliseSublanguage(newChapter, newVariant)}`,
- 1000
- );
- }
- },
- /**
- * Note that the PLAYGROUND_EXTERNAL_SELECT action is made to
- * select the library for playground.
- * This is because assessments do not have a chapter & library select, the question
- * specifies the chapter and library to be used.
- *
- * To abstract this to assessments, the state structure must be manipulated to store
- * the external library name in a WorkspaceState (as compared to IWorkspaceManagerState).
- *
- * @see IWorkspaceManagerState @see WorkspaceState
- */
- externalLibrarySelect: function* (action) {
- const { workspaceLocation, externalLibraryName: newExternalLibraryName } = action.payload;
- const [chapter, globals, oldExternalLibraryName]: [
- Chapter,
- Array<[string, any]>,
- ExternalLibraryName
- ] = yield select((state: OverallState) => [
- state.workspaces[workspaceLocation].context.chapter,
- state.workspaces[workspaceLocation].globals,
- state.workspaces[workspaceLocation].externalLibrary
- ]);
- const symbols = externalLibraries.get(newExternalLibraryName)!;
+ const chapterChanged: boolean = newChapter !== oldChapter || newVariant !== oldVariant;
+ const toChangeChapter: boolean =
+ newChapter === Chapter.FULL_JS
+ ? chapterChanged && (yield call(showFullJSDisclaimer))
+ : newChapter === Chapter.FULL_TS
+ ? chapterChanged && (yield call(showFullTSDisclaimer))
+ : chapterChanged;
+
+ if (toChangeChapter) {
const library: Library = {
- chapter,
+ chapter: newChapter,
+ variant: newVariant,
external: {
- name: newExternalLibraryName,
+ name: externalLibraryName,
symbols
},
globals
};
- if (newExternalLibraryName !== oldExternalLibraryName || action.payload.initialise) {
- yield put(actions.changeExternalLibrary(newExternalLibraryName, workspaceLocation));
- yield put(actions.beginClearContext(workspaceLocation, library, true));
- yield put(actions.clearReplOutput(workspaceLocation));
- if (!action.payload.initialise) {
- yield call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000);
- }
- }
- },
- /**
- * Handles the side effect of resetting the WebGL context when context is reset.
- *
- * @see webGLgraphics.js under 'public/externalLibs/graphics' for information on
- * the function.
- */
- beginClearContext: function* (action): any {
- yield call([DataVisualizer, DataVisualizer.clear]);
- yield call([CseMachine, CseMachine.clear]);
- const globals: Array<[string, any]> = action.payload.library.globals as Array<[string, any]>;
- for (const [key, value] of globals) {
- window[key as any] = value;
+ yield put(actions.beginClearContext(workspaceLocation, library, false));
+ yield put(actions.clearReplOutput(workspaceLocation));
+ yield put(actions.debuggerReset(workspaceLocation));
+ if (workspaceLocation !== 'stories') yield put(actions.resetSideContent(workspaceLocation));
+ yield call(
+ showSuccessMessage,
+ `Switched to ${styliseSublanguage(newChapter, newVariant)}`,
+ 1000
+ );
+ }
+ },
+ /**
+ * Note that the PLAYGROUND_EXTERNAL_SELECT action is made to
+ * select the library for playground.
+ * This is because assessments do not have a chapter & library select, the question
+ * specifies the chapter and library to be used.
+ *
+ * To abstract this to assessments, the state structure must be manipulated to store
+ * the external library name in a WorkspaceState (as compared to IWorkspaceManagerState).
+ *
+ * @see IWorkspaceManagerState @see WorkspaceState
+ */
+ [WorkspaceActions.externalLibrarySelect.type]: function* (action) {
+ const { workspaceLocation, externalLibraryName: newExternalLibraryName } = action.payload;
+ const [chapter, globals, oldExternalLibraryName]: [
+ Chapter,
+ Array<[string, any]>,
+ ExternalLibraryName
+ ] = yield select((state: OverallState) => [
+ state.workspaces[workspaceLocation].context.chapter,
+ state.workspaces[workspaceLocation].globals,
+ state.workspaces[workspaceLocation].externalLibrary
+ ]);
+ const symbols = externalLibraries.get(newExternalLibraryName)!;
+ const library: Library = {
+ chapter,
+ external: {
+ name: newExternalLibraryName,
+ symbols
+ },
+ globals
+ };
+ if (newExternalLibraryName !== oldExternalLibraryName || action.payload.initialise) {
+ yield put(actions.changeExternalLibrary(newExternalLibraryName, workspaceLocation));
+ yield put(actions.beginClearContext(workspaceLocation, library, true));
+ yield put(actions.clearReplOutput(workspaceLocation));
+ if (!action.payload.initialise) {
+ yield call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000);
}
+ }
+ },
+ /**
+ * Handles the side effect of resetting the WebGL context when context is reset.
+ *
+ * @see webGLgraphics.js under 'public/externalLibs/graphics' for information on
+ * the function.
+ */
+ [WorkspaceActions.beginClearContext.type]: function* (action): any {
+ yield call([DataVisualizer, DataVisualizer.clear]);
+ yield call([CseMachine, CseMachine.clear]);
+ const globals: Array<[string, any]> = action.payload.library.globals as Array<[string, any]>;
+ for (const [key, value] of globals) {
+ window[key as any] = value;
+ }
+ yield put(
+ actions.endClearContext(
+ {
+ ...action.payload.library,
+ moduleParams: {
+ runes: {},
+ phaser: Phaser
+ }
+ },
+ action.payload.workspaceLocation
+ )
+ );
+ yield undefined;
+ },
+ [WorkspaceActions.navigateToDeclaration.type]: function* (action) {
+ const workspaceLocation = action.payload.workspaceLocation;
+ const code: string = yield select(
+ // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
+ (state: OverallState) => state.workspaces[workspaceLocation].editorTabs[0].value
+ );
+ const context: Context = yield select(
+ (state: OverallState) => state.workspaces[workspaceLocation].context
+ );
+
+ const result = findDeclaration(code, context, {
+ line: action.payload.cursorPosition.row + 1,
+ column: action.payload.cursorPosition.column
+ });
+ if (result) {
+ // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
yield put(
- actions.endClearContext(
- {
- ...action.payload.library,
- moduleParams: {
- runes: {},
- phaser: Phaser
- }
- },
- action.payload.workspaceLocation
- )
- );
- yield undefined;
- },
- navigateToDeclaration: function* (action) {
- const workspaceLocation = action.payload.workspaceLocation;
- const code: string = yield select(
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- (state: OverallState) => state.workspaces[workspaceLocation].editorTabs[0].value
+ actions.moveCursor(action.payload.workspaceLocation, 0, {
+ row: result.start.line - 1,
+ column: result.start.column
+ })
);
- const context: Context = yield select(
- (state: OverallState) => state.workspaces[workspaceLocation].context
- );
-
- const result = findDeclaration(code, context, {
- line: action.payload.cursorPosition.row + 1,
- column: action.payload.cursorPosition.column
- });
- if (result) {
- // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added.
- yield put(
- actions.moveCursor(action.payload.workspaceLocation, 0, {
- row: result.start.line - 1,
- column: result.start.column
- })
- );
- }
- },
- // TODO: Should be takeLeading, not takeEvery
- runAllTestcases: function* (action): any {
+ }
+ },
+ [WorkspaceActions.runAllTestcases.type]: {
+ takeLeading: function* (action): any {
const { workspaceLocation } = action.payload;
yield call(evalEditorSaga, workspaceLocation);
@@ -500,6 +479,6 @@ const WorkspaceSaga = combineSagaHandlers(
}
}
}
-);
+});
export default WorkspaceSaga;
diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts
index e707c49261..a92f0fde07 100644
--- a/src/commons/sagas/__tests__/BackendSaga.ts
+++ b/src/commons/sagas/__tests__/BackendSaga.ts
@@ -9,13 +9,13 @@ import { UsernameRoleGroup } from 'src/pages/academy/adminPanel/subcomponents/Ad
import DashboardActions from '../../../features/dashboard/DashboardActions';
import SessionActions from '../../application/actions/SessionActions';
import {
- GameState,
+ type GameState,
Role,
- SALanguage,
- Story,
+ type SALanguage,
+ type Story,
SupportedLanguage
} from '../../application/ApplicationTypes';
-import {
+import type {
AdminPanelCourseRegistration,
CourseConfiguration,
CourseRegistration,
@@ -23,10 +23,10 @@ import {
User
} from '../../application/types/SessionTypes';
import {
- Assessment,
- AssessmentConfiguration,
+ type Assessment,
+ type AssessmentConfiguration,
AssessmentStatuses,
- Question
+ type Question
} from '../../assessment/AssessmentTypes';
import {
mockAssessmentOverviews,
@@ -43,7 +43,7 @@ import {
showWarningMessage
} from '../../utils/notifications/NotificationsHelper';
import WorkspaceActions from '../../workspace/WorkspaceActions';
-import { WorkspaceLocation } from '../../workspace/WorkspaceTypes';
+import type { WorkspaceLocation } from '../../workspace/WorkspaceTypes';
import BackendSaga from '../BackendSaga';
import {
getAssessment,
@@ -837,7 +837,7 @@ describe('Test CHANGE_SUBLANGUAGE action', () => {
test('when chapter is changed', () => {
const sublang: SALanguage = {
chapter: Chapter.SOURCE_4,
- variant: Variant.CONCURRENT,
+ variant: Variant.NATIVE,
displayName: 'Source \xa74 Concurrent',
mainLanguage: SupportedLanguage.JAVASCRIPT,
supports: {}
@@ -859,7 +859,7 @@ describe('Test CHANGE_SUBLANGUAGE action', () => {
[
call(putCourseConfig, mockTokens, {
sourceChapter: Chapter.SOURCE_4,
- sourceVariant: Variant.CONCURRENT
+ sourceVariant: Variant.NATIVE
}),
{ ok: true }
]
diff --git a/src/commons/sagas/__tests__/SafeEffects.ts b/src/commons/sagas/__tests__/SafeEffects.ts
new file mode 100644
index 0000000000..c298b4f075
--- /dev/null
+++ b/src/commons/sagas/__tests__/SafeEffects.ts
@@ -0,0 +1,35 @@
+import * as Sentry from '@sentry/browser';
+import { call } from 'redux-saga/effects';
+import { expectSaga } from 'redux-saga-test-plan';
+
+import { wrapSaga } from '../SafeEffects';
+
+jest.spyOn(Sentry, 'captureException');
+
+// Silence console error
+jest.spyOn(console, 'error').mockImplementation(x => {});
+
+describe('Test wrapSaga', () => {
+ test('wrapSaga is transparent', async () => {
+ const mockFn = jest.fn();
+ const wrappedSaga = wrapSaga(function* () {
+ yield call(mockFn);
+ });
+
+ await expectSaga(wrappedSaga).silentRun();
+
+ expect(mockFn).toHaveBeenCalledTimes(1);
+ });
+
+ test('wrapSaga handles errors appropriately', async () => {
+ const errorToThrow = new Error();
+ const wrappedSaga = wrapSaga(function* () {
+ throw errorToThrow;
+ });
+
+ await expectSaga(wrappedSaga).silentRun();
+
+ expect(Sentry.captureException).toHaveBeenCalledWith(errorToThrow);
+ expect(console.error).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/commons/sagas/__tests__/WorkspaceSaga.ts b/src/commons/sagas/__tests__/WorkspaceSaga.ts
index bd6f4f1159..cc45ecfa45 100644
--- a/src/commons/sagas/__tests__/WorkspaceSaga.ts
+++ b/src/commons/sagas/__tests__/WorkspaceSaga.ts
@@ -1,6 +1,13 @@
-import { Context, IOptions, Result, resume, runFilesInContext, runInContext } from 'js-slang';
+import {
+ type Context,
+ type IOptions,
+ type Result,
+ resume,
+ runFilesInContext,
+ runInContext
+} from 'js-slang';
import createContext from 'js-slang/dist/createContext';
-import { Chapter, ErrorType, Finished, SourceError, Variant } from 'js-slang/dist/types';
+import { Chapter, ErrorType, type Finished, type SourceError, Variant } from 'js-slang/dist/types';
import { call } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
@@ -11,10 +18,15 @@ import {
defaultState,
fullJSLanguage,
fullTSLanguage,
- OverallState
+ type OverallState
} from '../../application/ApplicationTypes';
import { externalLibraries, ExternalLibraryName } from '../../application/types/ExternalTypes';
-import { Library, Testcase, TestcaseType, TestcaseTypes } from '../../assessment/AssessmentTypes';
+import {
+ type Library,
+ type Testcase,
+ type TestcaseType,
+ TestcaseTypes
+} from '../../assessment/AssessmentTypes';
import { mockRuntimeContext } from '../../mocks/ContextMocks';
import { mockTestcases } from '../../mocks/GradingMocks';
import {
@@ -22,7 +34,7 @@ import {
showWarningMessage
} from '../../utils/notifications/NotificationsHelper';
import WorkspaceActions from '../../workspace/WorkspaceActions';
-import { WorkspaceLocation, WorkspaceState } from '../../workspace/WorkspaceTypes';
+import type { WorkspaceLocation, WorkspaceState } from '../../workspace/WorkspaceTypes';
import workspaceSaga from '../WorkspaceSaga';
import { evalCodeSaga } from '../WorkspaceSaga/helpers/evalCode';
import { evalEditorSaga } from '../WorkspaceSaga/helpers/evalEditor';
@@ -148,7 +160,6 @@ describe('EVAL_EDITOR', () => {
{ '/prepend.js': programPrependValue },
'/prepend.js',
{
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
@@ -168,7 +179,6 @@ describe('EVAL_EDITOR', () => {
'/playground/program.js',
context,
{
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
@@ -236,12 +246,12 @@ describe('EVAL_REPL', () => {
.put(WorkspaceActions.sendReplInputToOutput(replValue, workspaceLocation))
// also calls evalCode here
.call(runFilesInContext, { '/code.js': replValue }, '/code.js', context, {
- scheduler: 'preemptive',
originalMaxExecTime: 1000,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
})
.dispatch({
type: WorkspaceActions.evalRepl.type,
@@ -279,8 +289,8 @@ describe('DEBUG_RESUME', () => {
editorValueFilePath,
context,
execTime,
- workspaceLocation,
- WorkspaceActions.evalEditor.type
+ WorkspaceActions.evalEditor.type,
+ workspaceLocation
)
.withState(state)
.silentRun();
@@ -419,7 +429,7 @@ describe('EVAL_TESTCASE', () => {
args: [
{ '/prepend.js': programPrependValue },
'/prepend.js',
- { scheduler: 'preemptive', originalMaxExecTime: execTime }
+ { originalMaxExecTime: execTime }
]
})
// running the prepend block should return 'boink', but silent run -> not written to REPL
@@ -433,7 +443,7 @@ describe('EVAL_TESTCASE', () => {
{ '/value.js': editorValue },
'/value.js',
context,
- { scheduler: 'preemptive', originalMaxExecTime: execTime }
+ { originalMaxExecTime: execTime }
]
})
// running the student's program should return 69, which is NOT written to REPL (silent)
@@ -446,7 +456,7 @@ describe('EVAL_TESTCASE', () => {
args: [
{ '/postpend.js': programPostpendValue },
'/postpend.js',
- { scheduler: 'preemptive', originalMaxExecTime: execTime }
+ { originalMaxExecTime: execTime }
]
})
// running the postpend block should return true, but silent run -> not written to REPL
@@ -797,12 +807,12 @@ describe('evalCode', () => {
context = createContext(); // mockRuntimeContext();
value = 'test value';
options = {
- scheduler: 'preemptive',
originalMaxExecTime: 1000,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
};
lastDebuggerResult = { status: 'error' };
state = generateDefaultState(workspaceLocation, { lastDebuggerResult: { status: 'error' } });
@@ -816,8 +826,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide([
@@ -827,12 +837,12 @@ describe('evalCode', () => {
]
])
.call(runFilesInContext, files, codeFilePath, context, {
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
})
.put(InterpreterActions.evalInterpreterSuccess(value, workspaceLocation))
.silentRun();
@@ -845,20 +855,23 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide([
- [call(runFilesInContext, files, codeFilePath, context, options), { status: 'suspended' }]
+ [
+ call(runFilesInContext, files, codeFilePath, context, options),
+ { status: 'suspended-cse-eval' }
+ ]
])
.call(runFilesInContext, files, codeFilePath, context, {
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
})
.put(InterpreterActions.endDebuggerPause(workspaceLocation))
.put(InterpreterActions.evalInterpreterSuccess('Breakpoint hit!', workspaceLocation))
@@ -872,17 +885,20 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
+ .provide([
+ [call(runFilesInContext, files, codeFilePath, context, options), { status: 'error' }]
+ ])
.call(runFilesInContext, files, codeFilePath, context, {
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
})
.put.like({ action: { type: InterpreterActions.evalInterpreterError.type } })
.silentRun();
@@ -896,7 +912,6 @@ describe('evalCode', () => {
});
runFilesInContext(files, codeFilePath, context, {
- scheduler: 'preemptive',
originalMaxExecTime: 1000,
useSubst: false
}).then(result => (context = (result as Finished).context));
@@ -907,17 +922,17 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.call(runFilesInContext, files, codeFilePath, context, {
- scheduler: 'preemptive',
originalMaxExecTime: execTime,
stepLimit: 1000,
useSubst: false,
throwInfiniteLoops: true,
- envSteps: -1
+ envSteps: -1,
+ executionMethod: 'auto'
})
.put(InterpreterActions.evalInterpreterError(context.errors, workspaceLocation))
.silentRun();
@@ -933,8 +948,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- WorkspaceActions.evalEditor.type
+ WorkspaceActions.evalEditor.type,
+ workspaceLocation
)
.withState(state)
.silentRun();
@@ -949,8 +964,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide([[call(resume, lastDebuggerResult), { status: 'finished', value }]])
@@ -968,11 +983,11 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
- .provide([[call(resume, lastDebuggerResult), { status: 'suspended' }]])
+ .provide([[call(resume, lastDebuggerResult), { status: 'suspended-cse-eval' }]])
.call(resume, lastDebuggerResult)
.put(InterpreterActions.endDebuggerPause(workspaceLocation))
.put(InterpreterActions.evalInterpreterSuccess('Breakpoint hit!', workspaceLocation))
@@ -988,8 +1003,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.call(resume, lastDebuggerResult)
@@ -1006,8 +1021,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide({
@@ -1036,8 +1051,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide({
@@ -1065,8 +1080,8 @@ describe('evalCode', () => {
codeFilePath,
context,
execTime,
- workspaceLocation,
- actionType
+ actionType,
+ workspaceLocation
)
.withState(state)
.provide([
@@ -1104,7 +1119,6 @@ describe('evalTestCode', () => {
context = mockRuntimeContext();
value = 'another test value';
options = {
- scheduler: 'preemptive',
originalMaxExecTime: 1000,
throwInfiniteLoops: true
};
diff --git a/src/commons/utils/ActionsHelper.ts b/src/commons/utils/ActionsHelper.ts
index 33ff9e6aae..129fcc17a5 100644
--- a/src/commons/utils/ActionsHelper.ts
+++ b/src/commons/utils/ActionsHelper.ts
@@ -10,7 +10,7 @@ import AchievementActions from '../../features/achievement/AchievementActions';
import DashboardActions from '../../features/dashboard/DashboardActions';
import GitHubActions from '../../features/github/GitHubActions';
import GroundControlActions from '../../features/groundControl/GroundControlActions';
-import * as PersistenceActions from '../../features/persistence/PersistenceActions';
+import PersistenceActions from '../../features/persistence/PersistenceActions';
import PlaygroundActions from '../../features/playground/PlaygroundActions';
import RemoteExecutionActions from '../../features/remoteExecution/RemoteExecutionActions';
import SourcecastActions from '../../features/sourceRecorder/sourcecast/SourcecastActions';
@@ -19,7 +19,7 @@ import SourcereelActions from '../../features/sourceRecorder/sourcereel/Sourcere
import StoriesActions from '../../features/stories/StoriesActions';
import VscodeActions from '../application/actions/VscodeActions';
import { FeatureFlagsActions } from '../featureFlags';
-import { ActionType } from './TypeHelper';
+import type { ActionType } from './TypeHelper';
export const actions = {
...AchievementActions,
diff --git a/src/commons/utils/JsSlangHelper.ts b/src/commons/utils/JsSlangHelper.ts
index ab88e89db8..dab9f04110 100644
--- a/src/commons/utils/JsSlangHelper.ts
+++ b/src/commons/utils/JsSlangHelper.ts
@@ -1,12 +1,18 @@
/* tslint:disable: ban-types*/
import createSlangContext, { defineBuiltin, importBuiltins } from 'js-slang/dist/createContext';
-import { Chapter, Context, CustomBuiltIns, Value, Variant } from 'js-slang/dist/types';
+import {
+ type Chapter,
+ type Context,
+ type CustomBuiltIns,
+ type Value,
+ Variant
+} from 'js-slang/dist/types';
import { stringify } from 'js-slang/dist/utils/stringify';
import { difference, keys } from 'lodash';
import CseMachine from 'src/features/cseMachine/CseMachine';
import DataVisualizer from '../../features/dataVisualizer/dataVisualizer';
-import { Data } from '../../features/dataVisualizer/dataVisualizerTypes';
+import type { Data } from '../../features/dataVisualizer/dataVisualizerTypes';
import DisplayBufferService from './DisplayBufferService';
/**
diff --git a/src/commons/utils/TypeHelper.ts b/src/commons/utils/TypeHelper.ts
index 56e62d4b1a..289b729d2f 100644
--- a/src/commons/utils/TypeHelper.ts
+++ b/src/commons/utils/TypeHelper.ts
@@ -1,3 +1,5 @@
+import type { actions, SourceActionType } from './ActionsHelper';
+
export type MaybePromise = T extends Promise ? V : U;
export type PromiseResolveType = MaybePromise;
@@ -157,11 +159,29 @@ export function objectValues(obj: Record) {
return Object.values(obj) as T[];
}
+/**
+ * Utility type for getting the key-value tuple types
+ * of a record
+ */
+type DeconstructRecord> = Exclude<
+ {
+ [K in keyof T]: [K, T[K]];
+ }[keyof T],
+ undefined
+>[];
+
/**
* Type safe `Object.entries`
*/
-export function objectEntries(
- obj: Partial>
-): [K, V][] {
- return Object.entries(obj) as [K, V][];
+export function objectEntries>(obj: T) {
+ return Object.entries(obj) as DeconstructRecord;
}
+
+/**
+ * Utility for extracting the ActionCreator type from all the action
+ * creators using the specific type string
+ */
+export type ActionTypeToCreator = Extract<
+ (typeof actions)[keyof typeof actions],
+ (...args: any[]) => { type: T }
+>;
diff --git a/src/commons/workspace/WorkspaceActions.ts b/src/commons/workspace/WorkspaceActions.ts
index 6c21ac1a28..49da980224 100644
--- a/src/commons/workspace/WorkspaceActions.ts
+++ b/src/commons/workspace/WorkspaceActions.ts
@@ -1,20 +1,19 @@
-import { createAction } from '@reduxjs/toolkit';
import type { Context } from 'js-slang';
import { Chapter, Variant } from 'js-slang/dist/types';
-import { AllColsSortStates, GradingColumnVisibility } from '../../features/grading/GradingTypes';
-import { SALanguage } from '../application/ApplicationTypes';
-import { ExternalLibraryName } from '../application/types/ExternalTypes';
-import { Library } from '../assessment/AssessmentTypes';
-import { HighlightedLines, Position } from '../editor/EditorTypes';
+import type {
+ AllColsSortStates,
+ GradingColumnVisibility
+} from '../../features/grading/GradingTypes';
+import type { SALanguage } from '../application/ApplicationTypes';
+import type { ExternalLibraryName } from '../application/types/ExternalTypes';
+import type { Library } from '../assessment/AssessmentTypes';
+import type { HighlightedLines, Position } from '../editor/EditorTypes';
import { createActions } from '../redux/utils';
-import { UploadResult } from '../sideContent/content/SideContentUpload';
-import {
+import type { UploadResult } from '../sideContent/content/SideContentUpload';
+import type {
EditorTabState,
SubmissionsTableFilters,
- TOGGLE_USING_UPLOAD,
- UPDATE_LAST_DEBUGGER_RESULT,
- UPLOAD_FILES,
WorkspaceLocation,
WorkspaceLocationsWithTools,
WorkspaceState
@@ -250,6 +249,10 @@ const newActions = createActions('workspace', {
updateCse,
workspaceLocation
}),
+ toggleUsingUpload: (usingUpload: boolean, workspaceLocation: WorkspaceLocationsWithTools) => ({
+ usingUpload,
+ workspaceLocation
+ }),
updateCurrentStep: (steps: number, workspaceLocation: WorkspaceLocation) => ({
steps,
workspaceLocation
@@ -266,6 +269,14 @@ const newActions = createActions('workspace', {
changepointSteps,
workspaceLocation
}),
+ updateLastDebuggerResult: (lastDebuggerResult: any, workspaceLocation: WorkspaceLocation) => ({
+ lastDebuggerResult,
+ workspaceLocation
+ }),
+ uploadFiles: (files: UploadResult, workspaceLocation: WorkspaceLocation) => ({
+ files,
+ workspaceLocation
+ }),
// For grading table
increaseRequestCounter: 0,
decreaseRequestCounter: 0,
@@ -274,31 +285,4 @@ const newActions = createActions('workspace', {
updateGradingColumnVisibility: (filters: GradingColumnVisibility) => ({ filters })
});
-export const updateLastDebuggerResult = createAction(
- UPDATE_LAST_DEBUGGER_RESULT,
- (lastDebuggerResult: any, workspaceLocation: WorkspaceLocation) => ({
- payload: { lastDebuggerResult, workspaceLocation }
- })
-);
-
-export const toggleUsingUpload = createAction(
- TOGGLE_USING_UPLOAD,
- (usingUpload: boolean, workspaceLocation: WorkspaceLocationsWithTools) => ({
- payload: { usingUpload, workspaceLocation }
- })
-);
-
-export const uploadFiles = createAction(
- UPLOAD_FILES,
- (files: UploadResult, workspaceLocation: WorkspaceLocation) => ({
- payload: { files, workspaceLocation }
- })
-);
-
-// For compatibility with existing code (actions helper)
-export default {
- ...newActions,
- updateLastDebuggerResult,
- toggleUsingUpload,
- uploadFiles
-};
+export default newActions;
diff --git a/src/commons/workspace/WorkspaceReducer.ts b/src/commons/workspace/WorkspaceReducer.ts
index a4cc98a8c3..7e4f241ea9 100644
--- a/src/commons/workspace/WorkspaceReducer.ts
+++ b/src/commons/workspace/WorkspaceReducer.ts
@@ -1,5 +1,4 @@
-import { createReducer, Reducer } from '@reduxjs/toolkit';
-import { stringify } from 'js-slang/dist/utils/stringify';
+import { createReducer, type Reducer } from '@reduxjs/toolkit';
import { SourcecastReducer } from '../../features/sourceRecorder/sourcecast/SourcecastReducer';
import { SourcereelReducer } from '../../features/sourceRecorder/sourcereel/SourcereelReducer';
@@ -8,24 +7,24 @@ import InterpreterActions from '../application/actions/InterpreterActions';
import {
createDefaultWorkspace,
defaultWorkspaceManager,
- ErrorOutput,
- InterpreterOutput,
- NotificationOutput,
- ResultOutput
+ type ErrorOutput,
+ type InterpreterOutput,
+ type NotificationOutput,
+ type ResultOutput
} from '../application/ApplicationTypes';
import {
setEditorSessionId,
setSessionDetails,
setSharedbConnected
} from '../collabEditing/CollabEditingActions';
-import { SourceActionType } from '../utils/ActionsHelper';
+import type { SourceActionType } from '../utils/ActionsHelper';
import { createContext } from '../utils/JsSlangHelper';
import { handleCseAndStepperActions } from './reducers/cseReducer';
import { handleDebuggerActions } from './reducers/debuggerReducer';
import { handleEditorActions } from './reducers/editorReducer';
import { handleReplActions } from './reducers/replReducer';
import WorkspaceActions from './WorkspaceActions';
-import { WorkspaceLocation, WorkspaceManagerState } from './WorkspaceTypes';
+import type { WorkspaceLocation, WorkspaceManagerState } from './WorkspaceTypes';
export const getWorkspaceLocation = (action: any): WorkspaceLocation => {
return action.payload ? action.payload.workspaceLocation : 'assessment';
@@ -67,7 +66,7 @@ export const WorkspaceReducer: Reducer
break;
}
- state = oldWorkspaceReducer(state, action);
+ // state = oldWorkspaceReducer(state, action);
state = newWorkspaceReducer(state, action);
return state;
};
@@ -159,11 +158,10 @@ const newWorkspaceReducer = createReducer(defaultWorkspaceManager, builder => {
})
.addCase(InterpreterActions.evalInterpreterSuccess, (state, action) => {
const workspaceLocation = getWorkspaceLocation(action);
- const execType = state[workspaceLocation].context.executionMethod;
const tokens = state[workspaceLocation].tokenCount;
const newOutputEntry: Partial = {
- type: action.payload.type as 'result' | undefined,
- value: execType === 'interpreter' ? action.payload.value : stringify(action.payload.value)
+ type: action.payload.type as 'result',
+ value: action.payload.value
};
const lastOutput: InterpreterOutput = state[workspaceLocation].output.slice(-1)[0];
@@ -356,15 +354,15 @@ const newWorkspaceReducer = createReducer(defaultWorkspaceManager, builder => {
state.playground.context.chapter = chapter;
state.playground.context.variant = variant;
})
- // .addCase(notifyProgramEvaluated, (state, action) => {
- // const workspaceLocation = getWorkspaceLocation(action);
- // const debuggerContext = state[workspaceLocation].debuggerContext;
- // debuggerContext.result = action.payload.result;
- // debuggerContext.lastDebuggerResult = action.payload.lastDebuggerResult;
- // debuggerContext.code = action.payload.code;
- // debuggerContext.context = action.payload.context;
- // debuggerContext.workspaceLocation = action.payload.workspaceLocation;
- // })
+ .addCase(WorkspaceActions.notifyProgramEvaluated, (state, action) => {
+ const workspaceLocation = getWorkspaceLocation(action);
+ const debuggerContext = state[workspaceLocation].debuggerContext;
+ debuggerContext.result = action.payload.result;
+ debuggerContext.lastDebuggerResult = action.payload.lastDebuggerResult;
+ debuggerContext.code = action.payload.code;
+ debuggerContext.context = action.payload.context;
+ debuggerContext.workspaceLocation = action.payload.workspaceLocation;
+ })
.addCase(WorkspaceActions.toggleUsingUpload, (state, action) => {
const { workspaceLocation } = action.payload;
if (workspaceLocation === 'playground' || workspaceLocation === 'sicp') {
@@ -382,33 +380,3 @@ const newWorkspaceReducer = createReducer(defaultWorkspaceManager, builder => {
state[workspaceLocation].lastDebuggerResult = action.payload.lastDebuggerResult;
});
});
-
-/** Temporarily kept to prevent conflicts */
-const oldWorkspaceReducer: Reducer = (
- state = defaultWorkspaceManager,
- action
-) => {
- const workspaceLocation = getWorkspaceLocation(action);
-
- switch (action.type) {
- case WorkspaceActions.notifyProgramEvaluated.type: {
- const debuggerContext = {
- ...state[workspaceLocation].debuggerContext,
- result: action.payload.result,
- lastDebuggerResult: action.payload.lastDebuggerResult,
- code: action.payload.code,
- context: action.payload.context,
- workspaceLocation: action.payload.workspaceLocation
- };
- return {
- ...state,
- [workspaceLocation]: {
- ...state[workspaceLocation],
- debuggerContext
- }
- };
- }
- default:
- return state;
- }
-};
diff --git a/src/commons/workspace/WorkspaceTypes.ts b/src/commons/workspace/WorkspaceTypes.ts
index b81d293b81..ea9f137a9e 100644
--- a/src/commons/workspace/WorkspaceTypes.ts
+++ b/src/commons/workspace/WorkspaceTypes.ts
@@ -1,18 +1,18 @@
import type { Context } from 'js-slang';
-import { AllColsSortStates, GradingColumnVisibility } from '../../features/grading/GradingTypes';
-import { SourcecastWorkspaceState } from '../../features/sourceRecorder/sourcecast/SourcecastTypes';
-import { SourcereelWorkspaceState } from '../../features/sourceRecorder/sourcereel/SourcereelTypes';
-import { InterpreterOutput } from '../application/ApplicationTypes';
+import type {
+ AllColsSortStates,
+ GradingColumnVisibility
+} from '../../features/grading/GradingTypes';
+import type { SourcecastWorkspaceState } from '../../features/sourceRecorder/sourcecast/SourcecastTypes';
+import type { SourcereelWorkspaceState } from '../../features/sourceRecorder/sourcereel/SourcereelTypes';
+import type { InterpreterOutput } from '../application/ApplicationTypes';
import { ExternalLibraryName } from '../application/types/ExternalTypes';
-import { AutogradingResult, Testcase } from '../assessment/AssessmentTypes';
-import { HighlightedLines, Position } from '../editor/EditorTypes';
-import { UploadResult } from '../sideContent/content/SideContentUpload';
+import type { AutogradingResult, Testcase } from '../assessment/AssessmentTypes';
+import type { HighlightedLines, Position } from '../editor/EditorTypes';
+import type { UploadResult } from '../sideContent/content/SideContentUpload';
export const EVAL_SILENT = 'EVAL_SILENT';
-export const UPDATE_LAST_DEBUGGER_RESULT = 'UPDATE_LAST_DEBUGGER_RESULT';
-export const TOGGLE_USING_UPLOAD = 'TOGGLE_USING_UPLOAD';
-export const UPLOAD_FILES = 'UPLOAD_FILES';
export type WorkspaceLocation = keyof WorkspaceManagerState;
export type WorkspaceLocationsWithTools = Extract;
diff --git a/src/commons/workspace/__tests__/WorkspaceReducer.ts b/src/commons/workspace/__tests__/WorkspaceReducer.ts
index 9bfe49b50f..b688749fd4 100644
--- a/src/commons/workspace/__tests__/WorkspaceReducer.ts
+++ b/src/commons/workspace/__tests__/WorkspaceReducer.ts
@@ -8,23 +8,25 @@ import {
} from 'src/commons/collabEditing/CollabEditingActions';
import {
- CodeOutput,
+ type CodeOutput,
createDefaultWorkspace,
defaultWorkspaceManager,
- InterpreterOutput,
- RunningOutput
+ type InterpreterOutput,
+ type ResultOutput,
+ type RunningOutput
} from '../../application/ApplicationTypes';
import { ExternalLibraryName } from '../../application/types/ExternalTypes';
-import { Library, Testcase, TestcaseTypes } from '../../assessment/AssessmentTypes';
-import { HighlightedLines, Position } from '../../editor/EditorTypes';
+import { type Library, type Testcase, TestcaseTypes } from '../../assessment/AssessmentTypes';
+import type { HighlightedLines, Position } from '../../editor/EditorTypes';
import Constants from '../../utils/Constants';
import { createContext } from '../../utils/JsSlangHelper';
import WorkspaceActions from '../WorkspaceActions';
import { WorkspaceReducer } from '../WorkspaceReducer';
-import {
+import type {
EditorTabState,
PlaygroundWorkspaceState,
WorkspaceLocation,
+ WorkspaceLocationsWithTools,
WorkspaceManagerState
} from '../WorkspaceTypes';
@@ -39,8 +41,8 @@ const locations: ReadonlyArray = [
'sicp'
] as const;
-function generateActions(type: string, payload: any = {}): any[] {
- return locations.map(l => ({ type, payload: { ...payload, workspaceLocation: l } }));
+function generateActions(func: (loc: WorkspaceLocation) => T) {
+ return locations.map(func);
}
// cloneDeep not required for proper redux
@@ -91,7 +93,7 @@ describe('BROWSE_REPL_HISTORY_DOWN', () => {
};
const replDownDefaultState: WorkspaceManagerState = generateDefaultWorkspace({ replHistory });
- const actions = generateActions(WorkspaceActions.browseReplHistoryDown.type, { replHistory });
+ const actions = generateActions(WorkspaceActions.browseReplHistoryDown);
actions.forEach(action => {
let result = WorkspaceReducer(replDownDefaultState, action);
@@ -135,7 +137,7 @@ describe('BROWSE_REPL_HISTORY_DOWN', () => {
};
const replDownDefaultState: WorkspaceManagerState = generateDefaultWorkspace({ replHistory });
- const actions = generateActions(WorkspaceActions.browseReplHistoryDown.type, { replHistory });
+ const actions = generateActions(WorkspaceActions.browseReplHistoryDown);
actions.forEach(action => {
const result = WorkspaceReducer(replDownDefaultState, action);
@@ -158,7 +160,7 @@ describe('BROWSE_REPL_HISTORY_UP', () => {
replHistory,
replValue
});
- const actions = generateActions(WorkspaceActions.browseReplHistoryUp.type, { replHistory });
+ const actions = generateActions(WorkspaceActions.browseReplHistoryUp);
actions.forEach(action => {
let result = WorkspaceReducer(replUpDefaultState, action);
@@ -235,7 +237,7 @@ describe('CLEAR_REPL_INPUT', () => {
test('clears replValue', () => {
const replValue = 'test repl value';
const clearReplDefaultState: WorkspaceManagerState = generateDefaultWorkspace({ replValue });
- const actions = generateActions(WorkspaceActions.clearReplInput.type);
+ const actions = generateActions(WorkspaceActions.clearReplInput);
actions.forEach(action => {
const result = WorkspaceReducer(clearReplDefaultState, action);
@@ -255,7 +257,7 @@ describe('CLEAR_REPL_OUTPUT', () => {
test('clears output', () => {
const output: InterpreterOutput[] = [{ type: 'code', value: 'test repl input' }];
const clearReplDefaultState: WorkspaceManagerState = generateDefaultWorkspace({ output });
- const actions = generateActions(WorkspaceActions.clearReplOutput.type);
+ const actions = generateActions(WorkspaceActions.clearReplOutput);
actions.forEach(action => {
const result = WorkspaceReducer(clearReplDefaultState, action);
@@ -286,7 +288,7 @@ describe('CLEAR_REPL_OUTPUT_LAST', () => {
}
];
const clearReplLastPriorState: WorkspaceManagerState = generateDefaultWorkspace({ output });
- const actions = generateActions(WorkspaceActions.clearReplOutputLast.type);
+ const actions = generateActions(WorkspaceActions.clearReplOutputLast);
actions.forEach(action => {
const result = WorkspaceReducer(clearReplLastPriorState, action);
@@ -310,7 +312,7 @@ describe('DEBUG_RESET', () => {
isRunning,
isDebugging
});
- const actions = generateActions(InterpreterActions.debuggerReset.type);
+ const actions = generateActions(InterpreterActions.debuggerReset);
actions.forEach(action => {
const result = WorkspaceReducer(debugResetDefaultState, action);
@@ -333,7 +335,7 @@ describe('DEBUG_RESUME', () => {
const debugResumeDefaultState: WorkspaceManagerState = generateDefaultWorkspace({
isDebugging
});
- const actions = generateActions(InterpreterActions.debuggerResume.type);
+ const actions = generateActions(InterpreterActions.debuggerResume);
actions.forEach(action => {
const result = WorkspaceReducer(debugResumeDefaultState, action);
@@ -371,7 +373,7 @@ describe('END_CLEAR_CONTEXT', () => {
globals: mockGlobals
};
- const actions = generateActions(WorkspaceActions.endClearContext.type, { library });
+ const actions = generateActions(l => WorkspaceActions.endClearContext(library, l));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -404,7 +406,7 @@ describe('END_DEBUG_PAUSE', () => {
test('sets isRunning to false and isDebugging to true', () => {
const isRunning = true;
const debugPauseDefaultState: WorkspaceManagerState = generateDefaultWorkspace({ isRunning });
- const actions = generateActions(InterpreterActions.endDebuggerPause.type);
+ const actions = generateActions(InterpreterActions.endDebuggerPause);
actions.forEach(action => {
const result = WorkspaceReducer(debugPauseDefaultState, action);
@@ -429,7 +431,7 @@ describe('END_INTERRUPT_EXECUTION', () => {
isRunning,
isDebugging
});
- const actions = generateActions(InterpreterActions.endInterruptExecution.type);
+ const actions = generateActions(InterpreterActions.endInterruptExecution);
actions.forEach(action => {
const result = WorkspaceReducer(interruptExecutionDefaultState, action);
@@ -452,7 +454,7 @@ describe('EVAL_EDITOR', () => {
const evalEditorDefaultState: WorkspaceManagerState = generateDefaultWorkspace({
isDebugging
});
- const actions = generateActions(WorkspaceActions.evalEditor.type);
+ const actions = generateActions(WorkspaceActions.evalEditor);
actions.forEach(action => {
const result = WorkspaceReducer(evalEditorDefaultState, action);
@@ -490,7 +492,7 @@ describe('EVAL_INTERPRETER_ERROR', () => {
isRunning,
isDebugging
});
- const actions = generateActions(InterpreterActions.evalInterpreterError.type);
+ const actions = generateActions(l => InterpreterActions.evalInterpreterError([], l));
actions.forEach(action => {
const result = WorkspaceReducer(evalEditorDefaultState, action);
@@ -501,7 +503,10 @@ describe('EVAL_INTERPRETER_ERROR', () => {
...evalEditorDefaultState[location],
isRunning: false,
isDebugging: false,
- output: [{ ...outputWithRunningOutput[0] }, { consoleLogs: ['console-log-test-2'] }]
+ output: [
+ { ...outputWithRunningOutput[0] },
+ { type: 'errors', errors: [], consoleLogs: ['console-log-test-2'] }
+ ]
}
});
});
@@ -516,7 +521,7 @@ describe('EVAL_INTERPRETER_ERROR', () => {
isDebugging
});
- const actions = generateActions(InterpreterActions.evalInterpreterError.type);
+ const actions = generateActions(l => InterpreterActions.evalInterpreterError([], l));
actions.forEach(action => {
const result = WorkspaceReducer(evalEditorDefaultState, action);
@@ -530,7 +535,7 @@ describe('EVAL_INTERPRETER_ERROR', () => {
output: [
{ ...outputWithRunningAndCodeOutput[0] },
{ ...outputWithRunningAndCodeOutput[1] },
- { consoleLogs: [] }
+ { type: 'errors', errors: [], consoleLogs: [] }
]
}
});
@@ -550,7 +555,7 @@ describe('EVAL_INTERPRETER_SUCCESS', () => {
editorTabs: [{ highlightedLines, breakpoints }]
});
- const actions = generateActions(InterpreterActions.evalInterpreterSuccess.type);
+ const actions = generateActions(l => InterpreterActions.evalInterpreterSuccess(undefined, l));
actions.forEach(action => {
const result = WorkspaceReducer(evalEditorDefaultState, action);
@@ -562,7 +567,7 @@ describe('EVAL_INTERPRETER_SUCCESS', () => {
isRunning: false,
output: [
{ ...outputWithRunningOutput[0] },
- { consoleLogs: ['console-log-test-2'], value: 'undefined' }
+ { type: 'result', consoleLogs: ['console-log-test-2'], value: undefined }
]
}
});
@@ -580,7 +585,13 @@ describe('EVAL_INTERPRETER_SUCCESS', () => {
editorTabs: [{ highlightedLines, breakpoints }]
});
- const actions = generateActions(InterpreterActions.evalInterpreterSuccess.type);
+ const expectedOutput: ResultOutput = {
+ type: 'result',
+ consoleLogs: [],
+ value: undefined
+ };
+
+ const actions = generateActions(l => InterpreterActions.evalInterpreterSuccess(undefined, l));
actions.forEach(action => {
const result = WorkspaceReducer(evalEditorDefaultState, action);
@@ -593,7 +604,7 @@ describe('EVAL_INTERPRETER_SUCCESS', () => {
output: [
{ ...outputWithRunningAndCodeOutput[0] },
{ ...outputWithRunningAndCodeOutput[1] },
- { consoleLogs: [], value: 'undefined' }
+ expectedOutput
]
}
});
@@ -603,7 +614,7 @@ describe('EVAL_INTERPRETER_SUCCESS', () => {
describe('EVAL_REPL', () => {
test('sets isRunning to true', () => {
- const actions = generateActions(WorkspaceActions.evalRepl.type);
+ const actions = generateActions(WorkspaceActions.evalRepl);
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -651,10 +662,7 @@ describe('EVAL_TESTCASE_FAILURE', () => {
const evalFailureDefaultState: WorkspaceManagerState = generateDefaultWorkspace({
editorTestcases
});
- const actions = generateActions(InterpreterActions.evalTestcaseFailure.type, {
- value,
- index: 1
- });
+ const actions = generateActions(l => InterpreterActions.evalTestcaseFailure(value, l, 1));
actions.forEach(action => {
const result = WorkspaceReducer(evalFailureDefaultState, action);
@@ -683,10 +691,7 @@ describe('EVAL_TESTCASE_SUCCESS', () => {
editorTestcases
});
- const actions = generateActions(InterpreterActions.evalTestcaseSuccess.type, {
- value,
- index: 1
- });
+ const actions = generateActions(l => InterpreterActions.evalTestcaseSuccess(value, l, 1));
actions.forEach(action => {
const result = WorkspaceReducer(testcaseSuccessDefaultState, action);
@@ -715,10 +720,7 @@ describe('EVAL_TESTCASE_SUCCESS', () => {
editorTestcases
});
- const actions = generateActions(InterpreterActions.evalTestcaseSuccess.type, {
- value,
- index: 0
- });
+ const actions = generateActions(l => InterpreterActions.evalTestcaseSuccess(value, l, 0));
actions.forEach(action => {
const result = WorkspaceReducer(testcaseSuccessDefaultState, action);
@@ -743,9 +745,8 @@ describe('HANDLE_CONSOLE_LOG', () => {
test('works correctly with RunningOutput', () => {
const logString = 'test-log-string';
const consoleLogDefaultState = generateDefaultWorkspace({ output: outputWithRunningOutput });
- const actions = generateActions(InterpreterActions.handleConsoleLog.type, {
- logString: [logString]
- });
+ const actions = generateActions(l => InterpreterActions.handleConsoleLog(l, logString));
+
actions.forEach(action => {
const result = WorkspaceReducer(cloneDeep(consoleLogDefaultState), action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -771,9 +772,7 @@ describe('HANDLE_CONSOLE_LOG', () => {
const consoleLogDefaultState = generateDefaultWorkspace({
output: outputWithRunningAndCodeOutput
});
- const actions = generateActions(InterpreterActions.handleConsoleLog.type, {
- logString: [logString]
- });
+ const actions = generateActions(l => InterpreterActions.handleConsoleLog(l, logString));
actions.forEach(action => {
const result = WorkspaceReducer(consoleLogDefaultState, action);
@@ -795,9 +794,7 @@ describe('HANDLE_CONSOLE_LOG', () => {
const logString = 'test-log-string-3';
const consoleLogDefaultState = generateDefaultWorkspace({ output: [] });
- const actions = generateActions(InterpreterActions.handleConsoleLog.type, {
- logString: [logString]
- });
+ const actions = generateActions(l => InterpreterActions.handleConsoleLog(l, logString));
actions.forEach(action => {
const result = WorkspaceReducer(consoleLogDefaultState, action);
@@ -864,10 +861,7 @@ describe('RESET_TESTCASE', () => {
editorTestcases
});
- const actions = generateActions(WorkspaceActions.resetTestcase.type, {
- index: 1
- });
-
+ const actions = generateActions(l => WorkspaceActions.resetTestcase(l, 1));
actions.forEach(action => {
const result = WorkspaceReducer(resetTestcaseDefaultState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -899,7 +893,9 @@ describe('RESET_WORKSPACE', () => {
replValue: 'test repl value'
};
- const actions = generateActions(WorkspaceActions.resetWorkspace.type, { workspaceOptions });
+ const actions = generateActions(l =>
+ WorkspaceActions.resetWorkspace(l, workspaceOptions as any)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(resetWorkspaceDefaultState, action);
@@ -934,10 +930,7 @@ describe('SEND_REPL_INPUT_TO_OUTPUT', () => {
});
const newOutput = 'new-output-test';
- const actions = generateActions(WorkspaceActions.sendReplInputToOutput.type, {
- type: 'code',
- value: newOutput
- });
+ const actions = generateActions(l => WorkspaceActions.sendReplInputToOutput(newOutput, l));
const newArray = [newOutput].concat(replHistory.records);
newArray.pop();
@@ -975,10 +968,7 @@ describe('SEND_REPL_INPUT_TO_OUTPUT', () => {
});
const newOutput = '';
- const actions = generateActions(WorkspaceActions.sendReplInputToOutput.type, {
- type: 'code',
- value: newOutput
- });
+ const actions = generateActions(l => WorkspaceActions.sendReplInputToOutput(newOutput, l));
actions.forEach(action => {
const result = WorkspaceReducer(inputToOutputDefaultState, action);
@@ -998,7 +988,7 @@ describe('SEND_REPL_INPUT_TO_OUTPUT', () => {
describe('SET_EDITOR_SESSION_ID', () => {
test('sets editorSessionId correctly', () => {
const editorSessionId = 'test_editor_session_id';
- const actions = generateActions(setEditorSessionId.type, { editorSessionId });
+ const actions = generateActions(l => setEditorSessionId(l, editorSessionId));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -1017,7 +1007,7 @@ describe('SET_EDITOR_SESSION_ID', () => {
describe('SET_SHAREDB_CONNECTED', () => {
test('sets sharedbConnected correctly', () => {
const connected = true;
- const actions = generateActions(setSharedbConnected.type, { connected });
+ const actions = generateActions(l => setSharedbConnected(l, connected));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -1035,7 +1025,7 @@ describe('SET_SHAREDB_CONNECTED', () => {
describe('TOGGLE_EDITOR_AUTORUN', () => {
test('toggles isEditorAutorun correctly', () => {
- const actions = generateActions(WorkspaceActions.toggleEditorAutorun.type);
+ const actions = generateActions(WorkspaceActions.toggleEditorAutorun);
actions.forEach(action => {
let result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -1105,7 +1095,7 @@ describe('UPDATE_CURRENT_SUBMISSION_ID', () => {
describe('SET_FOLDER_MODE', () => {
test('sets isFolderModeEnabled correctly', () => {
const isFolderModeEnabled = true;
- const actions = generateActions(WorkspaceActions.setFolderMode.type, { isFolderModeEnabled });
+ const actions = generateActions(l => WorkspaceActions.setFolderMode(l, isFolderModeEnabled));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -1142,9 +1132,9 @@ describe('UPDATE_ACTIVE_EDITOR_TAB_INDEX', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateActiveEditorTabIndex.type, {
- activeEditorTabIndex
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateActiveEditorTabIndex(l, activeEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1159,9 +1149,9 @@ describe('UPDATE_ACTIVE_EDITOR_TAB_INDEX', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateActiveEditorTabIndex.type, {
- activeEditorTabIndex
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateActiveEditorTabIndex(l, activeEditorTabIndex)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1188,9 +1178,9 @@ describe('UPDATE_ACTIVE_EDITOR_TAB_INDEX', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateActiveEditorTabIndex.type, {
- activeEditorTabIndex
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateActiveEditorTabIndex(l, activeEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1205,6 +1195,14 @@ describe('UPDATE_ACTIVE_EDITOR_TAB', () => {
};
test('overrides the active editor tab correctly', () => {
+ // function testAction(
+ // func: (l: WorkspaceLocation) => T,
+ // payload?: any
+ // ) {
+ // const defaultWorkspaceState: WorkspaceManagerState = generateDefaultWorkspace(payload)
+ // const actions = generateActions(func)
+ // }
+
const defaultWorkspaceState: WorkspaceManagerState = generateDefaultWorkspace({
activeEditorTabIndex: 1,
editorTabs: [
@@ -1221,9 +1219,9 @@ describe('UPDATE_ACTIVE_EDITOR_TAB', () => {
]
});
- const actions = generateActions(WorkspaceActions.updateActiveEditorTab.type, {
- activeEditorTabOptions
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateActiveEditorTab(l, activeEditorTabOptions)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1263,9 +1261,9 @@ describe('UPDATE_ACTIVE_EDITOR_TAB', () => {
editorTabs: []
});
- const actions = generateActions(WorkspaceActions.updateActiveEditorTab.type, {
- activeEditorTabOptions
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateActiveEditorTab(l, activeEditorTabOptions)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1295,10 +1293,9 @@ describe('UPDATE_EDITOR_VALUE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateEditorValue.type, {
- editorTabIndex,
- newEditorValue
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateEditorValue(l, editorTabIndex, newEditorValue)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1313,10 +1310,9 @@ describe('UPDATE_EDITOR_VALUE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateEditorValue.type, {
- editorTabIndex,
- newEditorValue
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateEditorValue(l, editorTabIndex, newEditorValue)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1331,10 +1327,9 @@ describe('UPDATE_EDITOR_VALUE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.updateEditorValue.type, {
- editorTabIndex,
- newEditorValue
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.updateEditorValue(l, editorTabIndex, newEditorValue)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1362,7 +1357,7 @@ describe('UPDATE_EDITOR_BREAKPOINTS', () => {
breakpoints: []
};
const editorTabs: EditorTabState[] = [zerothEditorTab, firstEditorTab];
- const newBreakpoints = [null, null, 'ace_breakpoint', null, 'ace_breakpoint'];
+ const newBreakpoints = [null, null, 'ace_breakpoint', null, 'ace_breakpoint'] as string[];
test('throws an error if the editor tab index is negative', () => {
const editorTabIndex = -1;
@@ -1371,10 +1366,9 @@ describe('UPDATE_EDITOR_BREAKPOINTS', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorBreakpoint.type, {
- editorTabIndex,
- newBreakpoints
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorBreakpoint(l, editorTabIndex, newBreakpoints)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1389,10 +1383,9 @@ describe('UPDATE_EDITOR_BREAKPOINTS', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorBreakpoint.type, {
- editorTabIndex,
- newBreakpoints
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorBreakpoint(l, editorTabIndex, newBreakpoints)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1407,10 +1400,9 @@ describe('UPDATE_EDITOR_BREAKPOINTS', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorBreakpoint.type, {
- editorTabIndex,
- newBreakpoints
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorBreakpoint(l, editorTabIndex, newBreakpoints)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1450,10 +1442,9 @@ describe('UPDATE_EDITOR_HIGHLIGHTED_LINES', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorHighlightedLines.type, {
- editorTabIndex,
- newHighlightedLines
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorHighlightedLines(l, editorTabIndex, newHighlightedLines)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1468,10 +1459,9 @@ describe('UPDATE_EDITOR_HIGHLIGHTED_LINES', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorHighlightedLines.type, {
- editorTabIndex,
- newHighlightedLines
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorHighlightedLines(l, editorTabIndex, newHighlightedLines)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1486,10 +1476,9 @@ describe('UPDATE_EDITOR_HIGHLIGHTED_LINES', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.setEditorHighlightedLines.type, {
- editorTabIndex,
- newHighlightedLines
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.setEditorHighlightedLines(l, editorTabIndex, newHighlightedLines)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1532,10 +1521,9 @@ describe('MOVE_CURSOR', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.moveCursor.type, {
- editorTabIndex,
- newCursorPosition
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.moveCursor(l, editorTabIndex, newCursorPosition)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
@@ -1550,11 +1538,9 @@ describe('MOVE_CURSOR', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.moveCursor.type, {
- editorTabIndex,
- newCursorPosition
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.moveCursor(l, editorTabIndex, newCursorPosition)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('Editor tab index must have a corresponding editor tab!');
@@ -1568,11 +1554,9 @@ describe('MOVE_CURSOR', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.moveCursor.type, {
- editorTabIndex,
- newCursorPosition
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.moveCursor(l, editorTabIndex, newCursorPosition)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1610,7 +1594,7 @@ describe('ADD_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.addEditorTab.type, { filePath, editorValue });
+ const actions = generateActions(l => WorkspaceActions.addEditorTab(l, filePath, editorValue));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1642,7 +1626,7 @@ describe('ADD_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.addEditorTab.type, { filePath, editorValue });
+ const actions = generateActions(l => WorkspaceActions.addEditorTab(l, filePath, editorValue));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1698,11 +1682,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('Previous editor tab index must be non-negative!');
@@ -1717,11 +1699,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow(
@@ -1738,11 +1718,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('New editor tab index must be non-negative!');
@@ -1757,11 +1735,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('New editor tab index must have a corresponding editor tab!');
@@ -1776,11 +1752,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1806,10 +1780,9 @@ describe('SHIFT_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.shiftEditorTab.type, {
- previousEditorTabIndex,
- newEditorTabIndex
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.shiftEditorTab(l, previousEditorTabIndex, newEditorTabIndex)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -1850,8 +1823,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('Editor tab index must be non-negative!');
@@ -1865,8 +1837,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const resultThunk = () => WorkspaceReducer(defaultWorkspaceState, action);
expect(resultThunk).toThrow('Editor tab index must have a corresponding editor tab!');
@@ -1880,8 +1851,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs: [zerothEditorTab]
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1907,8 +1877,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1934,8 +1903,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1961,8 +1929,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -1988,8 +1955,7 @@ describe('REMOVE_EDITOR_TAB', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTab.type, { editorTabIndex });
-
+ const actions = generateActions(l => WorkspaceActions.removeEditorTab(l, editorTabIndex));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2031,9 +1997,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
@@ -2050,10 +2016,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs: [zerothEditorTab]
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2079,10 +2044,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2108,10 +2072,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2137,10 +2100,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2166,10 +2128,9 @@ describe('REMOVE_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabForFile.type, {
- removedFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabForFile(l, removedFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2228,10 +2189,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
// Note: we stringify because context contains functions which cause
@@ -2247,10 +2207,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2276,10 +2235,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2305,10 +2263,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2334,10 +2291,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2363,10 +2319,9 @@ describe('REMOVE_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.removeEditorTabsForDirectory.type, {
- removedDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.removeEditorTabsForDirectory(l, removedDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2408,11 +2363,9 @@ describe('RENAME_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.renameEditorTabForFile.type, {
- oldFilePath,
- newFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.renameEditorTabForFile(l, oldFilePath, newFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
// Note: we stringify because context contains functions which cause
@@ -2429,11 +2382,9 @@ describe('RENAME_EDITOR_TAB_FOR_FILE', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.renameEditorTabForFile.type, {
- oldFilePath,
- newFilePath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.renameEditorTabForFile(l, oldFilePath, newFilePath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2479,11 +2430,9 @@ describe('RENAME_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.renameEditorTabsForDirectory.type, {
- oldDirectoryPath,
- newDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.renameEditorTabsForDirectory(l, oldDirectoryPath, newDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
// Note: we stringify because context contains functions which cause
@@ -2499,11 +2448,9 @@ describe('RENAME_EDITOR_TABS_FOR_DIRECTORY', () => {
editorTabs
});
- const actions = generateActions(WorkspaceActions.renameEditorTabsForDirectory.type, {
- oldDirectoryPath,
- newDirectoryPath
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.renameEditorTabsForDirectory(l, oldDirectoryPath, newDirectoryPath)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceState, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2532,10 +2479,9 @@ describe('RENAME_EDITOR_TABS_FOR_DIRECTORY', () => {
describe('UPDATE_HAS_UNSAVED_CHANGES', () => {
test('sets hasUnsavedChanges correctly', () => {
const hasUnsavedChanges = true;
- const actions = generateActions(WorkspaceActions.updateHasUnsavedChanges.type, {
- hasUnsavedChanges
- });
-
+ const actions = generateActions(l =>
+ WorkspaceActions.updateHasUnsavedChanges(l, hasUnsavedChanges)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
const location: WorkspaceLocation = action.payload.workspaceLocation;
@@ -2553,7 +2499,7 @@ describe('UPDATE_HAS_UNSAVED_CHANGES', () => {
describe('UPDATE_REPL_VALUE', () => {
test('sets replValue correctly', () => {
const newReplValue = 'test new repl value';
- const actions = generateActions(WorkspaceActions.updateReplValue.type, { newReplValue });
+ const actions = generateActions(l => WorkspaceActions.updateReplValue(newReplValue, l));
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
@@ -2572,7 +2518,9 @@ describe('UPDATE_REPL_VALUE', () => {
describe('TOGGLE_USING_SUBST', () => {
test('sets usingSubst correctly', () => {
const usingSubst = true;
- const actions = generateActions(WorkspaceActions.toggleUsingSubst.type, { usingSubst });
+ const actions = generateActions(l =>
+ WorkspaceActions.toggleUsingSubst(usingSubst, l as WorkspaceLocationsWithTools)
+ );
actions.forEach(action => {
const result = WorkspaceReducer(defaultWorkspaceManager, action);
diff --git a/src/features/achievement/AchievementActions.ts b/src/features/achievement/AchievementActions.ts
index 9509b9481d..cf16f35686 100644
--- a/src/features/achievement/AchievementActions.ts
+++ b/src/features/achievement/AchievementActions.ts
@@ -1,13 +1,14 @@
-import { AssessmentOverview } from 'src/commons/assessment/AssessmentTypes';
+import type { AssessmentOverview } from 'src/commons/assessment/AssessmentTypes';
import { createActions } from 'src/commons/redux/utils';
+import type { SideContentLocation } from 'src/commons/sideContent/SideContentTypes';
import {
- AchievementGoal,
- AchievementItem,
- AchievementUser,
+ type AchievementGoal,
+ type AchievementItem,
+ type AchievementUser,
EventType,
- GoalDefinition,
- GoalProgress
+ type GoalDefinition,
+ type GoalProgress
} from './AchievementTypes';
const AchievementActions = createActions('achievement', {
@@ -21,7 +22,10 @@ const AchievementActions = createActions('achievement', {
removeAchievement: (uuid: string) => uuid,
removeGoal: (uuid: string) => uuid,
updateOwnGoalProgress: (progress: GoalProgress) => progress,
- addEvent: (eventNames: EventType[]) => eventNames,
+ addEvent: (eventNames: EventType[], workspaceLocation?: SideContentLocation) => ({
+ workspaceLocation,
+ eventNames
+ }),
handleEvent: (loggedEvents: EventType[][]) => loggedEvents,
updateGoalProgress: (studentCourseRegId: number, progress: GoalProgress) => ({
studentCourseRegId,
diff --git a/src/features/github/GitHubActions.ts b/src/features/github/GitHubActions.ts
index 397a1030bd..33638a2aee 100644
--- a/src/features/github/GitHubActions.ts
+++ b/src/features/github/GitHubActions.ts
@@ -1,12 +1,9 @@
import { createActions } from 'src/commons/redux/utils';
const newActions = createActions('github', {
- githubOpenFile: () => ({}),
- githubSaveFile: () => ({}),
- githubSaveFileAs: () => ({})
+ githubOpenFile: 0,
+ githubSaveFile: 0,
+ githubSaveFileAs: 0
});
-// For compatibility with existing code (actions helper)
-export default {
- ...newActions
-};
+export default newActions;
diff --git a/src/features/persistence/PersistenceActions.ts b/src/features/persistence/PersistenceActions.ts
index 1670422c7a..0869f0eb9d 100644
--- a/src/features/persistence/PersistenceActions.ts
+++ b/src/features/persistence/PersistenceActions.ts
@@ -1,21 +1,12 @@
-import { createAction } from '@reduxjs/toolkit';
+import { createActions } from 'src/commons/redux/utils';
-import {
- PERSISTENCE_INITIALISE,
- PERSISTENCE_OPEN_PICKER,
- PERSISTENCE_SAVE_FILE,
- PERSISTENCE_SAVE_FILE_AS,
- PersistenceFile
-} from './PersistenceTypes';
+import type { PersistenceFile } from './PersistenceTypes';
-export const persistenceOpenPicker = createAction(PERSISTENCE_OPEN_PICKER, () => ({ payload: {} }));
+const PersistenceActions = createActions('persistence', {
+ persistenceOpenPicker: true,
+ persistenceSaveFile: (file: PersistenceFile) => file,
+ persistenceSaveFileAs: true,
+ persistenceInitialise: true
+});
-export const persistenceSaveFile = createAction(PERSISTENCE_SAVE_FILE, (file: PersistenceFile) => ({
- payload: file
-}));
-
-export const persistenceSaveFileAs = createAction(PERSISTENCE_SAVE_FILE_AS, () => ({
- payload: {}
-}));
-
-export const persistenceInitialise = createAction(PERSISTENCE_INITIALISE, () => ({ payload: {} }));
+export default PersistenceActions;
diff --git a/src/features/persistence/PersistenceTypes.ts b/src/features/persistence/PersistenceTypes.ts
index 08f915c4cf..8919c50d2a 100644
--- a/src/features/persistence/PersistenceTypes.ts
+++ b/src/features/persistence/PersistenceTypes.ts
@@ -1,8 +1,3 @@
-export const PERSISTENCE_OPEN_PICKER = 'PERSISTENCE_OPEN_PICKER';
-export const PERSISTENCE_SAVE_FILE_AS = 'PERSISTENCE_SAVE_FILE_AS';
-export const PERSISTENCE_SAVE_FILE = 'PERSISTENCE_SAVE_FILE';
-export const PERSISTENCE_INITIALISE = 'PERSISTENCE_INITIALISE';
-
export type PersistenceState = 'INACTIVE' | 'SAVED' | 'DIRTY';
export type PersistenceFile = {
diff --git a/src/features/stories/StoriesReducer.ts b/src/features/stories/StoriesReducer.ts
index 56a4cf09a8..3e99dcd5cd 100644
--- a/src/features/stories/StoriesReducer.ts
+++ b/src/features/stories/StoriesReducer.ts
@@ -1,18 +1,17 @@
-import { createReducer, Reducer } from '@reduxjs/toolkit';
-import { stringify } from 'js-slang/dist/utils/stringify';
+import { createReducer, type Reducer } from '@reduxjs/toolkit';
import { logOut } from 'src/commons/application/actions/CommonsActions';
import {
createDefaultStoriesEnv,
defaultStories,
- ErrorOutput,
- InterpreterOutput,
- ResultOutput
+ type ErrorOutput,
+ type InterpreterOutput,
+ type ResultOutput
} from '../../commons/application/ApplicationTypes';
-import { SourceActionType } from '../../commons/utils/ActionsHelper';
+import type { SourceActionType } from '../../commons/utils/ActionsHelper';
import StoriesActions from './StoriesActions';
import { DEFAULT_ENV } from './storiesComponents/UserBlogContent';
-import { StoriesState } from './StoriesTypes';
+import type { StoriesState } from './StoriesTypes';
export const StoriesReducer: Reducer = (
state = defaultStories,
@@ -73,10 +72,9 @@ const newStoriesReducer = createReducer(defaultStories, builder => {
})
.addCase(StoriesActions.evalStorySuccess, (state, action) => {
const env = getStoriesEnv(action);
- const execType = state.envs[env].context.executionMethod;
const newOutputEntry: Partial = {
type: action.payload.type as 'result' | undefined,
- value: execType === 'interpreter' ? action.payload.value : stringify(action.payload.value)
+ value: action.payload.value
};
const lastOutput: InterpreterOutput = state.envs[env].output.slice(-1)[0];
diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx
index 58aeabeebd..02bd08ee99 100644
--- a/src/pages/playground/Playground.tsx
+++ b/src/pages/playground/Playground.tsx
@@ -1,9 +1,9 @@
import { Classes } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
-import { HotkeyItem, useHotkeys } from '@mantine/hooks';
-import { AnyAction, Dispatch } from '@reduxjs/toolkit';
+import { type HotkeyItem, useHotkeys } from '@mantine/hooks';
+import type { AnyAction, Dispatch } from '@reduxjs/toolkit';
import { Ace, Range } from 'ace-builds';
-import { FSModule } from 'browserfs/dist/node/core/FS';
+import type { FSModule } from 'browserfs/dist/node/core/FS';
import classNames from 'classnames';
import { Chapter, Variant } from 'js-slang/dist/types';
import { isEqual } from 'lodash';
@@ -31,15 +31,10 @@ import {
showFulTSWarningOnUrlLoad,
showHTMLDisclaimer
} from 'src/commons/utils/WarningDialogHelper';
-import WorkspaceActions, { uploadFiles } from 'src/commons/workspace/WorkspaceActions';
-import { WorkspaceLocation } from 'src/commons/workspace/WorkspaceTypes';
+import WorkspaceActions from 'src/commons/workspace/WorkspaceActions';
+import type { WorkspaceLocation } from 'src/commons/workspace/WorkspaceTypes';
import GithubActions from 'src/features/github/GitHubActions';
-import {
- persistenceInitialise,
- persistenceOpenPicker,
- persistenceSaveFile,
- persistenceSaveFileAs
-} from 'src/features/persistence/PersistenceActions';
+import PersistenceActions from 'src/features/persistence/PersistenceActions';
import {
generateLzString,
playgroundConfigLanguage,
@@ -52,9 +47,9 @@ import {
getLanguageConfig,
isCseVariant,
isSourceLanguage,
- OverallState,
- ResultOutput,
- SALanguage
+ type OverallState,
+ type ResultOutput,
+ type SALanguage
} from '../../commons/application/ApplicationTypes';
import { ExternalLibraryName } from '../../commons/application/types/ExternalTypes';
import { ControlBarAutorunButtons } from '../../commons/controlBar/ControlBarAutorunButtons';
@@ -69,23 +64,23 @@ import { ControlBarToggleFolderModeButton } from '../../commons/controlBar/Contr
import { ControlBarGitHubButtons } from '../../commons/controlBar/github/ControlBarGitHubButtons';
import {
convertEditorTabStateToProps,
- NormalEditorContainerProps
+ type NormalEditorContainerProps
} from '../../commons/editor/EditorContainer';
-import { Position } from '../../commons/editor/EditorTypes';
+import type { Position } from '../../commons/editor/EditorTypes';
import { overwriteFilesInWorkspace } from '../../commons/fileSystem/utils';
import FileSystemView from '../../commons/fileSystemView/FileSystemView';
import MobileWorkspace, {
- MobileWorkspaceProps
+ type MobileWorkspaceProps
} from '../../commons/mobileWorkspace/MobileWorkspace';
import { SideBarTab } from '../../commons/sideBar/SideBar';
-import { SideContentTab, SideContentType } from '../../commons/sideContent/SideContentTypes';
+import { type SideContentTab, SideContentType } from '../../commons/sideContent/SideContentTypes';
import Constants, { Links } from '../../commons/utils/Constants';
import { generateLanguageIntroduction } from '../../commons/utils/IntroductionHelper';
import { convertParamToBoolean, convertParamToInt } from '../../commons/utils/ParamParseHelper';
-import { IParsedQuery, parseQuery } from '../../commons/utils/QueryHelper';
+import { type IParsedQuery, parseQuery } from '../../commons/utils/QueryHelper';
import Workspace, { WorkspaceProps } from '../../commons/workspace/Workspace';
import { initSession, log } from '../../features/eventLogging';
-import {
+import type {
CodeDelta,
Input,
SelectionRange
@@ -561,13 +556,15 @@ const Playground: React.FC = props => {
loggedInAs={persistenceUser}
isDirty={persistenceIsDirty}
key="googledrive"
- onClickSaveAs={() => dispatch(persistenceSaveFileAs())}
- onClickOpen={() => dispatch(persistenceOpenPicker())}
+ onClickSaveAs={() => dispatch(PersistenceActions.persistenceSaveFileAs())}
+ onClickOpen={() => dispatch(PersistenceActions.persistenceOpenPicker())}
onClickSave={
- persistenceFile ? () => dispatch(persistenceSaveFile(persistenceFile)) : undefined
+ persistenceFile
+ ? () => dispatch(PersistenceActions.persistenceSaveFile(persistenceFile))
+ : undefined
}
onClickLogOut={() => dispatch(SessionActions.logoutGoogle())}
- onPopoverOpening={() => dispatch(persistenceInitialise())}
+ onPopoverOpening={() => dispatch(PersistenceActions.persistenceInitialise())}
/>
);
}, [isFolderModeEnabled, persistenceFile, persistenceUser, persistenceIsDirty, dispatch]);
@@ -733,7 +730,9 @@ const Playground: React.FC = props => {
}
if (currentLang === Chapter.FULL_JAVA && process.env.NODE_ENV === 'development') {
- tabs.push(makeUploadTabFrom(files => dispatch(uploadFiles(files, workspaceLocation))));
+ tabs.push(
+ makeUploadTabFrom(files => dispatch(WorkspaceActions.uploadFiles(files, workspaceLocation)))
+ );
}
if (!usingRemoteExecution) {