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
6958
+
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
+ }
6933
6967
 
6934
- if (!expectedGlobals[ name ]) {
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
@@ -9904,15 +10280,26 @@ Role('Siesta.Role.ConsoleReporter', {
9904
10280
 
9905
10281
  does : Siesta.Role.CanStyleOutput,
9906
10282
 
10283
+ has : {
10284
+ // special flag which will be used by automation launchers to prevent the summary message
10285
+ // after every page
10286
+ needSummaryMessage : true
10287
+ },
10288
+
9907
10289
 
9908
10290
  after : {
9909
10291
 
10292
+ markMissingFile : function (desc) {
10293
+ this.warn("Test file [" + desc.url + "] not found.")
10294
+ },
10295
+
10296
+
9910
10297
  onTestSuiteStart : function () {
9911
10298
  },
9912
10299
 
9913
10300
 
9914
10301
  onTestSuiteEnd : function () {
9915
- this.log(this.getSummaryMessage())
10302
+ if (this.needSummaryMessage) this.log(this.getSummaryMessage())
9916
10303
 
9917
10304
  this.exit(this.getExitCode())
9918
10305
  },
@@ -9949,7 +10336,14 @@ Role('Siesta.Role.ConsoleReporter', {
9949
10336
  }
9950
10337
  }
9951
10338
 
9952
- if (result instanceof Siesta.Result.Diagnostic) text = this.styled(text, 'bold')
10339
+ if (result instanceof Siesta.Result.Diagnostic) {
10340
+ text = this.styled(text, 'bold')
10341
+
10342
+ if (result.isWarning) {
10343
+ this.warn(text)
10344
+ return
10345
+ }
10346
+ }
9953
10347
 
9954
10348
  if (needToShow) this.log(text)
9955
10349
  }
@@ -9958,8 +10352,13 @@ Role('Siesta.Role.ConsoleReporter', {
9958
10352
 
9959
10353
  methods : {
9960
10354
 
9961
- getSummaryMessage : function (lineBreaks) {
9962
- var allPassed = this.allPassed()
10355
+ warn : function (text) {
10356
+ this.log(this.styled('[WARN] ', 'red') + text)
10357
+ },
10358
+
10359
+
10360
+ getSummaryMessage : function (allPassed) {
10361
+ allPassed = allPassed != null ? allPassed : this.allPassed()
9963
10362
 
9964
10363
  return allPassed ? this.style().bold(this.style().green('All tests passed')) : this.style().bold(this.style().red('There are failures'))
9965
10364
  },
@@ -10144,13 +10543,14 @@ Class('Siesta.Harness.NodeJS', {
10144
10543
  ;
10145
10544
  ;
10146
10545
  Class('Siesta', {
10147
- /*PKGVERSION*/VERSION : '1.1.0',
10546
+ /*PKGVERSION*/VERSION : '1.1.5',
10148
10547
 
10149
10548
  // "my" should been named "static"
10150
10549
  my : {
10151
10550
 
10152
10551
  has : {
10153
- config : null
10552
+ config : null,
10553
+ activeHarness : null
10154
10554
  },
10155
10555
 
10156
10556
  methods : {