Skip to content

Commit 44aae0c

Browse files
authored
Support async command execution (#39)
* Support async command execution Change command API to support MaybePromise<void> instead of just plain voids. This enables async command execution e.g. for communicating with a modelserver Contributed on behalf of STMicroelectronics * Fix command test cases * Address review feedback
1 parent 44bd1e0 commit 44aae0c

File tree

9 files changed

+118
-93
lines changed

9 files changed

+118
-93
lines changed

packages/server/src/common/command/command-stack.spec.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import { expect } from 'chai';
1717
import { Container, ContainerModule } from 'inversify';
1818
import * as sinon from 'sinon';
19-
import { StubCommand, StubLogger } from '../test/mock-util';
19+
import { expectToThrowAsync, StubCommand, StubLogger } from '../test/mock-util';
2020
import { Logger } from '../utils/logger';
2121
import { DefaultCommandStack } from './command-stack';
2222

@@ -41,165 +41,165 @@ describe('test DefaultCommandStack', () => {
4141
});
4242

4343
describe('execute', () => {
44-
it('should execute the given command and become dirty', () => {
44+
it('should execute the given command and become dirty', async () => {
4545
expect(commandStack.isDirty).to.be.false;
46-
commandStack.execute(command1);
46+
await commandStack.execute(command1);
4747
expect(command1.execute.calledOnce).to.be.true;
4848
expect(commandStack.isDirty).to.be.true;
4949
});
5050

51-
it('should execute the given commands in order and become dirty', () => {
51+
it('should execute the given commands in order and become dirty', async () => {
5252
expect(commandStack.isDirty).to.be.false;
53-
commandStack.execute(command1);
54-
commandStack.execute(command2);
53+
await commandStack.execute(command1);
54+
await commandStack.execute(command2);
5555

5656
expect(command1.execute.calledOnce).to.be.true;
5757
expect(command2.execute.calledOnce).to.be.true;
5858
expect(command2.execute.calledAfter(command1.execute)).to.be.true;
5959
expect(commandStack.isDirty).to.be.true;
6060
});
6161

62-
it('should be able to undo after execute', () => {
62+
it('should be able to undo after execute', async () => {
6363
expect(commandStack.canUndo()).to.be.false;
64-
commandStack.execute(command1);
64+
await commandStack.execute(command1);
6565
expect(commandStack.canUndo()).to.be.true;
6666
});
6767

68-
it('should clear the redo stack after execution', () => {
68+
it('should clear the redo stack after execution', async () => {
6969
commandStack['commands'].push(command2);
7070
commandStack['top'] = -1;
7171
expect(commandStack.canRedo()).to.be.true;
7272

73-
commandStack.execute(command1);
73+
await commandStack.execute(command1);
7474
expect(commandStack.canRedo()).to.be.false;
7575
});
7676

77-
it('should flush the stack in case of an execution error', () => {
77+
it('should flush the stack in case of an execution error', async () => {
7878
command2.execute.throwsException();
7979
const flushSpy = sandbox.spy(commandStack, 'flush');
8080

81-
expect(() => commandStack.execute(command2)).to.throw();
81+
await expectToThrowAsync(() => commandStack.execute(command2));
8282
expect(command2.execute.calledOnce).to.be.true;
8383
expect(flushSpy.calledOnce).to.be.true;
8484
});
8585
});
8686

8787
describe('undo', () => {
88-
it('should do nothing if the command stack is empty', () => {
88+
it('should do nothing if the command stack is empty', async () => {
8989
expect(commandStack.isDirty).to.be.false;
9090

91-
commandStack.undo();
91+
await commandStack.undo();
9292
expect(commandStack.canUndo()).to.be.false;
9393
expect(commandStack.canRedo()).to.be.false;
9494
expect(commandStack.isDirty).to.be.false;
9595
});
9696

97-
it('should undo the command and become non-dirty again', () => {
97+
it('should undo the command and become non-dirty again', async () => {
9898
commandStack['commands'].push(command1);
9999
commandStack['top'] = 0;
100100
expect(commandStack.isDirty).to.be.true;
101101
expect(commandStack.canUndo()).to.be.true;
102102
expect(commandStack.canRedo()).to.be.false;
103103

104-
commandStack.undo();
104+
await commandStack.undo();
105105
expect(command1.undo.calledOnce).to.be.true;
106106
expect(commandStack.isDirty).to.be.false;
107107
expect(commandStack.canRedo()).to.be.true;
108108
expect(commandStack.canUndo()).to.be.false;
109109
});
110110

111-
it('should undo multiple command and become non-dirty again', () => {
111+
it('should undo multiple command and become non-dirty again', async () => {
112112
commandStack['commands'].push(command1, command2);
113113
commandStack['top'] = 1;
114114
expect(commandStack.isDirty).to.be.true;
115115
expect(commandStack.canUndo()).to.be.true;
116116
expect(commandStack.canRedo()).to.be.false;
117117

118-
commandStack.undo();
118+
await commandStack.undo();
119119
expect(command2.undo.calledOnce).to.be.true;
120120
expect(commandStack.canRedo()).to.be.true;
121121
expect(commandStack.canUndo()).to.be.true;
122122
expect(commandStack.isDirty).to.be.true;
123123

124-
commandStack.undo();
124+
await commandStack.undo();
125125
expect(command1.undo.calledOnce).to.be.true;
126126
expect(command1.undo.calledAfter(command2.undo)).to.be.true;
127127
expect(commandStack.isDirty).to.be.false;
128128
expect(commandStack.canRedo()).to.be.true;
129129
expect(commandStack.canUndo()).to.be.false;
130130
});
131-
it('should flush the stack in case of an execution error', () => {
131+
it('should flush the stack in case of an execution error', async () => {
132132
command2.undo.throwsException();
133133
const flushSpy = sandbox.spy(commandStack, 'flush');
134134
commandStack['commands'].push(command2);
135135
commandStack['top'] = 0;
136136

137-
expect(() => commandStack.undo()).to.throw();
137+
await expectToThrowAsync(() => commandStack.undo());
138138
expect(command2.undo.calledOnce).to.be.true;
139139
expect(flushSpy.calledOnce).to.be.true;
140140
});
141141
});
142142

143143
describe('redo', () => {
144-
it('should do nothing if the command stack is empty', () => {
144+
it('should do nothing if the command stack is empty', async () => {
145145
expect(commandStack.isDirty).to.be.false;
146146

147-
commandStack.redo();
147+
await commandStack.redo();
148148
expect(commandStack.canUndo()).to.be.false;
149149
expect(commandStack.canRedo()).to.be.false;
150150
expect(commandStack.isDirty).to.be.false;
151151
});
152152

153-
it('should redo the command and become dirty again', () => {
153+
it('should redo the command and become dirty again', async () => {
154154
commandStack['commands'].push(command1);
155155
commandStack['top'] = -1;
156156
expect(commandStack.isDirty).to.be.false;
157157
expect(commandStack.canUndo()).to.be.false;
158158
expect(commandStack.canRedo()).to.be.true;
159159

160-
commandStack.redo();
160+
await commandStack.redo();
161161
expect(command1.redo.calledOnce).to.be.true;
162162
expect(commandStack.isDirty).to.be.true;
163163
expect(commandStack.canRedo()).to.be.false;
164164
expect(commandStack.canUndo()).to.be.true;
165165
});
166166

167-
it('should undo multiple command and become non-dirty again', () => {
167+
it('should undo multiple command and become non-dirty again', async () => {
168168
commandStack['commands'].push(command2, command1);
169169
commandStack['top'] = -1;
170170
commandStack['saveIndex'] = -1;
171171
expect(commandStack.isDirty).to.be.false;
172172
expect(commandStack.canUndo()).to.be.false;
173173
expect(commandStack.canRedo()).to.be.true;
174174

175-
commandStack.redo();
175+
await commandStack.redo();
176176
expect(command2.redo.calledOnce).to.be.true;
177177
expect(commandStack.canRedo()).to.be.true;
178178
expect(commandStack.canUndo()).to.be.true;
179179
expect(commandStack.isDirty).to.be.true;
180180

181-
commandStack.redo();
181+
await commandStack.redo();
182182
expect(command1.redo.calledOnce).to.be.true;
183183
expect(command1.redo.calledAfter(command2.redo)).to.be.true;
184184
expect(commandStack.isDirty).to.be.true;
185185
expect(commandStack.canRedo()).to.be.false;
186186
expect(commandStack.canUndo()).to.be.true;
187187
});
188-
it('should flush the stack in case of an execution error', () => {
188+
it('should flush the stack in case of an execution error', async () => {
189189
command2.redo.throwsException();
190190
const flushSpy = sandbox.spy(commandStack, 'flush');
191191
commandStack['commands'].push(command2);
192192
commandStack['top'] = -1;
193193

194-
expect(() => commandStack.redo()).to.throw();
194+
await expectToThrowAsync(() => commandStack.redo());
195195
expect(command2.redo.calledOnce).to.be.true;
196196
expect(flushSpy.calledOnce).to.be.true;
197197
});
198-
it('should be able to undo after redo', () => {
198+
it('should be able to undo after redo', async () => {
199199
commandStack['commands'].push(command1);
200200
commandStack['top'] = -1;
201201
expect(commandStack.canUndo()).to.be.false;
202-
commandStack.redo();
202+
await commandStack.redo();
203203
expect(commandStack.canUndo()).to.be.true;
204204
});
205205
});

packages/server/src/common/command/command-stack.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515
********************************************************************************/
1616
import { inject, injectable } from 'inversify';
17+
import { MaybePromise } from '..';
1718
import { Logger } from '../utils/logger';
1819
import { Command } from './command';
1920

@@ -31,12 +32,12 @@ export interface CommandStack {
3132
* Clears any redoable commands not yet redone, adds the command to the stack and then invokes {@link Command.execute}.
3233
* @param command the command to execute.
3334
*/
34-
execute(command: Command): void;
35+
execute(command: Command): MaybePromise<void>;
3536

3637
/**
3738
* Removes the topmost (i.e. last executed) command from the stack and invokes {@link Command.undo}.
3839
*/
39-
undo(): void;
40+
undo(): MaybePromise<void>;
4041

4142
/**
4243
* Returns `true` if the top command on the stack can be undone.
@@ -46,7 +47,7 @@ export interface CommandStack {
4647
/**
4748
* Re-adds the last undo command on top of the stack and invokes {@link Command.redo}
4849
*/
49-
redo(): void;
50+
redo(): MaybePromise<void>;
5051

5152
/**
5253
* Returns `true` if there are redoable commands in the stack.
@@ -86,9 +87,9 @@ export class DefaultCommandStack implements CommandStack {
8687
*/
8788
protected saveIndex = -1;
8889

89-
execute(command: Command): void {
90+
async execute(command: Command): Promise<void> {
9091
try {
91-
command.execute();
92+
await command.execute();
9293
} catch (error) {
9394
this.handleError(error);
9495
}
@@ -104,11 +105,11 @@ export class DefaultCommandStack implements CommandStack {
104105
}
105106
}
106107

107-
undo(): void {
108+
async undo(): Promise<void> {
108109
if (this.canUndo()) {
109110
const command = this.commands[this.top--];
110111
try {
111-
command.undo();
112+
await command.undo();
112113
} catch (error) {
113114
this.handleError(error);
114115
}
@@ -121,11 +122,11 @@ export class DefaultCommandStack implements CommandStack {
121122
: false;
122123
}
123124

124-
redo(): void {
125+
async redo(): Promise<void> {
125126
if (this.canRedo()) {
126127
const command = this.commands[++this.top];
127128
try {
128-
command.redo();
129+
await command.redo();
129130
} catch (error) {
130131
this.handleError(error);
131132
}

packages/server/src/common/command/command.spec.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
********************************************************************************/
1616
import { expect } from 'chai';
1717
import * as sinon from 'sinon';
18-
import { StubCommand } from '../test/mock-util';
18+
import { expectToThrowAsync, StubCommand } from '../test/mock-util';
1919
import { CompoundCommand } from './command';
2020

2121
describe('CompoundCommand', () => {
@@ -32,18 +32,18 @@ describe('CompoundCommand', () => {
3232
});
3333

3434
describe('execute', () => {
35-
it('Should execute the subcommands in order', () => {
36-
compoundCommand.execute();
35+
it('Should execute the subcommands in order', async () => {
36+
await compoundCommand.execute();
3737
expect(command1.execute.calledOnce).to.be.true;
3838
expect(command2.execute.calledOnce).to.be.true;
3939
expect(command3.execute.calledOnce).to.be.true;
4040
expect(command1.execute.calledBefore(command2.execute)).to.be.true;
4141
expect(command2.execute.calledBefore(command3.execute)).to.be.true;
4242
});
43-
it('Should undo partially executed subcommands in case of an error', () => {
43+
it('Should undo partially executed subcommands in case of an error', async () => {
4444
command3.execute.throwsException();
4545

46-
expect(() => compoundCommand.execute()).to.throw();
46+
await expectToThrowAsync(() => compoundCommand.execute());
4747

4848
expect(command1.execute.calledOnce).to.be.true;
4949
expect(command2.execute.calledOnce).to.be.true;
@@ -54,19 +54,18 @@ describe('CompoundCommand', () => {
5454
});
5555

5656
describe('undo', () => {
57-
it('Should undo the subcommands in reverse order', () => {
58-
compoundCommand.undo();
57+
it('Should undo the subcommands in reverse order', async () => {
58+
await compoundCommand.undo();
5959
expect(command1.undo.calledOnce).to.be.true;
6060
expect(command2.undo.calledOnce).to.be.true;
6161
expect(command3.undo.calledOnce).to.be.true;
6262
expect(command1.undo.calledAfter(command2.undo)).to.be.true;
6363
expect(command2.undo.calledAfter(command3.undo)).to.be.true;
6464
});
6565

66-
it('Should redo partially undone subcommands in case of an error', () => {
66+
it('Should redo partially undone subcommands in case of an error', async () => {
6767
command1.undo.throwsException();
68-
69-
expect(() => compoundCommand.undo()).to.throw();
68+
await expectToThrowAsync(() => compoundCommand.undo());
7069

7170
expect(command1.undo.calledOnce).to.be.true;
7271
expect(command2.undo.calledOnce).to.be.true;

0 commit comments

Comments
 (0)