1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/tests/mochitest/pivot.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,524 @@ 1.4 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.5 + 1.6 +//////////////////////////////////////////////////////////////////////////////// 1.7 +// Constants 1.8 + 1.9 +const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE; 1.10 +const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN; 1.11 +const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT; 1.12 +const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH; 1.13 +const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE; 1.14 +const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE; 1.15 +const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY; 1.16 +const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY; 1.17 + 1.18 +const NS_ERROR_NOT_IN_TREE = 0x80780026; 1.19 +const NS_ERROR_INVALID_ARG = 0x80070057; 1.20 + 1.21 +//////////////////////////////////////////////////////////////////////////////// 1.22 +// Traversal rules 1.23 + 1.24 +/** 1.25 + * Rule object to traverse all focusable nodes and text nodes. 1.26 + */ 1.27 +var HeadersTraversalRule = 1.28 +{ 1.29 + getMatchRoles: function(aRules) 1.30 + { 1.31 + aRules.value = [ROLE_HEADING]; 1.32 + return aRules.value.length; 1.33 + }, 1.34 + 1.35 + preFilter: PREFILTER_INVISIBLE, 1.36 + 1.37 + match: function(aAccessible) 1.38 + { 1.39 + return FILTER_MATCH; 1.40 + }, 1.41 + 1.42 + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) 1.43 +} 1.44 + 1.45 +/** 1.46 + * Traversal rule for all focusable nodes or leafs. 1.47 + */ 1.48 +var ObjectTraversalRule = 1.49 +{ 1.50 + getMatchRoles: function(aRules) 1.51 + { 1.52 + aRules.value = []; 1.53 + return 0; 1.54 + }, 1.55 + 1.56 + preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT, 1.57 + 1.58 + match: function(aAccessible) 1.59 + { 1.60 + var rv = FILTER_IGNORE; 1.61 + var role = aAccessible.role; 1.62 + if (hasState(aAccessible, STATE_FOCUSABLE) && 1.63 + (role != ROLE_DOCUMENT && role != ROLE_INTERNAL_FRAME)) 1.64 + rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH; 1.65 + else if (aAccessible.childCount == 0 && 1.66 + role != ROLE_STATICTEXT && aAccessible.name.trim()) 1.67 + rv = FILTER_MATCH; 1.68 + 1.69 + return rv; 1.70 + }, 1.71 + 1.72 + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) 1.73 +}; 1.74 + 1.75 +//////////////////////////////////////////////////////////////////////////////// 1.76 +// Virtual state invokers and checkers 1.77 + 1.78 +/** 1.79 + * A checker for virtual cursor changed events. 1.80 + */ 1.81 +function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod) 1.82 +{ 1.83 + this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc); 1.84 + 1.85 + this.match = function VCChangedChecker_check(aEvent) 1.86 + { 1.87 + var event = null; 1.88 + try { 1.89 + event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent); 1.90 + } catch (e) { 1.91 + return false; 1.92 + } 1.93 + 1.94 + var expectedReason = VCChangedChecker.methodReasonMap[aPivotMoveMethod] || 1.95 + nsIAccessiblePivot.REASON_NONE; 1.96 + 1.97 + return event.reason == expectedReason; 1.98 + }; 1.99 + 1.100 + this.check = function VCChangedChecker_check(aEvent) 1.101 + { 1.102 + SimpleTest.info("VCChangedChecker_check"); 1.103 + 1.104 + var event = null; 1.105 + try { 1.106 + event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent); 1.107 + } catch (e) { 1.108 + SimpleTest.ok(false, "Does not support correct interface: " + e); 1.109 + } 1.110 + 1.111 + var position = aDocAcc.virtualCursor.position; 1.112 + var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc; 1.113 + var nameMatches = position && position.name == aIdOrNameOrAcc; 1.114 + var accMatches = position == aIdOrNameOrAcc; 1.115 + 1.116 + SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches", 1.117 + "expecting " + aIdOrNameOrAcc + ", got '" + 1.118 + prettyName(position)); 1.119 + 1.120 + if (aTextOffsets) { 1.121 + SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0], 1.122 + "wrong start offset"); 1.123 + SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1], 1.124 + "wrong end offset"); 1.125 + } 1.126 + 1.127 + var prevPosAndOffset = VCChangedChecker. 1.128 + getPreviousPosAndOffset(aDocAcc.virtualCursor); 1.129 + 1.130 + if (prevPosAndOffset) { 1.131 + SimpleTest.is(event.oldAccessible, prevPosAndOffset.position, 1.132 + "previous position does not match"); 1.133 + SimpleTest.is(event.oldStartOffset, prevPosAndOffset.startOffset, 1.134 + "previous start offset does not match"); 1.135 + SimpleTest.is(event.oldEndOffset, prevPosAndOffset.endOffset, 1.136 + "previous end offset does not match"); 1.137 + } 1.138 + }; 1.139 +} 1.140 + 1.141 +VCChangedChecker.prevPosAndOffset = {}; 1.142 + 1.143 +VCChangedChecker.storePreviousPosAndOffset = 1.144 + function storePreviousPosAndOffset(aPivot) 1.145 +{ 1.146 + VCChangedChecker.prevPosAndOffset[aPivot] = 1.147 + {position: aPivot.position, 1.148 + startOffset: aPivot.startOffset, 1.149 + endOffset: aPivot.endOffset}; 1.150 +}; 1.151 + 1.152 +VCChangedChecker.getPreviousPosAndOffset = 1.153 + function getPreviousPosAndOffset(aPivot) 1.154 +{ 1.155 + return VCChangedChecker.prevPosAndOffset[aPivot]; 1.156 +}; 1.157 + 1.158 +VCChangedChecker.methodReasonMap = { 1.159 + 'moveNext': nsIAccessiblePivot.REASON_NEXT, 1.160 + 'movePrevious': nsIAccessiblePivot.REASON_PREV, 1.161 + 'moveFirst': nsIAccessiblePivot.REASON_FIRST, 1.162 + 'moveLast': nsIAccessiblePivot.REASON_LAST, 1.163 + 'setTextRange': nsIAccessiblePivot.REASON_TEXT, 1.164 + 'moveNextByText': nsIAccessiblePivot.REASON_TEXT, 1.165 + 'movePreviousByText': nsIAccessiblePivot.REASON_TEXT, 1.166 + 'moveToPoint': nsIAccessiblePivot.REASON_POINT 1.167 +}; 1.168 + 1.169 +/** 1.170 + * Set a text range in the pivot and wait for virtual cursor change event. 1.171 + * 1.172 + * @param aDocAcc [in] document that manages the virtual cursor 1.173 + * @param aTextAccessible [in] accessible to set to virtual cursor's position 1.174 + * @param aTextOffsets [in] start and end offsets of text range to set in 1.175 + * virtual cursor. 1.176 + */ 1.177 +function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) 1.178 +{ 1.179 + this.invoke = function virtualCursorChangedInvoker_invoke() 1.180 + { 1.181 + VCChangedChecker. 1.182 + storePreviousPosAndOffset(aDocAcc.virtualCursor); 1.183 + SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets); 1.184 + aDocAcc.virtualCursor.setTextRange(aTextAccessible, 1.185 + aTextOffsets[0], 1.186 + aTextOffsets[1]); 1.187 + }; 1.188 + 1.189 + this.getID = function setVCRangeInvoker_getID() 1.190 + { 1.191 + return "Set offset in " + prettyName(aTextAccessible) + 1.192 + " to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")"; 1.193 + }; 1.194 + 1.195 + this.eventSeq = [ 1.196 + new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange") 1.197 + ]; 1.198 +} 1.199 + 1.200 +/** 1.201 + * Move the pivot and wait for virtual cursor change event. 1.202 + * 1.203 + * @param aDocAcc [in] document that manages the virtual cursor 1.204 + * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.) 1.205 + * @param aRule [in] traversal rule object 1.206 + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect 1.207 + * virtual cursor to land on after performing move method. 1.208 + * false if no move is expected. 1.209 + */ 1.210 +function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc) 1.211 +{ 1.212 + var expectMove = (aIdOrNameOrAcc != false); 1.213 + this.invoke = function virtualCursorChangedInvoker_invoke() 1.214 + { 1.215 + VCChangedChecker. 1.216 + storePreviousPosAndOffset(aDocAcc.virtualCursor); 1.217 + if (aPivotMoveMethod && aRule) { 1.218 + var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule); 1.219 + SimpleTest.is(!!moved, !!expectMove, 1.220 + "moved pivot with " + aPivotMoveMethod + 1.221 + " to " + aIdOrNameOrAcc); 1.222 + } else { 1.223 + aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc); 1.224 + } 1.225 + }; 1.226 + 1.227 + this.getID = function setVCPosInvoker_getID() 1.228 + { 1.229 + return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod; 1.230 + }; 1.231 + 1.232 + if (expectMove) { 1.233 + this.eventSeq = [ 1.234 + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod) 1.235 + ]; 1.236 + } else { 1.237 + this.eventSeq = []; 1.238 + this.unexpectedEventSeq = [ 1.239 + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc) 1.240 + ]; 1.241 + } 1.242 +} 1.243 + 1.244 +/** 1.245 + * Move the pivot by text and wait for virtual cursor change event. 1.246 + * 1.247 + * @param aDocAcc [in] document that manages the virtual cursor 1.248 + * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.) 1.249 + * @param aBoundary [in] boundary constant 1.250 + * @param aTextOffsets [in] start and end offsets of text range to set in 1.251 + * virtual cursor. 1.252 + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect 1.253 + * virtual cursor to land on after performing move method. 1.254 + * false if no move is expected. 1.255 + */ 1.256 +function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, aIdOrNameOrAcc) 1.257 +{ 1.258 + var expectMove = (aIdOrNameOrAcc != false); 1.259 + this.invoke = function virtualCursorChangedInvoker_invoke() 1.260 + { 1.261 + VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); 1.262 + SimpleTest.info(aDocAcc.virtualCursor.position); 1.263 + var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary); 1.264 + SimpleTest.is(!!moved, !!expectMove, 1.265 + "moved pivot by text with " + aPivotMoveMethod + 1.266 + " to " + aIdOrNameOrAcc); 1.267 + }; 1.268 + 1.269 + this.getID = function setVCPosInvoker_getID() 1.270 + { 1.271 + return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod + " in " + 1.272 + prettyName(aIdOrNameOrAcc) + ", " + boundaryToString(aBoundary) + 1.273 + ", [" + aTextOffsets + "]"; 1.274 + }; 1.275 + 1.276 + if (expectMove) { 1.277 + this.eventSeq = [ 1.278 + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod) 1.279 + ]; 1.280 + } else { 1.281 + this.eventSeq = []; 1.282 + this.unexpectedEventSeq = [ 1.283 + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc) 1.284 + ]; 1.285 + } 1.286 +} 1.287 + 1.288 + 1.289 +/** 1.290 + * Move the pivot to the position under the point. 1.291 + * 1.292 + * @param aDocAcc [in] document that manages the virtual cursor 1.293 + * @param aX [in] screen x coordinate 1.294 + * @param aY [in] screen y coordinate 1.295 + * @param aIgnoreNoMatch [in] don't unset position if no object was found at 1.296 + * point. 1.297 + * @param aRule [in] traversal rule object 1.298 + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect 1.299 + * virtual cursor to land on after performing move method. 1.300 + * false if no move is expected. 1.301 + */ 1.302 +function moveVCCoordInvoker(aDocAcc, aX, aY, aIgnoreNoMatch, 1.303 + aRule, aIdOrNameOrAcc) 1.304 +{ 1.305 + var expectMove = (aIdOrNameOrAcc != false); 1.306 + this.invoke = function virtualCursorChangedInvoker_invoke() 1.307 + { 1.308 + VCChangedChecker. 1.309 + storePreviousPosAndOffset(aDocAcc.virtualCursor); 1.310 + var moved = aDocAcc.virtualCursor.moveToPoint(aRule, aX, aY, 1.311 + aIgnoreNoMatch); 1.312 + SimpleTest.ok((expectMove && moved) || (!expectMove && !moved), 1.313 + "moved pivot"); 1.314 + }; 1.315 + 1.316 + this.getID = function setVCPosInvoker_getID() 1.317 + { 1.318 + return "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc; 1.319 + }; 1.320 + 1.321 + if (expectMove) { 1.322 + this.eventSeq = [ 1.323 + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint') 1.324 + ]; 1.325 + } else { 1.326 + this.eventSeq = []; 1.327 + this.unexpectedEventSeq = [ 1.328 + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc) 1.329 + ]; 1.330 + } 1.331 +} 1.332 + 1.333 +/** 1.334 + * Change the pivot modalRoot 1.335 + * 1.336 + * @param aDocAcc [in] document that manages the virtual cursor 1.337 + * @param aModalRootAcc [in] accessible of the modal root, or null 1.338 + * @param aExpectedResult [in] error result expected. 0 if expecting success 1.339 + */ 1.340 +function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult) 1.341 +{ 1.342 + this.invoke = function setModalRootInvoker_invoke() 1.343 + { 1.344 + var errorResult = 0; 1.345 + try { 1.346 + aDocAcc.virtualCursor.modalRoot = aModalRootAcc; 1.347 + } catch (x) { 1.348 + SimpleTest.ok( 1.349 + x.result, "Unexpected exception when changing modal root: " + x); 1.350 + errorResult = x.result; 1.351 + } 1.352 + 1.353 + SimpleTest.is(errorResult, aExpectedResult, 1.354 + "Did not get expected result when changing modalRoot"); 1.355 + }; 1.356 + 1.357 + this.getID = function setModalRootInvoker_getID() 1.358 + { 1.359 + return "Set modalRoot to " + prettyName(aModalRootAcc); 1.360 + }; 1.361 + 1.362 + this.eventSeq = []; 1.363 + this.unexpectedEventSeq = [ 1.364 + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc) 1.365 + ]; 1.366 +} 1.367 + 1.368 +/** 1.369 + * Add invokers to a queue to test a rule and an expected sequence of element ids 1.370 + * or accessible names for that rule in the given document. 1.371 + * 1.372 + * @param aQueue [in] event queue in which to push invoker sequence. 1.373 + * @param aDocAcc [in] the managing document of the virtual cursor we are 1.374 + * testing 1.375 + * @param aRule [in] the traversal rule to use in the invokers 1.376 + * @param aModalRoot [in] a modal root to use in this traversal sequence 1.377 + * @param aSequence [in] a sequence of accessible names or element ids to expect 1.378 + * with the given rule in the given document 1.379 + */ 1.380 +function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence) 1.381 +{ 1.382 + aDocAcc.virtualCursor.position = null; 1.383 + 1.384 + // Add modal root (if any) 1.385 + aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0)); 1.386 + 1.387 + aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0])); 1.388 + 1.389 + for (var i = 1; i < aSequence.length; i++) { 1.390 + var invoker = 1.391 + new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]); 1.392 + aQueue.push(invoker); 1.393 + } 1.394 + 1.395 + // No further more matches for given rule, expect no virtual cursor changes. 1.396 + aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false)); 1.397 + 1.398 + for (var i = aSequence.length-2; i >= 0; i--) { 1.399 + var invoker = 1.400 + new setVCPosInvoker(aDocAcc, "movePrevious", aRule, aSequence[i]); 1.401 + aQueue.push(invoker); 1.402 + } 1.403 + 1.404 + // No previous more matches for given rule, expect no virtual cursor changes. 1.405 + aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false)); 1.406 + 1.407 + aQueue.push(new setVCPosInvoker(aDocAcc, "moveLast", aRule, 1.408 + aSequence[aSequence.length - 1])); 1.409 + 1.410 + // No further more matches for given rule, expect no virtual cursor changes. 1.411 + aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false)); 1.412 + 1.413 + aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0])); 1.414 + 1.415 + // No previous more matches for given rule, expect no virtual cursor changes. 1.416 + aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false)); 1.417 + 1.418 + // Remove modal root (if any). 1.419 + aQueue.push(new setModalRootInvoker(aDocAcc, null, 0)); 1.420 +} 1.421 + 1.422 +/** 1.423 + * A checker for removing an accessible while the virtual cursor is on it. 1.424 + */ 1.425 +function removeVCPositionChecker(aDocAcc, aHiddenParentAcc) 1.426 +{ 1.427 + this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc); 1.428 + 1.429 + this.check = function removeVCPositionChecker_check(aEvent) { 1.430 + var errorResult = 0; 1.431 + try { 1.432 + aDocAcc.virtualCursor.moveNext(ObjectTraversalRule); 1.433 + } catch (x) { 1.434 + errorResult = x.result; 1.435 + } 1.436 + SimpleTest.is( 1.437 + errorResult, NS_ERROR_NOT_IN_TREE, 1.438 + "Expecting NOT_IN_TREE error when moving pivot from invalid position."); 1.439 + }; 1.440 +} 1.441 + 1.442 +/** 1.443 + * Put the virtual cursor's position on an object, and then remove it. 1.444 + * 1.445 + * @param aDocAcc [in] document that manages the virtual cursor 1.446 + * @param aPosNode [in] DOM node to hide after virtual cursor's position is 1.447 + * set to it. 1.448 + */ 1.449 +function removeVCPositionInvoker(aDocAcc, aPosNode) 1.450 +{ 1.451 + this.accessible = getAccessible(aPosNode); 1.452 + this.invoke = function removeVCPositionInvoker_invoke() 1.453 + { 1.454 + aDocAcc.virtualCursor.position = this.accessible; 1.455 + aPosNode.parentNode.removeChild(aPosNode); 1.456 + }; 1.457 + 1.458 + this.getID = function removeVCPositionInvoker_getID() 1.459 + { 1.460 + return "Bring virtual cursor to accessible, and remove its DOM node."; 1.461 + }; 1.462 + 1.463 + this.eventSeq = [ 1.464 + new removeVCPositionChecker(aDocAcc, this.accessible.parent) 1.465 + ]; 1.466 +} 1.467 + 1.468 +/** 1.469 + * A checker for removing the pivot root and then calling moveFirst, and 1.470 + * checking that an exception is thrown. 1.471 + */ 1.472 +function removeVCRootChecker(aPivot) 1.473 +{ 1.474 + this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent); 1.475 + 1.476 + this.check = function removeVCRootChecker_check(aEvent) { 1.477 + var errorResult = 0; 1.478 + try { 1.479 + aPivot.moveLast(ObjectTraversalRule); 1.480 + } catch (x) { 1.481 + errorResult = x.result; 1.482 + } 1.483 + SimpleTest.is( 1.484 + errorResult, NS_ERROR_NOT_IN_TREE, 1.485 + "Expecting NOT_IN_TREE error when moving pivot from invalid position."); 1.486 + }; 1.487 +} 1.488 + 1.489 +/** 1.490 + * Create a pivot, remove its root, and perform an operation where the root is 1.491 + * needed. 1.492 + * 1.493 + * @param aRootNode [in] DOM node of which accessible will be the root of the 1.494 + * pivot. Should have more than one child. 1.495 + */ 1.496 +function removeVCRootInvoker(aRootNode) 1.497 +{ 1.498 + this.pivot = gAccRetrieval.createAccessiblePivot(getAccessible(aRootNode)); 1.499 + this.invoke = function removeVCRootInvoker_invoke() 1.500 + { 1.501 + this.pivot.position = this.pivot.root.firstChild; 1.502 + aRootNode.parentNode.removeChild(aRootNode); 1.503 + }; 1.504 + 1.505 + this.getID = function removeVCRootInvoker_getID() 1.506 + { 1.507 + return "Remove root of pivot from tree."; 1.508 + }; 1.509 + 1.510 + this.eventSeq = [ 1.511 + new removeVCRootChecker(this.pivot) 1.512 + ]; 1.513 +} 1.514 + 1.515 +/** 1.516 + * A debug utility for writing proper sequences for queueTraversalSequence. 1.517 + */ 1.518 +function dumpTraversalSequence(aPivot, aRule) 1.519 +{ 1.520 + var sequence = []; 1.521 + if (aPivot.moveFirst(aRule)) { 1.522 + do { 1.523 + sequence.push("'" + prettyName(aPivot.position) + "'"); 1.524 + } while (aPivot.moveNext(aRule)) 1.525 + } 1.526 + SimpleTest.info("\n[" + sequence.join(", ") + "]\n"); 1.527 +}