siesta 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 : {