siesta 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
 
3
- Siesta 1.1.0
3
+ Siesta 1.1.5
4
4
  Copyright(c) 2009-2012 Bryntum AB
5
5
  http://bryntum.com/contact
6
6
  http://bryntum.com/products/siesta/license
@@ -6114,7 +6114,9 @@ Class('Siesta.Content.Manager', {
6114
6114
  require : true
6115
6115
  },
6116
6116
 
6117
- urls : Joose.I.Object
6117
+ urls : Joose.I.Object,
6118
+
6119
+ maxLoads : 5
6118
6120
  },
6119
6121
 
6120
6122
 
@@ -6142,29 +6144,42 @@ Class('Siesta.Content.Manager', {
6142
6144
  var errorCount = 0
6143
6145
 
6144
6146
  var total = 0
6145
- Joose.O.each(urls, function () { total++ })
6147
+ var urlsArray = []
6148
+ Joose.O.each(urls, function (value, url) { total++; urlsArray.push(url) })
6146
6149
 
6147
- if (total)
6148
- Joose.O.each(urls, function (value, url) {
6150
+ if (total) {
6151
+
6152
+ var loadSingle = function (url) {
6153
+ if (!url) return
6149
6154
 
6150
6155
  me.load(url, function (content) {
6151
6156
  if (errorCount) return
6152
6157
 
6153
6158
  urls[ url ] = content
6154
6159
 
6155
- if (++loadCount == total) callback && callback()
6160
+ if (++loadCount == total)
6161
+ callback && callback()
6162
+ else
6163
+ loadSingle(urlsArray.shift())
6156
6164
 
6157
6165
  }, ignoreErrors ? function () {
6158
6166
 
6159
- if (++loadCount == total) callback && callback()
6167
+ if (++loadCount == total)
6168
+ callback && callback()
6169
+ else
6170
+ loadSingle(urlsArray.shift())
6160
6171
 
6161
6172
  } : function () {
6162
6173
  errorCount++
6163
6174
 
6164
6175
  errback && errback(url)
6165
6176
  })
6166
- })
6167
- else
6177
+ }
6178
+
6179
+ // running only `maxLoads` "loading threads" at the same time
6180
+ for (var i = 0; i < this.maxLoads; i++) loadSingle(urlsArray.shift())
6181
+
6182
+ } else
6168
6183
  callback && callback()
6169
6184
  },
6170
6185
 
@@ -6206,6 +6221,8 @@ Class('Siesta.Result.Diagnostic', {
6206
6221
  isa : Siesta.Result,
6207
6222
 
6208
6223
  has : {
6224
+ isWarning : false,
6225
+
6209
6226
  isSimulatedEvent : false,
6210
6227
 
6211
6228
  // Used by simulated events
@@ -6217,8 +6234,7 @@ Class('Siesta.Result.Diagnostic', {
6217
6234
  methods : {
6218
6235
 
6219
6236
  toString : function () {
6220
- var message = '# ' + this.description;
6221
- return message;
6237
+ return '# ' + this.description
6222
6238
  }
6223
6239
  }
6224
6240
  });
@@ -6400,6 +6416,7 @@ Role('Siesta.Test.More', {
6400
6416
  'onload',
6401
6417
  'onerror',
6402
6418
  'StartTest',
6419
+ 'startTest',
6403
6420
  // will be reported in IE8 after overriding
6404
6421
  'setTimeout',
6405
6422
  'clearTimeout'
@@ -6636,7 +6653,7 @@ Role('Siesta.Test.More', {
6636
6653
 
6637
6654
  desc = desc || 'throwsOk';
6638
6655
 
6639
- var e = this.global.StartTest.exceptionCatcher(func)
6656
+ var e = this.getExceptionCatcher()(func)
6640
6657
 
6641
6658
  // assuming no one will throw undefined exception..
6642
6659
  if (e === undefined) {
@@ -6648,7 +6665,7 @@ Role('Siesta.Test.More', {
6648
6665
  return
6649
6666
  }
6650
6667
 
6651
- if (e instanceof this.global.StartTest.testError)
6668
+ if (e instanceof this.getTestErrorClass())
6652
6669
  //IE uses non-standard 'description' property for error msg
6653
6670
  e = e.message || e.description
6654
6671
 
@@ -6703,7 +6720,7 @@ Role('Siesta.Test.More', {
6703
6720
  func = [ desc, desc = func][ 0 ]
6704
6721
  }
6705
6722
 
6706
- var e = this.global.StartTest.exceptionCatcher(func)
6723
+ var e = this.getExceptionCatcher()(func)
6707
6724
 
6708
6725
  if (e === undefined)
6709
6726
  this.pass(desc)
@@ -6892,9 +6909,9 @@ Role('Siesta.Test.More', {
6892
6909
  *
6893
6910
  * This method has a synonym with singular name: `expectGlobal`
6894
6911
  *
6895
- * @param {String} name1 The name of global property
6896
- * @param {String} name2 The name of global property
6897
- * @param {String} nameN The name of global property
6912
+ * @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
6913
+ * @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
6914
+ * @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
6898
6915
  */
6899
6916
  expectGlobals : function () {
6900
6917
  this.expectedGlobals.push.apply(this.expectedGlobals, arguments)
@@ -6908,9 +6925,9 @@ Role('Siesta.Test.More', {
6908
6925
  *
6909
6926
  * You can enable this assertion to automatically happen at the end of each test, using {@link Siesta.Harness#autoCheckGlobals autoCheckGlobals} option of the harness.
6910
6927
  *
6911
- * @param {String} name1 The name of global property
6912
- * @param {String} name2 The name of global property
6913
- * @param {String} nameN The name of global property
6928
+ * @param {String/RegExp} name1 The name of global property or the regular expression to match several properties
6929
+ * @param {String/RegExp} name2 The name of global property or the regular expression to match several properties
6930
+ * @param {String/RegExp} nameN The name of global property or the regular expression to match several properties
6914
6931
  */
6915
6932
  verifyGlobals : function () {
6916
6933
  if (this.disableGlobalsCheck) {
@@ -6922,16 +6939,33 @@ Role('Siesta.Test.More', {
6922
6939
  this.expectGlobals.apply(this, arguments)
6923
6940
 
6924
6941
  var me = this
6925
- var expectedGlobals = {}
6926
- var failed = false
6942
+ var expectedStrings = {}
6943
+ var expectedRegExps = []
6927
6944
 
6928
- Joose.A.each(this.expectedGlobals.concat(this.browserGlobals), function (name) { expectedGlobals[ name ] = true })
6945
+ Joose.A.each(this.expectedGlobals.concat(this.browserGlobals), function (value) {
6946
+ if (me.typeOf(value) == 'RegExp')
6947
+ expectedRegExps.push(value)
6948
+ else
6949
+ expectedStrings[ value ] = true
6950
+ })
6929
6951
 
6930
6952
  this.diag('Global variables')
6931
6953
 
6954
+ var failed = false
6955
+
6932
6956
  for (var name in this.global) {
6957
+ if (expectedStrings[ name ]) continue
6933
6958
 
6934
- if (!expectedGlobals[ name ]) {
6959
+ var isExpected = false
6960
+
6961
+ for (var i = 0; i < expectedRegExps.length; i++) {
6962
+ if (expectedRegExps[ i ].test(name)) {
6963
+ isExpected = true
6964
+ break
6965
+ }
6966
+ }
6967
+
6968
+ if (!isExpected) {
6935
6969
  me.fail('Unexpected global found', 'Global name: ' + name + ', value: ' + Siesta.Util.Serializer.stringify(this.global[name]))
6936
6970
 
6937
6971
  failed = true
@@ -6968,7 +7002,7 @@ Role('Siesta.Test.More', {
6968
7002
  else
6969
7003
  this.fail(desc, annotation, result);
6970
7004
  },
6971
-
7005
+
6972
7006
 
6973
7007
  /**
6974
7008
  * Waits for passed checker method to return true (or any non-false value, like for example DOM element or array), and calls the callback when this happens.
@@ -6980,8 +7014,9 @@ Role('Siesta.Test.More', {
6980
7014
  * @param {Function} callback A function to call when the condition has been met. Will receive a result from checker function.
6981
7015
  * @param {Object} scope The scope for the callback
6982
7016
  * @param {Int} timeout The maximum amount of time (in milliseconds) to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
7017
+ * @param {Int} interval The polling interval (in milliseconds)
6983
7018
  */
6984
- waitFor : function (method, callback, scope, timeout) {
7019
+ waitFor : function (method, callback, scope, timeout, interval) {
6985
7020
  var description = this.typeOf(method) == 'Number' ? (method + ' ms') : ' condition to be fullfilled';
6986
7021
  var assertionName = 'waitFor';
6987
7022
 
@@ -6992,11 +7027,17 @@ Role('Siesta.Test.More', {
6992
7027
  callback = options.callback;
6993
7028
  scope = options.scope;
6994
7029
  timeout = options.timeout;
7030
+ interval = options.interval
7031
+
6995
7032
  description = options.description;
6996
7033
  assertionName = options.assertionName || assertionName;
7034
+
6997
7035
  }
6998
7036
 
6999
- var async = this.beginAsync(),
7037
+ interval = interval || this.waitForPollInterval
7038
+ timeout = timeout || this.waitForTimeout
7039
+
7040
+ var async = this.beginAsync(timeout + 2 * interval),
7000
7041
  me = this;
7001
7042
 
7002
7043
  var sourceLine = me.getSourceLine();
@@ -7015,13 +7056,12 @@ Role('Siesta.Test.More', {
7015
7056
  if (this.typeOf(method) == 'Number') {
7016
7057
  pollTimeout = originalSetTimeout(function() {
7017
7058
  me.endAsync(async);
7018
- callback.call(scope || me);
7059
+ me.processCallbackFromTest(callback, [], scope || me)
7060
+
7019
7061
  }, method);
7020
7062
 
7021
7063
  me.finalizeWaiting(waitAssertion, true, 'Waited ' + method + ' ms');
7022
7064
  } else {
7023
-
7024
- timeout = timeout || this.waitForTimeout
7025
7065
 
7026
7066
  var startDate = new Date()
7027
7067
 
@@ -7058,9 +7098,10 @@ Role('Siesta.Test.More', {
7058
7098
  me.endAsync(async);
7059
7099
 
7060
7100
  me.finalizeWaiting(waitAssertion, true, 'Waited ' + time + ' ms for ' + description);
7061
- callback.call(scope || me, result);
7101
+
7102
+ me.processCallbackFromTest(callback, [ result ], scope || me)
7062
7103
  } else
7063
- pollTimeout = originalSetTimeout(pollFunc, me.waitForPollInterval)
7104
+ pollTimeout = originalSetTimeout(pollFunc, interval)
7064
7105
  }
7065
7106
 
7066
7107
  pollFunc()
@@ -7072,8 +7113,8 @@ Role('Siesta.Test.More', {
7072
7113
  // returns "true" if "next" is used,
7073
7114
  analyzeChainStep : function (func) {
7074
7115
  var sources = func.toString()
7075
- var firstArg = sources.match(/function\s*[^(]*\((.*?)(?:,|\))/)[ 1 ]
7076
-
7116
+ var firstArg = sources.match(/function\s*[^(]*\(\s*(.*?)\s*(?:,|\))/)[ 1 ]
7117
+
7077
7118
  if (!firstArg) return false
7078
7119
 
7079
7120
  var body = sources.match(/\{([\s\S]*)\}/)[ 1 ]
@@ -7158,11 +7199,13 @@ Role('Siesta.Test.More', {
7158
7199
  observeTest : this
7159
7200
  })
7160
7201
 
7202
+ var sourceLine = me.getSourceLine();
7203
+
7161
7204
  // inline any arrays in the arguments into one array
7162
- var steps = Array.prototype.concat.apply([], arguments)
7205
+ var steps = Array.prototype.concat.apply([], arguments)
7163
7206
 
7164
- var len = steps.length
7165
- var args = []
7207
+ var len = steps.length
7208
+ var args = []
7166
7209
 
7167
7210
  Joose.A.each(steps, function (step, index) {
7168
7211
 
@@ -7170,10 +7213,29 @@ Role('Siesta.Test.More', {
7170
7213
 
7171
7214
  queue.addAsyncStep({
7172
7215
  processor : function (data) {
7173
- var async = me.beginAsync()
7216
+ var isWaitStep = step.action == 'wait' || step.waitFor
7217
+
7218
+ if (!isWaitStep) {
7219
+ var timeout = step.timeout || me.defaultTimeout
7220
+
7221
+ // + 100 to allow `waitFor` steps (which will be waiting the `timeout` time) to
7222
+ // generate their own failures
7223
+ var async = me.beginAsync(timeout + 100, function () {
7224
+ me.fail(
7225
+ 'The step in `t.chain()` call did not complete within required timeframe, chain can not proceed',
7226
+ {
7227
+ sourceLine : sourceLine,
7228
+ annotation : 'Step number: ' + (index + 1) + ' (1-based)' + (sourceLine ? '\nAt line : ' + sourceLine : ''),
7229
+ ownTextOnly : true
7230
+ }
7231
+ )
7232
+
7233
+ return true
7234
+ })
7235
+ }
7174
7236
 
7175
7237
  var nextFunc = function () {
7176
- me.endAsync(async)
7238
+ if (!isWaitStep) me.endAsync(async)
7177
7239
 
7178
7240
  args = Array.prototype.slice.call(arguments);
7179
7241
 
@@ -7194,9 +7256,23 @@ Role('Siesta.Test.More', {
7194
7256
  // and finalize the async frame manually, as the "nextFunc" for last step will never be called
7195
7257
  isLast && me.endAsync(async)
7196
7258
 
7259
+ } else if (me.typeOf(step) == 'String') {
7260
+ var action = new Siesta.Test.Action.Eval({
7261
+ actionString : step,
7262
+ next : nextFunc,
7263
+ test : me
7264
+ })
7265
+
7266
+ action.process()
7267
+
7197
7268
  } else {
7198
7269
  if (!step.args) step.args = args
7199
7270
 
7271
+ // Don't pass target to next step if it is a waitFor action, it does not make sense and messes up the arguments
7272
+ if (!isLast && (steps[ index + 1 ].waitFor || steps[ index + 1 ].action == 'wait')) {
7273
+ step.passTargetToNext = false;
7274
+ }
7275
+
7200
7276
  step.next = nextFunc
7201
7277
  step.test = me
7202
7278
 
@@ -7303,6 +7379,9 @@ Class('Siesta.Test', {
7303
7379
  results : Joose.I.Array,
7304
7380
 
7305
7381
  run : { required : true },
7382
+ startTestAnchor : null,
7383
+ exceptionCatcher : null,
7384
+ testErrorClass : null,
7306
7385
 
7307
7386
  harness : { required : true },
7308
7387
 
@@ -7428,9 +7507,16 @@ Class('Siesta.Test', {
7428
7507
  * @return {Object} Object with properties `{ ready : true/false, reason : 'description' }`
7429
7508
  */
7430
7509
  isReady: function() {
7510
+ // this should allow us to wait until the presense of "run" function
7511
+ // it will become available after call to StartTest method
7512
+ // which some users may call asynchronously, after some delay
7513
+ // see https://www.assembla.com/spaces/bryntum/tickets/379
7514
+ // in this case test can not be configured using object as 1st argument for StartTest
7515
+ this.run = this.run || this.getStartTestAnchor().args && this.getStartTestAnchor().args[ 0 ]
7516
+
7431
7517
  return {
7432
- ready : true,
7433
- reason : ''
7518
+ ready : this.typeOf(this.run) == 'Function',
7519
+ reason : 'No code provided to test'
7434
7520
  }
7435
7521
  },
7436
7522
 
@@ -7561,7 +7647,7 @@ Class('Siesta.Test', {
7561
7647
  var gotDesc = params.gotDesc || 'Got'
7562
7648
  var needDesc = params.needDesc || 'Need'
7563
7649
 
7564
- if (assertionName || sourceLine) strings.push(
7650
+ if (!params.ownTextOnly && (assertionName || sourceLine)) strings.push(
7565
7651
  'Failed assertion ' + (assertionName ? '[' + assertionName + '] ' : '') + this.formatSourceLine(sourceLine)
7566
7652
  )
7567
7653
 
@@ -7630,23 +7716,83 @@ Class('Siesta.Test', {
7630
7716
  },
7631
7717
 
7632
7718
 
7719
+ getStartTestAnchor : function () {
7720
+ return this.startTestAnchor
7721
+ },
7722
+
7723
+
7724
+ getExceptionCatcher : function () {
7725
+ return this.exceptionCatcher
7726
+ },
7727
+
7728
+
7729
+ getTestErrorClass : function () {
7730
+ return this.testErrorClass
7731
+ },
7732
+
7733
+
7734
+ processCallbackFromTest : function (callback, args, scope) {
7735
+ var me = this
7736
+
7737
+ if (this.transparentEx) {
7738
+ callback.apply(scope || this.global, args || [])
7739
+ } else {
7740
+ var e = this.getExceptionCatcher()(function(){
7741
+ callback.apply(scope || me.global, args || [])
7742
+ })
7743
+
7744
+ if (e) {
7745
+ this.failWithException(e)
7746
+
7747
+ // flow should be interrupted - exception detected
7748
+ return false
7749
+ }
7750
+ }
7751
+
7752
+ // flow can be continued
7753
+ return true
7754
+ },
7755
+
7756
+
7633
7757
  getStackTrace : function (e) {
7634
7758
  if (Object(e) !== e) return null
7635
7759
  if (!e.stack) return null
7636
7760
 
7637
- var text = e.stack
7638
- var traceLineRegex = /\((.*?)\)@(.*?):(\d+)\n/g;
7761
+ var text = e.stack + '';
7762
+ var isFirefox = /^@/.test(text)
7763
+ var lines = text.split('\n')
7764
+
7765
+ var result = []
7639
7766
  var match
7640
7767
 
7641
- var result = []
7768
+ for (var i = 0; i < lines.length; i++) {
7769
+ if (!lines[ i ]) continue
7770
+
7771
+ if (!i) {
7772
+ if (isFirefox)
7773
+ result.push(e + '')
7774
+ else {
7775
+ result.push(lines[ i ])
7776
+ continue;
7777
+ }
7778
+ }
7642
7779
 
7643
- for (var i = 0; match = traceLineRegex.exec(text); i++)
7644
- if (i)
7645
- // other lines
7646
- result.push('at line ' + match[ 3 ] + ' of ' + match[ 2 ])
7647
- else
7648
- // first line
7649
- result.push(match[ 1 ] + ' at line ' + match[ 3 ] + ' of ' + match[ 2 ])
7780
+ if (isFirefox) {
7781
+ match = /@(.*?):(\d+)/.exec(lines[ i ]);
7782
+
7783
+ // the format of stack trace in Firefox has changed, 080_exception_parsing should fail
7784
+ if (!match) return null
7785
+
7786
+ result.push(' at line ' + match[ 2 ] + ' of ' + match[ 1 ])
7787
+ } else {
7788
+ match = /\s*at\s(.*?):(\d+):(\d+)/.exec(lines[ i ]);
7789
+
7790
+ // the format of stack trace in Chrome has changed, 080_exception_parsing should fail
7791
+ if (!match) return null
7792
+
7793
+ result.push(' at line ' + match[ 2 ] + ', character ' + match[ 3 ] + ', of ' + match[ 1 ])
7794
+ }
7795
+ }
7650
7796
 
7651
7797
  if (!result.length) return null
7652
7798
 
@@ -7730,7 +7876,7 @@ Class('Siesta.Test', {
7730
7876
  * @param {String} desc The description of the assertion
7731
7877
  */
7732
7878
  is : function (got, expected, desc) {
7733
- if (got instanceof this.global.Date) {
7879
+ if (expected && got instanceof this.global.Date) {
7734
7880
  this.isDateEqual(got, expected, desc);
7735
7881
  } else if (got == expected)
7736
7882
  this.pass(desc)
@@ -7863,7 +8009,7 @@ Class('Siesta.Test', {
7863
8009
 
7864
8010
  /**
7865
8011
  * This method starts the "asynchronous frame". The test will wait for all asynchronous frames to complete before it will finalize.
7866
- * The frame can be finished with the {@link #endAsync} call.
8012
+ * The frame should be finished with the {@link #endAsync} call within the provided `time`, otherwise a failure will be reported.
7867
8013
  *
7868
8014
  * For example:
7869
8015
  *
@@ -7878,27 +8024,42 @@ Class('Siesta.Test', {
7878
8024
  *
7879
8025
  *
7880
8026
  * @param {Number} time The maximum time (in ms) to wait until force the finalization of this async frame. Optional. Default time is 15000 ms.
8027
+ * @param {Function} errback Optional. The function to call in case the call to {@link #endAsync} was not detected withing `time`. If function
8028
+ * will return any "truthy" value, the failure will not be reported (you can report own failure with this errback).
8029
+ *
7881
8030
  * @return {Object} The frame object, which can be used in {@link #endAsync} call
7882
8031
  */
7883
- beginAsync : function (time) {
8032
+ beginAsync : function (time, errback) {
8033
+ time = time || this.defaultTimeout
8034
+
7884
8035
  var me = this
7885
8036
  var originalSetTimeout = this.originalSetTimeout
7886
8037
 
8038
+ var index = this.timeoutsCount++
8039
+
7887
8040
  // in NodeJS `setTimeout` returns an object and not a simple ID, so we try hard to store that object under unique index
7888
8041
  // also using `setTimeout` from the scope of test - as timeouts in different scopes in browsers are mis-synchronized
7889
8042
  // can't just use `this.originalSetTimeout` because of scoping issues
7890
- var timeoutId = originalSetTimeout(function () {
7891
- me.endAsync(index)
7892
- }, time || this.defaultTimeout)
7893
-
7894
- var index = this.timeoutsCount++
8043
+ var timeoutId = originalSetTimeout(function () {
8044
+
8045
+ if (me.hasAsyncFrame(index)) {
8046
+ if (!errback || !errback.call(me, me)) me.fail('No matching `endAsync` call within ' + time + 'ms')
8047
+
8048
+ me.endAsync(index)
8049
+ }
8050
+ }, time)
7895
8051
 
7896
- this.timeoutIds[ index ] = timeoutId
8052
+ this.timeoutIds[ index ] = timeoutId
7897
8053
 
7898
8054
  return index
7899
8055
  },
7900
8056
 
7901
8057
 
8058
+ hasAsyncFrame : function (index) {
8059
+ return this.timeoutIds.hasOwnProperty(index)
8060
+ },
8061
+
8062
+
7902
8063
  /**
7903
8064
  * This method finalize the "asynchronous frame" started with {@link #beginAsync}.
7904
8065
  *
@@ -7931,7 +8092,6 @@ Class('Siesta.Test', {
7931
8092
 
7932
8093
 
7933
8094
  clearTimeouts : function () {
7934
- var me = this
7935
8095
  var originalClearTimeout = this.originalClearTimeout
7936
8096
 
7937
8097
  Joose.O.each(this.timeoutIds, function (value, id) {
@@ -8008,10 +8168,14 @@ Class('Siesta.Test', {
8008
8168
  originalClearTimeout : this.originalClearTimeout
8009
8169
  })
8010
8170
 
8011
- var exception = this.global.StartTest.exceptionCatcher(function(){
8171
+ var exception = this.getExceptionCatcher()(function(){
8012
8172
  code(todo)
8013
8173
  })
8014
8174
 
8175
+ todo.global = null
8176
+ todo.originalSetTimeout = null
8177
+ todo.originalClearTimeout = null
8178
+
8015
8179
  if (exception !== undefined) this.diag("TODO section threw an exception: [" + exception + "]")
8016
8180
  },
8017
8181
 
@@ -8049,7 +8213,8 @@ Class('Siesta.Test', {
8049
8213
  this.harness.onTestStart(this)
8050
8214
 
8051
8215
  /**
8052
- * This event is fired when the individual test case starts. When started, test may still be waiting for the
8216
+ * This event is fired when the individual test case starts. When *started*, test may still be waiting for the {@link #isReady} conditions
8217
+ * to be fullfilled. Once all conditions will be fullfilled, test will be *launched*.
8053
8218
  *
8054
8219
  * This event bubbles up to the {@link Siesta.Harness harness}, you can observe it on harness as well.
8055
8220
  *
@@ -8061,7 +8226,7 @@ Class('Siesta.Test', {
8061
8226
  this.fireEvent('teststart', this);
8062
8227
 
8063
8228
  if (alreadyFailedWithException) {
8064
- this.failWithException(alreadyFailedWithException)
8229
+ this.failWithException(alreadyFailedWithException)
8065
8230
 
8066
8231
  return
8067
8232
  }
@@ -8141,7 +8306,7 @@ Class('Siesta.Test', {
8141
8306
  else
8142
8307
  // in browser (where `timeoutId` is a number) - to the `idsToIndex` hash
8143
8308
  me.idsToIndex[ timeoutId ] = index
8144
-
8309
+
8145
8310
  return me.timeoutIds[ index ] = timeoutId
8146
8311
  }
8147
8312
 
@@ -8181,10 +8346,13 @@ Class('Siesta.Test', {
8181
8346
  global.clearTimeout = originalClearTimeout
8182
8347
  }
8183
8348
 
8184
- originalSetTimeout = me.originalSetTimeout = null
8185
- originalClearTimeout = me.originalClearTimeout = null
8349
+ originalSetTimeout = me.originalSetTimeout = null
8350
+ originalClearTimeout = me.originalClearTimeout = null
8186
8351
 
8187
- me.global = global = null
8352
+ me.global = global = null
8353
+ me.run = run = null
8354
+ me.exceptionCatcher = me.testErrorClass = null
8355
+ me.startTestAnchor = null
8188
8356
  }
8189
8357
 
8190
8358
  var run = this.run
@@ -8192,7 +8360,7 @@ Class('Siesta.Test', {
8192
8360
  if (this.transparentEx)
8193
8361
  run(me)
8194
8362
  else
8195
- var e = global.StartTest.exceptionCatcher(function(){
8363
+ var e = this.getExceptionCatcher()(function(){
8196
8364
  run(me)
8197
8365
  })
8198
8366
 
@@ -8214,11 +8382,12 @@ Class('Siesta.Test', {
8214
8382
  if (force) this.clearTimeouts()
8215
8383
 
8216
8384
  if (!Joose.O.isEmpty(this.timeoutIds)) {
8217
- if (!this.__timeoutWarning && this.overrideSetTimeout && this.lastActivityDate &&
8218
- new Date() - this.lastActivityDate > this.defaultTimeout)
8219
- {
8385
+ if (
8386
+ !this.__timeoutWarning && this.overrideSetTimeout && this.lastActivityDate &&
8387
+ new Date() - this.lastActivityDate > this.defaultTimeout * 2
8388
+ ) {
8220
8389
  this.diag('Your test is still considered to be running, if this is unexpected please see console for more information');
8221
- this.warn('Warning, your test has not finalized, most likely since a timer (setTimeout) is still active ' +
8390
+ this.warn('Your test [' + this.url + '] has not finalized, most likely since a timer (setTimeout) is still active. ' +
8222
8391
  'If this is the expected behavior, try setting "overrideSetTimeout : false" on your Harness configuration.');
8223
8392
  this.__timeoutWarning = true;
8224
8393
  }
@@ -8259,25 +8428,6 @@ Class('Siesta.Test', {
8259
8428
  this.fireEvent('testfinalize', this);
8260
8429
 
8261
8430
  this.callback && this.callback()
8262
-
8263
- // // attempting to clear all references to scope, but with delay, to allow
8264
- // // other potentially delayed actions to access `global`
8265
- // var me = this
8266
- //
8267
- // var originalSetTimeout = me.originalSetTimeout
8268
- //
8269
- // // setTimeout from the scope of harness
8270
- // originalSetTimeout(function () {
8271
- // if (me.overrideSetTimeout) {
8272
- // me.global.setTimeout = me.originalSetTimeout
8273
- // me.global.clearTimeout = me.originalClearTimeout
8274
- // }
8275
- //
8276
- // originalSetTimeout = me.originalSetTimeout = null
8277
- // me.originalClearTimeout = null
8278
- //
8279
- // me.global = null
8280
- // }, 700)
8281
8431
  },
8282
8432
 
8283
8433
 
@@ -8454,9 +8604,10 @@ Class('Siesta.Test', {
8454
8604
 
8455
8605
 
8456
8606
  warn : function (message) {
8457
- // TODO add "warning" diagnostic message with red background or somethin
8458
-
8459
- if (typeof console != 'undefined' && console.warn) console.warn(message)
8607
+ this.addResult(new Siesta.Result.Diagnostic({
8608
+ description : message,
8609
+ isWarning : true
8610
+ }))
8460
8611
  }
8461
8612
  }
8462
8613
 
@@ -8471,6 +8622,21 @@ Role('Siesta.Test.Todo', {
8471
8622
 
8472
8623
  methods : {
8473
8624
 
8625
+ getExceptionCatcher : function () {
8626
+ return this.parent.getExceptionCatcher()
8627
+ },
8628
+
8629
+
8630
+ getTestErrorClass : function () {
8631
+ return this.parent.getTestErrorClass()
8632
+ },
8633
+
8634
+
8635
+ getStartTestAnchor : function () {
8636
+ return this.parent.getStartTestAnchor()
8637
+ },
8638
+
8639
+
8474
8640
  addResult : function (result) {
8475
8641
  if (result instanceof Siesta.Result.Assertion) result.isTodo = true
8476
8642
 
@@ -8539,6 +8705,10 @@ Class('Siesta.Test.Action', {
8539
8705
  has : {
8540
8706
  args : null,
8541
8707
 
8708
+ /**
8709
+ * @cfg {String} desc When provided, once step is completed, a passing assertion with this text will be added to a test.
8710
+ * This configuration option can be useful to indicate the progress of "wait" steps
8711
+ */
8542
8712
  desc : null,
8543
8713
  test : { required : true },
8544
8714
  next : { required : true },
@@ -8598,6 +8768,8 @@ Class('Siesta.Test.Action.Done', {
8598
8768
 
8599
8769
  process : function () {
8600
8770
  this.test.done(this.delay)
8771
+
8772
+ this.next()
8601
8773
  }
8602
8774
  }
8603
8775
  });
@@ -8673,14 +8845,14 @@ Class('Siesta.Test.Action.Wait', {
8673
8845
  *
8674
8846
  * A number of milliseconds to wait before continuing.
8675
8847
  */
8676
- delay : 1000,
8848
+ delay : 1000,
8677
8849
 
8678
8850
  /**
8679
8851
  * @cfg {Number} timeout
8680
8852
  *
8681
8853
  * The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
8682
8854
  */
8683
- timeout : null,
8855
+ timeout : null,
8684
8856
 
8685
8857
  /**
8686
8858
  * @cfg {Array} args
@@ -8725,25 +8897,24 @@ Class('Siesta.Test.Action.Wait', {
8725
8897
  this.args = [ waitFor ];
8726
8898
  waitFor = '';
8727
8899
  }
8728
-
8900
+
8901
+ if (waitFor == null) {
8902
+ this.args = [ this.delay ];
8903
+ waitFor = '';
8904
+ }
8905
+
8729
8906
  if (this.test.typeOf(this.args) !== "Array") {
8730
8907
  this.args = [ this.args ];
8731
8908
  }
8732
8909
 
8733
- if (waitFor != null) {
8734
- // also allow full method names
8735
- waitFor = waitFor.replace(/^waitFor/, '')
8736
- var methodName = 'waitFor' + Joose.S.uppercaseFirst(waitFor);
8737
-
8738
- if (!test[methodName]){
8739
- throw 'Could not find a waitFor method named ' + methodName;
8740
- }
8741
- test[methodName].apply(test, this.args.concat(this.next, test, this.timeout || test.waitForTimeout));
8742
- } else {
8743
- var originalSetTimeout = test.originalSetTimeout;
8910
+ // also allow full method names
8911
+ waitFor = waitFor.replace(/^waitFor/, '')
8912
+ var methodName = 'waitFor' + Joose.S.uppercaseFirst(waitFor);
8744
8913
 
8745
- originalSetTimeout(this.next, this.delay);
8914
+ if (!test[methodName]){
8915
+ throw 'Could not find a waitFor method named ' + methodName;
8746
8916
  }
8917
+ test[methodName].apply(test, this.args.concat(this.next, test, this.timeout || test.waitForTimeout));
8747
8918
  }
8748
8919
  }
8749
8920
  });
@@ -8753,6 +8924,115 @@ Joose.A.each(['wait', 'delay'], function(name) {
8753
8924
  });;
8754
8925
  /**
8755
8926
 
8927
+ @class Siesta.Test.Action.Eval
8928
+ @extends Siesta.Test.Action
8929
+
8930
+ This action can be included in the `t.chain` steps only with a plain string. Siesta will examine the passed string,
8931
+ and call an apropriate method of the test class. String should have the following format:
8932
+
8933
+ methodName(params)
8934
+
8935
+ Method name is anything until the first parenthes. Method name may have an optional prefix `t.`.
8936
+ Everything in between of outermost parentheses will be treated as parameters for method call. For example:
8937
+
8938
+ t.chain(
8939
+ // string should look like a usual method call,
8940
+ // but arguments can't reference any variables
8941
+ // strings should be quoted, to include quoting symbol in string use double slash: \\
8942
+ 't.click("combo[type=some\\"Type] => .x-form-trigger")',
8943
+
8944
+ // leading "t." is optional, but quoting is not
8945
+ 'waitForComponent("combo[type=someType]")',
8946
+
8947
+ // JSON objects are ok, but they should be a valid JSON - ie object properties should be quoted
8948
+ 'myClick([ 10, 10 ], { "foo" : "bar" })',
8949
+ )
8950
+
8951
+ * **Note** You can pass the JSON objects as arguments, but they should be serialized as valid JSON - ie object properties should be quoted.
8952
+
8953
+ * **Note** A callback for next step in chain will be always appended to provided parameters. Make sure it is placed in a correct spot!
8954
+ For example if method signature is `t.someMethod(param1, param2, callback)` and you are calling this method as:
8955
+
8956
+ t.chain(
8957
+ `t.someMethod("text")`
8958
+ )
8959
+ it will fail - callback will be provided in place of `param2`. Instead call it as:
8960
+
8961
+ t.chain(
8962
+ `t.someMethod("text", null)`
8963
+ )
8964
+
8965
+ This action may save you few keystrokes, when you need to perform some action with static arguments (known prior the action).
8966
+
8967
+ */
8968
+ Class('Siesta.Test.Action.Eval', {
8969
+
8970
+ isa : Siesta.Test.Action,
8971
+
8972
+ has : {
8973
+ /**
8974
+ * @cfg {Object} options
8975
+ *
8976
+ * Any options that will be used when simulating the event. For information about possible
8977
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
8978
+ */
8979
+ actionString : null
8980
+ },
8981
+
8982
+
8983
+ methods : {
8984
+
8985
+ process : function () {
8986
+ var test = this.test
8987
+ var parsed = this.parseActionString(this.actionString)
8988
+
8989
+ if (parsed.error) {
8990
+ test.fail(parsed.error)
8991
+ this.next()
8992
+ return
8993
+ }
8994
+
8995
+ var methodName = parsed.methodName
8996
+
8997
+ if (!methodName || test.typeOf(test[ methodName ]) != 'Function') {
8998
+ test.fail("Invalid method name: " + methodName)
8999
+ this.next()
9000
+ return
9001
+ }
9002
+
9003
+ parsed.params.push(this.next)
9004
+
9005
+ test[ methodName ].apply(test, parsed.params)
9006
+ },
9007
+
9008
+
9009
+ parseActionString : function (actionString) {
9010
+ var match = /^\s*(.+?)\(\s*(.*)\s*\)\s*$/.exec(actionString)
9011
+
9012
+ if (!match) return {
9013
+ error : "Wrong format of the action string: " + actionString
9014
+ }
9015
+
9016
+ var methodName = match[ 1 ].replace(/^t\./, '')
9017
+
9018
+ try {
9019
+ var params = JSON.parse('[' + match[ 2 ] + ']')
9020
+ } catch (e) {
9021
+ return {
9022
+ error : "Can't parse arguments: " + match[ 2 ]
9023
+ }
9024
+ }
9025
+
9026
+ return {
9027
+ methodName : methodName,
9028
+ params : params
9029
+ }
9030
+ }
9031
+ }
9032
+ });
9033
+ ;
9034
+ /**
9035
+
8756
9036
  @class Siesta.Harness
8757
9037
 
8758
9038
  `Siesta.Harness` is an abstract base harness class in Siesta hierarchy. This class provides no UI,
@@ -8836,7 +9116,7 @@ Class('Siesta.Harness', {
8836
9116
 
8837
9117
  has : {
8838
9118
  /**
8839
- * @cfg {String} title The title of the test suite. Can contain HTML.
9119
+ * @cfg {String} title The title of the test suite. Can contain HTML. When provided in the test file descriptor - will change the name of test in the harness UI.
8840
9120
  */
8841
9121
  title : null,
8842
9122
 
@@ -8849,8 +9129,6 @@ Class('Siesta.Harness', {
8849
9129
  testClass : Siesta.Test,
8850
9130
  contentManagerClass : Siesta.Content.Manager,
8851
9131
 
8852
- testsByURL : Joose.I.Object,
8853
-
8854
9132
  // fields of test descriptor:
8855
9133
  // - id - either `url` or wbs + group - computed
8856
9134
  // - url
@@ -8872,7 +9150,12 @@ Class('Siesta.Harness', {
8872
9150
  descriptors : Joose.I.Array,
8873
9151
  descriptorsById : Joose.I.Object,
8874
9152
 
9153
+ launchCounter : 0,
9154
+
9155
+ launches : Joose.I.Object,
9156
+
8875
9157
  scopesByURL : Joose.I.Object,
9158
+ testsByURL : Joose.I.Object,
8876
9159
 
8877
9160
  /**
8878
9161
  * @cfg {Boolean} transparentEx When set to `true` harness will not try to catch any exception, thrown from the test code.
@@ -8935,7 +9218,7 @@ Class('Siesta.Harness', {
8935
9218
  * This option can be also specified in the test file descriptor.
8936
9219
  */
8937
9220
  expectedGlobals : Joose.I.Array,
8938
- // will be populated by `setup`
9221
+ // will be populated by `populateCleanScopeGlobals`
8939
9222
  cleanScopeGlobals : Joose.I.Array,
8940
9223
 
8941
9224
  /**
@@ -9035,6 +9318,17 @@ Class('Siesta.Harness', {
9035
9318
  */
9036
9319
  keepResults : true,
9037
9320
 
9321
+ /**
9322
+ * @cfg {Number} keepNLastResults
9323
+ *
9324
+ * Only meaningful when {@link #keepResults} is set to `false`. Indicates the number of the test results which still should be kept, for user examination.
9325
+ * Results are cleared when their total number exceed this value, based on FIFO order.
9326
+ */
9327
+ keepNLastResults : 2,
9328
+
9329
+ lastResultsURLs : Joose.I.Array,
9330
+ lastResultsByURL : Joose.I.Object,
9331
+
9038
9332
  /**
9039
9333
  * @cfg {Boolean} overrideSetTimeout When set to `false`, the tests will not override "setTimeout" from the context of each test
9040
9334
  * for asynchronous code tracking. User will need to use `beginAsync/endAsync` calls to indicate that test is still running.
@@ -9225,13 +9519,14 @@ Class('Siesta.Harness', {
9225
9519
  },
9226
9520
 
9227
9521
  /**
9228
- * This method will launch a test suite. It accepts a variable number of *test file descriptors*. A test file descritor is one of the following:
9522
+ * This method will launch a test suite. It accepts a variable number of *test file descriptors* or an array of such. A test file descritor is one of the following:
9229
9523
  *
9230
9524
  * - a string, containing a test file url
9231
9525
  * - an object containing the `url` property `{ url : '...', option1 : 'value1', option2 : 'value2' }`. The `url` property should point to the test file.
9232
9526
  * Other properties can contain values of some configuration options of the harness (marked accordingly). In this case, they will **override** the corresponding values,
9233
9527
  * provided to harness or parent descriptor.
9234
- * - an object `{ group : 'groupName', items : [], expanded : true, option1 : 'value1' }` specifying the folder of test files. The `items` property can contain an array of test file descriptors.
9528
+ * - an object `{ group : 'groupName', items : [], expanded : true, option1 : 'value1' }` specifying the folder of test files. The `expanded` property
9529
+ * sets the initial state of the folder - "collapsed/expanded". The `items` property can contain an array of test file descriptors.
9235
9530
  * Other properties will override the applicable harness options **for all child descriptors**.
9236
9531
  *
9237
9532
  * Groups (folder) may contain nested groups. Number of nesting levels is not limited.
@@ -9294,19 +9589,19 @@ Class('Siesta.Harness', {
9294
9589
  *
9295
9590
  * Values from this object takes the highest priority and will override any other configuration.
9296
9591
  *
9297
- * @param {Mixed} descriptor1
9592
+ * @param {Array/Mixed} descriptor1 or an array of descriptors
9298
9593
  * @param {Mixed} descriptor2
9299
9594
  * @param {Mixed} descriptorN
9300
9595
  */
9301
9596
  start : function () {
9302
9597
  // a bit hackish - used by Selenium reporter..
9303
- var me = __ACTIVE_HARNESS__ = this
9598
+ var me = Siesta.my.activeHarness = this
9304
9599
 
9305
9600
  this.mainPreset = new Siesta.Content.Preset({
9306
9601
  preload : this.processPreloadArray(this.preload)
9307
9602
  })
9308
9603
 
9309
- var descriptors = this.descriptors = Joose.A.map(arguments, function (desc, index) {
9604
+ var descriptors = this.descriptors = Joose.A.map(Array.prototype.concat.apply([], arguments), function (desc, index) {
9310
9605
  return me.normalizeDescriptor(desc, me, index)
9311
9606
  })
9312
9607
 
@@ -9334,6 +9629,8 @@ Class('Siesta.Harness', {
9334
9629
  if (desc.preset != me.mainPreset) presets.push(desc.preset)
9335
9630
 
9336
9631
  testScriptsPreset.addResource(desc.url)
9632
+
9633
+ me.deleteTestByURL(desc.url)
9337
9634
  })
9338
9635
 
9339
9636
  // cache either everything (this.cachePreload) or only the test files (to be able to show missing files / show content)
@@ -9359,8 +9656,9 @@ Class('Siesta.Harness', {
9359
9656
 
9360
9657
  // if testConfig contains the "preload" or "alsoPreload" key - then we need to update the preset of the descriptor
9361
9658
  if (testConfig && (testConfig.preload || testConfig.alsoPreload)) desc.preset = me.getDescriptorPreset(desc)
9362
- } else
9363
- desc.isMissing = true
9659
+ } else
9660
+ // allow subclasses to define there own logic when found missing test file
9661
+ me.markMissingFile(desc)
9364
9662
 
9365
9663
  me.normalizeScopeProvider(desc)
9366
9664
  })
@@ -9377,6 +9675,11 @@ Class('Siesta.Harness', {
9377
9675
  },
9378
9676
 
9379
9677
 
9678
+ markMissingFile : function (desc) {
9679
+ desc.isMissing = true
9680
+ },
9681
+
9682
+
9380
9683
  flattenDescriptors : function (descriptors, includeFolders) {
9381
9684
  var flatten = []
9382
9685
  var me = this
@@ -9568,10 +9871,10 @@ Class('Siesta.Harness', {
9568
9871
 
9569
9872
 
9570
9873
  getSeedingCode : function (desc) {
9571
- return 'StartTest = function () { StartTest.args = arguments };' +
9874
+ return 'StartTest = startTest = function () { StartTest.args = arguments };' +
9572
9875
  // for older IE - the try/catch should be from the same context as the exception
9573
9876
  'StartTest.exceptionCatcher = function (func) { var ex; try { func() } catch (e) { ex = e; }; return ex; };' +
9574
- 'StartTest.testError = Error;'
9877
+ 'StartTest.testErrorClass = Error;'
9575
9878
  },
9576
9879
 
9577
9880
 
@@ -9584,12 +9887,54 @@ Class('Siesta.Harness', {
9584
9887
  },
9585
9888
 
9586
9889
 
9890
+ keepTestResult : function (url) {
9891
+ // already keeping
9892
+ if (this.lastResultsByURL[ url ]) {
9893
+ var indexOf = -1
9894
+
9895
+ Joose.A.each(this.lastResultsURLs, function (resultUrl, i) {
9896
+ if (resultUrl == url) { indexOf = i; return false }
9897
+ })
9898
+
9899
+ this.lastResultsURLs.splice(indexOf, 1)
9900
+ this.lastResultsURLs.push(url)
9901
+
9902
+ return
9903
+ }
9904
+
9905
+ this.lastResultsURLs.push(url)
9906
+ this.lastResultsByURL[ url ] = true
9907
+
9908
+ if (this.lastResultsURLs.length > this.keepNLastResults) this.releaseTestResult()
9909
+ },
9910
+
9911
+
9912
+ releaseTestResult : function () {
9913
+ if (this.lastResultsURLs.length <= this.keepNLastResults) return
9914
+
9915
+ var url = this.lastResultsURLs.shift()
9916
+
9917
+ delete this.lastResultsByURL[ url ]
9918
+
9919
+ var test = this.getTestByURL(url)
9920
+
9921
+ if (test && test.isFinished()) this.cleanupScopeForURL(url)
9922
+ },
9923
+
9924
+
9925
+ isKeepingResultForURL : function (url) {
9926
+ return this.lastResultsByURL[ url ]
9927
+ },
9928
+
9929
+
9587
9930
  setupScope : function (desc) {
9588
9931
  var url = desc.url
9589
9932
  var scopeProvideClass = eval(desc.scopeProvider)
9590
9933
 
9591
9934
  this.cleanupScopeForURL(url)
9592
9935
 
9936
+ this.keepTestResult(url)
9937
+
9593
9938
  return this.scopesByURL[ url ] = new scopeProvideClass(this.getScopeProviderConfigFor(desc))
9594
9939
  },
9595
9940
 
@@ -9705,7 +10050,7 @@ Class('Siesta.Harness', {
9705
10050
  var args = startTestAnchor && startTestAnchor.args
9706
10051
 
9707
10052
  // pick either 1st or 2nd argument depending which one is a function
9708
- var runFunc = args && (typeof args[ 0 ] == 'function' && args[ 0 ] || args[ 1 ])
10053
+ var runFunc = args && (typeof args[ 0 ] == 'function' ? args[ 0 ] : args[ 1 ])
9709
10054
 
9710
10055
  me.launchTest({
9711
10056
  testHolder : testHolder,
@@ -9716,6 +10061,8 @@ Class('Siesta.Harness', {
9716
10061
  preloadErrors : preloadErrors,
9717
10062
  onErrorHandler : onErrorHandler,
9718
10063
 
10064
+ startTestAnchor : startTestAnchor,
10065
+
9719
10066
  runFunc : runFunc
9720
10067
  }, callback)
9721
10068
  });
@@ -9736,15 +10083,18 @@ Class('Siesta.Harness', {
9736
10083
  // after the scope setup, the `onerror` handler might be cleared - installing it again
9737
10084
  if (!this.getDescriptorConfig(desc, 'transparentEx')) scopeProvider.addOnErrorHandler(options.onErrorHandler)
9738
10085
 
9739
- var testConfig = me.getNewTestConfiguration(desc, scopeProvider, options.contentManager, options.urlOptions, options.runFunc)
10086
+ var testConfig = me.getNewTestConfiguration(desc, scopeProvider, options.contentManager, options.urlOptions, options.runFunc, options.startTestAnchor)
9740
10087
 
9741
10088
  testConfig.callback = function () {
9742
- if (!me.keepResults) me.cleanupScopeForURL(url)
10089
+ if (!me.keepResults) {
10090
+ if (!me.isKeepingResultForURL(url)) me.cleanupScopeForURL(url)
10091
+ }
9743
10092
 
9744
10093
  callback && callback()
9745
10094
  }
9746
10095
 
9747
- var test = options.testHolder.test = me.testsByURL[ url ] = new testClass(testConfig)
10096
+ var test = options.testHolder.test = new testClass(testConfig)
10097
+ this.saveTestWithURL(url, test)
9748
10098
 
9749
10099
  scopeProvider.scope.setTimeout(function() {
9750
10100
  //console.timeEnd('launch')
@@ -9755,7 +10105,7 @@ Class('Siesta.Harness', {
9755
10105
  },
9756
10106
 
9757
10107
 
9758
- getNewTestConfiguration : function (desc, scopeProvider, contentManager, options, runFunc) {
10108
+ getNewTestConfiguration : function (desc, scopeProvider, contentManager, options, runFunc, startTestAnchor) {
9759
10109
  var scope = scopeProvider.scope
9760
10110
 
9761
10111
  var config = {
@@ -9763,6 +10113,11 @@ Class('Siesta.Harness', {
9763
10113
 
9764
10114
  harness : this,
9765
10115
  run : runFunc,
10116
+
10117
+ startTestAnchor : startTestAnchor,
10118
+
10119
+ exceptionCatcher : startTestAnchor.exceptionCatcher,
10120
+ testErrorClass : startTestAnchor.testErrorClass,
9766
10121
 
9767
10122
  expectedGlobals : this.cleanScopeGlobals.concat(this.getDescriptorConfig(desc, 'expectedGlobals')),
9768
10123
  autoCheckGlobals : this.getDescriptorConfig(desc, 'autoCheckGlobals'),
@@ -9797,18 +10152,34 @@ Class('Siesta.Harness', {
9797
10152
  },
9798
10153
 
9799
10154
 
10155
+ getTestByURL : function (url) {
10156
+ return this.testsByURL[ url ]
10157
+ },
10158
+
10159
+
10160
+ saveTestWithURL : function (url, test) {
10161
+ this.testsByURL[ url ] = test
10162
+ },
10163
+
10164
+
10165
+ deleteTestByURL : function (url) {
10166
+ delete this.testsByURL[ url ]
10167
+ },
10168
+
10169
+
9800
10170
  allPassed : function () {
9801
10171
  var allPassed = true
9802
10172
  var me = this
9803
10173
 
9804
10174
  Joose.A.each(this.flattenDescriptors(this.descriptors), function (descriptor) {
9805
- var test = me.testsByURL[ descriptor.url ]
10175
+ // if at least one test is missing then something is wrong
10176
+ if (descriptor.isMissing) { allPassed = false; return false }
10177
+
10178
+ var test = me.getTestByURL(descriptor.url)
9806
10179
 
9807
10180
  // ignore missing tests (could be skipped by test filtering
9808
10181
  if (!test) return
9809
10182
 
9810
- if (descriptor.isMissing) { allPassed = false; return false }
9811
-
9812
10183
  allPassed = allPassed && test.isPassed()
9813
10184
  })
9814
10185
 
@@ -9825,6 +10196,11 @@ Class('Siesta.Harness', {
9825
10196
  if (!this[ methodName ]) throw "Can't generate report - missing the `" + methodName + "` method"
9826
10197
 
9827
10198
  return this[ methodName ](options)
10199
+ },
10200
+
10201
+
10202
+ typeOf : function (object) {
10203
+ return Object.prototype.toString.call(object).replace(/^\[object /, '').replace(/\]$/, '')
9828
10204
  }
9829
10205
  }
9830
10206
  // eof methods
@@ -18824,6 +19200,15 @@ delegate = function (event) {
18824
19200
  }
18825
19201
  };
18826
19202
  })(window);
19203
+
19204
+ jQuery.fn.center = function () {
19205
+ this.css("position","absolute");
19206
+ this.css("top", Math.max(0, (($(window).height() - this.outerHeight()) / 2) +
19207
+ $(window).scrollTop()) + "px");
19208
+ this.css("left", Math.max(0, (($(window).width() - this.outerWidth()) / 2) +
19209
+ $(window).scrollLeft()) + "px");
19210
+ return this;
19211
+ }
18827
19212
  ;
18828
19213
  Class("JooseX.SimpleRequest", {
18829
19214
 
@@ -19046,6 +19431,8 @@ Role('Siesta.Test.Action.Role.HasTarget', {
19046
19431
  */
19047
19432
  target : { required : false },
19048
19433
 
19434
+ normalizedTarget : null,
19435
+
19049
19436
  /**
19050
19437
  * @cfg {Object} el
19051
19438
  *
@@ -19053,7 +19440,7 @@ Role('Siesta.Test.Action.Role.HasTarget', {
19053
19440
  */
19054
19441
 
19055
19442
  /**
19056
- * @cfg {Boolean} passTargetToNext Whether to pass
19443
+ * @cfg {Boolean} passTargetToNext Whether to pass the target further on chain as the first argument
19057
19444
  */
19058
19445
  passTargetToNext : true
19059
19446
  },
@@ -19066,12 +19453,12 @@ Role('Siesta.Test.Action.Role.HasTarget', {
19066
19453
  var me = this
19067
19454
  var prevNext = this.next
19068
19455
 
19069
- // Needs to be 'resolved' a action instantiate time since the action may cause the selector not to be found
19070
- // e.g. unchecking a checkbox
19071
- var realTarget = me.test.normalizeActionTarget(me.getTarget());
19456
+ // // Needs to be 'resolved' at action instantiate time since the action may cause the selector not to be found
19457
+ // // e.g. unchecking a checkbox
19458
+ // var realTarget = me.test.normalizeActionTarget(me.getTarget());
19072
19459
 
19073
19460
  this.next = function () {
19074
- prevNext.call(this, realTarget);
19461
+ prevNext.call(this, me.normalizedTarget);
19075
19462
  }
19076
19463
  }
19077
19464
  },
@@ -19093,11 +19480,11 @@ Role('Siesta.Test.Action.Role.HasTarget', {
19093
19480
  var test = this.test;
19094
19481
  var target = this.target || test.getElementAtCursor();
19095
19482
 
19096
- if (test.typeOf(target) === 'Function') {
19097
- return this.__cachedTarget__ = target.call(test, this);
19098
- }
19483
+ if (test.typeOf(target) === 'Function') target = target.call(test, this);
19484
+
19485
+ this.normalizedTarget = test.normalizeActionTarget(target)
19099
19486
 
19100
- return this.__cachedTarget__ = target;
19487
+ return this.__cachedTarget__ = target
19101
19488
  }
19102
19489
  }
19103
19490
  });
@@ -19189,19 +19576,100 @@ Siesta.Test.ActionRegistry.registerAction('longpress', Siesta.Test.Action.LongPr
19189
19576
  ;
19190
19577
  /**
19191
19578
 
19192
- @class Siesta.Test.Action.MouseDown
19579
+ @class Siesta.Test.Action.Tap
19193
19580
  @extends Siesta.Test.Action
19194
19581
  @mixin Siesta.Test.Action.Role.HasTarget
19195
19582
 
19196
- This action can be included in a `t.chain` call with "mouseDown" shortcut:
19583
+ This action can be included in the `t.chain` call with "tap" shortcut:
19197
19584
 
19198
19585
  t.chain(
19199
19586
  {
19200
- action : 'mouseDown',
19587
+ action : 'tap',
19201
19588
  target : someDOMElement
19202
19589
  }
19203
19590
  )
19204
19591
 
19592
+ This action will perform a {@link Siesta.Test.Browser#tap tap} on the provided {@link #target}.
19593
+
19594
+ */
19595
+ Class('Siesta.Test.Action.Tap', {
19596
+
19597
+ isa : Siesta.Test.Action,
19598
+
19599
+ does : Siesta.Test.Action.Role.HasTarget,
19600
+
19601
+ has : {
19602
+ requiredTestMethod : 'tap'
19603
+ },
19604
+
19605
+
19606
+ methods : {
19607
+
19608
+ process : function () {
19609
+ this.test.tap(this.getTarget(), this.next)
19610
+ }
19611
+ }
19612
+ });
19613
+
19614
+
19615
+ Siesta.Test.ActionRegistry.registerAction('tap', Siesta.Test.Action.Tap);
19616
+ /**
19617
+
19618
+ @class Siesta.Test.Action.DoubleTap
19619
+ @extends Siesta.Test.Action
19620
+ @mixin Siesta.Test.Action.Role.HasTarget
19621
+
19622
+ This action will perform a {@link Siesta.Test.Browser#doubleClick double tap} on the provided {@link #target}.
19623
+
19624
+ This action can be included in the `t.chain` call with "doubletap" or "doubleTap" shortcuts:
19625
+
19626
+ t.chain(
19627
+ {
19628
+ action : 'doubletap',
19629
+ target : someDOMElement
19630
+ }
19631
+ )
19632
+
19633
+
19634
+ */
19635
+ Class('Siesta.Test.Action.DoubleTap', {
19636
+
19637
+ isa : Siesta.Test.Action,
19638
+
19639
+ does : Siesta.Test.Action.Role.HasTarget,
19640
+
19641
+ has : {
19642
+ requiredTestMethod : 'doubleTap'
19643
+ },
19644
+
19645
+
19646
+ methods : {
19647
+
19648
+ process : function () {
19649
+ this.test.doubleTap(this.getTarget(), this.next)
19650
+ }
19651
+ }
19652
+ });
19653
+
19654
+
19655
+ Siesta.Test.ActionRegistry.registerAction('doubletap', Siesta.Test.Action.DoubleTap)
19656
+ ;
19657
+ /**
19658
+
19659
+ @class Siesta.Test.Action.MouseDown
19660
+ @extends Siesta.Test.Action
19661
+ @mixin Siesta.Test.Action.Role.HasTarget
19662
+
19663
+ This action can be included in a `t.chain` call with "mouseDown" shortcut:
19664
+
19665
+ t.chain(
19666
+ {
19667
+ action : 'mouseDown',
19668
+ target : someDOMElement,
19669
+ options : { shiftKey : true } // Optionally hold shiftkey
19670
+ }
19671
+ )
19672
+
19205
19673
  This action will perform a {@link Siesta.Test.Browser#MouseDown MouseDown} on the provided {@link #target}.
19206
19674
 
19207
19675
  */
@@ -19212,7 +19680,15 @@ Class('Siesta.Test.Action.MouseDown', {
19212
19680
  does : Siesta.Test.Action.Role.HasTarget,
19213
19681
 
19214
19682
  has : {
19215
- requiredTestMethod : 'mouseDown'
19683
+ requiredTestMethod : 'mouseDown',
19684
+
19685
+ /**
19686
+ * @cfg {Object} options
19687
+ *
19688
+ * Any options that will be used when simulating the event. For information about possible
19689
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
19690
+ */
19691
+ options : null
19216
19692
  },
19217
19693
 
19218
19694
 
@@ -19220,7 +19696,7 @@ Class('Siesta.Test.Action.MouseDown', {
19220
19696
 
19221
19697
  process : function () {
19222
19698
  // This method is synchronous
19223
- this.test.mouseDown(this.getTarget());
19699
+ this.test.mouseDown(this.getTarget(), this.options);
19224
19700
 
19225
19701
  setTimeout(this.next, 100);
19226
19702
  }
@@ -19242,7 +19718,8 @@ This action can be included in a `t.chain` call with "mouseUp" shortcut:
19242
19718
  t.chain(
19243
19719
  {
19244
19720
  action : 'mouseUp',
19245
- target : someDOMElement
19721
+ target : someDOMElement,
19722
+ options : { shiftKey : true } // Optionally hold shiftkey
19246
19723
  }
19247
19724
  )
19248
19725
 
@@ -19256,7 +19733,15 @@ Class('Siesta.Test.Action.MouseUp', {
19256
19733
  does : Siesta.Test.Action.Role.HasTarget,
19257
19734
 
19258
19735
  has : {
19259
- requiredTestMethod : 'mouseUp'
19736
+ requiredTestMethod : 'mouseUp',
19737
+
19738
+ /**
19739
+ * @cfg {Object} options
19740
+ *
19741
+ * Any options that will be used when simulating the event. For information about possible
19742
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
19743
+ */
19744
+ options : null
19260
19745
  },
19261
19746
 
19262
19747
 
@@ -19264,7 +19749,7 @@ Class('Siesta.Test.Action.MouseUp', {
19264
19749
 
19265
19750
  process : function () {
19266
19751
  // This method is synchronous
19267
- this.test.mouseUp(this.getTarget());
19752
+ this.test.mouseUp(this.getTarget(), this.options);
19268
19753
 
19269
19754
  setTimeout(this.next, 100);
19270
19755
  }
@@ -19286,7 +19771,8 @@ This action can be included in the `t.chain` call with "click" shortcut:
19286
19771
  t.chain(
19287
19772
  {
19288
19773
  action : 'click',
19289
- target : someDOMElement
19774
+ target : someDOMElement,
19775
+ options : { shiftKey : true } // Optionally hold shiftkey
19290
19776
  }
19291
19777
  )
19292
19778
 
@@ -19300,21 +19786,29 @@ Class('Siesta.Test.Action.Click', {
19300
19786
  does : Siesta.Test.Action.Role.HasTarget,
19301
19787
 
19302
19788
  has : {
19303
- requiredTestMethod : 'click'
19789
+ requiredTestMethod : 'click',
19790
+
19791
+ /**
19792
+ * @cfg {Object} options
19793
+ *
19794
+ * Any options that will be used when simulating the event. For information about possible
19795
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
19796
+ */
19797
+ options : null
19304
19798
  },
19305
19799
 
19306
19800
 
19307
19801
  methods : {
19308
19802
 
19309
19803
  process : function () {
19310
- this.test.click(this.getTarget(), this.next)
19804
+ this.test.click(this.getTarget(), this.next, null, this.options);
19311
19805
  }
19312
19806
  }
19313
19807
  });
19314
19808
 
19315
19809
 
19316
- Siesta.Test.ActionRegistry.registerAction('click', Siesta.Test.Action.Click)
19317
- Siesta.Test.ActionRegistry.registerAction('tap', Siesta.Test.Action.Click);
19810
+ Siesta.Test.ActionRegistry.registerAction('click', Siesta.Test.Action.Click);
19811
+ ;
19318
19812
  /**
19319
19813
 
19320
19814
  @class Siesta.Test.Action.DoubleClick
@@ -19327,8 +19821,9 @@ This action can be included in the `t.chain` call with "doubleclick" or "doubleC
19327
19821
 
19328
19822
  t.chain(
19329
19823
  {
19330
- action : 'click',
19331
- target : someDOMElement
19824
+ action : 'doubleclick',
19825
+ target : someDOMElement,
19826
+ options : { shiftKey : true } // Optionally hold shiftkey
19332
19827
  }
19333
19828
  )
19334
19829
 
@@ -19341,21 +19836,28 @@ Class('Siesta.Test.Action.DoubleClick', {
19341
19836
  does : Siesta.Test.Action.Role.HasTarget,
19342
19837
 
19343
19838
  has : {
19344
- requiredTestMethod : 'doubleClick'
19839
+ requiredTestMethod : 'doubleClick',
19840
+
19841
+ /**
19842
+ * @cfg {Object} options
19843
+ *
19844
+ * Any options that will be used when simulating the event. For information about possible
19845
+ * config options, please see: https://developer.mozilla.org/en-US/docs/DOM/event.initMouseEvent
19846
+ */
19847
+ options : null
19345
19848
  },
19346
19849
 
19347
19850
 
19348
19851
  methods : {
19349
19852
 
19350
19853
  process : function () {
19351
- this.test.doubleClick(this.getTarget(), this.next)
19854
+ this.test.doubleClick(this.getTarget(), this.next, null, this.options)
19352
19855
  }
19353
19856
  }
19354
19857
  });
19355
19858
 
19356
19859
 
19357
19860
  Siesta.Test.ActionRegistry.registerAction('doubleclick', Siesta.Test.Action.DoubleClick)
19358
- Siesta.Test.ActionRegistry.registerAction('doubletap', Siesta.Test.Action.DoubleClick)
19359
19861
  ;
19360
19862
  /**
19361
19863
 
@@ -19402,6 +19904,7 @@ Class('Siesta.Test.Action.Type', {
19402
19904
  // By default use the current focused element as target
19403
19905
  this.target = this.target || this.test.global.document.activeElement;
19404
19906
 
19907
+ // additional "getTarget" to allow functions as "target" value
19405
19908
  this.test.type(this.getTarget(), this.text, this.next)
19406
19909
  }
19407
19910
  }
@@ -19788,7 +20291,7 @@ Role('Siesta.Test.Simulate.Mouse', {
19788
20291
 
19789
20292
  // Normalize target
19790
20293
  if (!this.isArray(target)) {
19791
- target = this.detectCenter(this.normalizeElement(target));
20294
+ target = this.detectCenter(this.normalizeElement(target), 'moveMouseTo');
19792
20295
  }
19793
20296
  this.moveMouse(this.currentPosition, target, callback, scope);
19794
20297
  },
@@ -19900,12 +20403,12 @@ Role('Siesta.Test.Simulate.Mouse', {
19900
20403
  queue.run(function () {
19901
20404
  me.endAsync(a);
19902
20405
 
19903
- callback && callback.call(scope || me);
20406
+ callback && me.processCallbackFromTest(callback, null, scope || me)
19904
20407
  })
19905
20408
  },
19906
20409
 
19907
20410
 
19908
- normalizeClickTarget : function (el) {
20411
+ normalizeClickTarget : function (el, clickMethod) {
19909
20412
  var doc = this.global.document
19910
20413
  var xy
19911
20414
 
@@ -19914,13 +20417,12 @@ Role('Siesta.Test.Simulate.Mouse', {
19914
20417
  if (this.isArray(el)) {
19915
20418
  xy = el;
19916
20419
  el = doc.elementFromPoint(xy[0], xy[1]) || doc.body;
19917
- options = { clientX : xy[0], clientY : xy[1] };
19918
20420
  } else {
19919
20421
  el = this.normalizeElement(el)
19920
20422
  doc = el.ownerDocument
19921
- xy = this.detectCenter(el);
20423
+ xy = this.detectCenter(el, clickMethod);
19922
20424
  el = doc.elementFromPoint(xy[0], xy[1]) || doc.body;
19923
- options = { clientX : xy[0], clientY : xy[1] };
20425
+ el && this.$(el).is(':visible');
19924
20426
  }
19925
20427
 
19926
20428
  if (!el) {
@@ -19935,14 +20437,18 @@ Role('Siesta.Test.Simulate.Mouse', {
19935
20437
  },
19936
20438
 
19937
20439
 
19938
- genericMouseClick : function (el, callback, scope, clickMethod) {
20440
+ genericMouseClick : function (el, callback, scope, options, clickMethod) {
19939
20441
  if (jQuery.isFunction(el)) {
19940
20442
  scope = callback;
19941
20443
  callback = el;
19942
20444
  el = null;
19943
20445
  }
19944
20446
 
19945
- var data = this.normalizeClickTarget(el)
20447
+ var data = this.normalizeClickTarget(el, clickMethod);
20448
+
20449
+ data.options = data.options || {};
20450
+
20451
+ $.extend(data.options, options);
19946
20452
 
19947
20453
  // the asynchronous case
19948
20454
  if (this.moveCursorBetweenPoints && callback) {
@@ -19973,10 +20479,11 @@ Role('Siesta.Test.Simulate.Mouse', {
19973
20479
  *
19974
20480
  * @param {Siesta.Test.ActionTarget} (optional) el One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
19975
20481
  * @param {Function} callback (optional) A function to call when the condition has been met.
19976
- * @param {Object} scope (optional) The scope for the callback
20482
+ * @param {Object} scope (optional) The scope for the callback
20483
+ * @param {Object} options (optional) Any options to use for the simulated DOM event
19977
20484
  */
19978
- click: function (el, callback, scope) {
19979
- this.genericMouseClick(el, callback, scope, 'simulateMouseClick')
20485
+ click: function (el, callback, scope, options) {
20486
+ this.genericMouseClick(el, callback, scope, options, 'simulateMouseClick')
19980
20487
  },
19981
20488
 
19982
20489
 
@@ -20000,10 +20507,11 @@ Role('Siesta.Test.Simulate.Mouse', {
20000
20507
  *
20001
20508
  * @param {Siesta.Test.ActionTarget} (optional) el One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
20002
20509
  * @param {Function} callback (optional) A function to call when the condition has been met.
20003
- * @param {Object} scope (optional) The scope for the callback
20510
+ * @param {Object} scope (optional) The scope for the callback
20511
+ * @param {Object} options (optional) Any options to use for the simulated DOM event
20004
20512
  */
20005
- rightClick: function (el, callback, scope) {
20006
- this.genericMouseClick(el, callback, scope, 'simulateRightClick')
20513
+ rightClick: function (el, callback, scope, options) {
20514
+ this.genericMouseClick(el, callback, scope, options, 'simulateRightClick')
20007
20515
  },
20008
20516
 
20009
20517
 
@@ -20027,10 +20535,11 @@ Role('Siesta.Test.Simulate.Mouse', {
20027
20535
  *
20028
20536
  * @param {Siesta.Test.ActionTarget} (optional) el One of the {@link Siesta.Test.ActionTarget} values to convert to DOM element
20029
20537
  * @param {Function} callback (optional) A function to call when the condition has been met.
20030
- * @param {Object} scope (optional) The scope for the callback
20538
+ * @param {Object} scope (optional) The scope for the callback
20539
+ * @param {Object} options (optional) Any options to use for the simulated DOM event
20031
20540
  */
20032
- doubleClick: function (el, callback, scope) {
20033
- this.genericMouseClick(el, callback, scope, 'simulateDoubleClick')
20541
+ doubleClick: function (el, callback, scope, options) {
20542
+ this.genericMouseClick(el, callback, scope, options, 'simulateDoubleClick')
20034
20543
  },
20035
20544
 
20036
20545
  /**
@@ -20130,7 +20639,7 @@ Role('Siesta.Test.Simulate.Mouse', {
20130
20639
  queue.run(function () {
20131
20640
  me.endAsync(async);
20132
20641
 
20133
- callback && callback.call(scope || me);
20642
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20134
20643
  })
20135
20644
  },
20136
20645
 
@@ -20169,7 +20678,7 @@ Role('Siesta.Test.Simulate.Mouse', {
20169
20678
  queue.run(function () {
20170
20679
  me.endAsync(async);
20171
20680
 
20172
- callback && callback.call(scope || me);
20681
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20173
20682
  })
20174
20683
  },
20175
20684
 
@@ -20212,7 +20721,7 @@ Role('Siesta.Test.Simulate.Mouse', {
20212
20721
  queue.run(function () {
20213
20722
  me.endAsync(async);
20214
20723
 
20215
- callback && callback.call(scope || me);
20724
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20216
20725
  })
20217
20726
  },
20218
20727
 
@@ -20285,7 +20794,7 @@ Role('Siesta.Test.Simulate.Mouse', {
20285
20794
  if (this.isArray(source)) {
20286
20795
  sourceXY = source;
20287
20796
  } else {
20288
- sourceXY = this.detectCenter(this.normalizeElement(source));
20797
+ sourceXY = this.detectCenter(this.normalizeElement(source), 'dragTo');
20289
20798
  }
20290
20799
 
20291
20800
  // Normalize target
@@ -20328,7 +20837,7 @@ Role('Siesta.Test.Simulate.Mouse', {
20328
20837
  if (this.isArray(source)) {
20329
20838
  sourceXY = source;
20330
20839
  } else {
20331
- sourceXY = this.detectCenter(this.normalizeElement(source));
20840
+ sourceXY = this.detectCenter(this.normalizeElement(source), 'dragBy');
20332
20841
  }
20333
20842
  targetXY = [ sourceXY[0] + delta[0], sourceXY[1] + delta[1] ];
20334
20843
 
@@ -20422,16 +20931,21 @@ Role('Siesta.Test.Simulate.Mouse', {
20422
20931
  queue.run(function () {
20423
20932
  me.endAsync(async)
20424
20933
 
20425
- callback && callback.call(scope || me)
20934
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20426
20935
  });
20427
20936
  },
20428
20937
 
20429
- detectCenter : function(el) {
20938
+ detectCenter : function (el, actionName, skipWarning) {
20430
20939
  var hidden = !this.isElementVisible(el);
20431
20940
 
20432
20941
  // Trigger mouseover in case source is hidden, possibly shown only when hovering over it (its x/y cannot be determined if display:none)
20433
20942
  if (hidden) {
20434
- this.simulateEvent(el, "mouseover", { clientX: 0, clientY: 0});
20943
+ this.simulateEvent(el, "mouseover", { clientX: 0, clientY: 0 });
20944
+
20945
+ if (!skipWarning && !this.isElementVisible(el)) this.fail(
20946
+ (actionName ? "Target element of action [" + actionName + "]" : "Target element of some action") +
20947
+ " is not visible: " + (el.id ? '#' + el.id : el)
20948
+ )
20435
20949
  }
20436
20950
  var center = this.findCenter(el);
20437
20951
  if (hidden) {
@@ -20774,21 +21288,22 @@ Role('Siesta.Test.Simulate.Keyboard', {
20774
21288
  * @param {Object} scope (optional) the scope for the callback
20775
21289
  */
20776
21290
  type: function (el, text, callback, scope) {
20777
- el = this.normalizeElement(el || this.getElementAtCursor());
21291
+ el = this.normalizeElement(el || this.global.document.activeElement);
20778
21292
 
20779
21293
  // Some browsers (IE/FF) do not overwrite selected text, do it manually.
20780
21294
  var selText = this.getSelectedText(el);
21295
+
20781
21296
  if (selText) {
20782
21297
  el.value = el.value.replace(selText, '');
20783
21298
  }
20784
21299
 
21300
+ var me = this
21301
+
20785
21302
  if (el.readOnly || el.disabled) {
20786
- callback && callback.call(scope || me)
21303
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20787
21304
 
20788
21305
  return;
20789
21306
  }
20790
-
20791
- var me = this
20792
21307
 
20793
21308
  // Extract normal chars, or special keys in brackets such as [TAB], [RIGHT] or [ENTER]
20794
21309
  var keys = (text + '').match(/\[([^\])]+\])|([^\[])/g) || [];
@@ -20838,7 +21353,7 @@ Role('Siesta.Test.Simulate.Keyboard', {
20838
21353
  queue.run(function () {
20839
21354
  me.endAsync(async)
20840
21355
 
20841
- callback && callback.call(scope || me)
21356
+ callback && me.processCallbackFromTest(callback, null, scope || me)
20842
21357
  })
20843
21358
  },
20844
21359
 
@@ -21125,6 +21640,37 @@ Role('Siesta.Test.ExtJSCore', {
21125
21640
  return {
21126
21641
  ready : true
21127
21642
  }
21643
+ },
21644
+
21645
+ // Overridden to deal with the different event firing mechanisms in Ext JS 3 vs 4
21646
+ // This code is required because in IE are simulated using fireEvent instead of dispatchEvent and it seems fireEvent will
21647
+ // will not update a checkbox 'checked' state properly so we're forcing the toggle to solve this situation.
21648
+ // This issue is only relevant in IE + Ext.
21649
+ //
21650
+ // Test case: 507_form_checkbox.t.js
21651
+ simulateMouseClick: function (el, callback, scope) {
21652
+
21653
+ // Force check toggle for input checkboxes
21654
+ if (this.simulateEventsWith === 'fireEvent' && (el.type === 'checkbox' || el.type === 'radio') && !el.disabled && !el.readOnly) {
21655
+ var oldState = el.checked;
21656
+
21657
+ if (callback) {
21658
+ this.SUPER(el, function() {
21659
+ if (el.checked === oldState) {
21660
+ el.checked = !oldState;
21661
+ }
21662
+ callback.call(scope || this);
21663
+ });
21664
+ } else {
21665
+ this.SUPER(el);
21666
+
21667
+ if (el.checked === oldState) {
21668
+ el.checked = !oldState;
21669
+ }
21670
+ }
21671
+ } else {
21672
+ this.SUPERARG(arguments);
21673
+ }
21128
21674
  }
21129
21675
  },
21130
21676
 
@@ -21228,11 +21774,33 @@ Role('Siesta.Test.ExtJSCore', {
21228
21774
 
21229
21775
  return component;
21230
21776
  },
21231
-
21232
-
21233
- compToEl : function (comp) {
21777
+
21778
+ /**
21779
+ * @private
21780
+ * @param {Ext.Component} comp the Ext.Component
21781
+ * @param {Boolean} locateInputEl For form fields, try to find the inner input element by default.
21782
+ * If you want to target the containing Component element, pass false instead.
21783
+ * @return {*}
21784
+ */
21785
+ compToEl : function (comp, locateInputEl) {
21786
+ var Ext = this.Ext();
21787
+
21234
21788
  if (!comp) return null
21235
-
21789
+
21790
+ locateInputEl = locateInputEl !== false;
21791
+
21792
+ // Ext JS
21793
+ if (Ext && Ext.form && Ext.form.Field && locateInputEl) {
21794
+ if (comp instanceof Ext.form.Field && comp.inputEl){
21795
+ return comp.inputEl;
21796
+ }
21797
+ }
21798
+
21799
+ // Sencha Touch: Form fields can have a child input component
21800
+ if (Ext && Ext.field && Ext.field.Field && comp instanceof Ext.field.Field && locateInputEl) {
21801
+ comp = comp.getComponent();
21802
+ }
21803
+
21236
21804
  // Ext JS vs Sencha Touch
21237
21805
  return comp.getEl ? comp.getEl() : (comp.el || comp.element);
21238
21806
  },
@@ -21262,17 +21830,14 @@ Role('Siesta.Test.ExtJSCore', {
21262
21830
  }
21263
21831
  }
21264
21832
 
21265
- if (el instanceof Ext.form.Field && el.inputEl) {
21266
- el = el.inputEl;
21267
- } else
21268
- if (el instanceof Ext.Component) {
21269
- el = this.compToEl(el);
21270
- var center = this.findCenter(el);
21271
-
21272
- el = this.elementFromPoint(center[0], center[1]) || el;
21273
- }
21274
-
21275
- // ExtJS Element
21833
+ if (Ext && Ext.Component && el instanceof Ext.Component) {
21834
+ el = this.compToEl(el);
21835
+ var center = this.findCenter(el);
21836
+
21837
+ el = this.elementFromPoint(center[0], center[1]) || el;
21838
+ }
21839
+
21840
+ // ExtJS Element
21276
21841
  if (el && el.dom) return el.dom
21277
21842
 
21278
21843
  // will also handle the case of conversion of array with coordinates to el
@@ -21373,7 +21938,7 @@ Role('Siesta.Test.ExtJSCore', {
21373
21938
  me.fail("Class: " + className + " was loaded")
21374
21939
  })
21375
21940
 
21376
- callback && callback()
21941
+ callback && me.processCallbackFromTest(callback)
21377
21942
  }
21378
21943
 
21379
21944
  var timeout = Ext.isIE ? 120000 : 30000,
@@ -21487,8 +22052,9 @@ Role('Siesta.Test.ExtJSCore', {
21487
22052
  cmp = cmp[0];
21488
22053
 
21489
22054
  if (!cmp.rendered) throw 'The source component of the composite query: ' + cmp.id + ' is not yet rendered';
21490
-
21491
- return this.compToEl(cmp).query(result[1]);
22055
+
22056
+
22057
+ return this.compToEl(cmp, false).query(result[1]);
21492
22058
  },
21493
22059
 
21494
22060
  /**
@@ -21766,7 +22332,7 @@ Role('Siesta.Test.ExtJS.Observable', {
21766
22332
  annotation : n + " '" + event + "' events were expected, but " + counter + ' were fired'
21767
22333
  });
21768
22334
 
21769
- callback && callback();
22335
+ callback && me.processCallbackFromTest(callback);
21770
22336
 
21771
22337
  }, timeOut);
21772
22338
 
@@ -22302,7 +22868,9 @@ Role('Siesta.Test.ExtJS.Component', {
22302
22868
  * @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
22303
22869
  */
22304
22870
  waitForComponent: function (component, rendered, callback, scope, timeout) {
22305
- var Ext = this.getExt();
22871
+ var Ext = this.getExt();
22872
+ var xtype
22873
+
22306
22874
  if (Ext.isString(component)) {
22307
22875
  xtype = Ext.ClassManager.get(component).xtype;
22308
22876
  } else {
@@ -22340,6 +22908,75 @@ Role('Siesta.Test.ExtJS.Component', {
22340
22908
  hasPosition: function (component, x, y, description) {
22341
22909
  component = this.normalizeComponent(component);
22342
22910
  this.isDeeply(component.getPosition(), [x, y], description);
22911
+ },
22912
+
22913
+
22914
+ /**
22915
+ * This assertion accepts variable number of Ext.Component instances (can be also provided as component query string).
22916
+ * Then it calls their "destroy" method and verifies that:
22917
+ * - there were no exceptions during destroy
22918
+ * - that each component was actually destoyed (since destroy can be canceled in the "beforedestroy" event listener)
22919
+ *
22920
+ * @param {Ext.Component/Array[Ext.Component]/String} components A single instance of Ext.Component, an array of such or a string with component query
22921
+ * @param {String} description The description of the assertion
22922
+ */
22923
+ destroysOk : function (components, description) {
22924
+ var Ext = this.Ext();
22925
+
22926
+ if (this.typeOf(components) != 'Array') {
22927
+ if (this.typeOf(components) == 'String')
22928
+ components = this.Ext().ComponentQuery.query(components);
22929
+ else
22930
+ components = [ components ]
22931
+ }
22932
+
22933
+ if (!components.length) {
22934
+ this.fail(description, {
22935
+ assertionName : 'destroysOk',
22936
+ annotation : 'No components provided, or component query returned empty result'
22937
+ })
22938
+
22939
+ return
22940
+ }
22941
+
22942
+ var currentComp
22943
+
22944
+ var e = this.getExceptionCatcher()(function () {
22945
+ Joose.A.each(components, function (component) {
22946
+ currentComp = component
22947
+
22948
+ component.destroy()
22949
+ })
22950
+ })
22951
+
22952
+ if (e !== undefined) {
22953
+ this.fail(description, {
22954
+ assertionName : 'destroysOk',
22955
+ got : e,
22956
+ gotDesc : 'Exception',
22957
+ annotation : 'Exception thrown while calling "destroy" method of ' + currentComp.id
22958
+ })
22959
+
22960
+ return
22961
+ }
22962
+
22963
+ var me = this
22964
+
22965
+ var allDestroyed = Joose.A.each(components, function (component) {
22966
+ // ExtJS ST
22967
+ if (!(component.isDestroyed || component.destroy == Ext.emptyFn)) {
22968
+ me.fail(description, {
22969
+ assertionName : 'destroysOk',
22970
+ annotation : 'Component [' + component.id + '] was not destroyed (probably destroy was canceled in the `beforedestroy` listener)'
22971
+ })
22972
+
22973
+ return false
22974
+ }
22975
+ })
22976
+
22977
+ if (allDestroyed === false) return
22978
+
22979
+ this.pass(description)
22343
22980
  }
22344
22981
  }
22345
22982
  });
@@ -22568,13 +23205,14 @@ Role('Siesta.Test.Element', {
22568
23205
 
22569
23206
 
22570
23207
  /**
22571
- * Returns true if the element is visible.
23208
+ * Returns true if the element is visible, checking jQuery :visible selector + style visibilty value.
22572
23209
  * @param {Siesta.Test.ActionTarget} el The element
22573
23210
  * @return {Boolean}
22574
23211
  */
22575
23212
  isElementVisible : function(el) {
22576
23213
  el = this.normalizeElement(el);
22577
- return !!el && this.$(el).is(':visible');
23214
+ // Jquery :visible doesn't take visibility into account
23215
+ return !!el && this.$(el).is(':visible') && el.style.visibility !== 'hidden';
22578
23216
  },
22579
23217
 
22580
23218
  /**
@@ -22750,7 +23388,7 @@ Role('Siesta.Test.Element', {
22750
23388
  assertionChecker()
22751
23389
  }
22752
23390
 
22753
- callback && callback.call(scope || me);
23391
+ callback && me.processCallbackFromTest(callback, null, scope || me)
22754
23392
  });
22755
23393
  },
22756
23394
 
@@ -23170,11 +23808,11 @@ Role('Siesta.Test.Element', {
23170
23808
  this.fail(description, {
23171
23809
  assertionName : 'elementIsAt',
23172
23810
  got : { x: xy[0], y : xy[1] },
23173
- gotDesc : 'Postion',
23811
+ gotDesc : 'Position',
23174
23812
  annotation : 'No element found at the specified position'
23175
23813
  });
23176
23814
  } else if (allowChildren) {
23177
- if (foundEl === el || $(foundEl).closest(el)) {
23815
+ if (foundEl === el || $(foundEl).closest(el).length > 0) {
23178
23816
  this.pass(description);
23179
23817
  } else {
23180
23818
  this.fail(description, {
@@ -23272,21 +23910,14 @@ Role('Siesta.Test.Element', {
23272
23910
 
23273
23911
  var foundEl = this.$(doc.elementFromPoint(xy[0], xy[1]) || doc.body);
23274
23912
 
23275
- if (!foundEl) {
23276
- this.fail(description, {
23277
- assertionName : 'selectorIsAt',
23278
- got : { x: xy[0], y : xy[1] },
23279
- gotDesc : 'Postion',
23280
- annotation : 'No element matching the passed selector found at the specified position'
23281
- });
23282
- }
23283
-
23284
23913
  if (foundEl.has(selector).length > 0 || foundEl.closest(selector).length > 0) {
23285
23914
  this.pass(description);
23286
23915
  } else {
23287
23916
  this.fail(description, {
23917
+ got : foundEl[0].outerHTML ? foundEl[0].outerHTML : foundEl[0].innerHTML,
23918
+ need : 'Element matching ' + selector,
23288
23919
  assertionName : 'selectorIsAt',
23289
- annotation : 'Passed selector does not match DOM content at xy position'
23920
+ annotation : 'Passed selector does not match any selector at [' + xy + ']'
23290
23921
  });
23291
23922
  }
23292
23923
  },
@@ -23413,7 +24044,11 @@ Role('Siesta.Test.Element', {
23413
24044
  })
23414
24045
  })
23415
24046
 
23416
- if (callback) steps.push(callback)
24047
+ var me = this
24048
+
24049
+ if (callback) steps.push(function () {
24050
+ me.processCallbackFromTest(callback)
24051
+ })
23417
24052
 
23418
24053
  this.chain.apply(this, steps)
23419
24054
  },
@@ -23430,7 +24065,7 @@ Role('Siesta.Test.Element', {
23430
24065
  *
23431
24066
  * t.clickSelector('.my-grid .x-grid-row', function () {})
23432
24067
  *
23433
- * The provided callback will receive
24068
+ * The provided callback will receive an array with DOM elements - result of query.
23434
24069
  *
23435
24070
  *
23436
24071
  * @param {String} selector The selector/xpath query
@@ -23726,7 +24361,7 @@ Class('Siesta.Test.Browser', {
23726
24361
  annotation : n + " '" + event + "' events were expected, but " + counter + ' were fired'
23727
24362
  });
23728
24363
 
23729
- callback && callback();
24364
+ callback && me.processCallbackFromTest(callback, null, scope || me)
23730
24365
 
23731
24366
  }, timeOut);
23732
24367
 
@@ -23893,7 +24528,47 @@ Class('Siesta.Test.SenchaTouch', {
23893
24528
  },
23894
24529
 
23895
24530
  methods : {
23896
-
24531
+ getTouchBundlePath : function() {
24532
+ var path;
24533
+ var testDescriptor = this.harness.getScriptDescriptor(this.url)
24534
+
24535
+ while (testDescriptor && !path) {
24536
+ if (testDescriptor.preload) {
24537
+ Joose.A.each(testDescriptor.preload, function (url) {
24538
+ if (url.match && url.match(/(.*sencha-touch-\d\.\d+\.\d+.*?)\/sencha-touch(.*)\.js/)) {
24539
+ path = url;
24540
+ return false;
24541
+ }
24542
+ });
24543
+ }
24544
+ testDescriptor = testDescriptor.parent;
24545
+ }
24546
+
24547
+ return path;
24548
+ },
24549
+
24550
+
24551
+ getTouchBundleFolder : function() {
24552
+ var folder;
24553
+ var testDescriptor = this.harness.getScriptDescriptor(this.url)
24554
+
24555
+ while (testDescriptor && !folder) {
24556
+ if (testDescriptor.preload) {
24557
+ Joose.A.each(testDescriptor.preload, function (url) {
24558
+ var regex = /(.*sencha-touch-\d\.\d+\.\d+.*?)\/sencha-touch(.*)\.js/;
24559
+ var match = regex.exec(url);
24560
+
24561
+ if (match) {
24562
+ folder = match[1];
24563
+ }
24564
+ });
24565
+ }
24566
+ testDescriptor = testDescriptor.parent;
24567
+ }
24568
+
24569
+ return folder;
24570
+ },
24571
+
23897
24572
  /**
23898
24573
  * This method taps the passed target, which can be of several different types, see {@link Siesta.Test.ActionTarget}
23899
24574
  *
@@ -23902,7 +24577,33 @@ Class('Siesta.Test.SenchaTouch', {
23902
24577
  * @param {Object} scope (optional) The scope for the callback
23903
24578
  */
23904
24579
  tap: function (target, callback, scope) {
23905
- this.click(target, callback, scope);
24580
+ var me = this;
24581
+
24582
+ target = this.normalizeElement(target);
24583
+
24584
+ var queue = new Siesta.Util.Queue({
24585
+ deferer : this.originalSetTimeout,
24586
+ deferClearer : this.originalClearTimeout,
24587
+
24588
+ interval : callback ? 30 : 0,
24589
+
24590
+ observeTest : this,
24591
+
24592
+ processor : function (data) {
24593
+ me.simulateEvent.apply(me, data);
24594
+ }
24595
+ })
24596
+
24597
+ queue.addStep([ target, "mousedown", {}, false ])
24598
+ queue.addStep([ target, "mouseup", {}, true ])
24599
+
24600
+ var async = me.beginAsync();
24601
+
24602
+ queue.run(function () {
24603
+ me.endAsync(async);
24604
+
24605
+ callback && me.processCallbackFromTest(callback, null, scope || me)
24606
+ })
23906
24607
  },
23907
24608
 
23908
24609
  /**
@@ -23913,7 +24614,36 @@ Class('Siesta.Test.SenchaTouch', {
23913
24614
  * @param {Object} scope (optional) The scope for the callback
23914
24615
  */
23915
24616
  doubleTap: function (target, callback, scope) {
23916
- this.doubleClick(target, callback, scope);
24617
+ var me = this;
24618
+
24619
+ target = this.normalizeElement(target);
24620
+
24621
+ var queue = new Siesta.Util.Queue({
24622
+ deferer : this.originalSetTimeout,
24623
+ deferClearer : this.originalClearTimeout,
24624
+
24625
+ interval : callback ? 30 : 0,
24626
+
24627
+ observeTest : this,
24628
+
24629
+ processor : function (data) {
24630
+ me.simulateEvent.apply(me, data);
24631
+ }
24632
+ })
24633
+
24634
+ queue.addStep([ target, "mousedown", {}, false ])
24635
+ queue.addStep([ target, "mouseup", {}, true ])
24636
+
24637
+ queue.addStep([ target, "mousedown", {}, false ])
24638
+ queue.addStep([ target, "mouseup", {}, true ])
24639
+
24640
+ var async = me.beginAsync();
24641
+
24642
+ queue.run(function () {
24643
+ me.endAsync(async);
24644
+
24645
+ callback && me.processCallbackFromTest(callback, null, scope || me)
24646
+ })
23917
24647
  },
23918
24648
 
23919
24649
  /**
@@ -23925,11 +24655,17 @@ Class('Siesta.Test.SenchaTouch', {
23925
24655
  */
23926
24656
  longpress: function (target, callback, scope) {
23927
24657
  var Ext = this.Ext();
24658
+ var me = this;
23928
24659
 
23929
24660
  this.simulateEvent(target, 'mousedown');
23930
24661
 
23931
24662
  var amount = Ext.event.recognizer.LongPress.prototype.config.minDuration;
23932
- this.waitFor(amount, callback, scope);
24663
+
24664
+ this.waitFor(amount, function() {
24665
+ me.simulateEvent(target, 'mouseup');
24666
+
24667
+ callback.call(scope || me);
24668
+ });
23933
24669
  },
23934
24670
 
23935
24671
  /**
@@ -23948,30 +24684,41 @@ Class('Siesta.Test.SenchaTouch', {
23948
24684
  start,
23949
24685
  end,
23950
24686
  edgeOffsetRatio = 10;
24687
+
24688
+ // Since this method accepts elements as target, we need to assure that we swipe at least about 150px
24689
+ // using Math.max below etc
23951
24690
 
23952
24691
  switch(direction) {
23953
24692
  case 'u':
23954
24693
  case 'up':
23955
- start = [box.x + box.width/2, (box.y + box.height*9/edgeOffsetRatio)];
23956
- end = [box.x + box.width/2, box.y + box.height/edgeOffsetRatio];
24694
+ start = [box.x + box.width/2, (box.y + box.height*9/edgeOffsetRatio)];
24695
+ end = [box.x + box.width/2, box.y + box.height/edgeOffsetRatio];
24696
+
24697
+ end[1] = Math.min(start[1] - 100, end[1]);
23957
24698
  break;
23958
24699
 
23959
24700
  case 'd':
23960
24701
  case 'down':
23961
- start = [box.x + box.width/2, (box.y + box.height/edgeOffsetRatio)];
23962
- end = [box.x + box.width/2, (box.y + box.height*9/edgeOffsetRatio)];
23963
- break;
24702
+ start = [box.x + box.width/2, (box.y + box.height/edgeOffsetRatio)];
24703
+ end = [box.x + box.width/2, (box.y + box.height*9/edgeOffsetRatio)];
23964
24704
 
23965
- case 'l':
23966
- case 'left':
23967
- start = [box.x + (box.width /edgeOffsetRatio), (box.y + box.height/2)];
23968
- end = [box.x + (box.width * 9/edgeOffsetRatio), (box.y + box.height/2)];
24705
+ end[1] = Math.max(start[1] + 100, end[1]);
23969
24706
  break;
23970
24707
 
23971
24708
  case 'r':
23972
24709
  case 'right':
23973
- start = [box.x + (box.width * 9/edgeOffsetRatio), (box.y + box.height/2)];
23974
- end = [box.x, (box.y + box.height/2)];
24710
+ start = [box.x + (box.width /edgeOffsetRatio), (box.y + box.height/2)];
24711
+ end = [box.x + (box.width * 9/edgeOffsetRatio), (box.y + box.height/2)];
24712
+
24713
+ end[0] = Math.max(start[0] + 100, end[0]);
24714
+ break;
24715
+
24716
+ case 'l':
24717
+ case 'left':
24718
+ start = [box.x + (box.width * 9/edgeOffsetRatio), (box.y + box.height/2)];
24719
+ end = [box.x + (box.width /edgeOffsetRatio), (box.y + box.height/2)];
24720
+
24721
+ end[0] = Math.min(start[0] - 100, end[0]);
23975
24722
  break;
23976
24723
 
23977
24724
  default:
@@ -23981,6 +24728,42 @@ Class('Siesta.Test.SenchaTouch', {
23981
24728
  this.dragTo(start, end, callback, scope);
23982
24729
  },
23983
24730
 
24731
+ /**
24732
+ * This method will simulate a finger move to an xy-coordinate or an element (the center of it)
24733
+ *
24734
+ * @param {Siesta.Test.ActionTarget} target Target point to move the mouse to.
24735
+ * @param {Function} callback (optional) To run this method async, provide a callback method to be called after the operation is completed.
24736
+ * @param {Object} scope (optional) the scope for the callback
24737
+ */
24738
+ moveFingerTo : function(target, callback, scope) {
24739
+ if (!target) {
24740
+ throw 'Trying to call moveFingerTo without a target';
24741
+ }
24742
+
24743
+ // Normalize target
24744
+ if (!this.isArray(target)) {
24745
+ target = this.detectCenter(this.normalizeElement(target), 'moveFingerTo');
24746
+ }
24747
+ this.moveMouse(this.currentPosition, target, callback, scope);
24748
+ },
24749
+
24750
+ /**
24751
+ * This method will simulate a finger move from current position relative by the x and y distances provided.
24752
+ *
24753
+ * @param {Siesta.Test.ActionTarget} target Target point to move the mouse to.
24754
+ * @param {Function} callback (optional) To run this method async, provide a callback method to be called after the operation is completed.
24755
+ * @param {Object} scope (optional) the scope for the callback
24756
+ */
24757
+ moveFingerBy : function(delta, callback, scope) {
24758
+ if (!delta) {
24759
+ throw 'Trying to call moveFingerBy without relative distances';
24760
+ }
24761
+
24762
+ var targetXY = [ this.currentPosition[0] + delta[0], this.currentPosition[1] + delta[1] ];
24763
+
24764
+ this.moveMouseTo(targetXY, callback, scope);
24765
+ },
24766
+
23984
24767
  // /**
23985
24768
  // * This method will simulate a swipe operation between either two points or on a single DOM element.
23986
24769
  // *
@@ -24038,7 +24821,7 @@ Class('Siesta.Test.SenchaTouch', {
24038
24821
  var inner = function() {
24039
24822
  if (checkerFn.call(scope || me, target)) {
24040
24823
  // We're done
24041
- callback.call(scope || me);
24824
+ me.processCallbackFromTest(callback, null, scope || me)
24042
24825
  } else {
24043
24826
  me.swipe(target, direction, function() {
24044
24827
  var as = me.beginAsync();
@@ -24443,7 +25226,9 @@ Class('Siesta.Harness.Browser', {
24443
25226
  */
24444
25227
  viewportHeight : 768,
24445
25228
 
24446
- needUI : true
25229
+ needUI : true,
25230
+
25231
+ isAutomated : false
24447
25232
  },
24448
25233
 
24449
25234
 
@@ -24486,6 +25271,10 @@ Class('Siesta.Harness.Browser', {
24486
25271
 
24487
25272
  onTestUpdate : function (test, result) {
24488
25273
  if (this.viewport) this.viewport.onTestUpdate(test, result)
25274
+
25275
+ if ((result instanceof Siesta.Result.Diagnostic) && result.isWarning && this.needUI) {
25276
+ if (typeof console != 'undefined' && console.warn) console.warn(result + '')
25277
+ }
24489
25278
  },
24490
25279
 
24491
25280
 
@@ -24522,11 +25311,6 @@ Class('Siesta.Harness.Browser', {
24522
25311
  },
24523
25312
 
24524
25313
 
24525
- isAutomated : function () {
24526
- return false
24527
- },
24528
-
24529
-
24530
25314
  configure : function() {
24531
25315
  this.SUPERARG(arguments);
24532
25316
 
@@ -24568,7 +25352,7 @@ Class('Siesta.Harness.Browser', {
24568
25352
 
24569
25353
  // if we here, then we were requested to show the UI for automated launch
24570
25354
  // auto-launch the test suite in this case
24571
- if (me.isAutomated()) SUPER.apply(me, args)
25355
+ if (me.isAutomated) SUPER.apply(me, args)
24572
25356
  };
24573
25357
 
24574
25358
  if (Ext.setup) {
@@ -24603,7 +25387,7 @@ Class('Siesta.Harness.Browser', {
24603
25387
  var sup = this.SUPER
24604
25388
 
24605
25389
  // delay the super setup until dom ready
24606
- if (!this.isAutomated()) {
25390
+ if (!this.isAutomated) {
24607
25391
  Ext.onReady(function () {
24608
25392
  Siesta.supports.init();
24609
25393
 
@@ -24743,16 +25527,28 @@ Class('Siesta.Harness.Browser', {
24743
25527
 
24744
25528
 
24745
25529
  showForcedIFrame : function (iframe, test) {
24746
- Ext.fly(iframe).addCls('tr-iframe-forced')
24747
- Ext.fly(iframe).removeCls('tr-iframe-hidden')
25530
+ $.rebindWindowContext(window);
25531
+ $(iframe).addClass('tr-iframe-forced')
25532
+ $(iframe).removeClass('tr-iframe-hidden')
24748
25533
 
24749
- Ext.fly(iframe).center()
25534
+ $(iframe).center()
24750
25535
  },
24751
25536
 
24752
25537
 
24753
25538
  hideForcedIFrame : function (iframe) {
24754
- Ext.fly(iframe).removeCls('tr-iframe-forced')
24755
- Ext.fly(iframe).addCls('tr-iframe-hidden')
25539
+ $.rebindWindowContext(window);
25540
+ $(iframe).removeClass('tr-iframe-forced')
25541
+ $(iframe).addClass('tr-iframe-hidden')
25542
+ },
25543
+
25544
+ getQueryParam : function (paramName) {
25545
+ var regex = new RegExp('(?:\\?|&)' + paramName + '=(.*?)(?:\\?|&|$)', 'i')
25546
+
25547
+ var match = regex.exec(window.location.search)
25548
+
25549
+ if (!match) return null
25550
+
25551
+ return match[ 1 ]
24756
25552
  }
24757
25553
  }
24758
25554
 
@@ -24931,160 +25727,179 @@ Ext.Container.override({
24931
25727
  }
24932
25728
  })
24933
25729
  ;
24934
- var config = {
24935
- idProperty : 'id',
25730
+ (function () {
25731
+ var config = {
25732
+ idProperty : 'id',
24936
25733
 
24937
- fields : [
24938
- 'id',
24939
- 'url',
24940
-
24941
- 'title',
24942
-
24943
- { name : 'passCount', type : 'int', defaultValue : 0 },
24944
- { name : 'failCount', type : 'int', defaultValue : 0 },
24945
- { name : 'todoPassCount', type : 'int', defaultValue : 0 },
24946
- { name : 'todoFailCount', type : 'int', defaultValue : 0 },
24947
-
24948
- { name : 'time', type : 'int', defaultValue : 0 },
24949
-
24950
- {
24951
- name : 'checked',
24952
- defaultValue : false
25734
+ fields : [
25735
+ 'id',
25736
+ 'url',
25737
+
25738
+ 'title',
25739
+
25740
+ { name : 'passCount', type : 'int', defaultValue : 0 },
25741
+ { name : 'failCount', type : 'int', defaultValue : 0 },
25742
+ { name : 'todoPassCount', type : 'int', defaultValue : 0 },
25743
+ { name : 'todoFailCount', type : 'int', defaultValue : 0 },
25744
+
25745
+ { name : 'time', type : 'int', defaultValue : 0 },
25746
+
25747
+ {
25748
+ name : 'checked',
25749
+ defaultValue : false
25750
+ },
25751
+
25752
+ {
25753
+ name : 'folderStatus',
25754
+ defaultValue : 'yellow'
25755
+ },
25756
+
25757
+ // will be set to true for all tests, once the users clicks "run"
25758
+ 'isStarting',
25759
+ // will be set to true, right before the scope preload begin
25760
+ 'isStarted',
25761
+ // will be set to true, after preload ends and tests launch
25762
+ { name : 'isRunning', type : 'boolean', defaultValue : false },
25763
+ { name : 'isMissing', type : 'boolean', defaultValue : false },
25764
+ { name : 'isFailed', type : 'boolean', defaultValue : false },
25765
+
25766
+ // composite objects
25767
+ 'assertionsStore',
25768
+ 'test',
25769
+ 'descriptor'
25770
+ ]
25771
+ };
25772
+
25773
+ Ext.define('Siesta.Harness.Browser.Model.TestFile', Ext.apply({
25774
+
25775
+ extend : 'Ext.data.Model',
25776
+
25777
+ init : function () {
25778
+ this.internalId = this.getId() || this.internalId
24953
25779
  },
24954
-
24955
- {
24956
- name : 'folderStatus',
24957
- defaultValue : 'yellow'
24958
- },
24959
-
24960
- // will be set to true for all tests, once the users clicks "run"
24961
- 'isStarting',
24962
- // will be set to true, right before the scope preload begin
24963
- 'isStarted',
24964
- // will be set to true, after preload ends and tests launch
24965
- { name : 'isRunning', type : 'boolean', defaultValue : false },
24966
- { name : 'isMissing', type : 'boolean', defaultValue : false },
24967
- { name : 'isFailed', type : 'boolean', defaultValue : false },
24968
-
24969
- // composite objects
24970
- 'assertionsStore',
24971
- 'test',
24972
- 'descriptor'
24973
- ]
24974
- };
24975
25780
 
24976
- Ext.define('Siesta.Harness.Browser.Model.TestFile', Ext.apply({
24977
25781
 
24978
- extend : 'Ext.data.Model',
25782
+ computeFolderStatus : function () {
25783
+ if (!this.childNodes.length) return 'yellow'
24979
25784
 
24980
- init : function () {
24981
- this.internalId = this.getId() || this.internalId
24982
- },
25785
+ var isWorking = false
25786
+ var hasFailed = false
25787
+ var allGreen = true
24983
25788
 
25789
+ Joose.A.each(this.childNodes, function (childNode) {
24984
25790
 
24985
- computeFolderStatus : function () {
24986
- if (!this.childNodes.length) return 'yellow'
24987
-
24988
- var isWorking = false
24989
- var hasFailed = false
24990
- var allGreen = true
24991
-
24992
- Joose.A.each(this.childNodes, function (childNode) {
24993
-
24994
- if (childNode.isLeaf()) {
24995
- var test = childNode.get('test')
24996
-
24997
- if (test && test.isFailed()) {
24998
- allGreen = false
24999
- hasFailed = true
25000
-
25001
- // stop iteration
25002
- return false
25003
- }
25004
-
25005
- if (!test && childNode.get('isStarting')) isWorking = true
25006
- if (test && !test.isFinished()) isWorking = true
25007
- if (test && !test.isPassed()) allGreen = false
25008
- if (!test) allGreen = false
25009
-
25010
- } else {
25011
- var status = childNode.computeFolderStatus()
25012
-
25013
- if (status == 'red') {
25014
- allGreen = false
25015
- hasFailed = true
25016
-
25017
- // stop iteration
25018
- return false
25019
- }
25020
-
25021
- if (status == 'working') {
25022
- isWorking = true
25023
-
25024
- // stop iteration
25025
- return false
25791
+ if (childNode.isLeaf()) {
25792
+ var test = childNode.get('test')
25793
+
25794
+ if (test && test.isFailed()) {
25795
+ allGreen = false
25796
+ hasFailed = true
25797
+
25798
+ // stop iteration
25799
+ return false
25800
+ }
25801
+
25802
+ if (!test && childNode.get('isStarting')) isWorking = true
25803
+ if (test && !test.isFinished()) isWorking = true
25804
+ if (test && !test.isPassed()) allGreen = false
25805
+ if (!test) allGreen = false
25806
+
25807
+ } else {
25808
+ var status = childNode.computeFolderStatus()
25809
+
25810
+ if (status == 'red') {
25811
+ allGreen = false
25812
+ hasFailed = true
25813
+
25814
+ // stop iteration
25815
+ return false
25816
+ }
25817
+
25818
+ if (status == 'working') {
25819
+ isWorking = true
25820
+
25821
+ // stop iteration
25822
+ return false
25823
+ }
25824
+
25825
+ if (status == 'yellow') allGreen = false
25026
25826
  }
25027
-
25028
- if (status == 'yellow') allGreen = false
25029
- }
25030
- })
25031
-
25032
- if (isWorking) return 'working'
25033
- if (hasFailed) return 'red'
25034
- if (allGreen) return 'green'
25035
-
25036
- return 'yellow'
25037
- },
25827
+ })
25038
25828
 
25829
+ if (isWorking) return 'working'
25830
+ if (hasFailed) return 'red'
25831
+ if (allGreen) return 'green'
25039
25832
 
25040
- updateFolderStatus : function () {
25041
- this.set('folderStatus', this.computeFolderStatus())
25042
-
25043
- var parentNode = this.parentNode
25044
-
25045
- if (parentNode && !parentNode.isRoot()) parentNode.updateFolderStatus()
25046
- },
25833
+ return 'yellow'
25834
+ },
25835
+
25836
+
25837
+ updateFolderStatus : function () {
25838
+ this.set('folderStatus', this.computeFolderStatus())
25047
25839
 
25048
- getFailedAssertions : function() {
25049
- var failed = [];
25050
- var as = this.get('assertionsStore');
25840
+ var parentNode = this.parentNode
25051
25841
 
25052
- if (as) {
25053
- as.each(function(assertion) {
25054
- if (assertion.get('passed') === false) {
25055
- failed.push(assertion);
25842
+ if (parentNode && !parentNode.isRoot()) parentNode.updateFolderStatus()
25843
+ },
25844
+
25845
+ getFailedAssertions : function () {
25846
+ var failed = [];
25847
+ var as = this.get('assertionsStore');
25848
+
25849
+ if (as) {
25850
+ as.each(function (assertion) {
25851
+ if (assertion.get('passed') === false) {
25852
+ failed.push(assertion);
25853
+ }
25854
+ });
25855
+ }
25856
+
25857
+ return failed;
25858
+ }
25859
+ }, (Ext.getVersion && Ext.getVersion('touch')) ? { config : config } : config), function () {
25860
+ var isSenchaTouch = Ext.getVersion && Ext.getVersion('touch')
25861
+
25862
+ if (!isSenchaTouch) {
25863
+ Ext.data.NodeInterface.decorate(this);
25864
+
25865
+ this.override({
25866
+ expand : function () {
25867
+ Ext.suspendLayouts();
25868
+ this.callParent(arguments);
25869
+ Ext.resumeLayouts();
25056
25870
  }
25057
25871
  });
25058
25872
  }
25059
-
25060
- return failed;
25061
- }
25062
- }, (Ext.getVersion && Ext.getVersion('touch')) ? { config : config } : config ))
25873
+ })
25874
+ })();
25063
25875
  ;
25064
- var config = {
25065
- idProperty : 'index',
25066
-
25067
- fields : [
25068
- 'index',
25069
- 'summaryFailure',
25070
- { name : 'passed', type : 'boolean', defaultValue : false },
25071
- { name : 'isTodo', type : 'boolean', defaultValue : false },
25072
- { name : 'isWaitFor', type : 'boolean', defaultValue : false },
25073
- { name : 'completed', type : 'boolean', defaultValue : false },
25074
- 'description',
25075
- 'annotation',
25076
- 'type',
25077
- 'sourceLine',
25078
-
25079
- // For logging simulated events (will also have a type as for diagnostic messages)
25080
- { name : 'isSimulatedEvent', type : 'boolean', defaultValue : false },
25081
- 'eventType'
25082
- ]
25083
- }
25876
+ (function () {
25877
+ var config = {
25878
+ idProperty : 'index',
25879
+
25880
+ fields : [
25881
+ 'index',
25882
+ 'summaryFailure',
25883
+ { name : 'passed', type : 'boolean', defaultValue : false },
25884
+ { name : 'isTodo', type : 'boolean', defaultValue : false },
25885
+ { name : 'isWaitFor', type : 'boolean', defaultValue : false },
25886
+ { name : 'completed', type : 'boolean', defaultValue : false },
25887
+ 'description',
25888
+ 'annotation',
25889
+ 'type',
25890
+ 'sourceLine',
25891
+ 'isWarning',
25892
+
25893
+ // For logging simulated events (will also have a type as for diagnostic messages)
25894
+ { name : 'isSimulatedEvent', type : 'boolean', defaultValue : false },
25895
+ 'eventType'
25896
+ ]
25897
+ };
25084
25898
 
25085
- Ext.define('Siesta.Harness.Browser.Model.Assertion', Ext.apply({
25086
- extend : 'Ext.data.Model'
25087
- }, (Ext.getVersion && Ext.getVersion('touch')) ? { config : config } : config ))
25899
+ Ext.define('Siesta.Harness.Browser.Model.Assertion', Ext.apply({
25900
+ extend : 'Ext.data.Model'
25901
+ }, (Ext.getVersion && Ext.getVersion('touch')) ? { config : config } : config));
25902
+ })();
25088
25903
  ;
25089
25904
  Ext.define('Siesta.Harness.Browser.UI.MouseVisualizer', {
25090
25905
 
@@ -25494,6 +26309,7 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25494
26309
 
25495
26310
  Joose.A.each(harness.flattenDescriptors(descriptors), function (descriptor) {
25496
26311
  var test = flatStore.getById(descriptor.id);
26312
+
25497
26313
  me.lastTests.push(test);
25498
26314
 
25499
26315
  test.set({
@@ -25533,7 +26349,11 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25533
26349
  var testRecord = flatStore.getById(descriptor.id);
25534
26350
 
25535
26351
  if (testRecord.isLeaf()) testRecord.get('assertionsStore').removeAll(true)
26352
+
25536
26353
  testRecord.reject(true);
26354
+
26355
+ // HACK mark record as not having any test results
26356
+ testRecord.isCleared = true
25537
26357
  });
25538
26358
  },
25539
26359
 
@@ -25545,6 +26365,7 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25545
26365
  Ext.select('.ghost-cursor-click-indicator').each(function(el) { el.destroy(); });
25546
26366
  },
25547
26367
 
26368
+
25548
26369
  toggleResults : function() {
25549
26370
  if (this.lastTests.length > 1) {
25550
26371
  var store = this.createFailedAssertionsStore(this.lastTests);
@@ -25559,6 +26380,7 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25559
26380
  }
25560
26381
  },
25561
26382
 
26383
+
25562
26384
  createFailedAssertionsStore : function(tests) {
25563
26385
  var store = new Ext.data.Store({
25564
26386
  model : 'Siesta.Harness.Browser.Model.Assertion'
@@ -25667,6 +26489,8 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25667
26489
 
25668
26490
  onTestStart : function (test) {
25669
26491
  var testRecord = this.flatStore.getById(test.url)
26492
+
26493
+ testRecord.isCleared = false
25670
26494
 
25671
26495
  testRecord.beginEdit()
25672
26496
 
@@ -25781,18 +26605,22 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.MainPanel', {
25781
26605
 
25782
26606
 
25783
26607
  onItemDisclose : function (list, testModel, target, index) {
25784
- if (testModel.get('assertionsStore').getCount() == 0) this.launchTest(testModel);
26608
+ if (testModel.isCleared) this.launchTest(testModel);
25785
26609
 
25786
26610
  this.showResultPanel(testModel);
25787
26611
  },
25788
26612
 
25789
26613
 
25790
- showResultPanel : function() {
26614
+ showResultPanel : function (testModel) {
26615
+ var resultList = this.getResultList()
26616
+
26617
+ if (testModel) resultList.showTest(testModel);
26618
+
25791
26619
  this.suiteBar.down("button[ui='back']").enable()
25792
26620
 
25793
26621
  this.getLayout().getAnimation().setReverse(false)
25794
26622
 
25795
- this.setActiveItem(this.resultList);
26623
+ this.setActiveItem(resultList);
25796
26624
  },
25797
26625
 
25798
26626
 
@@ -25958,7 +26786,7 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
25958
26786
 
25959
26787
  config : {
25960
26788
  itemTpl : new Ext.XTemplate(
25961
- '<div class="{[this.getRowClass(values)]}">{description} {[values.annotation ? ("<br/>" + values.annotation) : ""]}</div>',
26789
+ '<div class="{[this.getRowClass(values)]}">{description} {[values.annotation ? ("<br/>" + Ext.String.htmlEncode(values.annotation)) : ""]}</div>',
25962
26790
  {
25963
26791
  getRowClass: function(data){
25964
26792
  if (data.type === 'Siesta.Result.Diagnostic') {
@@ -25977,10 +26805,12 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
25977
26805
  testRecord : null
25978
26806
  },
25979
26807
 
26808
+
25980
26809
  constructor : function() {
25981
26810
  this.callParent(arguments);
25982
26811
  },
25983
26812
 
26813
+
25984
26814
  getIFrame : function () {
25985
26815
  if (this.testRecord) {
25986
26816
  var test = this.testRecord.get('test');
@@ -25990,10 +26820,11 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
25990
26820
  return null;
25991
26821
  },
25992
26822
 
26823
+
25993
26824
  alignIFrame : function () {
25994
26825
  var iframe = this.getIFrame();
25995
26826
 
25996
- Ext.fly(iframe).addCls('active')
26827
+ iframe && Ext.fly(iframe).addCls('active')
25997
26828
 
25998
26829
  var test = this.testRecord && this.testRecord.get('test')
25999
26830
 
@@ -26002,6 +26833,7 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
26002
26833
  }
26003
26834
  },
26004
26835
 
26836
+
26005
26837
  hideIFrame : function () {
26006
26838
  var iframe = this.getIFrame()
26007
26839
 
@@ -26014,15 +26846,20 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
26014
26846
  }
26015
26847
  },
26016
26848
 
26849
+
26017
26850
  toggleFrameVisible : function() {
26018
26851
  var iframe = this.getIFrame()
26019
26852
  iframe && Ext.fly(iframe).toggleCls('active');
26020
26853
  },
26021
26854
 
26855
+
26022
26856
  isFrameVisible : function () {
26023
- return Ext.fly(this.getIFrame()).hasCls('active');
26857
+ var iframe = this.getIFrame()
26858
+
26859
+ return iframe && Ext.fly(iframe).hasCls('active') || false;
26024
26860
  },
26025
26861
 
26862
+
26026
26863
  showTest : function (testFile) {
26027
26864
  this.addCls('result-list-running');
26028
26865
 
@@ -26032,8 +26869,9 @@ Ext.define('Siesta.Harness.Browser.UI_Mobile.ResultList', {
26032
26869
  }
26033
26870
 
26034
26871
  var url = testFile.get('url');
26035
-
26036
- this.container.innerElement.setHtml('');
26872
+
26873
+ // Nickolay: Had to disable this line, do we need it?
26874
+ // this.container.innerElement.setHtml('');
26037
26875
  this.setStore(testFile.get('assertionsStore'));
26038
26876
 
26039
26877
  this.testUrl = url;
@@ -26083,53 +26921,55 @@ A Class representing the browser harness. This class provides a web-based UI and
26083
26921
  The default value of the `testClass` configuration option in this class is {@link Siesta.Test.SenchaTouch}, which inherits from
26084
26922
  {@link Siesta.Test.Browser} and contains various Sencha Touch-specific assertions. Use this harness class when testing Sencha Touch applications.
26085
26923
 
26924
+ * **Note** Make sure, you've checked the {@link #performSetup} configuration option.
26925
+
26086
26926
  This file is for reference only, for a getting start guide and manual, please refer to <a href="#!/guide/siesta_getting_started">Getting Started Guide</a>.
26087
26927
 
26088
26928
  Synopsys
26089
26929
  ========
26090
26930
 
26091
- var Harness = Siesta.Harness.Browser.SenchaTouch;
26092
-
26093
- Harness.configure({
26094
- title : 'Awesome Sencha Touch Application Test Suite',
26931
+ var Harness = Siesta.Harness.Browser.SenchaTouch;
26095
26932
 
26096
- transparentEx : true,
26097
-
26098
- preload : [
26099
- "http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js",
26100
- "../awesome-project-all.js"
26101
- ]
26102
- })
26103
-
26104
-
26105
- Harness.start(
26106
- // simple string - url relative to harness file
26107
- 'sanity.t.js',
26933
+ Harness.configure({
26934
+ title : 'Awesome Sencha Touch Application Test Suite',
26935
+
26936
+ transparentEx : true,
26937
+
26938
+ preload : [
26939
+ "http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js",
26940
+ "../awesome-project-all.js"
26941
+ ]
26942
+ })
26108
26943
 
26109
- // test file descriptor with own configuration options
26110
- {
26111
- url : 'basic.t.js',
26112
-
26113
- // replace `preload` option of harness
26114
- preload : [
26115
- "http://cdn.sencha.io/ext-4.0.6/ext-all-debug.js",
26116
- "../awesome-project-all.js"
26117
- ]
26118
- },
26119
26944
 
26120
- // groups ("folders") of test files (possibly with own options)
26121
- {
26122
- group : 'Sanity',
26123
-
26124
- autoCheckGlobals : false,
26125
-
26126
- items : [
26127
- 'data/crud.t.js',
26128
- ...
26129
- ]
26130
- },
26131
- ...
26132
- )
26945
+ Harness.start(
26946
+ // simple string - url relative to harness file
26947
+ 'sanity.t.js',
26948
+
26949
+ // test file descriptor with own configuration options
26950
+ {
26951
+ url : 'basic.t.js',
26952
+
26953
+ // replace `preload` option of harness
26954
+ preload : [
26955
+ "http://cdn.sencha.io/ext-4.0.6/ext-all-debug.js",
26956
+ "../awesome-project-all.js"
26957
+ ]
26958
+ },
26959
+
26960
+ // groups ("folders") of test files (possibly with own options)
26961
+ {
26962
+ group : 'Sanity',
26963
+
26964
+ autoCheckGlobals : false,
26965
+
26966
+ items : [
26967
+ 'data/crud.t.js',
26968
+ ...
26969
+ ]
26970
+ },
26971
+ ...
26972
+ )
26133
26973
 
26134
26974
 
26135
26975
  */
@@ -26143,17 +26983,31 @@ Class('Siesta.Harness.Browser.SenchaTouch', {
26143
26983
 
26144
26984
  has: {
26145
26985
  /**
26146
- * @cfg {Class} testClass The test class which will be used for creating test instances, defaults to {@link Siesta.Test.ExtJS}.
26147
- * You can subclass {@link Siesta.Test.ExtJS} and provide a new class.
26986
+ * @cfg {Class} testClass The test class which will be used for creating test instances, defaults to {@link Siesta.Test.SenchaTouch}.
26987
+ * You can subclass {@link Siesta.Test.SenchaTouch} and provide a new class.
26148
26988
  *
26149
26989
  * This option can be also specified in the test file descriptor.
26150
26990
  */
26151
- testClass: Siesta.Test.SenchaTouch,
26991
+ testClass : Siesta.Test.SenchaTouch,
26152
26992
 
26993
+ /**
26994
+ * @cfg {Boolean} transparentEx
26995
+ */
26153
26996
  transparentEx : true,
26154
26997
  keepResults : false,
26998
+ keepNLastResults : 0,
26999
+
27000
+ /**
27001
+ * @cfg {Boolean} performSetup When set to `true`, Siesta will perform a `Ext.setup()` call, so you can safely assume there's a viewport for example.
27002
+ * If, however your test code, performs `Ext.setup()` itself, you need to disable this option.
27003
+ *
27004
+ * This option can be also specified in the test file descriptor.
27005
+ */
26155
27006
  performSetup : true,
26156
27007
 
27008
+ /**
27009
+ * @cfg {String} runCore
27010
+ */
26157
27011
  runCore : 'sequential',
26158
27012
 
26159
27013
  /**
@@ -26163,34 +27017,55 @@ Class('Siesta.Harness.Browser.SenchaTouch', {
26163
27017
  *
26164
27018
  * This option can be also specified in the test file descriptor.
26165
27019
  */
26166
- loaderPath: null
27020
+ loaderPath : null,
27021
+
27022
+ isRunningOnMobile : true,
27023
+ useExtJSUI : true
26167
27024
  },
26168
27025
 
26169
27026
 
26170
27027
  methods: {
27028
+
27029
+ setup : function () {
27030
+ // TODO fix proper mobile detection, since Ext may be absent in "no-ui" harness
27031
+ this.isRunningOnMobile = typeof Ext !== 'undefined' && Ext.getVersion && Ext.getVersion('touch')
27032
+
27033
+ if (!this.isRunningOnMobile) this.keepNLastResults = 2
27034
+
27035
+ this.SUPERARG(arguments)
27036
+ },
27037
+
26171
27038
 
26172
27039
  getNewTestConfiguration: function (desc, scopeProvider, contentManager, options, runFunc) {
26173
27040
  var config = this.SUPERARG(arguments)
26174
27041
 
26175
- config.performSetup = this.getDescriptorConfig(desc, 'performSetup')
26176
- config.loaderPath = this.getDescriptorConfig(desc, 'loaderPath')
27042
+ config.performSetup = this.getDescriptorConfig(desc, 'performSetup')
27043
+ config.loaderPath = this.getDescriptorConfig(desc, 'loaderPath')
26177
27044
 
26178
27045
  return config
26179
27046
  },
26180
27047
 
27048
+
26181
27049
  createViewport: function (config) {
27050
+ if (!this.isRunningOnMobile && this.useExtJSUI) return Ext.create("Siesta.Harness.Browser.UI.ExtViewport", config);
27051
+
26182
27052
  var mainPanel = Ext.create('Siesta.Harness.Browser.UI_Mobile.MainPanel', config);
27053
+
26183
27054
  Ext.Viewport.add(mainPanel);
26184
27055
 
26185
27056
  return mainPanel;
26186
27057
  },
26187
27058
 
27059
+
26188
27060
  showForcedIFrame : function (iframe, test) {
26189
- Ext.fly(iframe).setStyle({
27061
+ $.rebindWindowContext(window);
27062
+
27063
+ $(iframe).setStyle({
26190
27064
  'z-index' : 100000
26191
27065
  });
26192
27066
  },
26193
27067
 
27068
+
26194
27069
  onBeforeScopePreload : function (scopeProvider, url) {
26195
27070
  var setupEventTranslation = function() {
26196
27071
  Ext.event.publisher.TouchGesture.override({
@@ -26280,13 +27155,14 @@ Class('Siesta.Harness.Browser.SenchaTouch', {
26280
27155
  ;
26281
27156
  ;
26282
27157
  Class('Siesta', {
26283
- /*PKGVERSION*/VERSION : '1.1.0',
27158
+ /*PKGVERSION*/VERSION : '1.1.5',
26284
27159
 
26285
27160
  // "my" should been named "static"
26286
27161
  my : {
26287
27162
 
26288
27163
  has : {
26289
- config : null
27164
+ config : null,
27165
+ activeHarness : null
26290
27166
  },
26291
27167
 
26292
27168
  methods : {