http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/CreateBitmapReferences.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/CreateBitmapReferences.as b/mustella/src/main/flex/CreateBitmapReferences.as new file mode 100644 index 0000000..0e755a6 --- /dev/null +++ b/mustella/src/main/flex/CreateBitmapReferences.as @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +import flash.display.DisplayObject; + +[Mixin] +/** + * A "marker" class that causes test scripts to write out + * bitmaps to the urls instead of reading and comparing + * so that baselines/reference-points can be created for + * future comparing. + */ +public class CreateBitmapReferences +{ + + /** + * Mixin callback that gets everything ready to go. + * The UnitTester waits for an event before starting + */ + public static function init(root:DisplayObject):void + { + UnitTester.createBitmapReferences = true; + /// change this to suit your locally running server: + UnitTester.bitmapServerPrefix = "http://localhost:9998/baselines/baseline.jsp?filename="; + } + +} + +}
http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DesktopMacSettings.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DesktopMacSettings.as b/mustella/src/main/flex/DesktopMacSettings.as new file mode 100644 index 0000000..5cbe37c --- /dev/null +++ b/mustella/src/main/flex/DesktopMacSettings.as @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +import flash.display.DisplayObject; + +[Mixin] +/** + * By including this mixin via CompileMustellaSwfs, we + * can set up some variables for UnitTester to use for + * a run of mobile tests on the desktop. + */ +public class DesktopMacSettings +{ + public static function init(root:DisplayObject):void + { + if( UnitTester.cv == null ){ + UnitTester.cv = new ConditionalValue(); + } + + UnitTester.cv.os = "mac"; + } +} +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DesktopWinSettings.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DesktopWinSettings.as b/mustella/src/main/flex/DesktopWinSettings.as new file mode 100644 index 0000000..fe2c0e2 --- /dev/null +++ b/mustella/src/main/flex/DesktopWinSettings.as @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +import flash.display.DisplayObject; + +[Mixin] +/** + * By including this mixin via CompileMustellaSwfs, we + * can set up some variables for UnitTester to use for + * a run of mobile tests on the desktop. + */ +public class DesktopWinSettings +{ + public static function init(root:DisplayObject):void + { + if( UnitTester.cv == null ){ + UnitTester.cv = new ConditionalValue(); + } + + UnitTester.cv.os = "win"; + } +} +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DeviceNames.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DeviceNames.as b/mustella/src/main/flex/DeviceNames.as new file mode 100644 index 0000000..c27d865 --- /dev/null +++ b/mustella/src/main/flex/DeviceNames.as @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.system.Capabilities; +} + +/** + * allowed values for deviceName + * + */ +public class DeviceNames +{ + public static const ANDROID:String = "android"; + public static const IOS:String = "ios"; + public static const QNX:String = "qnx"; + public static const MAC:String = "mac"; + public static const WIN:String = "win"; + + public static const OS_VALUES:Array = [WIN, MAC, ANDROID, IOS, QNX]; + public static const OS_VERSION_VALUES:Array = ["android22", "android23", "android3", "android4", "ios3x", "ios40", "ios41", "ios5", "ios6"]; + public static const DEVICE_VALUES:Array = ["air","desire","droid","droid2","droidX","evo","incredible","nexusOne","playbook"]; + + public static function getFromOS ():String + { + COMPILE::SWF + { + if (Capabilities.os.substring (0, Capabilities.os.indexOf (" ")) == "Windows" ) + { + return WIN; + } else if (Capabilities.os.substring (0, Capabilities.os.indexOf (" ")) == "Mac" ) + { + return MAC; + + } else + { + return ""; + } + } + COMPILE::JS + { + return ""; + } + } + +} +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DispatchEvent.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DispatchEvent.as b/mustella/src/main/flex/DispatchEvent.as new file mode 100644 index 0000000..b2b4e97 --- /dev/null +++ b/mustella/src/main/flex/DispatchEvent.as @@ -0,0 +1,141 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.display.DisplayObjectContainer; +import flash.display.InteractiveObject; +import flash.events.Event; +import flash.system.ApplicationDomain; +} + +/** + * The test step that fakes an event. Do not use for MouseEvent or KeyboardEvent + * MXML attributes: + * bubbles + * cancelable + * eventClass + * properties + * target + * type + * waitTarget (optional) + * waitEvent (optional) + * timeout (optional); + */ +public class DispatchEvent extends TestStep +{ + + /** + * @private + */ + COMPILE::SWF + override public function execute(root:Object, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean + { + if (waitEvent && waitTarget == null) + waitTarget = target; + return super.execute(root, context, testCase, testResult); + } + + /** + * Set the target's property to the specified value + */ + COMPILE::SWF + override protected function doStep():void + { + UnitTester.blockFocusEvents = false; + + var actualTarget:Object = context.stringToObject(target); + if (!actualTarget) + { + testResult.doFail("Target " + target + " not found"); + UnitTester.blockFocusEvents = false; + return; + } + + var c:Class = ApplicationDomain.currentDomain.getDefinition(eventClass) as Class; + var event:Event = new c(type, bubbles, cancelable); + if (properties) + { + for (var s:String in properties) + { + event[s] = properties[s]; + } + } + try + { + actualTarget.dispatchEvent(event); + } + catch (e2:Error) + { + TestOutput.logResult("Exception thrown in DispatchEvent."); + testResult.doFail (e2.getStackTrace()); + } + + UnitTester.blockFocusEvents = true; + } + + /** + * The qualified name of the class for the event + * i.e. flash.events.Event + */ + public var eventClass:String; + + /** + * The object that receives the mouse event + */ + public var target:String; + + /** + * The type of the event to send (mouseUp, mouseDown, etc). + */ + public var type:String; + + /** + * The bubbles property on the Event (optional) + */ + public var bubbles:Boolean; + + /** + * The cancelable property on the Event (optional) + */ + public var cancelable:Boolean; + + + /** + * The relatedObject property on the MouseEvent (optional) + */ + public var properties:Object; + + + /** + * customize string representation + */ + override public function toString():String + { + var s:String = "DispatchEvent: target = "; + s += target; + if (type) + s += ", type = " + type; + return s; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DispatchKeyEvent.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DispatchKeyEvent.as b/mustella/src/main/flex/DispatchKeyEvent.as new file mode 100644 index 0000000..a8d5b48 --- /dev/null +++ b/mustella/src/main/flex/DispatchKeyEvent.as @@ -0,0 +1,673 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.display.DisplayObjectContainer; +import flash.display.InteractiveObject; +import flash.events.Event; +import flash.events.FocusEvent; +import flash.events.KeyboardEvent; +import flash.events.TextEvent; +import flash.system.Capabilities; +import flash.text.TextField; +import flash.ui.Keyboard; +} + +/** + * The test step that fakes a keyboard event + * MXML attributes: + * type (optional) + * charCode + * ctrlKey (optional) + * keyCode (optional) + * keyLocation (optional) + * shiftKey (optional) + * waitTarget (optional) + * waitEvent (optional) + * timeout (optional); + * cancelable (optional) + */ + + +public class DispatchKeyEvent extends TestStep +{ + // These are constants from flash.ui.Keyboard. They are not + // available in non-AIR compilations, so they are reproduced here + // to avoid compile errors. + // If they change in AIR, these will need to be updated. + public static const FLASH_UI_KEYBOARD_BACK:uint = 0x01000016; + public static const FLASH_UI_KEYBOARD_MENU:uint = 0x01000012; + public static const FLASH_UI_KEYBOARD_SEARCH:uint = 0x0100001F; + + private var inDispatchKey:Boolean; + private var gotFocusIn:Boolean; + private var charSequence:Array; + private var keySequence:Array; + private var currentRepeat:int; + private var currentKey:int; + private var sendBoth:Boolean; + + /** + * Set the target's property to the specified value + */ + COMPILE::SWF + override protected function doStep():void + { + UnitTester.blockFocusEvents = false; + + sendBoth = false; + + if (!type) + { + sendBoth = true; + type = "keyDown"; + } + + var i:int; + var n:int; + charSequence = new Array(); + keySequence = new Array(); + + + if (charCode) + { + charSequence.push(charCode); + keySequence.push(keyCode ? keyCode : CharCodeToKeyCode[charCode] ? CharCodeToKeyCode[charCode] : charCode); + } + else if (keyCode) + { + charSequence.push(KeyCodeToCharCode[keyCode] ? KeyCodeToCharCode[keyCode] : 0); + keySequence.push(keyCode); + } + else if (char) + { + n = char.length; + for (i = 0; i < n; i++) + { + var c:uint = char.charCodeAt(i) + charSequence.push(c); + keySequence.push(CharCodeToKeyCode[c]); + } + } + else if (key || keys) + { + var sequence:Array; + if (key) + sequence = [ key ]; + else + sequence = keys; + n = sequence.length; + for (i = 0; i < n; i++) + { + var kc:uint = Keyboard[sequence[i]]; + if (kc == 0) + { + testResult.doFail(key + " is not a valid flash.ui.Keyboard constant"); + UnitTester.blockFocusEvents = true; + return; + } + keySequence.push(kc); + charSequence.push(KeyCodeToCharCode[kc] ? KeyCodeToCharCode[kc] : 0); + } + } + else + { + testResult.doFail("no keys specified"); + UnitTester.blockFocusEvents = true; + return; + } + + try + { + for (i = 0; i < repeatCount; i++) + { + var m:int = charSequence.length; + for (var j:int = 0; j < m; j++) + { + var event:KeyboardEvent = new KeyboardEvent(type, true, cancelable); // all keyboard events bubble + event.ctrlKey = ctrlKey; + event.shiftKey = shiftKey; + event.charCode = charSequence[j]; + event.keyCode = keySequence[j]; + event.keyLocation = keyLocation; + + if (keySequence[j] == Keyboard.TAB) + { + // if we don't see a focusIn, focus is being set + // asynchronously so we need to wait. + currentRepeat = i; + currentKey = j; + gotFocusIn = false; + root.addEventListener("focusIn", focusInHandler); + inDispatchKey = true; + dispatchKey(j, event); + inDispatchKey = false; + if (!gotFocusIn) + break; + } + else + dispatchKey(j, event); + } + } + } + catch (e1:Error) + { + TestOutput.logResult("Exception thrown in DispatchKeyEvent."); + testResult.doFail (e1.getStackTrace()); + } + + UnitTester.blockFocusEvents = true; + } + + /** + + * (Optional) name of a UI object whose Window/Stage + + * will be used to dispatch the event + + */ + + public var window:String; + + + + /** + * The type of the event to send (keyUp, keyDown, etc). + * If not set, we'll send both a keyDown and a keyUp + */ + public var type:String; + + /** + * The char or sequence of chars to send as a string/char if you don't know the charCode (optional) + */ + public var char:String; + + /** + * The charCode property on the KeyboardEvent (optional) + */ + public var charCode:uint; + + /** + * The ctrlKey property on the KeyboardEvent (optional) + */ + public var ctrlKey:Boolean; + + /** + * The Keyboard key if you don't know the keyCode (optional) + */ + public var key:String; + + /** + * The sequence of keys (optional) e.g ["LEFT", "UP"] + */ + public var keys:Array; + + /** + * The keyCode property on the KeyboardEvent (optional) + */ + public var keyCode:uint; + + /** + * The keyLocation property on the KeyboardEvent (optional) + */ + public var keyLocation:uint; + + /** + * The number of times to repeat the sequence (optional) + */ + public var repeatCount:uint = 1; + + /** + * The shiftKey property on the KeyboardEvent (optional) + */ + public var shiftKey:Boolean; + + /** + * Designate the created event to be cancelable. by default, they are not + */ + public var cancelable:Boolean = false; + + /** + * The FlashPlayer TextField doesn't actually handle keyboard events so we have to + * emulate them + */ + COMPILE::SWF + private function emulateKey(actualTarget:Object, event:KeyboardEvent):void + { + var begin:int = actualTarget.selectionBeginIndex; + var end:int = actualTarget.selectionEndIndex; + var caret:int = actualTarget.caretIndex; + // trace("begin =", begin, "end =", end, "caret =", caret); + + if (event.keyCode == Keyboard.LEFT) + { + if (event.shiftKey) + { + if (caret > 0) + { + if (caret == begin) + { + begin--; + // last param defines caret position + actualTarget.setSelection(end, begin); + } + else if (caret == end) + { + end--; + if (end < begin) + begin = end; + actualTarget.setSelection(end, begin); + + } + } + } + else + { + if (begin != end) + actualTarget.setSelection(begin, begin); + + else if (caret > 0) + actualTarget.setSelection(caret - 1, caret - 1); + } + } + else if (event.keyCode == Keyboard.RIGHT) + { + if (event.shiftKey) + { + if (caret < actualTarget.length) + { + if (caret == end) + { + end++; + actualTarget.setSelection(begin, end); + + } + else if (caret == begin) + { + begin++; + if (end < begin) + end = begin; + // last param defines caret position + actualTarget.setSelection(end, begin); + + } + } + } + else + { + if (begin != end) + actualTarget.setSelection(end, end); + + else if (caret > 0) + actualTarget.setSelection(caret + 1, caret + 1); + } + } + } + + COMPILE::SWF + private function focusInHandler(focusEvent:Event):void + { + gotFocusIn = true; + root.removeEventListener("focusIn", focusInHandler); + if (inDispatchKey) + return; + + for (var i:int = currentRepeat; i < repeatCount; i++) + { + var m:int = charSequence.length; + for (var j:int = currentKey + 1; j < m; j++) + { + var event:KeyboardEvent = new KeyboardEvent(type, true, cancelable); // all keyboard events bubble + event.ctrlKey = ctrlKey; + event.shiftKey = shiftKey; + event.charCode = charSequence[j]; + event.keyCode = keySequence[j]; + event.keyLocation = keyLocation; + + if (keySequence[j] == Keyboard.TAB) + { + currentRepeat = i; + currentKey = j; + gotFocusIn = false; + root.addEventListener("focusIn", focusInHandler); + inDispatchKey = true; + dispatchKey(j, event); + inDispatchKey = false; + if (!gotFocusIn) + break; + } + else + dispatchKey(j, event); + } + } + } + + COMPILE::SWF + private function dispatchKey(index:int, event:KeyboardEvent):void + { + // note that we don't check Window activation since we want to run in the background + // and window activation is a player function + + var actualTarget:Object; + + if (window) + { + actualTarget = context.stringToObject(window); + actualTarget = actualTarget.stage.focus; + } + else + { + actualTarget = root.stage.focus; + if (!actualTarget) + { + actualTarget = UnitTester.getFocus(); + } + } + + // BACK, MENU, and SEARCH are buttons on mobile (Android) devices. + // On Android devices right now, actualTarget is still null at this point. Dispatching the event to the stage works. + // Using the constants in flash.ui.Keyboard will cause an error in a non-AIR runs, so the constants are also defined + // in this file, above. There is risk here. + if (keySequence[index] == FLASH_UI_KEYBOARD_BACK || + keySequence[index] == FLASH_UI_KEYBOARD_MENU || + keySequence[index] == FLASH_UI_KEYBOARD_SEARCH){ + + actualTarget = root.stage; + } + + if (actualTarget) + { + var targetType:TypeInfo = context.getTypeInfo(actualTarget); + var isTextView:Boolean = targetType.isAssignableTo("spark.components::RichEditableText"); + + + if (actualTarget is TextField) + { + if (event.charCode) + { + if (actualTarget.type == "input") + { + actualTarget.replaceSelectedText(String.fromCharCode(event.charCode)); + // actualTarget.dispatchEvent(new Event("change", true)); + actualTarget.dispatchEvent(new Event("change")); + } + } + else + { + if (actualTarget.selectable) + emulateKey(actualTarget, event); + } + } + + actualTarget.dispatchEvent(event); + + + + if (isTextView) + { + if (event.keyCode == Keyboard.DELETE || + + event.keyCode == Keyboard.BACKSPACE || + + event.keyCode == Keyboard.INSERT || + + ctrlKey) + + { + + // don't send TEXT_INPUT event + + } + + else + + { + + var textEvent:TextEvent = new TextEvent(TextEvent.TEXT_INPUT, true, true); + + textEvent.text = String.fromCharCode(charSequence[index]); + + actualTarget.dispatchEvent(textEvent); + + } + + } + + if (keySequence[index] == Keyboard.TAB && type == "keyDown") + { + var fm:Object; + var newTarget:Object = actualTarget; + while (!fm && newTarget) + { + if ("focusManager" in newTarget) + fm = newTarget["focusManager"]; + newTarget = newTarget.parent; + } + newTarget = null; + if (fm) + { + try + { + newTarget = fm.getNextFocusManagerComponent(shiftKey); + } + catch (e:Error) + { + // ignore error thrown here. Should only throw if the + // current FM became inactive as a result of dispatching + // the key event. We don't really care too much about + // getting an accurate newTarget in this case because + // newTarget is often wrong since the Player is offering + // it up and the Player has that wonky algorithm for + // determining newTarget. In theory, none of our code + // truly cares as long as it doesn't point to old focus + // object. + } + } + + actualTarget.dispatchEvent(new FocusEvent(FocusEvent.KEY_FOCUS_CHANGE, true, true, InteractiveObject(newTarget), shiftKey, Keyboard.TAB)); + } + + if (sendBoth) + { + event = new KeyboardEvent("keyUp", true, cancelable); + event.ctrlKey = ctrlKey; + event.shiftKey = shiftKey; + event.charCode = charSequence[index]; + event.keyCode = keySequence[index]; + event.keyLocation = keyLocation; + actualTarget.dispatchEvent(event); + } + } + else + { + if (keySequence[index] == Keyboard.TAB && type == "keyDown") + { + + var thisRoot:DisplayObject + + // note that we don't check Window activation since we want to run in the background + // and window activation is a player function + if (window) + { + thisRoot = context.stringToObject(window).root; + } + else + thisRoot = root; + try + { + thisRoot.stage.dispatchEvent(new FocusEvent(FocusEvent.KEY_FOCUS_CHANGE, true, true, InteractiveObject(actualTarget), shiftKey, Keyboard.TAB)); + } + catch(se2:SecurityError) + { + thisRoot.dispatchEvent(new FocusEvent(FocusEvent.KEY_FOCUS_CHANGE, true, true, InteractiveObject(actualTarget), shiftKey, Keyboard.TAB)); + } + } + + } + } + + private var KeyCodeToCharCode:Object = { + 8: 8, + 13: 13, + 96: 48, + 97: 49, + 98: 50, + 99: 51, + 100: 52, + 101: 53, + 102: 54, + 103: 55, + 104: 56, + 105: 57, + 106: 42, + 107: 43, + 109: 45, + 110: 46, + 111: 47 + } + + private var CharCodeToKeyCode:Object = { + 13: 13, + 33: 49, + 34: 222, + 35: 51, + 36: 52, + 37: 53, + 38: 55, + 39: 222, + 40: 57, + 41: 48, + 42: 56, + 43: 187, + 44: 188, + 45: 189, + 46: 190, + 47: 191, + 48: 48, + 49: 49, + 50: 50, + 51: 51, + 52: 52, + 53: 53, + 54: 54, + 55: 55, + 56: 56, + 57: 57, + 58: 186, + 59: 186, + 60: 188, + 61: 187, + 62: 190, + 63: 191, + 64: 50, + 65: 65, + 66: 66, + 67: 67, + 68: 68, + 69: 69, + 70: 70, + 71: 71, + 72: 72, + 73: 73, + 74: 74, + 75: 75, + 76: 76, + 77: 77, + 78: 78, + 79: 79, + 80: 80, + 81: 81, + 82: 82, + 83: 83, + 84: 84, + 85: 85, + 86: 86, + 87: 87, + 88: 88, + 89: 89, + 90: 90, + 91: 219, + 92: 220, + 93: 221, + 94: 54, + 95: 189, + 96: 192, + 97: 65, + 98: 66, + 99: 67, + 100: 68, + 101: 69, + 102: 70, + 103: 71, + 104: 72, + 105: 73, + 106: 74, + 107: 75, + 108: 76, + 109: 77, + 110: 78, + 111: 79, + 112: 80, + 113: 81, + 114: 82, + 115: 83, + 116: 84, + 117: 85, + 118: 86, + 119: 87, + 120: 88, + 121: 89, + 122: 90, + 123: 219, + 124: 220, + 125: 221, + 126: 192 + } + + /** + * customize string representation + */ + override public function toString():String + { + var s:String = "DispatchKeyEvent"; + if (charCode) + s += ": charCode = " + charCode.toString(); + if (keyCode) + s += ": keyCode = " + keyCode.toString(); + if (char) + s += ": char = " + char; + if (key) + s += ": key = " + key; + if (keys) + s += ": keys = " + keys.toString(); + if (type) + s += ", type = " + type; + if (shiftKey) + s += ", shiftKey = " + shiftKey.toString(); + if (ctrlKey) + s += ", ctrlKey = " + ctrlKey.toString(); + if (repeatCount) + s += ", repeatCount = " + repeatCount.toString(); + return s; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DispatchMouseClickEvent.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DispatchMouseClickEvent.as b/mustella/src/main/flex/DispatchMouseClickEvent.as new file mode 100644 index 0000000..3f4574a --- /dev/null +++ b/mustella/src/main/flex/DispatchMouseClickEvent.as @@ -0,0 +1,298 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.display.DisplayObjectContainer; +import flash.display.InteractiveObject; +import flash.events.MouseEvent; +import flash.geom.Point; +import flash.text.TextField; +} + +/** + * The test step that fakes a mouse event + * MXML attributes: + * target + * type + * ctrlKey (optional) + * delta (optional) + * localX + * localY + * relatedObject (optional) + * shiftKey (optional) + * stageX + * stageY + * waitTarget (optional) + * waitEvent (optional) + * timeout (optional); + */ +public class DispatchMouseClickEvent extends TestStep +{ + + /** + * @private + */ + COMPILE::SWF + override public function execute(root:Object, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean + { + if (waitEvent && waitTarget == null) + waitTarget = target; + return super.execute(root, context, testCase, testResult); + } + + /** + * Set the target's property to the specified value + */ + COMPILE::SWF + override protected function doStep():void + { + UnitTester.blockFocusEvents = false; + + var actualTarget:Object = context.stringToObject(target); + if (!actualTarget) + { + testResult.doFail("Target " + target + " not found"); + UnitTester.blockFocusEvents = true; + return; + } + dispatchMouseEvent(actualTarget, "mouseDown"); + dispatchMouseEvent(actualTarget, "mouseUp"); + dispatchMouseEvent(actualTarget, "click"); + + UnitTester.blockFocusEvents = true; + + } + + COMPILE::SWF + private function dispatchMouseEvent(actualTarget:Object, type:String):void + { + var event:MouseEvent = new MouseEvent(type, true); // all mouse events bubble + event.ctrlKey = ctrlKey; + event.shiftKey = shiftKey; + event.buttonDown = type == "mouseDown"; + event.delta = delta; + if (relatedObject && relatedObject.length > 0) + { + event.relatedObject = InteractiveObject(context.stringToObject(relatedObject)); + } + + var stagePt:Point; + if (!isNaN(localX) && !isNaN(localY)) + { + stagePt = actualTarget.localToGlobal(new Point(localX, localY)); + } + else if (!isNaN(stageX) && !isNaN(stageY)) + { + stagePt = new Point(stageX, stageY); + } + else + { + stagePt = actualTarget.localToGlobal(new Point(0, 0)); + } + try { + root[mouseX] = stagePt.x; + root[mouseY] = stagePt.y; + UnitTester.setMouseXY(stagePt); + if (root["topLevelSystemManager"] != root) + { + root["topLevelSystemManager"][mouseX] = stagePt.x; + root["topLevelSystemManager"][mouseY] = stagePt.y; + } + } catch (e:Error) {} // some scenarios don't support this + + if (actualTarget is DisplayObjectContainer) + { + var targets:Array = actualTarget.stage.getObjectsUnderPoint(stagePt); + var arr:Array = UnitTester.getObjectsUnderPoint(DisplayObject(actualTarget), stagePt); + targets = targets.concat(arr); + + for (var i:int = targets.length - 1; i >= 0; i--) + { + if (targets[i] is InteractiveObject) + { + if (targets[i] is TextField && !targets[i].selectable) + { + actualTarget = targets[i].parent; + break; + } + + if (isMouseTarget(InteractiveObject(targets[i]))) + { + actualTarget = targets[i]; + break; + } + } +/* else + { + try + { + actualTarget = targets[i].parent; + while (actualTarget) + { + if (actualTarget is InteractiveObject) + { + if (isMouseTarget(InteractiveObject(actualTarget))) + { + break; + } + } + actualTarget = actualTarget.parent; + } + if (actualTarget && actualTarget != root) + break; + } + catch (e:Error) + { + if (actualTarget) + break; + } + } +*/ } + } + + var localPt:Point = actualTarget.globalToLocal(stagePt); + event.localX = localPt.x; + event.localY = localPt.y; + + if (actualTarget is TextField) + { + if (type == "mouseDown") + { + var charIndex:int = actualTarget.getCharIndexAtPoint(event.localX, event.localY); + actualTarget.setSelection(charIndex + 1, charIndex + 1); + } + } + + try + { + actualTarget.dispatchEvent(event); + } + catch (e2:Error) + { + TestOutput.logResult("Exception thrown in DispatchMouseClickEvent."); + testResult.doFail (e2.getStackTrace()); + return; + } + } + + /** + * The object that receives the mouse event + */ + public var target:String; + + /** + * The ctrlKey property on the MouseEvent (optional) + */ + public var ctrlKey:Boolean; + + /** + * The delta property on the MouseEvent (optional) + */ + public var delta:int; + + /** + * The localX property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var localX:Number; + + /** + * The localY property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var localY:Number; + + /** + * The stageX property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var stageX:Number; + + /** + * The stageY property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var stageY:Number; + + /** + * The shiftKey property on the MouseEvent (optional) + */ + public var shiftKey:Boolean; + + /** + * The relatedObject property on the MouseEvent (optional) + */ + public var relatedObject:String; + + COMPILE::SWF + private function isMouseTarget(target:InteractiveObject):Boolean + { + if (!target.mouseEnabled) + return false; + + // Examine parent chain for "mouseChildren" set to false: + try + { + var parent:DisplayObjectContainer = target.parent; + while (parent) + { + if (!parent.mouseChildren) + return false; + parent = parent.parent; + } + } + catch (e1:Error) + { + } + + return true; + } + + /** + * customize string representation + */ + COMPILE::SWF + override public function toString():String + { + var s:String = "DispatchMouseClickEvent: target = "; + s += target; + if (!isNaN(localX)) + s += ", localX = " + localX.toString(); + if (!isNaN(localY)) + s += ", localY = " + localY.toString(); + if (!isNaN(stageX)) + s += ", stageX = " + stageX.toString(); + if (!isNaN(stageY)) + s += ", stageY = " + stageY.toString(); + if (shiftKey) + s += ", shiftKey = " + shiftKey.toString(); + if (ctrlKey) + s += ", ctrlKey = " + ctrlKey.toString(); + if (relatedObject) + s += ", relatedObject = " + relatedObject.toString(); + if (delta) + s += ", delta = " + delta.toString(); + return s; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DispatchMouseEvent.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DispatchMouseEvent.as b/mustella/src/main/flex/DispatchMouseEvent.as new file mode 100644 index 0000000..39f8e5d --- /dev/null +++ b/mustella/src/main/flex/DispatchMouseEvent.as @@ -0,0 +1,306 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +COMPILE::SWF +{ +import flash.display.DisplayObject; +import flash.display.DisplayObjectContainer; +import flash.display.InteractiveObject; +import flash.text.TextField; +import flash.events.MouseEvent; +import flash.geom.Point; +} + +/** + * The test step that fakes a mouse event + * MXML attributes: + * target + * type + * ctrlKey (optional) + * delta (optional) + * localX + * localY + * relatedObject (optional) + * shiftKey (optional) + * stageX + * stageY + * waitTarget (optional) + * waitEvent (optional) + * timeout (optional); + */ +public class DispatchMouseEvent extends TestStep +{ + /** + * @private + */ + COMPILE::SWF + override public function execute(root:Object, context:UnitTester, testCase:TestCase, testResult:TestResult):Boolean + { + if (waitEvent && waitTarget == null) + waitTarget = target; + return super.execute(root, context, testCase, testResult); + } + + /** + * Set the target's property to the specified value + */ + COMPILE::SWF + override protected function doStep():void + { + UnitTester.blockFocusEvents = false; + + var actualTarget:Object = context.stringToObject(target); + if (!actualTarget) + { + testResult.doFail("Target " + target + " not found"); + UnitTester.blockFocusEvents = false; + return; + } + + var event:MouseEvent = new MouseEvent(type, true); // all mouse events bubble + event.ctrlKey = ctrlKey; + event.shiftKey = shiftKey; + event.buttonDown = buttonDown || type == "mouseDown"; + event.delta = delta; + if (relatedObject && relatedObject.length > 0) + { + event.relatedObject = InteractiveObject(context.stringToObject(relatedObject)); + if (!event.relatedObject) + { + testResult.doFail("RelatedObject " + relatedObject + " not found"); + UnitTester.blockFocusEvents = false; + return; + } + } + + var stagePt:Point; + if (!isNaN(localX) && !isNaN(localY)) + { + stagePt = actualTarget.localToGlobal(new Point(localX, localY)); + } + else if (!isNaN(stageX) && !isNaN(stageY)) + { + stagePt = new Point(stageX, stageY); + } + else + { + stagePt = actualTarget.localToGlobal(new Point(0, 0)); + } + try { + root[mouseX] = stagePt.x; + root[mouseY] = stagePt.y; + UnitTester.setMouseXY(stagePt); + if (root["topLevelSystemManager"] != root) + { + root["topLevelSystemManager"][mouseX] = stagePt.x; + root["topLevelSystemManager"][mouseY] = stagePt.y; + } + } catch (e:Error) {}; // some scenarios don't support this + + if (actualTarget is DisplayObjectContainer) + { + var targets:Array = actualTarget.stage.getObjectsUnderPoint(stagePt); + var arr:Array = UnitTester.getObjectsUnderPoint(DisplayObject(actualTarget), stagePt); + targets = targets.concat(arr); + + for (var i:int = targets.length - 1; i >= 0; i--) + { + if (targets[i] is InteractiveObject) + { + if (targets[i] is TextField && !targets[i].selectable) + { + actualTarget = targets[i].parent; + break; + } + + if (isMouseTarget(InteractiveObject(targets[i]))) + { + actualTarget = targets[i]; + break; + } + } +/* else + { + try + { + actualTarget = targets[i].parent; + while (actualTarget) + { + if (actualTarget is InteractiveObject) + { + if (isMouseTarget(InteractiveObject(actualTarget))) + { + break; + } + } + actualTarget = actualTarget.parent; + } + if (actualTarget && actualTarget != root) + break; + } + catch (e:Error) + { + if (actualTarget) + break; + } + } +*/ } + } + + var localPt:Point = actualTarget.globalToLocal(stagePt); + event.localX = localPt.x; + event.localY = localPt.y; + + if (actualTarget is TextField) + { + if (type == "mouseDown") + { + var charIndex:int = actualTarget.getCharIndexAtPoint(event.localX, event.localY); + actualTarget.setSelection(charIndex + 1, charIndex + 1); + } + } + + try + { + actualTarget.dispatchEvent(event); + } + catch (e2:Error) + { + TestOutput.logResult("Exception thrown in DispatchMouseClickEvent."); + testResult.doFail (e2.getStackTrace()); + } + + UnitTester.blockFocusEvents = true; + } + + /** + * The object that receives the mouse event + */ + public var target:String; + + /** + * The type of the event to send (mouseUp, mouseDown, etc). + */ + public var type:String; + + /** + * The buttonDown property on the MouseEvent (optional) + */ + public var buttonDown:Boolean; + + /** + * The ctrlKey property on the MouseEvent (optional) + */ + public var ctrlKey:Boolean; + + /** + * The delta property on the MouseEvent (optional) + */ + public var delta:int; + + /** + * The localX property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var localX:Number; + + /** + * The localY property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var localY:Number; + + /** + * The stageX property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var stageX:Number; + + /** + * The stageY property on the MouseEvent (optional) + * Either set stageX/stageY or localX/localY, but not both. + */ + public var stageY:Number; + + /** + * The shiftKey property on the MouseEvent (optional) + */ + public var shiftKey:Boolean; + + /** + * The relatedObject property on the MouseEvent (optional) + */ + public var relatedObject:String; + + COMPILE::SWF + private function isMouseTarget(target:InteractiveObject):Boolean + { + if (!target.mouseEnabled) + return false; + + // Examine parent chain for "mouseChildren" set to false: + try + { + var parent:DisplayObjectContainer = target.parent; + while (parent) + { + if (!parent.mouseChildren) + return false; + parent = parent.parent; + } + } + catch (e1:Error) + { + } + + return true; + } + + /** + * customize string representation + */ + COMPILE::SWF + override public function toString():String + { + var s:String = "DispatchMouseEvent: target = "; + s += target; + if (type) + s += ", type = " + type; + if (!isNaN(localX)) + s += ", localX = " + localX.toString(); + if (!isNaN(localY)) + s += ", localY = " + localY.toString(); + if (!isNaN(stageX)) + s += ", stageX = " + stageX.toString(); + if (!isNaN(stageY)) + s += ", stageY = " + stageY.toString(); + if (shiftKey) + s += ", shiftKey = " + shiftKey.toString(); + if (ctrlKey) + s += ", ctrlKey = " + ctrlKey.toString(); + if (relatedObject) + s += ", relatedObject = " + relatedObject.toString(); + if (delta) + s += ", delta = " + delta.toString(); + return s; + } +} + +} http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DragAndDropMain.mxml ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DragAndDropMain.mxml b/mustella/src/main/flex/DragAndDropMain.mxml new file mode 100644 index 0000000..5efe2b4 --- /dev/null +++ b/mustella/src/main/flex/DragAndDropMain.mxml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" > + + <mx:Script> + <![CDATA[ + // uncomment this to write bitmaps + // public var createBitmapReferences:CreateBitmapReferences; + + public var script:DragAndDropScript; + + // public var cbTester2:CBTester2; + + ]]> + </mx:Script> + + <mx:List id="lb" dragEnabled="true" dragMoveEnabled="true" initialize="lb.dataProvider=['alpha', 'beta', 'gamma', 'delta']" /> + + <mx:List id="lb1" dropEnabled="true" initialize="lb1.dataProvider=['apple']" /> + + <!-- probably best to uncomment one at a time --> + <!--<EventSniffer />--> + <!--<PixelSniffer />--> +</mx:Application> http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/DragAndDropScript.mxml ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/DragAndDropScript.mxml b/mustella/src/main/flex/DragAndDropScript.mxml new file mode 100644 index 0000000..7561fbb --- /dev/null +++ b/mustella/src/main/flex/DragAndDropScript.mxml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<UnitTester xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" testSWF="NSMain.mxml"> + + <!-- this set of lines form a template that must be in each unit test --> + <mx:Script> + <![CDATA[ + public static function init(o:DisplayObject):void + { + } + ]]> + </mx:Script> + <mx:Metadata> + <![CDATA[ + [Mixin] + ]]> + </mx:Metadata> + <!-- end of set of lines that must be in each unit test --> + + <mx:Script> + <![CDATA[ + ]]> + </mx:Script> + + <testCases> + <TestCase testID="myProp1"> + <body> + <DispatchMouseEvent target="lb" type="mouseOver" localX="10" localY="30" /> + <DispatchMouseEvent target="lb" type="mouseDown" localX="10" localY="30" /> + <DispatchMouseEvent target="lb" type="mouseMove" buttonDown="true" localX="2" localY="30" waitTarget="lb" waitEvent="dragStart" /> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="228" stageY="57" + waitTarget="mx.managers::DragManager.mx_internal:dragProxy" waitEvent="move"/> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="208" stageY="77" + waitTarget="mx.managers::DragManager.mx_internal:dragProxy" waitEvent="move"/> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="208" stageY="107" + waitTarget="mx.managers::DragManager.mx_internal:dragProxy" waitEvent="move"/> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="218" stageY="157" + waitTarget="mx.managers::DragManager.mx_internal:dragProxy" waitEvent="move"/> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="228" stageY="217" + waitTarget="mx.managers::DragManager.mx_internal:dragProxy" waitEvent="move"/> + <DispatchMouseEvent target="stage" type="mouseMove" buttonDown="true" stageX="238" stageY="237" waitTarget="lb1" waitEvent="dragEnter"/> + <DispatchMouseEvent target="stage" type="mouseUp" stageX="238" stageY="237" waitTarget="lb1" waitEvent="dragDrop" /> + <AssertEvent target="lb" eventName="dragComplete" eventClass="mx.events::DragEvent" /> + <AssertPropertyValue target="lb.dataProvider" propertyName="length" value="3" /> + <AssertPropertyValue target="lb1.dataProvider" propertyName="length" value="2" /> + </body> + </TestCase> + </testCases> +</UnitTester> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/EffectTesting.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/EffectTesting.as b/mustella/src/main/flex/EffectTesting.as new file mode 100644 index 0000000..507c6f1 --- /dev/null +++ b/mustella/src/main/flex/EffectTesting.as @@ -0,0 +1,645 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package +{ + import flash.events.Event; + import flash.events.TimerEvent; + import flash.utils.Timer; + import flash.utils.getQualifiedClassName; + + import mx.collections.ArrayCollection; + import mx.core.IVisualElement; + import mx.core.IVisualElementContainer; + import mx.core.UIComponent; + import mx.effects.CompositeEffect; + import mx.effects.Effect; + import mx.events.EffectEvent; + import mx.geom.TransformOffsets; + import mx.states.Transition; + + import spark.primitives.supportClasses.GraphicElement; + + /** + * + * This class provides some APIs that can be useful for writing Mustella effects + * and transitions tests. This will be instrumental in the Catalyst matrix tests. + * + * It might be useful to think about building a set of TestStep classes that wrap + * some of this functionality. + */ + public class EffectTesting + { + // whether to seek the effect on effectStart (default: false) + public static var requestedSeek:Boolean = false; + + // what time to seek to in an effect (default: NaN - seek to the end of the effect) + public static var requestedSeekTime:Number = NaN; + + // the current effect being played + [Bindable] public static var currentEffect:Effect; + + // the document that ready events will be dispatched from and transitions will be pulled from + private static var rootDocument:UIComponent; + + // the character used to separate the elements in the expected values string + private static var elementSeparator:String = "|"; + + // the character used to separate the values in the expected values string + private static var propertySeparator:String = ","; + + // keep track of the details of the latest comparison to ease further investigation + private static var lastResult:ArrayCollection; + + /** + * Sets up for an effect test. This allows you to seek to a specific time in an effect. + * + * Call this method after the ResetComponent in your (non-transition) effects test. + */ + public static function setupEffectTest(document:Object, effect:Effect):String { + + // reset the test properties + resetProperties(document); + + // null check the effect + if (effect == null) + throw new Error("ERROR: You must provide a non-null effect to test."); + + // set the current effect + currentEffect = effect; + + // handle the effectEnd event + currentEffect.removeEventListener(EffectEvent.EFFECT_END, handleEffectEnd); + currentEffect.addEventListener(EffectEvent.EFFECT_END, handleEffectEnd); + + // dispatches a setupComplete event and returns setupComplete String so you can + // use this with either an AssertMethodValue or RunCode in Mustella + rootDocument.dispatchEvent(new Event('setupComplete')); + return "setupComplete"; + } + + /** + * Sets up for a transitions test. This allows you to seek to a specific time in a transition. + * + * It parses all of the transitions in a document and sets up event listeners in a way that allows + * seeking to a specific time in the transition. + * + * Call this method after the ResetComponent in your transitions test. + */ + public static function setupTransitionTest(document:Object):String { + + resetProperties(document); + + var transitions:Array = rootDocument.transitions; + + // don't manage any listeners if there aren't any transitions + if (transitions == null) + throw new Error("ERROR: document has no transitions"); + + // add event listeners to each transition + for each (var t:Transition in transitions){ + + // remove the effectStart event listener and add it again so we don't pile them up + t.effect.removeEventListener(EffectEvent.EFFECT_START, handleEffectStart); + t.effect.addEventListener(EffectEvent.EFFECT_START, handleEffectStart); + + // remove the effectEnd event listener and add it again so we don't pile them up + t.effect.removeEventListener(EffectEvent.EFFECT_END, handleEffectEnd); + t.effect.addEventListener(EffectEvent.EFFECT_END, handleEffectEnd); + } + + // dispatches a setupComplete event and returns setupComplete String so you can + // use this with either an AssertMethodValue or RunCode in Mustella + rootDocument.dispatchEvent(new Event('setupComplete')); + return "setupComplete"; + } + + /** + * Called by the setup methods to reset the properties in this class + */ + private static function resetProperties(document:Object):void { + + // null checks + if (document == null) + throw new Error("ERROR: You must provide a non-null document."); + + if (!(document is UIComponent)) + throw new Error("ERROR: document must be a UIComponent"); + + // reset the rootDocument + rootDocument = document as UIComponent; + + // reset the seek information + requestedSeek = false; + requestedSeekTime = NaN; + } + + /** + * Called on effect start, kicks off the seek behavior if it is requested. + */ + private static function handleEffectStart(event:EffectEvent):void { + trace('effect start'); + + currentEffect = event.target as Effect; + + // seek if it was requested + if (requestedSeek){ + + // wait roughly a frame then pause the effect before seeking + var timer:Timer = new Timer(0); + timer.repeatCount = 1; + timer.addEventListener(TimerEvent.TIMER, function(e:Event):void{ seekCurrentEffect(); }); + timer.start(); + } + } + + /** + * Pauses then seeks to the position in the current effect. Fires an event when that is done. + */ + public static function seekCurrentEffect():void { + var seekTime:Number = requestedSeekTime; + var c:CompositeEffect = currentEffect as CompositeEffect; + + // seek to the end if a specific seek time was not requested + if (isNaN(seekTime)){ + // set the seekTime to the end of the effect + if (c){ + // if its a Parallel/Sequence then use the compositeDuration that also handle startDelay + seekTime = c.compositeDuration; + } else { + // just a plain effect so use startDelay + duration + seekTime = currentEffect.startDelay + currentEffect.duration; + } + } + + trace('effect seek to ' + seekTime); + + // pause then seek + currentEffect.pause(); + currentEffect.playheadTime = seekTime; + + // dispatch a ready event on the document + rootDocument.dispatchEvent(new Event("seekAssertionReady")); + } + + /** + * TODO: The inclusion of this method in the API is not fully baked. + * This method's name/signature/existance could change in the future + * when it is properly implemented. + */ + public static function seekCurrentEffectTo(time:Number):void { + currentEffect.playheadTime = time; + rootDocument.dispatchEvent(new Event("seekAssertionReady")); + } + + /** + * TODO: The inclusion of this method in the API is not fully baked. + * This method's name/signature/existance could change in the future + * when it is properly implemented. + */ + public static function getCurrentEffectDuration():Number { + var c:CompositeEffect = currentEffect as CompositeEffect; + + if (c){ + // if its a Parallel/Sequence then use the compositeDuration that also handle startDelay + return c.compositeDuration; + } else { + // just a plain effect so use startDelay + duration + return currentEffect.startDelay + currentEffect.duration; + } + } + + /** + * Resumes the current effect + */ + public static function resumeCurrentEffect():void { + trace("effect resume"); + currentEffect.resume(); + } + + /** + * Fires an event after the effectEnd event that signifies an assertion is now valid. + * + * In a transition this gets called after the state values have been slammed in. + */ + private static function handleEffectEnd(e:EffectEvent):void { + trace('effect end'); + + // dispatch a ready event on the document + rootDocument.dispatchEvent(new Event("endAssertionReady")); + } + + /** + * Given a root element it compares a set of properties across that element and any of its ancestors. + * + * Sample usage: + * + * assertPropertySet(test1, 'width, height', '70,22|10,10', 0) + * outputs: 'FAIL: test1.width: expected 70 +/- 0, but received 100') + * + * @param rootContainer - the root element to inspect + * @param propertyNameString - a string deliminated with a character that lists the properties to inspect + * @param expectedValuesString - a string deliminiated with a character that lists the values to expect + * @param tolerance - the amount of difference between actual and expected is allowed before failure + * @param depth - how deep to recurse in the rootContainer + * + * @return - a string of either "PASS" or "FAIL: ..." with a failure message + */ + public static function assertPropertySet(rootContainer:IVisualElement, propertyNamesString:String, + expectedValuesString:String, tolerance:Number = 0, depth:int = -1):String { + return checkPropertySet(rootContainer, false, propertyNamesString, expectedValuesString, tolerance, depth); + } + + /** + * Given a root element it compares a set of properties across that element and any of its ancestors + * using the postLayoutTransformOffsets object of those elements. + * + * Sample usage: + * + * assertPostLayoutPropertySet(test1, 'rotationX, rotationY', '45,45|0,0', 0) + * outputs: 'FAIL: test1.rotationX: expected 45 +/- 0, but received 0') + * + * Use null as an expected value if postLayoutTransformOffsets is null for example: + * - properties: 'rotationX,rotationY' + * - expected string: 'null,null|null,null' + * + * @param rootContainer - the root element to inspect + * @param propertyNameString - a string deliminated with a character that lists the properties to inspect + * @param expectedValuesString - a string deliminiated with a character that lists the values to expect + * @param tolerance - the amount of difference between actual and expected is allowed before failure + * @param depth - how deep to recurse in the rootContainer + * + * @return - a string of either "PASS" or "FAIL: ..." with a failure message + */ + public static function assertPostLayoutPropertySet(rootContainer:IVisualElement, propertyNamesString:String, + expectedValuesString:String, tolerance:Number = 0, depth:int = -1):String { + return checkPropertySet(rootContainer, true, propertyNamesString, expectedValuesString, tolerance, depth); + } + + /** + * Workhorse method that is exposed via the two public assert methods. + * + * Given a root element it compares a set of properties across that element and any of its ancestors + * + * @param rootContainer - the root element to inspect + * @param postLayout - whether to look at the postLayoutTransformOffsets object of an element + * @param propertyNameString - a string deliminated with a character that lists the properties to inspect + * @param expectedValuesString - a string deliminiated with a character that lists the values to expect + * @param tolerance - the amount of difference between actual and expected is allowed before failure + * @param depth - how deep to recurse in the rootContainer + * + * @return - a string of either "PASS" or "FAIL: ..." with a failure message + * + */ + private static function checkPropertySet(rootContainer:IVisualElement, postLayout:Boolean, propertyNamesString:String, + expectedValuesString:String, tolerance:Number = 0, depth:int = -1):String { + + // reset the result of the last comparison + // add to this collection at any point a comparison happens + lastResult = new ArrayCollection(); + + // get the list of elements to inspect properties of + var elementsToInspect:Array = getElementsToInspect(rootContainer, depth); + + // get the list of properties to inspect on each element + var propertyNames:Array = getPropertyNames(propertyNamesString); + + // split up the expectedValue string into values for each element + var expectedElementValues:Array = expectedValuesString.split(elementSeparator); + + // string that represents the reason for fail + var failString:String = ""; + + if (elementsToInspect.length != expectedElementValues.length){ + // this will also catch existance failures, for example if an + // element is supposed to be included or excluded from a state + failString = "FAIL: number of elements (" + elementsToInspect.length + ") != number of expected elements (" + expectedElementValues.length + ")"; + logResult(failString); + return failString; + } + + // Go through each of the elements recursively in the rootContainer + for (var i:int = 0; i < elementsToInspect.length; i++){ + var element:IVisualElement = elementsToInspect[i]; + var expectedPropertyValues:Array = expectedElementValues[i].split(propertySeparator); + + // check for a malformed expected string + if (propertyNames.length != expectedPropertyValues.length){ + failString = "FAIL: number of properties != number of expected values for " + getElementId(element); + logResult(failString); + return failString; + } + + // log that we are checking this property + logResult(getElementId(element)); + + // check each property value + for (var j:int = 0; j < propertyNames.length; j++){ + + var propertyName:String = propertyNames[j]; + var e:* = expectedPropertyValues[j]; + var a:*; + + // First need to decide whether to grab the property values from + // the element or its postLayoutTransformOffsets + if (postLayout){ + if (element.postLayoutTransformOffsets){ + a = element.postLayoutTransformOffsets[propertyName]; + } else { + a = null; + } + } else { + a = element[propertyName]; + } + + // prepare the log object for this property + var logItem:Object = new Object(); + logItem.target = getElementId(element); + logItem.propertyName = propertyName; + logItem.actual = a; + logItem.expected = e; + logItem.tolerance = tolerance; + logItem.postLayout = postLayout; + logItem.depth = "TODO"; // TODO: one day might want to keep track of the depth of this item + logItem.result = "Unknown"; + + // + // String comparison + // + + // First just check if expected == actual via a simple string comparison. + // If so then move on to the next propertyName, otherwise investigate further + // via null and number comparisons. + if (String(e) == String(a)){ + // this property passed + + // log the pass + logResult("PASS", logItem); + + continue; + } + + // + // Null comparison + // + + // expected == actual == null so this is fine, continue to next propertyName + if (e == 'null' && a == null){ + + // log the pass + logResult("PASS", logItem); + + continue; + } + + // expected or actual is null, but not both (because of above) so fail + if (e == 'null' || a == null){ + failString = "FAIL: " + describeFailureLocation(element, propertyName, postLayout) + ": " + a + ", but expected " + e; + + // log the fail + logResult(failString, logItem); + + return failString; + } + + // + // Number comparison + // + + // This approach assumes that it's ok treating undefined and NaN the same. + // This is because Number(undefined) gets turned into NaN, if this is a limitation + // might have to revisit this in the future. + var expectedValue:Number = Number(e); + var actualValue:Number = Number(a); + + // + // NaN comparison + // + + // expected == actual == NaN, so this is fine, continue to next propertyName + if (isNaN(actualValue) && isNaN(expectedValue)){ + + // log the pass + logResult("PASS", logItem); + + continue; + } + + // expected or actual is NaN, but not both (because of above) so fail + if (isNaN(actualValue) || isNaN(expectedValue)){ + failString = "FAIL: " + describeFailureLocation(element, propertyName, postLayout) + ": expected " + + expectedValue + ' plus or minus ' + tolerance + ", but received " + actualValue; + + // log the fail + logResult(failString, logItem); + + return failString; + } + + // + // Number tolerance comparison + // + + // expected differs from actual by more than the tolerance so fail + if (Math.abs(actualValue - expectedValue) > tolerance){ + failString = "FAIL: " + describeFailureLocation(element, propertyName, postLayout) + ": expected " + + expectedValue + ' plus or minus ' + tolerance + ", but received " + actualValue; + + // log the fail + logResult(failString, logItem); + + return failString; + } + + // at this point the property passed + + // log the pass + logResult("PASS", logItem); + } + // at this point the element passed, no need to log here + } + + return "PASS"; + } + + /** + * Adds a result to the log. + * + * @param result - a simple string to add to the log + * @param details - an object that if not null is added to the log after setting details.result equal to the first parameter + */ + private static function logResult(result:String, details:Object = null):void { + if (details != null){ + details.result = result; + lastResult.addItem(details); + } else { + lastResult.addItem(result); + } + } + + /** + * Returns the log of the last assertion result + */ + public static function getLastResult():ArrayCollection { + return lastResult; + } + + /** + * Generates a string that describes what property of what element has failed. + * + * ex: + * target.width + * target.postLayoutTransformOffsets.width + */ + private static function describeFailureLocation(element:IVisualElement, propertyName:String, postLayout:Boolean):String{ + var output:String = ""; + + output += getElementId(element); + + if (postLayout) + output += ".postLayoutTransformOffsets"; + + output += "." + propertyName; + + return output; + } + + /** + * Given a root element and a string of property names this returns the formatted string of + * each property value against that element and all descendants in a format that the assertion + * methods require. + * + * @param rootContainer + * @param propertyNamesString - ex: 'width, height, alpha' + * @param postLayout - set to true if you want to access the properties of the postLayoutTransformOffsets + * @param requestedDepth - the depth to recurse (-1 by default for full recursion) + * + * @return string + */ + public static function generatePropertySet(rootContainer:IVisualElement, propertyNamesString:String, postLayout:Boolean = false, requestedDepth:int = -1):String { + // get the list of elements to inspect properties of + var elementsToInspect:Array = getElementsToInspect(rootContainer, requestedDepth); + var propertyNames:Array = getPropertyNames(propertyNamesString); + var output:String = ""; + + // for each element + for (var i:int = 0; i < elementsToInspect.length; i++){ + var e:IVisualElement = elementsToInspect[i]; + + // for each property + for (var j:int = 0; j < propertyNames.length; j++){ + // the property name + var propertyName:String = propertyNames[j]; + + // concatenate the value + if (postLayout){ + if (e.postLayoutTransformOffsets){ + // access the value via the transform offsets + output += e.postLayoutTransformOffsets[propertyName]; + } else { + // the transform offsets are null + output += "null"; + } + } else { + // access the value directly + output += e[propertyName]; + } + + // concatenate the value separator + if (j < propertyNames.length - 1) + output += ","; + } + + // concatenate the element separator + if (i < elementsToInspect.length - 1) + output += elementSeparator; + } + + return output; + } + + /** + * Returns an array of property names parsed from a comma separated string with + * spaces removed. + */ + private static function getPropertyNames(s:String):Array { + // strip spaces + while (s.indexOf(" ") != -1){ + s = s.replace(' ',''); + } + + return s.split(propertySeparator); + } + + /** + * Returns the id of an element, if one is not defined then it returns the class name + */ + private static function getElementId(element:IVisualElement):String { + var s:String = String(Object(element).id); + + return (s != "null") ? s : flash.utils.getQualifiedClassName(element).split("::")[1]; + } + + /** + * Returns an array of all elements in a root element. If the element is not a + * container then it just returns an array of that element. + */ + public static function getElementsToInspect(root:IVisualElement, requestedDepth:int):Array { + var output:Array = new Array(); + + if (root is IVisualElementContainer){ + // if its a container then recursively get all the elements to requestedDepth + output = getDescendants(root as IVisualElementContainer, requestedDepth); + } else { + // just return the element + output.push(root); + } + + return output; + } + + /** + * Recursively generates an array of all elements in a given container (including itself) to a requested depth + */ + private static function getDescendants(rootContainer:IVisualElementContainer, requestedDepth:int, depth:int = 0):Array{ + var output:Array = new Array(); + + // push the container element + output.push(rootContainer); + + // return if we've gone past the requested depth (and a requestedDepth of not -1) + if (requestedDepth != -1 && (depth >= requestedDepth)){ + return output; + } + + for (var i:int = 0; i < rootContainer.numElements; i++){ + var e:IVisualElement = rootContainer.getElementAt(i); + if (e is IVisualElementContainer){ + // recursively get the elements of the container + output = output.concat(getDescendants(e as IVisualElementContainer, requestedDepth, depth+1)); + } else { + // push the non-container element + output.push(e); + } + } + + return output; + } + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-asjs/blob/27eb06f5/mustella/src/main/flex/EnableRemoteImageDiff.as ---------------------------------------------------------------------- diff --git a/mustella/src/main/flex/EnableRemoteImageDiff.as b/mustella/src/main/flex/EnableRemoteImageDiff.as new file mode 100644 index 0000000..d462a34 --- /dev/null +++ b/mustella/src/main/flex/EnableRemoteImageDiff.as @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package { + +import flash.display.DisplayObject; +import flash.net.*; +import flash.events.Event; + +[Mixin] +/** + * A "marker" class that causes test scripts to write out + * bitmaps to the urls instead of reading and comparing + * so that baselines/reference-points can be created for + * future comparing. + */ +public class EnableRemoteImageDiff +{ + + /** + * Mixin callback that gets everything ready to go. + * The UnitTester waits for an event before starting + */ + public static function init(root:DisplayObject):void + { + CompareBitmap.useRemoteDiffer = true; + } + + +} +}