forked from bitovi/funcunit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
funcunit.js
1633 lines (1523 loc) · 52.7 KB
/
funcunit.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//what we need from javascriptmvc or other places
steal.plugins('funcunit/qunit',
'funcunit/qunit/rhino')
.then('resources/jquery','resources/json','resources/selector')
.plugins('funcunit/synthetic')
//Now describe FuncUnit
.then(function(){
//this gets the global object, even in rhino
var window = (function(){return this }).call(null),
//if there is an old FuncUnit, use that for settings
oldFunc = window.FuncUnit;
/**
* @class FuncUnit
* @tag core
* @test test.html
* @download http://github.com/downloads/jupiterjs/funcunit/funcunit-beta-5.zip
* FuncUnit provides powerful functional testing as an add on to [http://docs.jquery.com/QUnit QUnit].
* The same tests can be run
* in the browser, or with Selenium. It also lets you automate basic
* QUnit tests in [EnvJS](http://www.envjs.com/) - a command line browser.
*
* <h2>Example:</h2>
* The following tests that an AutoSuggest returns 5 results.
* <a href='funcunit/autosuggest/funcunit.html'>See it in action!</a> (Make sure
* you turn off your popup blocker!).
@codestart
module("autosuggest",{
setup: function() {
S.open('autosuggest.html')
}
});
test("JavaScript results",function(){
S('input').click().type("JavaScript")
// wait until we have some results
S('.autocomplete_item').visible(function(){
equal( S('.autocomplete_item').size(), 5, "there are 5 results")
})
});
@codeend
*
* <h2>Basic Setup</h2>
* <h3>Setup with JavaScriptMVC</h3>
* If you're setting up FuncUnit with JavaScriptMVC,
* use [steal.static.plugins] to get the funcunit plugin. If you used
* JavaScriptMVC's generators, it will setup a testing skeleton for you.
* <h3>Setup with Stand-alone funcunit.js</h3>
* Lets say you want to test <code>pages/mypage.html</code> and
* you've installed funcunit in test/funcunit.</br>
* Steps:
* <ol>
* <li>Create a HTML file (pages/mypage_test.html) that loads
* <code><b>qunit.css</b></code>, <code><b>funcunit.js</b></code>,
* and <code><b>mypage_test.js</b></code>. We'll create mypage_test.js in step #2.
@codestart html
<html>
<head>
<link href='../funcunit/<b>qunit.css</b>'
type='text/css'
rel='stylesheet' />
<script src='../funcunit/<b>funcunit.js</b>'
type='text/javascript' ></script>
<script src='<b>mypage_test.js</b>'
type='text/javascript'></script>
<title>MyPage Test Suite</title>
</head>
<body>
<h1 id="qunit-header">MyPage Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>
@codeend
* </li>
* <li>Create a JS file (<code>pages/mypage_test.js</code>) for your tests. The skeleton should like:
@codestart
module("APPNAME", {
setup: function() {
// opens the page you want to test
$.open("myPage.html");
}
})
test("page has content", function(){
ok( S("body *").size(), "There be elements in that there body")
})
@codeend
* </li>
* <li>Open your html page (<code>mytest.html</code>) in a browser. Did it pass?
* If not check the paths.
* <div class='whisper'>P.S. Your page and test files don't have to be in the same folder; however,
* on the filesystem, Firefox and Chrome don't let you access parent folders. We wanted the
* demo to work without having to host these files.
* </div>
*
* </li>
* <li>Now run your test in Selenium. In windows:
@codestart text
> envjs ../../pages/mypage_test.html
@codeend
In Linux / Mac:
@codestart text
> ./envjs ../../pages/mypage_test.html
@codeend
<div class='whisper'>This will run mytest.html on the filesystem. To run it served, just
pass in the url of your test page: <pre>envjs http://localhost/pages/mypage_test.html</pre>.
</div>
</li>
* </ol>
* <h2>Writing Tests</h2>
* <p>Writing tests is super easy and follows this pattern:</p>
<ol>
<li>Open a page with [FuncUnit.static.open S.open].
@codestart
S.open("//myapp/myapp.html")
@codeend
</li>
<li>Do some things
@codestart
//click something
S('#myButton').click()
//type something
S('#myInput').type("hello")
@codeend
</li>
<li>Wait for the page to change:
@codestart
//Wait until it is visible
S('#myMenu').visible()
//wait until something exists
S('#myArea').exists()
//waits a second
S.wait(1000);
@codeend
</li>
<li>Check your page in a callback:
@codestart
S('#myMenu').visible(function(){
//check that offset is right
equals(S('#myMenu').offset().left,
500,
"menu is in the right spot");
})
@codeend
</li>
</ol>
<h2>Actions, Waits, and Getters</h2>
<p>FuncUnit supports three types of commands: asynchronous actions and waits,
and synchronous getters.</p>
<p><b>Actions</b> are used to simulate user behavior such as clicking, typing, moving the mouse.</p>
<p><b>Waits</b> are used to pause the test script until a condition has been met.</p>
<p><b>Getters</b> are used to get information about elements in the page</p>
<p>Typically, a test looks like a series of action and wait commands followed by qUnit test of
the result of a getter command. Getter commands are almost always in a action or wait callback.</p>
<h3>Actions</h3>
Actions simulate user behavior. FuncUnit provides the following actions:
<ul>
<li><code>[FuncUnit.static.open open]</code> - Opens a page.</li>
<li><code>[FuncUnit.prototype.click click]</code> - clicks an element (mousedown, mouseup, click).</li>
<li><code>[FuncUnit.prototype.dblclick dblclick]</code> - two clicks followed by a dblclick.</li>
<li><code>[FuncUnit.prototype.rightClick rightClick]</code> - a right mousedown, mouseup, and contextmenu.</li>
<li><code>[FuncUnit.prototype.type type]</code> - Types characters into an element.</li>
<li><code>[FuncUnit.prototype.move move]</code> - mousemove, mouseover, and mouseouts from one element to another.</li>
<li><code>[FuncUnit.prototype.drag drag]</code> - a drag motion from one element to another.</li>
<li><code>[FuncUnit.prototype.scroll scroll]</code> - scrolls an element.</li>
</ul>
<p>Actions run asynchronously, meaning they do not complete all their events immediately.
However, each action is queued so that you can write actions (and waits) linearly.</p>
<p>The following might simulate typing and resizing a "resizable" textarea plugin:</p>
@codestart
S.open('resizableTextarea.html');
S('textarea').click().type("Hello World");
S('.resizer').drag("+20 +20");
@codeend
<h3>Getters</h3>
Getters are used to test the conditions of the page. Most getter commands correspond to a jQuery
method of the same name. The following getters are provided:
<table style='font-family: monospace'>
<tr>
<th colspan='2'>Dimensions</th> <th>Attributes</th> <th>Position</th> <th>Selector</th> <th>Style</th>
</tr>
<tr>
<td>[FuncUnit.prototype.width width]</td>
<td>[FuncUnit.prototype.height height]</td>
<td>[FuncUnit.prototype.attr attr]</td>
<td>[FuncUnit.prototype.position position]</td>
<td>[FuncUnit.prototype.size size]</td>
<td>[FuncUnit.prototype.css css]</td>
</tr>
<tr>
<td>[FuncUnit.prototype.innerWidth innerWidth]</td>
<td>[FuncUnit.prototype.innerHeight innerHeight]</td>
<td>[FuncUnit.prototype.hasClass hasClass]</td>
<td>[FuncUnit.prototype.offset offset]</td>
<td>[FuncUnit.prototype.exists exists]</td>
<td>[FuncUnit.prototype.visible visible]</td>
</tr>
<tr>
<td>[FuncUnit.prototype.outerWidth outerWidth]</td>
<td>[FuncUnit.prototype.outerHeight outerHeight]</td>
<td>[FuncUnit.prototype.val val]</td>
<td>[FuncUnit.prototype.scrollLeft scrollLeft]</td>
<td>[FuncUnit.prototype.missing missing]</td>
<td>[FuncUnit.prototype.invisible invisible]</td>
</tr>
<tr>
<td colspan='2'></td>
<td>[FuncUnit.prototype.text text]</td>
<td>[FuncUnit.prototype.scrollTop scrollTop]</td>
</tr>
<tr>
<td colspan='2'></td>
<td>[FuncUnit.prototype.html html]</td>
</tr>
</table>
<p>
As getters return synchronously, it's important that they happen after the action or wait command completes.
This is why getters are typically found in an action or wait command's callback:
</p>
The following checks that the textarea is 20 pixels taller after the drag.
@codestart
//save textarea reference
var txtarea = S('textarea'),
// save references to width and height
startingWidth = txtarea.width(),
startingHeight = txtarea.height();
S.open('resizableTextarea.html');
S('textarea').click().type("Hello World");
S('.resizer').drag("+20 +20", function(){
equals(txtarea.width(),
startingWidth,
"width stays the same");
equals(txtarea.height(),
startingHeight+20,
"height got bigger");
});
@codeend
<h3>Waits</h3>
<p>Waits are used to wait for a specific condition to be met before continuing to the next wait or
action command. Every getter commands can become a wait command when given a check value or function.
For
example, the following waits until the width of an element is 200 pixels and tests its offset.
</p>
@codestart
var sm = S("#sliderMenu");
sm.width( 200, function(){
var offset = sm.offset();
equals( offset.left, 200)
equals( offset.top, 200)
})
@codeend
<p>You can also provide a test function that when true, continues to the next action or wait command.
The following is equivalent to the previous example:
</p>
@codestart
var sm = S("#sliderMenu");
sm.width(
function( width ) {
return width == 200;
},
function(){
var offset = sm.offset();
equals( offset.left, 200)
equals( offset.top, 200)
}
)
@codeend
<div class='whisper'>Notice that the test function is provided the width of the element to use to check.</div>
<p>In addition to all the getter functions, FuncUnit provides:
</p>
<ul>
<li>[FuncUnit.static.wait S.wait] - waits a timeout before continuing.</li>
<li>[FuncUnit.prototype.wait S().wait] - waits a timeout before continuing.</li>
<li>[FuncUnit S(function(){})] - code runs between actions (like a wait with timeout = 0).</li>
</ul>
<h2>Automated Testing with Selenium</h2>
<p>FuncUnit has a command line mode, which allows you to run your tests as part of a checkin script or nightly build.
The Selenium server is used to automate opening browsers, and FuncUnit commands are sent to the test window via Selenium RC.</p>
<p>The envjs script (written for both Windows and OS X/Linux), is used to load your test page (the
same one that runs tests in the browser) in Env.js, a simulated browser running on Rhino. The
test page recognizes its running in the Rhino context and issues commands to Selenium accordingly.</p>
<p>Running these command line tests is simple:</p>
@codestart
my\path\to\envjs my\path\to\funcunit.html
@codeend
<p>Configuring settings for the command line mode will be covered next.</p>
<h3>Configuration</h3>
<p>FuncUnit loads a settings.js file every time it is runs in Selenium mode. This file defines
configuration that tells Selenium how to run. You can change which browsers run, their location,
the domain to serve from, and the speed of test execution.</p>
<p>FuncUnit looks first in the same directory as the funcunit page you're running tests from for
settings.js. For example if you're running FuncUnit like this:</p>
@codestart
funcunit\envjs mxui\combobox\funcunit.html
@codeend
<p>It will look first for mxui/combobox/settings.js.</p>
<p>Then it looks in its own root directory, where a default settings.js exists.
This is to allow you to create different settings for different projects.</p>
<h3>Setting Browsers</h3>
<p>FuncUnit.browsers is an array that defines which browsers Selenium opens and runs your tests in.
This is defined in settings.js. If this null it will default to a standard set of browsers for your OS
(FF and IE on Windows, FF on everything else). You populate it with strings like the following:</p>
@codestart
browsers: ["*firefox", "*iexplore", "*safari", "*googlechrome"]
@codeend
To define a custom path to a browser, put this in the string following the browser name like this:</p>
@codestart
browsers: ["*custom /path/to/my/browser"]
@codeend
See the [http://release.seleniumhq.org/selenium-remote-control/0.9.0/doc/java/com/thoughtworks/selenium/DefaultSelenium.html#DefaultSelenium Selenium docs] for more information on customizing browsers and other settings.</p>
## 64-bit Java
Some users will find Selenium has trouble opening while using 64 bit java (on Windows). You will see an error like
Could not start Selenium session: Failed to start new browser session. This is because Selenium
looks in the 64-bit Program Files directory, and there is no Firefox there. To fix this, change
browsers to include the path like this:
@codestart
FuncUnit.browsers = ["*firefox C:\\PROGRA~2\\MOZILL~1\\firefox.exe", "*iexplore"]
@codeend
<h3>Filesystem for Faster Tests</h3>
<p>You might want to use envjs to open local funcunit pages, but test pages on your server. This is possible, you
just have to change FuncUnit.href or FuncUnit.jmvcRoot. This file can load locally while everything else is
using a server because it is a static file and loads static script files.</p>
<p>Set jmvcRoot to point to the location you want your pages to load from, like this:</p>
@codestart
jmvcRoot: "localhost:8000"
@codeend
<p>Then make sure your test paths contain // in them to signify something relative to the jmvcRoot.
For example, S.open("//funcunit/test/myapp.html") would open a page at
http://localhost:8000/funcunit/test/myapp.html.</p>
<p>To load the command page from filesystem, start your test like you normally do:</p>
@codestart
funcunit\envjs path\to\funcunit.html
@codeend
<h3>Running From Safari and Chrome</h3>
<p>Certain browsers, like Safari and Chrome, don't run Selenium tests from filesystem because
of security resrictions. To get around this you have to run pages served from a server. The
downside of this is the test takes longer to start up, compared to loading from filesystem.</p>
<p>To run served pages, you must 1) provide an absolute path in your envjs path and 2) provide an absolute path
in jmvcRoot.</p>
<p>For example, to run cookbook FuncUnit tests from Google Chrome, I'd set the browsers and jmvcRoot like this:</p>
@codestart
browsers: ["*googlechrome"],
jmvcRoot: "http://localhost:8000/framework/",
@codeend
<p>then I'd start up Selenium like this:</p>
@codestart
funcunit\envjs http://localhost:8000/framework/cookbook/funcunit.html
@codeend
<p>To run Safari 5 in Windows, you should use the safariproxy browser string like this:</p>
@codestart
browsers: ["*safariproxy"],
@codeend
Mac Safari is just "*safari".
<h3>Slow Mode</h3>
<p>You can slow down the amount of time between tests by setting FuncUnit.speed. By default, FuncUnit commands
in Selenium will run as soon as the previous command is complete. If you set FuncUnit.speed to "slow" this
becomes 500ms between commands. You may also provide a number of milliseconds.
Slow mode is useful while debugging.</p>
<h2>Limitations</h2>
<ul>
<li>Selenium doesn't run Chrome/Opera/Safari on the filesystem.</li>
</ul>
<h2>Troubleshooting</h2>
<p>If you have trouble getting Selenium tests to run in IE, there are some settings that you can to change. First, disable the security settings for pages that run from the filesystem. To do this, open the Internet Options in IE and select the "Advanced" tab, and enable the option to "Allow active content to run in files on My Computer." This is what it looks like:</p>
@image jmvc/images/iesecurity.png
<p>You may also get an error that the popup blocker is enabled, which prevents the tests from running. It's actually not the popup blocker that causes this, but the fix is just as easy. Simply disable "Protected Mode" in IE, which is also in the Internet Options:</p>
@image jmvc/images/iepopups.png
*
* @constructor
* selects something in the other page
* @param {String|Function|Object} selector FuncUnit behaves differently depending if
* the selector is a string, a function, or an object.
* <h5>String</h5>
* The selector is treated as a css selector.
* jQuery/Sizzle is used as the selector so any selector it understands
* will work with funcUnit. FuncUnit does not perform the selection until a
* command is called upon this selector. This makes aliasing the selectors to
* JavaScript variables a great technique.
* <h5>Function</h5>
* If a function is provided, it will add that function to the action queue to be run
* after previous actions and waits.
* <h5>Object</h5>
* If you want to reference the window or document, pass <code>S.window</code>
* or <code>S.window.document</code> to the selector.
*
* @param {Number} [context] If provided, the context is the frame number in the
* document.frames array to use as the context of the selector. For example, if you
* want to select something in the first iframe of the page:
*
* S("a.mylink",0)
*/
FuncUnit = function(selector, context){
// if someone wraps a funcunit selector
if(selector && selector.funcunit === true){
return selector;
}
if(typeof selector == "function"){
return FuncUnit.wait(0, selector);
}
return new FuncUnit.init(selector, context)
}
/**
* @Static
*/
window.jQuery.extend(FuncUnit,oldFunc)
window.jQuery.extend(FuncUnit,{
//move jquery and clear it out
jquery : jQuery.noConflict(true),
/**
* @attribute href
* The location of the page running the tests on the server and where relative paths passed in to [FuncUnit.static.open] will be
* referenced from.
* <p>This is typically where the test page runs on the server. It can be set before calls to [FuncUnit.static.open]:</p>
@codestart
test("opening something", function(){
S.href = "http://localhost/tests/mytest.html"
S.open("../myapp")
...
})
@codeend
*/
// href comes from settings
/**
* @attribute jmvcRoot
* jmvcRoot should be set to url of JMVC's root folder.
* <p>This is used to calculate JMVC style paths (paths that begin with //).
* This is the prefered method of referencing pages if
* you want to test on the filesystem and test on the server.</p>
* <p>This is usually set in the global config file in <code>funcunit/settings.js</code> like:</p>
@codestart
FuncUnit = {jmvcRoot: "http://localhost/script/" }
@codeend
*/
// jmvcRoot comes from settings
/**
* Opens a page. It will error if the page can't be opened before timeout.
* <h3>Example</h3>
@codestart
//a full url
S.open("http://localhost/app/app.html")
//from jmvc root (FuncUnit.jmvcRoot must be set)
S.open("//app/app.html")
@codeend
* <h3>Paths in Selenium</h3>
* Selenium runs the testing page from the filesystem and by default will look for pages on the filesystem unless provided a full
* url or information that can translate a partial path into a full url. FuncUnit uses [FuncUnit.static.jmvcRoot]
* and [FuncUnit.static.href] to
* translate partial paths.
<table>
<tr>
<th>path</th>
<th>jmvcRoot</th>
<th>href</th>
<th>resulting url</th>
</tr>
<tr>
<td>//myapp/mypage.html</td>
<td>null</td>
<td>null</td>
<td>file:///C:/development/cookbook/public/myapp/mypage.html</td>
</tr>
<tr>
<td>//myapp/mypage.html</td>
<td>http://localhost/</td>
<td></td>
<td>http://localhost/myapp/mypage.html</td>
</tr>
<tr>
<td>http://foo.com</td>
<td></td>
<td></td>
<td>http://foo.com</td>
</tr>
<tr>
<td>../mypage.html</td>
<td></td>
<td>http://localhost/myapp/funcunit.html</td>
<td>http://localhost/mypage.html</td>
</tr>
</table>
*
* @param {String} path a full or partial url to open. If a partial is given,
* @param {Function} callback
* @param {Number} timeout
*/
open: function( path, callback, timeout ) {
var fullPath = FuncUnit.getAbsolutePath(path),
temp;
if(typeof callback != 'function'){
timeout = callback;
callback = undefined;
}
FuncUnit.add({
method: function(success, error){ //function that actually does stuff, if this doesn't call success by timeout, error will be called, or can call error itself
steal.dev.log("Opening " + path)
FuncUnit._open(fullPath, error);
FuncUnit._onload(function(){
FuncUnit._opened();
success()
}, error);
},
callback: callback,
error: "Page " + path + " not loaded in time!",
timeout: timeout || 30000
});
},
/**
* @hide
* Gets a path, will use steal if present
* @param {String} path
*/
getAbsolutePath: function( path ) {
if(typeof(steal) == "undefined" || steal.root == null){
return path;
}
var fullPath,
root = FuncUnit.jmvcRoot || steal.root.path;
if (/^\/\//.test(path)) {
fullPath = new steal.File(path.substr(2)).joinFrom(root);
}
else {
fullPath = path;
}
if(/^http/.test(path))
fullPath = path;
return fullPath;
},
/**
* @attribute browsers
* Used to configure the browsers selenium uses to run FuncUnit tests.
* If you need to learn how to configure selenium, and we haven't filled in this page,
* post a note on the forum and we will fill this out right away.
*/
// for feature detection
support : {},
/**
* @attribute window
* Use this to refer to the window of the application page. You can also
* reference window.document.
* @codestart
* S(S.window).innerWidth(function(w){
* ok(w > 1000, "window is more than 1000 px wide")
* })
* @codeend
*/
window : {
document: {}
},
_opened: function() {}
});
(function(){
//the queue of commands waiting to be run
var queue = [],
//are we in a callback function (something we pass to a FuncUnit plugin)
incallback = false,
//where we should add things in a callback
currentPosition = 0;
FuncUnit.
/**
* @hide
* Adds a function to the queue. The function is passed within an object that
* can have several other properties:
* method : the method to be called. It will be provided a success and error function to call
* callback : an optional callback to be called after the function is done
* error : an error message if the command fails
* timeout : the time until success should be called
* bind : an object that will be 'this' of the success
* stop :
*/
add = function(handler){
//if we are in a callback, add to the current position
if (incallback) {
queue.splice(currentPosition,0,handler)
currentPosition++;
}
else {
//add to the end
queue.push(handler);
}
//if our queue has just started, stop qunit
//call done to call the next command
if (queue.length == 1 && ! incallback) {
stop();
setTimeout(FuncUnit._done, 13)
}
}
//this is called after every command
// it gets the next function from the queue
FuncUnit._done = function(){
var next,
timer,
speed = 0;
if(FuncUnit.speed == "slow"){
speed = 500;
}
else if (FuncUnit.speed){
speed = FuncUnit.speed;
}
if (queue.length > 0) {
next = queue.shift();
currentPosition = 0;
// set a timer that will error
//call next method
setTimeout(function(){
timer = setTimeout(function(){
next.stop && next.stop();
ok(false, next.error);
FuncUnit._done();
},
(next.timeout || 10000) + speed)
next.method( //success
function(){
//make sure we don't create an error
clearTimeout(timer);
//mark in callback so the next set of add get added to the front
incallback = true;
if (next.callback)
next.callback.apply(next.bind || null, arguments);
incallback = false;
FuncUnit._done();
}, //error
function(message){
clearTimeout(timer);
ok(false, message);
FuncUnit._done();
})
}, speed);
}
else {
start();
}
}
FuncUnit.
/**
* Waits a timeout before running the next command. Wait is an action and gets
* added to the queue.
* @codestart
* S.wait(100, function(){
* equals( S('#foo').innerWidth(), 100, "innerWidth is 100");
* })
* @codeend
* @param {Number} [time] The timeout in milliseconds. Defaults to 5000.
* @param {Function} [callback] A callback that will run
* after the wait has completed,
* but before any more queued actions.
*/
wait = function(time, callback){
if(typeof time == 'function'){
callback = time;
time = undefined;
}
time = time != null ? time : 5000
FuncUnit.add({
method : function(success, error){
steal.dev.log("Waiting "+time)
setTimeout(success, time)
},
callback : callback,
error : "Couldn't wait!",
timeout : time + 1000
});
return this;
}
/**
* @hide
* @function repeat
* Takes a function that will be called over and over until it is successful.
*/
FuncUnit.repeat = function(checker, callback, error, timeout){
var interval,
stopped = false ,
stop = function(){
clearTimeout(interval)
stopped = true;
};
FuncUnit.add({
method : function(success, error){
interval = setTimeout(function(){
var result = null;
try {
result = checker()
}
catch (e) {
//should we throw this too error?
}
if (result) {
success();
}else if(!stopped){
interval = setTimeout(arguments.callee, 10)
}
}, 10);
},
callback : callback,
error : error,
timeout : timeout,
stop : stop
});
}
FuncUnit.makeArray = function(arr){
var narr = [];
for (var i = 0; i < arr.length; i++) {
narr[i] = arr[i]
}
return narr;
}
FuncUnit.
/**
* @hide
* Converts a string into a Native JS type.
* @param {Object} str
*/
convert = function(str){
//if it is an object and not null, eval it
if (str !== null && typeof str == "object") {
return object;
}
str = String(str);
switch (str) {
case "false":
return false;
case "null":
return null;
case "true":
return true;
case "undefined":
return undefined;
default:
if (/^\d+\.\d+$/.test(str) || /^\d+$/.test(str)) {
return 1 * str;
}
return str;
}
}
})();
/**
* @prototype
*/
FuncUnit.init = function(s, c){
this.selector = s;
this.context = c == null ? FuncUnit.window.document : c;
}
FuncUnit.init.prototype = {
funcunit : true,
/**
* Types text into an element. This makes use of [Syn.prototype.type] and works in
* a very similar way.
* <h3>Quick Examples</h3>
* @codestart
* //types hello world
* S('#bar').type('hello world')
*
* //submits a form by typing \r
* S("input[name=age]").type("27\r")
*
* //types FuncUnit, then deletes the Unit
* S('#foo').type("FuncUnit\b\b\b\b")
*
* //types JavaScriptMVC, then removes the MVC
* S('#zar').type("JavaScriptMVC[left][left][left]"+
* "[delete][delete][delete]")
*
* //types JavaScriptMVC, then selects the MVC and
* //deletes it
* S('#zar').type("JavaScriptMVC[shift]"+
* "[left][left][left]"+
* "[shift-up][delete]")
* @codeend
* <h2>Characters</h2>
* You can type the characters found in [Syn.static.keycodes].
*
* @param {String} text the text you want to type
* @param {Function} [callback] a callback that is run after typing, but before the next action.
* @return {FuncUnit} returns the funcUnit for chaining.
*/
type: function( text, callback ) {
var selector = this.selector,
context = this.context;
FuncUnit.add({
method : function(success, error){
steal.dev.log("Typing "+text+" on "+selector)
FuncUnit.$(selector, context, "triggerSyn", "_type", text, success)
},
callback : callback,
error : "Could not type " + text + " into " + this.selector,
bind : this
});
return this;
},
/**
* Waits until an element exists before running the next action.
* @codestart
* //waits until #foo exists before clicking it.
* S("#foo").exists().click()
* @codeend
* @param {Function} [callback] a callback that is run after the selector exists, but before the next action.
* @return {FuncUnit} returns the funcUnit for chaining.
*/
exists: function( callback ) {
if(true){
return this.size(function(size){
return size > 0;
}, callback)
}
return this.size() == 0;
},
/**
* Waits until no elements are matched by the selector. Missing is equivalent to calling
* <code>.size(0, callback);</code>
* @codestart
* //waits until #foo leaves before continuing to the next action.
* S("#foo").missing()
* @codeend
* @param {Function} [callback] a callback that is run after the selector exists, but before the next action
* @return {FuncUnit} returns the funcUnit for chaining.
*/
missing: function( callback ) {
return this.size(0, callback)
},
/**
* Waits until the funcUnit selector is visible.
* @codestart
* //waits until #foo is visible.
* S("#foo").visible()
* @codeend
* @param {Function} [callback] a callback that runs after the funcUnit is visible, but before the next action.
* @return [funcUnit] returns the funcUnit for chaining.
*/
visible: function( callback ) {
var self = this,
sel = this.selector,
ret;
this.selector += ":visible"
if(true){
return this.size(function(size){
return size > 0;
}, function(){
self.selector = sel;
callback && callback();
})
}else{
ret = this.size() > 0;
this.selector = sel;
return ret;
}
},
/**
* Waits until the selector is invisible.
* @codestart
* //waits until #foo is invisible.
* S("#foo").invisible()
* @codeend
* @param {Function} [callback] a callback that runs after the selector is invisible, but before the next action.
* @return [funcUnit] returns the funcUnit selector for chaining.
*/
invisible: function( callback ) {
var self = this,
sel = this.selector,
ret;
this.selector += ":visible"
return this.size(0, function(){
self.selector = sel;
callback && callback();
})
},
/**
* Drags an element into another element or coordinates.
* This takes the same paramameters as [Syn.prototype.move move].
* @param {String|Object} options A selector or coordinates describing the motion of the drag.
* <h5>Options as a Selector</h5>
* Passing a string selector to drag the mouse. The drag runs to the center of the element
* matched by the selector. The following drags from the center of #foo to the center of #bar.
* @codestart
* S('#foo').drag('#bar')
* @codeend
* <h5>Options as Coordinates</h5>
* You can pass in coordinates as clientX and clientY:
* @codestart
* S('#foo').drag('100x200')
* @codeend
* Or as pageX and pageY
* @codestart
* S('#foo').drag('100X200')
* @codeend
* Or relative to the start position
* S('#foo').drag('+10 +20')
* <h5>Options as an Object</h5>
* You can configure the duration, start, and end point of a drag by passing in a json object.
* @codestart
* //drags from 0x0 to 100x100 in 2 seconds
* S('#foo').drag({
* from: "0x0",
* to: "100x100",
* duration: 2000
* })
* @codeend
* @param {Function} [callback] a callback that runs after the drag, but before the next action.
* @return {funcUnit} returns the funcunit selector for chaining.
*/
drag: function( options, callback ) {
if(typeof options == 'string'){
options = {to: options}
}
options.from = this.selector;
var selector = this.selector,
context = this.context;
FuncUnit.add({
method: function(success, error){
steal.dev.log("dragging " + selector)
FuncUnit.$(selector, context, "triggerSyn", "_drag", options, success)
},
callback: callback,
error: "Could not drag " + this.selector,
bind: this
})