44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7+ import * as Blockly from 'blockly' ;
78import * as chai from 'chai' ;
89import { Browser , Key } from 'webdriverio' ;
910import {
@@ -24,4 +25,184 @@ suite('Move tests', function () {
2425 this . browser = await testSetup ( testFileLocations . MOVE_TEST_BLOCKS ) ;
2526 await this . browser . pause ( PAUSE_TIME ) ;
2627 } ) ;
28+
29+ // When a move of a statement block begins, it is expected that only
30+ // that block (and all blocks connected to its inputs) will be
31+ // moved, with subsequent statement blocks below it in the stack
32+ // reattached to where the moving block was - i.e., that a stack
33+ // heal will occur.
34+ test . only ( 'Start moving statement blocks' , async function ( ) {
35+ for ( let i = 1 ; i < 7 ; i ++ ) {
36+ // Navigate to statement_<i>.
37+ await tabNavigateToWorkspace ( this . browser ) ;
38+ await setCurrentCursorNodeById ( this . browser , `statement_${ i } ` ) ;
39+
40+ // Get information about parent connection of selected block,
41+ // and block connected to selected block's next connection.
42+ const info = await getSelectedNeighbourInfo ( this . browser ) ;
43+
44+ chai . assert ( info . parentId , 'selected block has no parent block' ) ;
45+ chai . assert (
46+ typeof info . parentIndex === 'number' ,
47+ 'parent connection index not found' ,
48+ ) ;
49+ chai . assert ( info . nextId , 'selected block has no next block' ) ;
50+
51+ // Start move.
52+ await this . browser . keys ( 'm' ) ;
53+
54+ // Check that the moving block has nothing connected it its
55+ // next/previous connections, and same thing connected to value
56+ // input.
57+ const newInfo = await getSelectedNeighbourInfo ( this . browser ) ;
58+ chai . assert (
59+ newInfo . parentId === null ,
60+ 'moving block should have no parent block' ,
61+ ) ;
62+ chai . assert (
63+ newInfo . nextId === null ,
64+ 'moving block should have no next block' ,
65+ ) ;
66+ chai . assert . strictEqual (
67+ newInfo . valueId ,
68+ info . valueId ,
69+ 'moving block should have same attached value block' ,
70+ ) ;
71+
72+ // Get ID of next block now connected to the (former) parent
73+ // connection of the currently-moving block (skipping insertion
74+ // markers), and make sure it's same as the ID of the block that
75+ // was formerly attached to the moving block's next connection.
76+ const newNextId = await this . browser . execute (
77+ ( parentId : string , index : number ) => {
78+ const parent = Blockly . getMainWorkspace ( ) . getBlockById ( parentId ) ;
79+ if ( ! parent ) throw new Error ( 'parent block gone' ) ;
80+ let block = parent . getConnections_ ( true ) [ index ] . targetBlock ( ) ;
81+ while ( block ?. isInsertionMarker ( ) ) {
82+ block = block . getNextBlock ( ) ;
83+ }
84+ return block ?. id ;
85+ } ,
86+ info . parentId ,
87+ info . parentIndex ,
88+ ) ;
89+ chai . assert . strictEqual (
90+ newNextId ,
91+ info . nextId ,
92+ 'former parent connection should be connected to former next block' ,
93+ ) ;
94+
95+ // Abort move.
96+ await this . browser . keys ( Key . Escape ) ;
97+ }
98+ } ) ;
99+
100+ // When a move of a value block begins, it is expected that block
101+ // and all blocks connected to its inputs will be moved - i.e., that
102+ // a stack heal (really: unary operator chain heal) will NOT occur.
103+ test . only ( 'Start moving value blocks' , async function ( ) {
104+ for ( let i = 1 ; i < 7 ; i ++ ) {
105+ // Navigate to statement_<i>.
106+ await tabNavigateToWorkspace ( this . browser ) ;
107+ await setCurrentCursorNodeById ( this . browser , `value_${ i } ` ) ;
108+
109+ // Get information about parent connection of selected block,
110+ // and block connected to selected block's value input.
111+ const info = await getSelectedNeighbourInfo ( this . browser ) ;
112+
113+ chai . assert ( info . parentId , 'selected block has no parent block' ) ;
114+ chai . assert (
115+ typeof info . parentIndex === 'number' ,
116+ 'parent connection index not found' ,
117+ ) ;
118+ chai . assert ( info . valueId , 'selected block has no child value block' ) ;
119+
120+ // Start move.
121+ await this . browser . keys ( 'm' ) ;
122+
123+ // Check that the moving block has nothing connected it its
124+ // next/previous connections, and same thing connected to value
125+ // input.
126+ const newInfo = await getSelectedNeighbourInfo ( this . browser ) ;
127+ chai . assert (
128+ newInfo . parentId === null ,
129+ 'moving block should have no parent block' ,
130+ ) ;
131+ chai . assert (
132+ newInfo . nextId === null ,
133+ 'moving block should have no next block' ,
134+ ) ;
135+ chai . assert . strictEqual (
136+ newInfo . valueId ,
137+ info . valueId ,
138+ 'moving block should have same attached value block' ,
139+ ) ;
140+
141+ // Check the (former) parent connection of the currently-moving
142+ // block is (skipping insertion markers) either unconnected or
143+ // connected to a shadow block, and that is is not the block
144+ // (originally and still) connected to the moving block's zeroth
145+ // value input.
146+ const newValueInfo = await this . browser . execute (
147+ ( parentId : string , index : number ) => {
148+ const parent = Blockly . getMainWorkspace ( ) . getBlockById ( parentId ) ;
149+ if ( ! parent ) throw new Error ( 'parent block gone' ) ;
150+ let block = parent . getConnections_ ( true ) [ index ] . targetBlock ( ) ?? null ;
151+ while ( block ?. isInsertionMarker ( ) ) {
152+ block = block . inputList [ 0 ] . connection ?. targetBlock ( ) ?? null ;
153+ }
154+ return {
155+ id : block ?. id ?? null ,
156+ shadow : block ?. isShadow ( ) ?? null ,
157+ } ;
158+ } ,
159+ info . parentId ,
160+ info . parentIndex ,
161+ ) ;
162+ chai . assert (
163+ newValueInfo . id === null || newValueInfo . shadow ,
164+ 'former parent connection should be unconnected (or shadow)' ,
165+ ) ;
166+ chai . assert . notStrictEqual (
167+ newValueInfo . id ,
168+ info . valueId ,
169+ 'former parent connection should NOT be connected to value block' ,
170+ ) ;
171+
172+ // Abort move.
173+ await this . browser . keys ( Key . Escape ) ;
174+ }
175+ } ) ;
27176} ) ;
177+
178+ /**
179+ * Get information about the currently-selected block's parent and
180+ * child blocks.
181+ *
182+ * N.B. explicitly converts any undefined values to null because
183+ * browser.execute does this implicitly and so otherwise this function
184+ * would return values that were not compliant with its own inferred
185+ * type signature!
186+ *
187+ * @returns A promise setting to an object containing the parent block
188+ * ID, index of parent connection, next block ID, and ID of the block
189+ * connected to the zeroth value value input.
190+ */
191+ function getSelectedNeighbourInfo ( browser : WebdriverIO . Browser ) {
192+ return browser . execute ( ( ) => {
193+ const block = Blockly . getFocusManager ( ) . getFocusedNode ( ) as
194+ | Blockly . BlockSvg
195+ | undefined ;
196+ if ( ! block ) throw new Error ( 'no selected block' ) ;
197+ const parent = block ?. getParent ( ) ;
198+ return {
199+ parentId : parent ?. id ?? null ,
200+ parentIndex :
201+ parent
202+ ?. getConnections_ ( true )
203+ . findIndex ( ( conn ) => conn . targetBlock ( ) === block ) ?? null ,
204+ nextId : block ?. getNextBlock ( ) ?. id ?? null ,
205+ valueId : block ?. inputList [ 0 ] . connection ?. targetBlock ( ) ?. id ?? null ,
206+ } ;
207+ } ) ;
208+ }
0 commit comments