@@ -32,10 +32,12 @@ suite('Dragger', function () {
3232 * @returns {{x: number, y: number} } Viewport coordinates at the block origin.
3333 */
3434 function blockOriginClient ( block ) {
35- const screenCoords = Blockly . utils . svgMath . wsToScreenCoordinates (
36- block . workspace ,
37- block . getRelativeToSurfaceXY ( ) ,
38- ) ;
35+ const ws = block . workspace ;
36+ let point = block . getRelativeToSurfaceXY ( ) ;
37+ if ( ws . isMutator ) {
38+ point = point . scale ( ws . options . parentWorkspace . scale ) ;
39+ }
40+ const screenCoords = Blockly . utils . svgMath . wsToScreenCoordinates ( ws , point ) ;
3941 return { x : screenCoords . x , y : screenCoords . y } ;
4042 }
4143
@@ -64,13 +66,29 @@ suite('Dragger', function () {
6466 return block . getSvgRoot ( ) . classList . contains ( 'blocklyDraggingDelete' ) ;
6567 }
6668
69+ /**
70+ * @param {!Blockly.WorkspaceSvg } workspace The workspace with a trashcan.
71+ * @returns {boolean } Whether the trashcan lid open style is applied.
72+ */
73+ function hasTrashLidOpen ( workspace ) {
74+ return workspace . trashcan . svgGroup . classList . contains ( 'blocklyTrashOpen' ) ;
75+ }
76+
77+ /**
78+ * @param {!Blockly.WorkspaceSvg } workspace The workspace to zoom.
79+ * @param {number } scale The target zoom factor.
80+ */
81+ function setWorkspaceScale ( workspace , scale ) {
82+ workspace . setScale ( scale ) ;
83+ }
84+
6785 /**
6886 * Simulates pressing on the block center and dragging to a viewport point.
6987 *
7088 * @param {!Blockly.BlockSvg } block The block to drag.
7189 * @param {{x: number, y: number} } pointerEnd The viewport point to drag to.
72- * @returns {{dragger: !Blockly.dragging.Dragger, dragEvent: !PointerEvent} }
73- * The dragger and final pointer event from the simulated drag .
90+ * @returns {{dragger: !Blockly.dragging.Dragger, dragEvent: !PointerEvent, block: !Blockly.BlockSvg } }
91+ * The dragger, final pointer event, and block being dragged .
7492 */
7593 function dragBlock ( block , pointerEnd ) {
7694 const start = blockCenterClient ( block ) ;
@@ -86,7 +104,7 @@ suite('Dragger', function () {
86104 dragger . onDragStart ( dragStartEvent ) ;
87105 dragger . onDrag ( dragEvent , totalDelta ) ;
88106
89- return { dragger, dragEvent} ;
107+ return { dragger, dragEvent, block : dragger . draggable } ;
90108 }
91109
92110 setup ( function ( ) {
@@ -110,25 +128,45 @@ suite('Dragger', function () {
110128 sharedTestTeardown . call ( this ) ;
111129 } ) ;
112130
113- [
114- { name : 'trashcan' , rectKey : 'trashRect' } ,
115- { name : 'toolbox' , rectKey : 'toolboxRect' } ,
116- ] . forEach ( ( { name, rectKey} ) => {
117- test ( `applies delete styling and deletes when dragged to ${ name } ` , function ( ) {
118- const deleteRect = this [ rectKey ] ;
119- const { dragger, dragEvent} = dragBlock (
120- this . block ,
121- rectCenterClient ( deleteRect ) ,
122- ) ;
131+ const zoomLevels = [
132+ { name : 'default scale' , scale : null } ,
133+ { name : 'zoomed in' , scale : 1.5 } ,
134+ { name : 'zoomed out' , scale : 0.7 } ,
135+ ] ;
123136
124- assert . isTrue (
125- deleteRect . contains ( dragEvent . clientX , dragEvent . clientY ) ,
126- `Expected cursor to be inside ${ name } delete area` ,
127- ) ;
128- assert . isTrue ( hasDeleteStyle ( this . block ) ) ;
137+ zoomLevels . forEach ( ( { name : zoomName , scale} ) => {
138+ [
139+ { name : 'trashcan' , rectKey : 'trashRect' , checkLid : true } ,
140+ { name : 'toolbox' , rectKey : 'toolboxRect' , checkLid : false } ,
141+ ] . forEach ( ( { name, rectKey, checkLid} ) => {
142+ test ( `applies delete styling and deletes when dragged to ${ name } at ${ zoomName } ` , function ( ) {
143+ if ( scale !== null ) {
144+ setWorkspaceScale ( this . workspace , scale ) ;
145+ this . trashRect = this . workspace . trashcan . getClientRect ( ) ;
146+ this . toolboxRect = this . workspace . toolbox . getClientRect ( ) ;
147+ }
129148
130- dragger . onDragEnd ( dragEvent ) ;
131- assert . isTrue ( this . block . isDeadOrDying ( ) ) ;
149+ const deleteRect = this [ rectKey ] ;
150+ const { dragger, dragEvent, block} = dragBlock (
151+ this . block ,
152+ rectCenterClient ( deleteRect ) ,
153+ ) ;
154+
155+ assert . isTrue (
156+ deleteRect . contains ( dragEvent . clientX , dragEvent . clientY ) ,
157+ `Expected cursor to be inside ${ name } delete area` ,
158+ ) ;
159+ assert . isTrue ( hasDeleteStyle ( block ) ) ;
160+ if ( checkLid ) {
161+ assert . isTrue (
162+ hasTrashLidOpen ( this . workspace ) ,
163+ 'Expected trashcan lid to be open' ,
164+ ) ;
165+ }
166+
167+ dragger . onDragEnd ( dragEvent ) ;
168+ assert . isTrue ( block . isDeadOrDying ( ) ) ;
169+ } ) ;
132170 } ) ;
133171 } ) ;
134172
@@ -140,12 +178,12 @@ suite('Dragger', function () {
140178 x : deleteAreaRect . right - 5 ,
141179 y : originBefore . y ,
142180 } ;
143- const { dragger, dragEvent} = dragBlock ( this . block , {
181+ const { dragger, dragEvent, block } = dragBlock ( this . block , {
144182 x : start . x + desiredOrigin . x - originBefore . x ,
145183 y : start . y + desiredOrigin . y - originBefore . y ,
146184 } ) ;
147185
148- const originAfter = blockOriginClient ( this . block ) ;
186+ const originAfter = blockOriginClient ( block ) ;
149187 assert . isTrue (
150188 deleteAreaRect . contains ( originAfter . x , originAfter . y ) ,
151189 'Expected block origin to overlap delete area' ,
@@ -154,9 +192,112 @@ suite('Dragger', function () {
154192 deleteAreaRect . contains ( dragEvent . clientX , dragEvent . clientY ) ,
155193 'Expected cursor to be outside delete area' ,
156194 ) ;
157- assert . isFalse ( hasDeleteStyle ( this . block ) ) ;
195+ assert . isFalse ( hasDeleteStyle ( block ) ) ;
158196
159197 dragger . onDragEnd ( dragEvent ) ;
160- assert . isFalse ( this . block . isDeadOrDying ( ) ) ;
198+ assert . isFalse ( block . isDeadOrDying ( ) ) ;
199+ } ) ;
200+
201+ suite ( 'Mutator' , function ( ) {
202+ /**
203+ * Opens a mutator on a controls_if block and returns the mutator workspace.
204+ *
205+ * @param {!Blockly.WorkspaceSvg } workspace The main workspace.
206+ * @returns {!Promise<!Blockly.WorkspaceSvg> } The mutator workspace.
207+ */
208+ async function openMutator ( workspace ) {
209+ const block = Blockly . serialization . blocks . append (
210+ {
211+ 'type' : 'controls_if' ,
212+ 'extraState' : {
213+ 'elseIfCount' : 0 ,
214+ } ,
215+ } ,
216+ workspace ,
217+ ) ;
218+ block . initSvg ( ) ;
219+ block . render ( ) ;
220+ const icon = block . getIcon ( Blockly . icons . MutatorIcon . TYPE ) ;
221+ await icon . setBubbleVisible ( true ) ;
222+ return icon . getWorkspace ( ) ;
223+ }
224+
225+ test ( 'deletes flyout block when pointer is over flyout delete area at zoomed scale' , async function ( ) {
226+ for ( let i = 0 ; i < 3 ; i ++ ) {
227+ this . workspace . zoomCenter ( 1 ) ;
228+ }
229+
230+ const mutatorWorkspace = await openMutator ( this . workspace ) ;
231+ this . clock . runAll ( ) ;
232+ mutatorWorkspace . recordDragTargets ( ) ;
233+
234+ const flyout = mutatorWorkspace . getFlyout ( ) ;
235+ const flyoutRect = flyout . getClientRect ( ) ;
236+ assert . isNotNull ( flyoutRect ) ;
237+
238+ const flyoutBlock = flyout
239+ . getWorkspace ( )
240+ . getBlocksByType ( 'controls_if_elseif' ) [ 0 ] ;
241+ flyoutBlock . initSvg ( ) ;
242+ flyoutBlock . render ( ) ;
243+
244+ const { dragger, dragEvent, block} = dragBlock (
245+ flyoutBlock ,
246+ rectCenterClient ( flyoutRect ) ,
247+ ) ;
248+
249+ assert . isTrue (
250+ flyoutRect . contains ( dragEvent . clientX , dragEvent . clientY ) ,
251+ 'Expected cursor to be inside flyout delete area' ,
252+ ) ;
253+ assert . isTrue ( hasDeleteStyle ( block ) ) ;
254+
255+ dragger . onDragEnd ( dragEvent ) ;
256+ assert . isTrue ( block . isDeadOrDying ( ) ) ;
257+ } ) ;
258+
259+ test ( 'does not apply delete styling when only block origin overlaps flyout delete area at zoomed scale' , async function ( ) {
260+ for ( let i = 0 ; i < 3 ; i ++ ) {
261+ this . workspace . zoomCenter ( 1 ) ;
262+ }
263+
264+ const mutatorWorkspace = await openMutator ( this . workspace ) ;
265+ this . clock . runAll ( ) ;
266+ mutatorWorkspace . recordDragTargets ( ) ;
267+
268+ const flyout = mutatorWorkspace . getFlyout ( ) ;
269+ const flyoutRect = flyout . getClientRect ( ) ;
270+ assert . isNotNull ( flyoutRect ) ;
271+
272+ const workspaceBlock = mutatorWorkspace . newBlock ( 'controls_if_elseif' ) ;
273+ workspaceBlock . initSvg ( ) ;
274+ workspaceBlock . render ( ) ;
275+ workspaceBlock . moveBy ( 200 , 50 ) ;
276+
277+ const start = blockCenterClient ( workspaceBlock ) ;
278+ const originBefore = blockOriginClient ( workspaceBlock ) ;
279+ const desiredOrigin = {
280+ x : flyoutRect . right - 5 ,
281+ y : originBefore . y ,
282+ } ;
283+ const { dragger, dragEvent, block} = dragBlock ( workspaceBlock , {
284+ x : start . x + desiredOrigin . x - originBefore . x ,
285+ y : start . y + desiredOrigin . y - originBefore . y ,
286+ } ) ;
287+
288+ const originAfter = blockOriginClient ( block ) ;
289+ assert . isTrue (
290+ flyoutRect . contains ( originAfter . x , originAfter . y ) ,
291+ 'Expected block origin to overlap flyout delete area' ,
292+ ) ;
293+ assert . isFalse (
294+ flyoutRect . contains ( dragEvent . clientX , dragEvent . clientY ) ,
295+ 'Expected cursor to be outside flyout delete area' ,
296+ ) ;
297+ assert . isFalse ( hasDeleteStyle ( block ) ) ;
298+
299+ dragger . onDragEnd ( dragEvent ) ;
300+ assert . isFalse ( block . isDeadOrDying ( ) ) ;
301+ } ) ;
161302 } ) ;
162303} ) ;
0 commit comments