Skip to content

Commit 32bb84e

Browse files
committed
Allow copying from readonly workspace and add cut tests
Also cleans up logic a bit
1 parent f1b44db commit 32bb84e

File tree

2 files changed

+131
-14
lines changed

2 files changed

+131
-14
lines changed

core/shortcut_items.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export function registerDelete() {
9494
}
9595

9696
let copyData: ICopyData | null = null;
97-
let copyWorkspace: WorkspaceSvg | null = null;
9897
let copyCoords: Coordinate | null = null;
9998

10099
/**
@@ -156,7 +155,6 @@ export function registerCopy() {
156155
return (
157156
!!focused &&
158157
!!targetWorkspace &&
159-
!targetWorkspace.isReadOnly() &&
160158
!targetWorkspace.isDragging() &&
161159
!getFocusManager().ephemeralFocusTaken() &&
162160
isCopyable(focused)
@@ -179,7 +177,6 @@ export function registerCopy() {
179177
targetWorkspace.hideChaff();
180178
}
181179
copyData = focused.toCopyData();
182-
copyWorkspace = targetWorkspace;
183180
copyCoords =
184181
isDraggable(focused) && focused.workspace == targetWorkspace
185182
? focused.getRelativeToSurfaceXY()
@@ -220,7 +217,6 @@ export function registerCut() {
220217
return false;
221218
}
222219
copyData = focused.toCopyData();
223-
copyWorkspace = workspace;
224220
copyCoords = isDraggable(focused)
225221
? focused.getRelativeToSurfaceXY()
226222
: null;
@@ -264,7 +260,11 @@ export function registerPaste() {
264260
);
265261
},
266262
callback(workspace: WorkspaceSvg, e: Event) {
267-
if (!copyData || !copyWorkspace) return false;
263+
if (!copyData) return false;
264+
const targetWorkspace = workspace.isFlyout
265+
? workspace.targetWorkspace
266+
: workspace;
267+
if (!targetWorkspace || targetWorkspace.isReadOnly()) return false;
268268

269269
if (e instanceof PointerEvent) {
270270
// The event that triggers a shortcut would conventionally be a KeyboardEvent.
@@ -273,32 +273,32 @@ export function registerPaste() {
273273
// at the mouse coordinates where the menu was opened, and this PointerEvent
274274
// is where the menu was opened.
275275
const mouseCoords = svgMath.screenToWsCoordinates(
276-
copyWorkspace,
276+
targetWorkspace,
277277
new Coordinate(e.clientX, e.clientY),
278278
);
279-
return !!clipboard.paste(copyData, copyWorkspace, mouseCoords);
279+
return !!clipboard.paste(copyData, targetWorkspace, mouseCoords);
280280
}
281281

282282
if (!copyCoords) {
283283
// If we don't have location data about the original copyable, let the
284284
// paster determine position.
285-
return !!clipboard.paste(copyData, copyWorkspace);
285+
return !!clipboard.paste(copyData, targetWorkspace);
286286
}
287287

288-
const {left, top, width, height} = copyWorkspace
288+
const {left, top, width, height} = targetWorkspace
289289
.getMetricsManager()
290290
.getViewMetrics(true);
291291
const viewportRect = new Rect(top, top + height, left, left + width);
292292

293293
if (viewportRect.contains(copyCoords.x, copyCoords.y)) {
294294
// If the original copyable is inside the viewport, let the paster
295295
// determine position.
296-
return !!clipboard.paste(copyData, copyWorkspace);
296+
return !!clipboard.paste(copyData, targetWorkspace);
297297
}
298298

299299
// Otherwise, paste in the middle of the viewport.
300300
const centerCoords = new Coordinate(left + width / 2, top + height / 2);
301-
return !!clipboard.paste(copyData, copyWorkspace, centerCoords);
301+
return !!clipboard.paste(copyData, targetWorkspace, centerCoords);
302302
},
303303
keyCodes: [ctrlV, metaV],
304304
};

tests/mocha/shortcut_items_test.js

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,17 @@ suite('Keyboard Shortcut Items', function () {
173173
});
174174
});
175175
});
176-
// Do not copy a block if a workspace is in readonly mode.
177-
suite('Not called when readOnly is true', function () {
176+
// Allow copying a block if a workspace is in readonly mode.
177+
suite('Called when readOnly is true', function () {
178178
testCases.forEach(function (testCase) {
179179
const testCaseName = testCase[0];
180180
const keyEvent = testCase[1];
181-
runReadOnlyTest(keyEvent, testCaseName);
181+
test(testCaseName, function () {
182+
this.workspace.setIsReadOnly(true);
183+
this.injectionDiv.dispatchEvent(keyEvent);
184+
sinon.assert.calledOnce(this.copySpy);
185+
sinon.assert.calledOnce(this.hideChaffSpy);
186+
});
182187
});
183188
});
184189
// Do not copy a block if a drag is in progress.
@@ -238,6 +243,118 @@ suite('Keyboard Shortcut Items', function () {
238243
});
239244
});
240245

246+
suite('Cut', function () {
247+
setup(function () {
248+
this.block = setSelectedBlock(this.workspace);
249+
this.copySpy = sinon.spy(this.block, 'toCopyData');
250+
this.disposeSpy = sinon.spy(this.block, 'dispose');
251+
this.hideChaffSpy = sinon.spy(
252+
Blockly.WorkspaceSvg.prototype,
253+
'hideChaff',
254+
);
255+
});
256+
const testCases = [
257+
[
258+
'Control X',
259+
createKeyDownEvent(Blockly.utils.KeyCodes.X, [
260+
Blockly.utils.KeyCodes.CTRL,
261+
]),
262+
],
263+
[
264+
'Meta X',
265+
createKeyDownEvent(Blockly.utils.KeyCodes.X, [
266+
Blockly.utils.KeyCodes.META,
267+
]),
268+
],
269+
];
270+
// Cut a block.
271+
suite('Simple', function () {
272+
testCases.forEach(function (testCase) {
273+
const testCaseName = testCase[0];
274+
const keyEvent = testCase[1];
275+
test(testCaseName, function () {
276+
this.injectionDiv.dispatchEvent(keyEvent);
277+
sinon.assert.calledOnce(this.copySpy);
278+
sinon.assert.calledOnce(this.disposeSpy);
279+
sinon.assert.calledOnce(this.hideChaffSpy);
280+
});
281+
});
282+
});
283+
// Do not cut a block if a workspace is in readonly mode.
284+
suite('Not called when readOnly is true', function () {
285+
testCases.forEach(function (testCase) {
286+
const testCaseName = testCase[0];
287+
const keyEvent = testCase[1];
288+
test(testCaseName, function () {
289+
this.workspace.setIsReadOnly(true);
290+
this.injectionDiv.dispatchEvent(keyEvent);
291+
sinon.assert.notCalled(this.copySpy);
292+
sinon.assert.notCalled(this.disposeSpy);
293+
sinon.assert.notCalled(this.hideChaffSpy);
294+
});
295+
});
296+
});
297+
// Do not cut a block if a drag is in progress.
298+
suite('Drag in progress', function () {
299+
testCases.forEach(function (testCase) {
300+
const testCaseName = testCase[0];
301+
const keyEvent = testCase[1];
302+
test(testCaseName, function () {
303+
sinon.stub(this.workspace, 'isDragging').returns(true);
304+
this.injectionDiv.dispatchEvent(keyEvent);
305+
sinon.assert.notCalled(this.copySpy);
306+
sinon.assert.notCalled(this.disposeSpy);
307+
sinon.assert.notCalled(this.hideChaffSpy);
308+
});
309+
});
310+
});
311+
// Do not cut a block if is is not deletable.
312+
suite('Block is not deletable', function () {
313+
testCases.forEach(function (testCase) {
314+
const testCaseName = testCase[0];
315+
const keyEvent = testCase[1];
316+
test(testCaseName, function () {
317+
sinon
318+
.stub(Blockly.common.getSelected(), 'isOwnDeletable')
319+
.returns(false);
320+
this.injectionDiv.dispatchEvent(keyEvent);
321+
sinon.assert.notCalled(this.copySpy);
322+
sinon.assert.notCalled(this.disposeSpy);
323+
sinon.assert.notCalled(this.hideChaffSpy);
324+
});
325+
});
326+
});
327+
// Do not cut a block if it is not movable.
328+
suite('Block is not movable', function () {
329+
testCases.forEach(function (testCase) {
330+
const testCaseName = testCase[0];
331+
const keyEvent = testCase[1];
332+
test(testCaseName, function () {
333+
sinon
334+
.stub(Blockly.common.getSelected(), 'isOwnMovable')
335+
.returns(false);
336+
this.injectionDiv.dispatchEvent(keyEvent);
337+
sinon.assert.notCalled(this.copySpy);
338+
sinon.assert.notCalled(this.disposeSpy);
339+
sinon.assert.notCalled(this.hideChaffSpy);
340+
});
341+
});
342+
});
343+
test('Not called when connection is focused', function () {
344+
// Restore the stub behavior called during setup
345+
Blockly.getFocusManager().getFocusedNode.restore();
346+
347+
setSelectedConnection(this.workspace);
348+
const event = createKeyDownEvent(Blockly.utils.KeyCodes.C, [
349+
Blockly.utils.KeyCodes.CTRL,
350+
]);
351+
this.injectionDiv.dispatchEvent(event);
352+
sinon.assert.notCalled(this.copySpy);
353+
sinon.assert.notCalled(this.disposeSpy);
354+
sinon.assert.notCalled(this.hideChaffSpy);
355+
});
356+
});
357+
241358
suite('Undo', function () {
242359
setup(function () {
243360
this.undoSpy = sinon.spy(this.workspace, 'undo');

0 commit comments

Comments
 (0)