Skip to content
Newer
Older
100644 646 lines (433 sloc) 21.4 KB
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
1 <!DOCTYPE html>
67e5a2f @ianb update template
authored
2 <html class="no-js">
3 <head>
4 <meta charset="utf-8">
5 <link rel="stylesheet" href="doctest.css">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
7 <title>A doctest.js tutorial</title>
8 <meta name="description" content="An introduction to the Javascript testing framework, doctest.js">
9 <meta name="viewport" content="width=device-width">
10 <link rel="stylesheet" href=".resources/boilerplate/css/normalize.min.css">
11 <link rel="stylesheet" href=".resources/boilerplate/css/main.css">
12 <link rel="stylesheet" href=".resources/doc.css">
13 <script src=".resources/boilerplate/js/vendor/modernizr-2.6.1.min.js"></script>
14 <script src="doctest.js"></script>
15 <!-- EXTRA_HEAD -->
85de751 @ianb Intro page and prettying
authored
16 <script type="text/javascript" src="./.resources/toc.js"></script>
17 <!-- /EXTRA_HEAD -->
67e5a2f @ianb update template
authored
18 </head>
19 <body class="autodoctest">
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
20
67e5a2f @ianb update template
authored
21 <a href="http://github.com/ianb/doctestjs"><img
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
22 style="position: absolute; top: 0; right: 0; border: 0;"
23 src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"
67e5a2f @ianb update template
authored
24 alt="Fork me on GitHub"></a>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
25
67e5a2f @ianb update template
authored
26 <div class="header-container">
27 <header class="wrapper clearfix">
28 <h1 class="title"><a href="/">Doctest.js</a>: <!-- TITLE -->A Tutorial <!-- /TITLE --></h1>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
29
67e5a2f @ianb update template
authored
30 <!--
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
31 <nav>
32 <ul>
67e5a2f @ianb update template
authored
33 <li><a href="#">nav ul li a</a></li>
34 <li><a href="#">nav ul li a</a></li>
35 <li><a href="#">nav ul li a</a></li>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
36 </ul>
37 </nav>
67e5a2f @ianb update template
authored
38 -->
39
40 </header>
41 </div>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
42
67e5a2f @ianb update template
authored
43 <div class="main-container">
44 <div class="main wrapper clearfix">
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
45
46
67e5a2f @ianb update template
authored
47 <!-- BODY -->
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
48
85de751 @ianb Intro page and prettying
authored
49 <div id="contents"></div>
50
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
51 <article>
52 <section id="introduction">
53 <header>
54 <h1>What's it like?</h1>
55 </header>
56
57 <p>
58
59 So you've decided to finally get religion when it comes to testing your Javascript code? Or, you feel like testing just isn't as easy as it could be, and want to find a better way to test your Javascript code? Or even: you've thought about or tried doing Test Driven Development but you've found it hard to get going? Let's do this...
60
61 </p>
62
63 <p>
64
65 Doctest.js is basically <em>example code</em> and then <em>expected output</em>. This is really what most tests look like, but instead of lots of <code>assertEqual(example, expected)</code> this example/expected combination is embedded into the structure of the test.
66
67 </p>
68
69 <aside>
70 <h3>Page Setup</h3>
71
72 <p>
73 See <a href="#html">Setting Up Your HTML</a> to see how to get your test running in the browser.
74 </p>
75 </aside>
76
77 <p>
78
79 I'm going to get right into how the test code looks, but to actually <em>use</em> doctest.js you have to setup an HTML file in a specific format. That is described later in the <a href="#html">HTML section</a>.
80
81 </p>
82
83 <p>
84
85 The structure looks like something you've probably seen before. We add one new function, <code>print()</code>, that works a lot like <code>console.log()</code>. Then we have a comment that shows what we expect to be output. A really simple example:
86
87 <pre class="commenttest">
88 function factorial(n) {
89 if (typeof n != "number") {
90 throw "You must give a number";
91 }
92 if (n <= 0) {
93 return 1;
94 }
95 return n * factorial(n-1);
96 }
97
98 print(factorial(4))
99 // => 25
100 </pre>
101
102 </p>
103
104 <p>
105
106 See what I did there? 25 is totally the wrong answer! Also see what happened, the test just ran and told us so! There's also a summary of all the tests; if you do nothing it shows up at the top of the page, but in the interest of introducing the summary, here it is:
107
108 </p>
109
110 <p id="doctest-output" style="border: 1px solid #999"></p>
111
112 <p>
113
114 You'll notice it shows a failure (or more than one &mdash; it's the summary for all the examples in this tutorial). It also has a link to each failure, so you can jump to the problematic section.
115
116 </p>
117
118 <aside>
119 <h3>Quoting</h3>
120
121 <p>
122 If you are writing your tests inline in the document, remember that you don't need to quote <code>></code> as <code>&amp;gt;</code>, that's only needed for <code>&lt;</code>
123 </p>
124 </aside>
125
126 <p>
127
128 Let's look at what we did there: <code>print(factorial(4))</code> and <code>// => 25</code> &mdash; the output is just a comment that starts with <code>=></code>.
129
130 </p>
131
132 </section>
133
134 <section id="errors">
135 <header>
136 <h3>Testing for error conditions</h3>
137 </header>
138
139 <p>
140
141 You can also test errors:
142
143 <pre class="commenttest">
144 print(factorial(null));
145 // => Error: You must give a number
146 </pre>
147
148 When an exception is thrown it will print out <code>Error: (error text)</code> which you can match against. This way you can test for error conditions just like you test how "correct" invocations work. Note that the <code>print()</code> isn't really necessary here, you could do this just as well:
149
150 <pre class="commenttest">
151 factorial(null);
152 // => Error: You must give a number
153 </pre>
154
155 </p>
156
157 </section>
158
159 <section id="print">
160 <header>
161 <h3><code>print()</code> and output matching</h3>
162 </header>
163
85de751 @ianb Intro page and prettying
authored
164 <aside>
165 <p>For more detail on printing see <a href="reference.html#print">the reference</a>.</p>
166 </aside>
167
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
168 <p>
169
170 <code>print()</code> pretty-prints things. This is important, because you have to "expect" the same output that <code>print()</code> produces. You can give multiple arguments, like with <code>console.log</code>.
171
172 <pre class="commenttest">
173 print({someProperty: 123, something: {a: 1, b: 2}, "foo": 123.1032, "another-property": [1,2,3,4]});
174 /* =>
175 {
176 "another-property": [1, 2, 3, 4],
177 foo: 123.1032,
178 someProperty: 123,
179 something: {a: 1, b: 2}
180 }
181 */
182 </pre>
183
184 You might notice that the attributes are alphabetized and are quoted only when necessary. If it's a small object it stays on one line:
185
186 <pre class="commenttest">
187 print({someProperty: 123});
188 // => {someProperty: 123}
189 </pre>
190
191 </p>
192
193 <p>
194
195 But sometimes the output is unpredictable; or rather you can predict it will change. When that's the case you can basically put a wildcard in the expected output: <code>...</code> &mdash; that will match anything, including multiple lines. In addition you can use <code>?</code> to match one word-like-thing (a number, symbol, etc; not <code>&quot;</code> or whitespace or other symbols). You can use it like this:
196
197 <pre class="commenttest">
198 print({
199 date: new Date(),
200 timestamp: Date.now()
201 });
202
203 // => {date: ..., timestamp: ?}
204 </pre>
205
206 </p>
207
208 <p>
209
210 You might notice that it <em>passes</em>, but you still get to see the actual output. This is a great way to show information that you can review, without actually testing. For instance, you might be testing something that connects to a server, in that case you might want to do this:
211
212 <pre class="commenttest">
213 var server = {url: "http://localhost:8000"} // or some calculated value
214 print(server.url);
215 // => ...
216 </pre>
217
218 Now if everything seems breaky you can be 100% sure of what server you are connecting to.
219
220 </p>
221
222 <p>
223
224 If you have a variable that is dynamic but you still care about the value, you should do something like this:
225
226 <pre class="commenttest">
227 var date = Date.now();
228 print(date == date, date);
229 // => true ...
230 </pre>
231
232 Think of this pattern of <code>print(x == y)</code> as a kind of <code>assertEqual()</code> equivalent.
233
234 </p>
235
236 </section>
237
238 <section id="async">
239 <header>
240 <h3>Testing async code</h3>
241 </header>
242
85de751 @ianb Intro page and prettying
authored
243 <aside>
244 <p>For more on async and <code>wait</code> see <a href="reference.html#wait">the reference</a>.</p>
245 </aside>
246
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
247 <p>
248
249 This is all well and good, but lots of code in Javascript is <em>asynchronous</em>, meaning that you don't just call a function that returns a value. Doctest.js has an answer to that too: a great answer!
250
251 </p>
252
253 <p>
254
255 For our example we'll use <code>XMLHttpRequest</code>, a common source of asynchronosity. We'll test a request (just a loopback request, but if you are testing a foreign service you'd need <a href="http://enable-cors.org/">CORS access</a>). When we instantiate and setup the request we don't have anything really to test &mdash; we want to test what happens when the request <em>completes</em>.
256
257 </p>
258
259 <p>
260
261 To do this we'll use <code>wait()</code> &mdash; when this function is calls the test runner will wait at the point where it sees <code>// =></code>, for a certain amount of time or until a certain condition is met. Only then will it compare all the output that has happened to what was expected, and run the next chunk of test.
262
263 </p>
264
265 <p>
266
267 You can use this like: <code>wait(function () {return true when done})</code> or <code>wait(millisecondsToWait)</code>. We'll use the first form, which is almost always better, since it allows the test to continue more quickly. Tests also always time out eventually (by default the timeout is 5000 milliseconds, i.e., 5 seconds &mdash; by convention everything in Javascript is in milliseconds).
268
269 <pre class="commenttest">
270 var endpoint = location.href;
271 print(endpoint);
272 // => ...
273
274 var req = new XMLHttpRequest();
275 req.open("GET", endpoint);
276 req.onreadystatechange = function () {
277 if (req.readyState != 4) {
278 // hasn't actually finished
279 return;
280 }
281 print("Result:", req.status, req.getResponseHeader('content-type'));
282 };
283 req.send();
284
285 wait(function () {return req.readyState == 4;});
286
287 print("Current state:", req.readyState);
288
289 /* =>
290 Current state: 1
291 Result: 200 text/html
292 */
293 </pre>
294
295 I put in something tricky there to try to clarify what <code>wait()</code> really does. You'll notice there's a call to <code>wait()</code> that makes sure that <code>req.readyState == 4</code> (that's the code that means the request is finished). But right after when we do <code>print(req.readyState)</code> it shows a readyState of 1. That's because the <em>entire</em> block is printed (from the previous <code>// =></code> output up until the next one). But the test runner keeps collecting output and doesn't run the next section until that <code>wait()</code> clause returns true.
296
297 </p>
298
299 <p>
300
301 Another thing to note is that <code>wait()</code> needs to be called when that block of code is run &mdash; it can't be inside a function that isn't called. That said, if you write test helper functions (and you should!), it often works well to put those calls in the helper function. We'll see an example of that next...
302
303 </p>
304
305 </section>
306
307 <section id="spy">
308 <header>
309 <h3>The Spy</h3>
310 </header>
311
85de751 @ianb Intro page and prettying
authored
312 <aside>
313 <p>For more information on <code>Spy</code> see <a href="reference.html#spy">the reference</a>.</p>
314 </aside>
315
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
316 <p>
317
318 Note: the next example will use some jQuery, just for the heck of it, though there is no special support for jQuery or other frameworks in Doctest.
319
320 </p>
321
322 <p>
323
324 If you use these tools you might end up writing code like this quite a lot:
325
326 <pre class="commenttest">
327 // We've embedded a button just below this element
328 var button = $('#example-button');
329 // Just to highlight what we're working with:
330 button.css({border: '1px dotted #f00'});
331 button.click(function () {
332 print('Button clicked');
333 });
334
335 // Now we test that our event handler gets called when we do an artificial click of the button:
336 button.click();
337 // => Button clicked
338 </pre>
339
340 <button id="example-button" type="button">Example Button</button>
341
342 </p>
343
344 <p>
345
346 But maybe we are curious about the arguments passed to that handler &mdash; even though we ignored the arguments, there was one passed. And we might want to show what <code>this</code> is bound to; <code>this</code> is kind of like an invisible extra argument passed to every function invocation. We could make a fancier <code>print()</code> statement there. But instead, there's also a handy tool for tracking calls: <code>Spy</code>.
347
348 </p>
349
350 <p>
351
352 An example:
353
354 <pre class="commenttest">
355 var button = $('#example-button2');
356 button.css({border: '1px dotted #00f'});
357 button.click(Spy('button.click'));
358 button.click();
359
360 // => ...
361 </pre>
362
363 <button id="example-button2" type="button">Example Button 2</button>
364
365 </p>
366
367 <p>
368
369 That's a lot more information! Let's break it down:
370
371 <pre>
372 <b style="color: #00a">&lt;button id="example-button2" style="border: 1px solid rgb(0, 0, 255); " type="button">Example Button 2&lt;/button>.</b><span style="color: #0a0">button.click</span>({ ...
373 </pre>
374
375 There's two bits of information here. The first is the value (in <span style="color: #00a">blue</span>) of <code>this</code>, which is the <code>#example-button2</code> element. You'll notice it shows the HTML of the element. If you want Spy to ignore this you can use <code>Spy('button.click', {ignoreThis: true})</code>.
376
377 </p>
378
379 <p>
380
381 The second value (in <span style="color: #0a0">green</span>) is the name we gave the Spy when we created it. Note that Spy names are also identifiers, that is, <code>Spy('button.click') === Spy('button.click')</code>.
382
383 </p>
384
385 <p>
386
387 Next of course is all the arguments. There's a lot of arguments there. They are... interesting. You'll notice some references to <code>...recursive...</code> which is what you get when you have self-referencing data structures. But maybe you want to test just a little of that structure without testing all of it. You might do something like this:
388
389 <pre class="commenttest">
390 // Spy('button.click') fetches the same Spy we were using before, which still has all its call information
391 // .formatCall() shows the way the Spy was last called.
392 print(Spy('button.click').formatCall());
393
394 /* =>
395 &lt;button...&lt;/button>.button.click({
396 currentTarget: &lt;button...
397 ...
398 timeStamp: ?,
399 type: "click"
400 })
401 */
402
403 </pre>
404
405 So we've tested that the <code>type</code> is click, that it has a timeStamp (though not the value) and that the <code>currentTarget</code> is a button (presumably the button we bound it to). We still get to <em>see</em> all the other information, we just aren't <em>testing</em> it. This can be helpful in the future when you realize there's more you want to test &mdash; you can look at the test output and transcribe more into the test. Or when something fails later you might want to inspect that output to make sure everything is what you expect (and when you see something unexpected that's also a great time to expand your test).
406
407 </p>
408
409 <p>
410
411 Spies have a bunch of options, and act as a kind of mock object as well. You can pass in options as the second argument, like <code>Spy('name', {options...})</code>. Some highlights:
412
413
414 <dl>
415 <dt><code>applies</code></dt>
416 <dd>This is a function that the Spy "wraps". So if you do <code>Spy('click', {applies: function (event) {this.remove(); return false;}})</code> then you'll get the same output printed, but you'll also run <code>this.remove()</code>.</dd>
417
418 <dt><code>writes</code></dt>
419 <dd>If you set this to <code>false</code> then it won't automatically print out the calls. The values of the calls will still be recorded, and you can use <code>aSpy.formatCall()</code> to see them.</dd>
420
421 <dt><code>ignoreThis</code></dt>
422 <dd><p>Lots of code binds <code>this</code> without intending too. It's really easy in Javascript to do this. For instance, if you do <code>handlers[i]()</code> then <code>this</code> will be <code>handlers</code>. (Instead you might do <code>var handler = handlers[i]; handler()</code>)</p>
423
424 <p>Anyway, sometimes you don't care about <code>this</code>, and using <code>{ignoreThis: true}</code> lets you do that.
425 </p></dd>
426
427 <dt><code>returns</code></dt>
428 <dd>If you want the Spy to return a value when its called, give the value here. Normally it returns <code>undefined</code>.</dd>
429
430 <dt><code>throwError</code></dt>
431 <dd>This makes the Spy throw the given error anytime it is called.</dd>
432
433 <dt><code>wait</code></dt>
434 <dd>If you use <code>Spy('name', {wait: true})</code> then the test will wait until the Spy has been called. This is a pretty common pattern. It's basically the same as <code>Spy('name').wait()</code>.</dd>
435
436 </dl>
437
438 </p>
439
440 <p>
441
442 You can set values like <code>Spy.defaultOptions.writes = false</code> if you want to set one of these by default.
443
444 </p>
445
446 <p>
447
448 If you want to inspect how the Spy has been called, you can check a few attributes:
449
450 <dl>
451
452 <dt><code>.called</code></dt>
453 <dd>True once this Spy has been called.</dd>
454
455 <dt><code>.self</code> and <code>.selfList</code></dt>
456 <dd>This is the value of <code>this</code>, or <code>.selfList</code> contains a history for each call.</dd>
457
458 <dt><code>.args</code> and <code>.argList</code></dt>
459 <dd>The arguments the function was called with, or <code>.argList</code> is a history of arguments.</dd>
460
461 </dl>
462
463 </p>
464
465 </section>
466
467 <section id="console">
468 <header>
469 <h3>console.log</h3>
470 </header>
471
472 <p>
473
474 This isn't a feature you have to <em>do</em> anything about, it's just there for you, so I'm just going to point it out.
475
476 </p>
477
478 <p>
479
480 When you use <code>console.log</code> (or any of its friends, like <code>console.warn</code>) those messages will be captured (in addition to going to the log as normal), and the output will be shown in the specific test where they happened. A quick example:
481
482 <pre class="commenttest">
483 function enumProps(object) {
484 console.log('obj', object);
485 var result = {}
486 for (var attr in object) {
487 if (typeof object[attr] == "number" && attr.toUpperCase() == attr) {
488 result[object[attr]] = attr;
489 }
490 }
491 return result;
492 }
493
494 print(enumProps($('#example-button')[0]));
495
496 // => {...}
497
498 </pre>
499
500 You can think of it a little like <code>print()</code> goes to stdout, and <code>console.log()</code> goes to stderr.
501
502 </p>
503
504 </section>
505
506 <section id="abort">
507 <header>
508 <h3>Giving Up</h3>
509 </header>
510
511 <p>
512
513 Tests often require some feature or setup to be usable at all. When it's not setup right you'll just get a bunch of meaningless failures. For this reason there's a way to abort all your tests. If you call <code>Abort()</code> then no further tests will be run. If you want to connect to a server, for instance, you might check that the server is really there, and if not then just abort the rest of the tests. For example:
514
515 <pre>
516 $.ajax({
517 url: '/ping',
518 success: Spy('ping', {wait: true, ignoreThis: true}),
519 error: function () {
520 Abort("Server isn't up");
521 }
522 });
523
524 // => ping(...)
525 </pre>
526
527 </p>
528
529 </section>
530
531 <section id="html">
532 <header>
533 <h2>Setting Up Your HTML</h2>
534 </header>
535
85de751 @ianb Intro page and prettying
authored
536 <aside>
537 <p>For more on the HTML setup see <a href="reference.html#format">the reference documentation</a>.</p>
538 </aside>
539
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
540 <p>
541
542 I wanted to show you all the cool features of doctest first, but you can't actually use any of them unless you set up a test runner page. Luckily the page is pretty simple. Let's say you've put doctest.js into <code>doctest/</code>:
543
544 <pre>
545 &lt;DOCTYPE html>
546 &lt;html>
547 &lt;head>
548 &lt;meta charset="UTF-8">
549 &lt;title>My Test&lt;/title>
550 &lt;script src="doctest/doctest.js">&lt;/script>
551 &lt;link href="doctest/doctest.css" rel="stylesheet">
552 <b>&lt;script src="mylibrary.js">&lt;/script></b>
553 &lt;/head>
554 &lt;body class="autodoctest">
555
556 A test:
557
558 &lt;pre class="commenttest">
559 test goes here
560 &lt;/pre>
561
562 &lt;/body>&lt;/html>
563 </pre>
564
565 </p>
566
567 <p>
568
569 Mostly it's just boilerplate: you have to include <code>doctest.js</code> and <code>doctest.css</code> and of course any libraries or dependencies of the thing you are testing. You also must use <b><code>&lt;body class="autodoctest"></code></b> &mdash; that's what tells doctest.js you want to find and run tests right away.
570
571 </p>
572
573 <p>
574
575 Each test then is in a <code>&lt;pre class="commenttest"></code>. You might not want to actully write your tests <em>inside</em> the HTML, and instead put them in a separate Javascript file. To do that use:
576
577 <pre>
578 &lt;pre class="commenttest" href="./my_tests.js">&lt;/pre>
579 </pre>
580
581 This will load the test code from <code>./my_tests.js</code> and inline it into the element. This is how I personally write most of my tests, though when moving between a narrative and tests (as I am doing in this tutorial) it is nice to keep the tests together with the descriptions.
582
583 </p>
584
585 <p>
586
935c252 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
587 Note that specifically when you use <code>href="URL.js"</code> you can include the comment <code><b>// == SECTION</b> Section header</code>, and the <code>&lt;pre</code> element will turn into multiple elements with headers.
588
589 </p>
590
591 <p>
592
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
593 A pass/fail summary is automatically added to the top of the page, though you can use <code>&lt;div id="doctest-output">&lt;/div></code> to position it someplace specific (as we did in this tutorial).
594
595 </p>
596
597 </section>
598
599 </article>
600
601 <!--
602 <aside>
603 <h3>aside</h3>
604
605 <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam sodales urna non odio egestas tempor. Nunc vel vehicula ante. Etiam bibendum iaculis libero, eget molestie nisl pharetra in. In semper consequat est, eu porta velit mollis nec. Curabitur posuere enim eget turpis feugiat tempor. Etiam ullamcorper lorem dapibus velit suscipit ultrices.</p>
606
607 </aside>
608 -->
609
610 <!-- /BODY -->
611
67e5a2f @ianb update template
authored
612 </div> <!-- #main -->
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
613
67e5a2f @ianb update template
authored
614 </div> <!-- #main-container -->
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
615
67e5a2f @ianb update template
authored
616 <div class="footer-container">
617 <footer class="wrapper">
618 <h3>doctest.js is by <a href="http://ianbicking.org">Ian Bicking</a>.
619 It's on <a href="https://github.com/ianb/doctestjs">github</a>!</h3>
620 </footer>
621 </div>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
622
67e5a2f @ianb update template
authored
623 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
624 <script>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
625 window.jQuery || document.write('<script src=".resources/boilerplate/js/vendor/jquery-1.8.1.min.js"><\/script>')
67e5a2f @ianb update template
authored
626 </script>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
627
67e5a2f @ianb update template
authored
628 <script src=".resources/boilerplate/js/main.js"></script>
4909b88 @ianb Rearrange templates and general arrangement. Added a tutorial
authored
629
630 <script type="text/javascript">
631
632 var _gaq = _gaq || [];
633 _gaq.push(['_setAccount', 'UA-34921728-1']);
634 _gaq.push(['_trackPageview']);
635
636 (function() {
637 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
638 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
639 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
640 })();
641
642 </script>
643
644 </body>
645 </html>
Something went wrong with that request. Please try again.