├── .editorconfig
├── .gitignore
├── .nvmrc
├── LICENSE.md
├── README.md
├── assets
└── fonts
│ └── leaguegothic
│ ├── LICENSE
│ └── league_gothic-webfont.ttf
├── css
├── main.css
├── print.css
├── reset.css
└── webfonts
│ ├── BryantWebMedium.eot
│ ├── BryantWebMedium.ttf
│ └── BryantWebMedium.woff
├── examples
├── casper
│ ├── drupal.js
│ ├── package-lock.json
│ ├── package.json
│ ├── picturefill.js
│ ├── test-js-code.js
│ └── user-actions.js
├── grunt
│ ├── devperf
│ │ ├── Gruntfile.js
│ │ └── package.json
│ ├── jshint
│ │ ├── Gruntfile.js
│ │ ├── example.js
│ │ └── package.json
│ ├── pagespeed
│ │ ├── Gruntfile.js
│ │ └── package.json
│ ├── perfbudget
│ │ ├── Gruntfile.js
│ │ └── package.json
│ └── phantomas
│ │ ├── Gruntfile.js
│ │ └── package.json
├── targets
│ ├── README.md
│ ├── jquery-3.3.1-min.js
│ ├── modernizr-3.6.0-custom.js
│ ├── test-js-code.html
│ ├── test-user-actions-p1.html
│ ├── test-user-actions-p2.html
│ └── test-user-actions-p3.html
└── wraith
│ ├── configs
│ └── demo.yaml
│ └── javascript
│ └── snap.js
├── favicon.ico
├── img
├── 4k-logo-square-500px.png
├── browserstack-screenshots.png
├── delaypackage.gif
├── grunt-devperf-graph.png
├── grunt-perfbudget-output.png
├── grunt-phantomas-graph.png
├── jshint.png
├── timing-overview.png
├── visitor-flow.png
└── wraith-example.png
├── index.html
├── js
├── reveal.js
└── reveal.min.js
└── lib
├── classList.js
├── github.css
├── prism.css
├── prism.js
└── zenburn.css
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig for my slides
2 | [*.*]
3 | indent_style = space
4 | indent_size = 2
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore all node.js dependencies
2 | # - if you want to use the grunt.js examples, run "npm install" in each directory
3 | *node_modules
4 |
5 | # ignore Wraith
6 | examples/wraith/wraith/*
7 |
8 | # ignore grunt-pagespeed API key file
9 | examples/grunt/pagespeed/settings.json
10 |
11 | # ignore grunt-perfbudget API key file
12 | examples/grunt/perfbudget/settings.json
13 |
14 | # ignore example grunt-phantomas output
15 | examples/grunt/phantomas/reports/*
16 | examples/grunt/phantomas/screenshots/*
17 |
18 | # ignore example grunt-devperf output
19 | examples/grunt/devperf/reports/*
20 |
21 | # ignore wraith output
22 | examples/wraith/shots
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 8.11.1
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | For the presentation: http://creativecommons.org/licenses/by/3.0/
2 |
3 | Author: Chris Ruppel
4 |
5 | --------------------------------------------------------------------------------
6 |
7 | For [reveal.js](http://lab.hakim.se/reveal-js/):
8 |
9 | Copyright (C) 2012 Hakim El Hattab, http://hakim.se
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of this software and associated documentation files (the "Software"), to deal
13 | in the Software without restriction, including without limitation the rights
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | copies of the Software, and to permit persons to whom the Software is
16 | furnished to do so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in
19 | all copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 | THE SOFTWARE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Automated Frontend Testing
2 |
3 | An intro to automated frontend testing. This is an enormous field containing
4 | many skillsets, but this presentation covers the basics of testing in the
5 | context of building websites. It covers functional testing, performance testing,
6 | and QA tools for development teams.
7 |
8 | ## Examples
9 |
10 | This slide deck comes with many examples of the concepts discussed within the
11 | slides. Browse the `examples` folder to experiment with real, working code
12 | snippets to help get you started.
13 |
14 | ## Presented at:
15 |
16 | * [DrupalCon Austin 2014](https://austin2014.drupal.org/session/automated-frontend-testing.html)
17 | * [DrupalCon Amsterdam 2014](https://amsterdam2014.drupal.org/session/automated-frontend-testing.html)
18 | * [Frontend United 2015](https://frontendunited.org/)
19 |
--------------------------------------------------------------------------------
/assets/fonts/leaguegothic/LICENSE:
--------------------------------------------------------------------------------
1 | SIL Open Font License (OFL)
2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
3 |
--------------------------------------------------------------------------------
/assets/fonts/leaguegothic/league_gothic-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/assets/fonts/leaguegothic/league_gothic-webfont.ttf
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Main styles for reveal.js
3 | *
4 | * @author Hakim El Hattab
5 | */
6 |
7 |
8 | /*********************************************
9 | * FONT-FACE DEFINITIONS
10 | *********************************************/
11 |
12 | @font-face {
13 | font-family: 'Bryant';
14 | src: url('webfonts/BryantWebMedium.eot'); /* IE9 Compat Modes */
15 | src: url('webfonts/BryantWebMedium.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
16 | url('webfonts/BryantWebMedium.woff') format('woff'), /* Modern Browsers */
17 | url('webfonts/BryantWebMedium.ttf') format('truetype'); /* Safari, Android, iOS */
18 | }
19 |
20 |
21 | /*********************************************
22 | * GLOBAL STYLES
23 | *********************************************/
24 |
25 | html, body {
26 | padding: 0;
27 | margin: 0;
28 | width: 100%;
29 | height: 100%;
30 | min-height: 600px;
31 | }
32 |
33 | body {
34 | position: relative;
35 | padding: 0;
36 | margin: 0;
37 | overflow: hidden;
38 |
39 | font-family: 'Bryant', 'Helvetica Neue', Helvetica, sans-serif;
40 | font-size: 36px;
41 | font-weight: 200;
42 | letter-spacing: -0.02em;
43 | color: #4D4D4D;
44 |
45 | background: #1c1e20;
46 | background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPHJhZGlhbEdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY3g9IjUwJSIgY3k9IjUwJSIgcj0iNzUlIj4KICAgIDxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM1NTVhNWYiIHN0b3Atb3BhY2l0eT0iMSIvPgogICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjMWMxZTIwIiBzdG9wLW9wYWNpdHk9IjEiLz4KICA8L3JhZGlhbEdyYWRpZW50PgogIDxyZWN0IHg9Ii01MCIgeT0iLTUwIiB3aWR0aD0iMTAxIiBoZWlnaHQ9IjEwMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
47 | background: -moz-radial-gradient(center, ellipse cover, rgba(85,90,95,1) 0%, rgba(28,30,32,1) 100%);
48 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(85,90,95,1)), color-stop(100%,rgba(28,30,32,1)));
49 | background: -webkit-radial-gradient(center, ellipse cover, rgba(85,90,95,1) 0%,rgba(28,30,32,1) 100%);
50 | background: -o-radial-gradient(center, ellipse cover, rgba(85,90,95,1) 0%,rgba(28,30,32,1) 100%);
51 | background: -ms-radial-gradient(center, ellipse cover, rgba(85,90,95,1) 0%,rgba(28,30,32,1) 100%);
52 | background: radial-gradient(center, ellipse cover, rgba(85,90,95,1) 0%,rgba(28,30,32,1) 100%);
53 | }
54 |
55 | /*********************************************
56 | * HEADERS
57 | *********************************************/
58 | .reveal h1,
59 | .reveal h2,
60 | .reveal h3,
61 | .reveal h4 {
62 | margin: 0 0 20px 0;
63 |
64 | color: #4D4D4D;
65 |
66 | font-family: 'Bryant', 'Helvetica Neue', Helvetica, sans-serif;
67 | line-height: 0.9em;
68 | letter-spacing: 0.02em;
69 |
70 | text-shadow: 0px 0px 6px rgba(0,0,0,0.2);
71 | }
72 |
73 | .reveal .up {
74 | text-transform: uppercase;
75 | }
76 |
77 | .reveal h1 { font-size: 136px; }
78 | .reveal h2 { font-size: 76px; }
79 | .reveal h3 { font-size: 56px; }
80 | .reveal h4 { font-size: 36px; }
81 |
82 | .reveal h1.inverted,
83 | .reveal h2.inverted,
84 | .reveal h3.inverted,
85 | .reveal h4.inverted {
86 | color: #4D4D4D;
87 | text-shadow: 0px 0px 2px rgba(0,0,0,0.2);
88 | }
89 |
90 | .reveal h1 {
91 | text-shadow: 0 1px 0 #ccc,
92 | 0 2px 0 #c9c9c9,
93 | 0 3px 0 #bbb,
94 | 0 4px 0 #b9b9b9,
95 | 0 5px 0 #aaa,
96 | 0 6px 1px rgba(0,0,0,.1),
97 | 0 0 5px rgba(0,0,0,.1),
98 | 0 1px 3px rgba(0,0,0,.3),
99 | 0 3px 5px rgba(0,0,0,.2),
100 | 0 5px 10px rgba(0,0,0,.25),
101 | 0 20px 20px rgba(0,0,0,.15);
102 | }
103 |
104 |
105 | /*********************************************
106 | * VIEW FRAGMENTS
107 | *********************************************/
108 |
109 | .reveal .slides section .fragment {
110 | opacity: 0;
111 |
112 | -webkit-transition: all .2s ease;
113 | -moz-transition: all .2s ease;
114 | -ms-transition: all .2s ease;
115 | -o-transition: all .2s ease;
116 | transition: all .2s ease;
117 | }
118 | .reveal .slides section .fragment.visible {
119 | opacity: 1;
120 | }
121 |
122 |
123 | /*********************************************
124 | * DEFAULT ELEMENT STYLES
125 | *********************************************/
126 |
127 | .reveal .slides section {
128 | line-height: 1.2em;
129 | font-weight: normal;
130 | }
131 |
132 | .reveal strong,
133 | .reveal b {
134 | font-weight: bold;
135 | }
136 |
137 | .reveal em,
138 | .reveal i {
139 | font-style: italic;
140 | }
141 |
142 | .reveal ol,
143 | .reveal ul {
144 | display: inline-block;
145 |
146 | text-align: left;
147 | margin: 0 auto;
148 | }
149 |
150 | .reveal ol {
151 | list-style-type: decimal;
152 | }
153 |
154 | .reveal ul {
155 | list-style-type: disc;
156 | }
157 |
158 | .reveal ul ul {
159 | list-style-type: square;
160 | }
161 |
162 | .reveal ul ul ul {
163 | list-style-type: circle;
164 | }
165 |
166 | .reveal ul ul,
167 | .reveal ul ol,
168 | .reveal ol ol,
169 | .reveal ol ul {
170 | display: block;
171 | margin-left: 40px;
172 | }
173 |
174 | .reveal ul.checks {
175 | list-style-type: none;
176 | }
177 | .reveal ul.checks li {
178 | margin-top: .5em;
179 | }
180 | .reveal ul.checks li:before {
181 | content: '\2714 ';
182 | color: green;
183 | }
184 | .reveal ul.white li:before {
185 | color: white;
186 | }
187 |
188 | .reveal p {
189 | margin-bottom: 10px;
190 | }
191 |
192 | .reveal blockquote {
193 | display: block;
194 | position: relative;
195 | margin: 5px auto;
196 | padding: 5px;
197 |
198 | font-style: italic;
199 | background: rgba(255, 255, 255, 0.05);
200 | box-shadow: 0px 0px 2px rgba(0,0,0,0.2);
201 | }
202 | .reveal blockquote p:before {
203 | content: '“';
204 | }
205 | .reveal blockquote p:after {
206 | content: '”';
207 | }
208 |
209 | .reveal pre {
210 | display: block;
211 | position: relative;
212 | width: 120%;
213 | left: -10%;
214 | font-size: .8em;
215 | }
216 | .reveal code {
217 | background: #272822;
218 | font-family: monospace;
219 | }
220 |
221 |
222 | .reveal table th,
223 | .reveal table td {
224 | text-align: left;
225 | padding-right: .3em;
226 | }
227 |
228 | .reveal table th {
229 | text-shadow: rgb(255,255,255) 1px 1px 2px;
230 | }
231 |
232 | .reveal small {
233 | font-size: 60%;
234 | line-height: 1em;
235 | vertical-align: top;
236 | }
237 |
238 | .reveal q {
239 | font-style: italic;
240 | }
241 | .reveal q:before {
242 | content: '“';
243 | }
244 | .reveal q:after {
245 | content: '”';
246 | }
247 |
248 | .reveal a:not(.image) {
249 | color: #569CCD;
250 | text-decoration: none;
251 |
252 | -webkit-transition: all .2s ease;
253 | -moz-transition: all .2s ease;
254 | -ms-transition: all .2s ease;
255 | -o-transition: all .2s ease;
256 | transition: all .2s ease;
257 | }
258 |
259 | .reveal a:not(.image):hover {
260 | color: hsl(185, 85%, 70%);
261 | background: hsla(185, 25%, 20%, 0.4);
262 | text-shadow: none;
263 | border: none;
264 | border-radius: 2px;
265 | }
266 |
267 | .reveal section img {
268 | margin: 30px 0 0 0;
269 | background: rgba(255,255,255,0.12);
270 | border: 4px solid #eee;
271 |
272 | -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
273 | -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
274 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
275 |
276 | -webkit-transition: all .2s linear;
277 | -moz-transition: all .2s linear;
278 | -ms-transition: all .2s linear;
279 | -o-transition: all .2s linear;
280 | transition: all .2s linear;
281 | }
282 |
283 | .reveal a:hover img {
284 | background: rgba(255,255,255,0.2);
285 | border-color: #66B360;
286 |
287 | -webkit-box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
288 | -moz-box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
289 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
290 | }
291 |
292 | /* Allow for bigger images */
293 | .big-img .reveal section img {
294 | margin-top: -50px;
295 | margin-left: -150px;
296 | }
297 |
298 | img.bare {
299 | border: 0;
300 | background: none;
301 | box-shadow: none;
302 | }
303 |
304 | /* Remove text-transform */
305 | .reveal .nt {
306 | text-transform: none;
307 | }
308 |
309 | /* Examples should be subdued */
310 | .reveal .example {
311 | font-size: .9em;
312 | color: #888;
313 | }
314 |
315 |
316 | /*********************************************
317 | * CONTROLS
318 | *********************************************/
319 |
320 | .reveal .controls {
321 | display: none;
322 | position: fixed;
323 | width: 100px;
324 | height: 100px;
325 | z-index: 30;
326 |
327 | right: 0;
328 | bottom: 0;
329 | }
330 |
331 | .reveal .controls a {
332 | font-size: 30px;
333 | position: absolute;
334 | opacity: 0.1;
335 | color: #fff;
336 | }
337 | .reveal .controls a.enabled {
338 | opacity: 0.6;
339 | color: #7cb977;
340 |
341 | text-shadow: 0px 0px 2px hsla(185, 45%, 70%, 0.3);
342 | }
343 | .reveal .controls a.enabled:active {
344 | margin-top: 1px;
345 | }
346 |
347 | .reveal .controls .left {
348 | top: 30px;
349 | }
350 |
351 | .reveal .controls .right {
352 | left: 60px;
353 | top: 30px;
354 | }
355 |
356 | .reveal .controls .up {
357 | left: 30px;
358 | }
359 |
360 | .reveal .controls .down {
361 | left: 30px;
362 | top: 60px;
363 |
364 | }
365 |
366 |
367 | /*********************************************
368 | * PROGRESS BAR
369 | *********************************************/
370 |
371 | .reveal .progress {
372 | position: fixed;
373 | display: none;
374 | height: 3px;
375 | width: 100%;
376 | bottom: 0;
377 | left: 0;
378 |
379 | background: rgba(0,0,0,0.2);
380 | }
381 |
382 | .reveal .progress span {
383 | display: block;
384 | background: #66B360;
385 | height: 100%;
386 | width: 0px;
387 |
388 | -webkit-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
389 | -moz-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
390 | -ms-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
391 | -o-transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
392 | transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
393 | }
394 |
395 | /*********************************************
396 | * ROLLING LINKS
397 | *********************************************/
398 |
399 | .reveal .roll {
400 | display: inline-block;
401 | overflow: hidden;
402 |
403 | vertical-align: top;
404 |
405 | -webkit-perspective: 400px;
406 | -moz-perspective: 400px;
407 | -ms-perspective: 400px;
408 | perspective: 400px;
409 |
410 | -webkit-perspective-origin: 50% 50%;
411 | -moz-perspective-origin: 50% 50%;
412 | -ms-perspective-origin: 50% 50%;
413 | perspective-origin: 50% 50%;
414 | }
415 | .reveal .roll:hover {
416 | background: none;
417 | text-shadow: none;
418 | }
419 | .reveal .roll span {
420 | display: block;
421 | position: relative;
422 | padding: 0 2px;
423 |
424 | pointer-events: none;
425 |
426 | -webkit-transition: all 400ms ease;
427 | -moz-transition: all 400ms ease;
428 | -ms-transition: all 400ms ease;
429 | transition: all 400ms ease;
430 |
431 | -webkit-transform-origin: 50% 0%;
432 | -moz-transform-origin: 50% 0%;
433 | -ms-transform-origin: 50% 0%;
434 | transform-origin: 50% 0%;
435 |
436 | -webkit-transform-style: preserve-3d;
437 | -moz-transform-style: preserve-3d;
438 | -ms-transform-style: preserve-3d;
439 | transform-style: preserve-3d;
440 | }
441 | .reveal .roll:hover span {
442 | background: rgba(0,0,0,0.5);
443 |
444 | -webkit-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
445 | -moz-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
446 | -ms-transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
447 | transform: translate3d( 0px, 0px, -45px ) rotateX( 90deg );
448 | }
449 | .reveal .roll span:after {
450 | content: attr(data-title);
451 |
452 | display: block;
453 | position: absolute;
454 | left: 0;
455 | top: 0;
456 | padding: 0 2px;
457 |
458 | color: #fff;
459 | background: #66B360;
460 |
461 | -webkit-transform-origin: 50% 0%;
462 | -moz-transform-origin: 50% 0%;
463 | -ms-transform-origin: 50% 0%;
464 | transform-origin: 50% 0%;
465 |
466 | -webkit-transform: translate3d( 0px, 105%, 0px ) rotateX( -90deg );
467 | -moz-transform: translate3d( 0px, 105%, 0px ) rotateX( -90deg );
468 | -ms-transform: translate3d( 0px, 105%, 0px ) rotateX( -90deg );
469 | transform: translate3d( 0px, 105%, 0px ) rotateX( -90deg );
470 | }
471 |
472 |
473 | /*********************************************
474 | * SLIDES
475 | *********************************************/
476 |
477 | .reveal .slides {
478 | position: absolute;
479 | width: 900px;
480 | height: 600px;
481 |
482 | left: 50%;
483 | top: 50%;
484 | margin-left: -450px;
485 | margin-top: -320px;
486 | padding: 20px 0px;
487 |
488 | text-align: center;
489 |
490 | -webkit-transition: -webkit-perspective .4s ease;
491 | -moz-transition: -moz-perspective .4s ease;
492 | -ms-transition: -ms-perspective .4s ease;
493 | -o-transition: -o-perspective .4s ease;
494 | transition: perspective .4s ease;
495 |
496 | -webkit-perspective: 600px;
497 | -moz-perspective: 600px;
498 | -ms-perspective: 600px;
499 | perspective: 600px;
500 |
501 | -webkit-perspective-origin: 50% 25%;
502 | -moz-perspective-origin: 50% 25%;
503 | -ms-perspective-origin: 50% 25%;
504 | perspective-origin: 50% 25%;
505 | }
506 |
507 | .reveal .slides>section,
508 | .reveal .slides>section>section {
509 | display: none;
510 | position: absolute;
511 | width: 100%;
512 | min-height: 600px;
513 |
514 | z-index: 10;
515 |
516 | -webkit-transform-style: preserve-3d;
517 | -moz-transform-style: preserve-3d;
518 | -ms-transform-style: preserve-3d;
519 | transform-style: preserve-3d;
520 |
521 | -webkit-transition: all 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
522 | -moz-transition: all 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
523 | -ms-transition: all 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
524 | -o-transition: all 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
525 | transition: all 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
526 | }
527 |
528 | .reveal .slides>section.present {
529 | display: block;
530 | z-index: 11;
531 | opacity: 1;
532 | }
533 |
534 |
535 | /*********************************************
536 | * DEFAULT TRANSITION
537 | *********************************************/
538 |
539 | .reveal .slides>section.past {
540 | display: block;
541 | opacity: 0;
542 |
543 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
544 | -moz-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
545 | -ms-transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
546 | transform: translate3d(-100%, 0, 0) rotateY(-90deg) translate3d(-100%, 0, 0);
547 | }
548 | .reveal .slides>section.future {
549 | display: block;
550 | opacity: 0;
551 |
552 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
553 | -moz-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
554 | -ms-transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
555 | transform: translate3d(100%, 0, 0) rotateY(90deg) translate3d(100%, 0, 0);
556 | }
557 |
558 | .reveal .slides>section>section.past {
559 | display: block;
560 | opacity: 0;
561 |
562 | -webkit-transform: translate3d(0, -50%, 0) rotateX(70deg) translate3d(0, -50%, 0);
563 | -moz-transform: translate3d(0, -50%, 0) rotateX(70deg) translate3d(0, -50%, 0);
564 | -ms-transform: translate3d(0, -50%, 0) rotateX(70deg) translate3d(0, -50%, 0);
565 | transform: translate3d(0, -50%, 0) rotateX(70deg) translate3d(0, -50%, 0);
566 | }
567 | .reveal .slides>section>section.future {
568 | display: block;
569 | opacity: 0;
570 |
571 | -webkit-transform: translate3d(0, 50%, 0) rotateX(-70deg) translate3d(0, 50%, 0);
572 | -moz-transform: translate3d(0, 50%, 0) rotateX(-70deg) translate3d(0, 50%, 0);
573 | -ms-transform: translate3d(0, 50%, 0) rotateX(-70deg) translate3d(0, 50%, 0);
574 | transform: translate3d(0, 50%, 0) rotateX(-70deg) translate3d(0, 50%, 0);
575 | }
576 |
577 |
578 | /*********************************************
579 | * CONCAVE TRANSITION
580 | *********************************************/
581 |
582 | .reveal.concave .slides>section.past {
583 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
584 | -moz-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
585 | -ms-transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
586 | transform: translate3d(-100%, 0, 0) rotateY(90deg) translate3d(-100%, 0, 0);
587 | }
588 | .reveal.concave .slides>section.future {
589 | -webkit-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
590 | -moz-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
591 | -ms-transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
592 | transform: translate3d(100%, 0, 0) rotateY(-90deg) translate3d(100%, 0, 0);
593 | }
594 |
595 | .reveal.concave .slides>section>section.past {
596 | -webkit-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
597 | -moz-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
598 | -ms-transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
599 | transform: translate3d(0, -80%, 0) rotateX(-70deg) translate3d(0, -80%, 0);
600 | }
601 | .reveal.concave .slides>section>section.future {
602 | -webkit-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
603 | -moz-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
604 | -ms-transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
605 | transform: translate3d(0, 80%, 0) rotateX(70deg) translate3d(0, 80%, 0);
606 | }
607 |
608 |
609 | /*********************************************
610 | * LINEAR TRANSITION
611 | *********************************************/
612 |
613 | .reveal.linear .slides>section.past {
614 | -webkit-transform: translate(-150%, 0);
615 | -moz-transform: translate(-150%, 0);
616 | -ms-transform: translate(-150%, 0);
617 | -o-transform: translate(-150%, 0);
618 | transform: translate(-150%, 0);
619 | }
620 | .reveal.linear .slides>section.future {
621 | -webkit-transform: translate(150%, 0);
622 | -moz-transform: translate(150%, 0);
623 | -ms-transform: translate(150%, 0);
624 | -o-transform: translate(150%, 0);
625 | transform: translate(150%, 0);
626 | }
627 |
628 | .reveal.linear .slides>section>section.past {
629 | -webkit-transform: translate(0, -150%);
630 | -moz-transform: translate(0, -150%);
631 | -ms-transform: translate(0, -150%);
632 | -o-transform: translate(0, -150%);
633 | transform: translate(0, -150%);
634 | }
635 | .reveal.linear .slides>section>section.future {
636 | -webkit-transform: translate(0, 150%);
637 | -moz-transform: translate(0, 150%);
638 | -ms-transform: translate(0, 150%);
639 | -o-transform: translate(0, 150%);
640 | transform: translate(0, 150%);
641 | }
642 |
643 | /*********************************************
644 | * BOX TRANSITION
645 | *********************************************/
646 |
647 | .reveal.cube .slides {
648 | margin-top: -350px;
649 |
650 | -webkit-perspective-origin: 50% 25%;
651 | -moz-perspective-origin: 50% 25%;
652 | -ms-perspective-origin: 50% 25%;
653 | perspective-origin: 50% 25%;
654 |
655 | -webkit-perspective: 1300px;
656 | -moz-perspective: 1300px;
657 | -ms-perspective: 1300px;
658 | perspective: 1300px;
659 | }
660 |
661 | .reveal.cube .slides section {
662 | padding: 30px;
663 |
664 | -webkit-backface-visibility: hidden;
665 | -moz-backface-visibility: hidden;
666 | -ms-backface-visibility: hidden;
667 | backface-visibility: hidden;
668 |
669 | -webkit-box-sizing: border-box;
670 | -moz-box-sizing: border-box;
671 | box-sizing: border-box;
672 | }
673 | .reveal.cube .slides section:not(.stack):before {
674 | content: '';
675 | position: absolute;
676 | display: block;
677 | width: 100%;
678 | height: 100%;
679 | left: 0;
680 | top: 0;
681 | background: #232628;
682 | border-radius: 4px;
683 |
684 | -webkit-transform: translateZ( -20px );
685 | -moz-transform: translateZ( -20px );
686 | -ms-transform: translateZ( -20px );
687 | -o-transform: translateZ( -20px );
688 | transform: translateZ( -20px );
689 | }
690 | .reveal.cube .slides section:not(.stack):after {
691 | content: '';
692 | position: absolute;
693 | display: block;
694 | width: 90%;
695 | height: 30px;
696 | left: 5%;
697 | bottom: 0;
698 | background: none;
699 | z-index: 1;
700 |
701 | border-radius: 4px;
702 | box-shadow: 0px 95px 25px rgba(0,0,0,0.2);
703 |
704 | -webkit-transform: translateZ(-90px) rotateX( 65deg );
705 | -moz-transform: translateZ(-90px) rotateX( 65deg );
706 | -ms-transform: translateZ(-90px) rotateX( 65deg );
707 | -o-transform: translateZ(-90px) rotateX( 65deg );
708 | transform: translateZ(-90px) rotateX( 65deg );
709 | }
710 |
711 | .reveal.cube .slides>section.stack {
712 | padding: 0;
713 | background: none;
714 | }
715 |
716 | .reveal.cube .slides>section.past {
717 | -webkit-transform-origin: 100% 0%;
718 | -moz-transform-origin: 100% 0%;
719 | -ms-transform-origin: 100% 0%;
720 | transform-origin: 100% 0%;
721 |
722 | -webkit-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
723 | -moz-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
724 | -ms-transform: translate3d(-100%, 0, 0) rotateY(-90deg);
725 | transform: translate3d(-100%, 0, 0) rotateY(-90deg);
726 | }
727 |
728 | .reveal.cube .slides>section.future {
729 | -webkit-transform-origin: 0% 0%;
730 | -moz-transform-origin: 0% 0%;
731 | -ms-transform-origin: 0% 0%;
732 | transform-origin: 0% 0%;
733 |
734 | -webkit-transform: translate3d(100%, 0, 0) rotateY(90deg);
735 | -moz-transform: translate3d(100%, 0, 0) rotateY(90deg);
736 | -ms-transform: translate3d(100%, 0, 0) rotateY(90deg);
737 | transform: translate3d(100%, 0, 0) rotateY(90deg);
738 | }
739 |
740 | .reveal.cube .slides>section>section.past {
741 | -webkit-transform-origin: 0% 100%;
742 | -moz-transform-origin: 0% 100%;
743 | -ms-transform-origin: 0% 100%;
744 | transform-origin: 0% 100%;
745 |
746 | -webkit-transform: translate3d(0, -100%, 0) rotateX(90deg);
747 | -moz-transform: translate3d(0, -100%, 0) rotateX(90deg);
748 | -ms-transform: translate3d(0, -100%, 0) rotateX(90deg);
749 | transform: translate3d(0, -100%, 0) rotateX(90deg);
750 | }
751 |
752 | .reveal.cube .slides>section>section.future {
753 | -webkit-transform-origin: 0% 0%;
754 | -moz-transform-origin: 0% 0%;
755 | -ms-transform-origin: 0% 0%;
756 | transform-origin: 0% 0%;
757 |
758 | -webkit-transform: translate3d(0, 100%, 0) rotateX(-90deg);
759 | -moz-transform: translate3d(0, 100%, 0) rotateX(-90deg);
760 | -ms-transform: translate3d(0, 100%, 0) rotateX(-90deg);
761 | transform: translate3d(0, 100%, 0) rotateX(-90deg);
762 | }
763 |
764 |
765 | /*********************************************
766 | * PAGE TRANSITION
767 | *********************************************/
768 |
769 | .reveal.page .slides {
770 | margin-top: -350px;
771 |
772 | -webkit-perspective-origin: 50% 50%;
773 | -moz-perspective-origin: 50% 50%;
774 | -ms-perspective-origin: 50% 50%;
775 | perspective-origin: 50% 50%;
776 |
777 | -webkit-perspective: 3000px;
778 | -moz-perspective: 3000px;
779 | -ms-perspective: 3000px;
780 | perspective: 3000px;
781 | }
782 |
783 | .reveal.page .slides section {
784 | padding: 30px;
785 |
786 | -webkit-box-sizing: border-box;
787 | -moz-box-sizing: border-box;
788 | box-sizing: border-box;
789 | }
790 | .reveal.page .slides section.past {
791 | z-index: 12;
792 | }
793 | .reveal.page .slides section:not(.stack):before {
794 | content: '';
795 | position: absolute;
796 | display: block;
797 | width: 100%;
798 | height: 100%;
799 | left: 0;
800 | top: 0;
801 | background: #232628;
802 |
803 | -webkit-transform: translateZ( -20px );
804 | -moz-transform: translateZ( -20px );
805 | -ms-transform: translateZ( -20px );
806 | -o-transform: translateZ( -20px );
807 | transform: translateZ( -20px );
808 | }
809 | .reveal.page .slides section:not(.stack):after {
810 | content: '';
811 | position: absolute;
812 | display: block;
813 | width: 90%;
814 | height: 30px;
815 | left: 5%;
816 | bottom: 0;
817 | background: none;
818 | z-index: 1;
819 |
820 | border-radius: 4px;
821 | box-shadow: 0px 95px 25px rgba(0,0,0,0.2);
822 |
823 | -webkit-transform: translateZ(-90px) rotateX( 65deg );
824 | }
825 |
826 | .reveal.page .slides>section.stack {
827 | padding: 0;
828 | background: none;
829 | }
830 |
831 | .reveal.page .slides>section.past {
832 | -webkit-transform-origin: 0% 0%;
833 | -moz-transform-origin: 0% 0%;
834 | -ms-transform-origin: 0% 0%;
835 | transform-origin: 0% 0%;
836 |
837 | -webkit-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
838 | -moz-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
839 | -ms-transform: translate3d(-40%, 0, 0) rotateY(-80deg);
840 | transform: translate3d(-40%, 0, 0) rotateY(-80deg);
841 | }
842 |
843 | .reveal.page .slides>section.future {
844 | -webkit-transform-origin: 100% 0%;
845 | -moz-transform-origin: 100% 0%;
846 | -ms-transform-origin: 100% 0%;
847 | transform-origin: 100% 0%;
848 |
849 | -webkit-transform: translate3d(0, 0, 0);
850 | -moz-transform: translate3d(0, 0, 0);
851 | -ms-transform: translate3d(0, 0, 0);
852 | transform: translate3d(0, 0, 0);
853 | }
854 |
855 | .reveal.page .slides>section>section.past {
856 | -webkit-transform-origin: 0% 0%;
857 | -moz-transform-origin: 0% 0%;
858 | -ms-transform-origin: 0% 0%;
859 | transform-origin: 0% 0%;
860 |
861 | -webkit-transform: translate3d(0, -40%, 0) rotateX(80deg);
862 | -moz-transform: translate3d(0, -40%, 0) rotateX(80deg);
863 | -ms-transform: translate3d(0, -40%, 0) rotateX(80deg);
864 | transform: translate3d(0, -40%, 0) rotateX(80deg);
865 | }
866 |
867 | .reveal.page .slides>section>section.future {
868 | -webkit-transform-origin: 0% 100%;
869 | -moz-transform-origin: 0% 100%;
870 | -ms-transform-origin: 0% 100%;
871 | transform-origin: 0% 100%;
872 |
873 | -webkit-transform: translate3d(0, 0, 0);
874 | -moz-transform: translate3d(0, 0, 0);
875 | -ms-transform: translate3d(0, 0, 0);
876 | transform: translate3d(0, 0, 0);
877 | }
878 |
879 |
880 | /*********************************************
881 | * NEON THEME
882 | *********************************************/
883 |
884 | .reveal.neon a,
885 | .reveal.neon a:hover,
886 | .reveal.neon .controls a.enabled {
887 | color: #5de048;
888 | }
889 |
890 | .reveal.neon .progress span,
891 | .reveal.neon .roll span:after {
892 | background: #5de048;
893 | }
894 |
895 | .reveal.neon a.image:hover img {
896 | border-color: #5de048;
897 | }
898 |
899 |
900 | /*********************************************
901 | * Strikethrough for Title
902 | *********************************************/
903 | .insert-container {
904 | -webkit-perspective: 600px;
905 | -moz-perspective: 600px;
906 | -ms-perspective: 600px;
907 | perspective: 600px;
908 | -webkit-transform-style: preserve-3d;
909 | -moz-transform-style: preserve-3d;
910 | -ms-transform-style: preserve-3d;
911 | transform-style: preserve-3d;
912 | }
913 | .insert {
914 | color: #f77 !important;
915 | display: block;
916 | width: auto;
917 | position: absolute;
918 | -webkit-transform: translateX(13.2em) translateY(-.8em) rotateX(5deg) rotateY(5deg) rotateZ(10deg);
919 | -moz-transform: translateX(13.2em) translateY(-.8em) rotateX(5deg) rotateY(5deg) rotateZ(10deg);
920 | -ms-transform: translateX(13.2em) translateY(-.8em) rotateX(5deg) rotateY(5deg) rotateZ(10deg);
921 | transform: translateX(13.2em) translateY(-.8em) rotateX(5deg) rotateY(5deg) rotateZ(10deg);
922 | }
923 | .insert:before {
924 | content: '^';
925 | color: #f77 !important;
926 | display: block;
927 | font-size: 1.8em;
928 | -webkit-transform: translateY(.4em);
929 | -moz-transform: translateY(.4em);
930 | -ms-transform: translateY(.4em);
931 | transform: translateY(.4em);
932 | }
933 |
934 | /*********************************************
935 | * OVERVIEW
936 | *********************************************/
937 |
938 | .reveal.overview .slides {
939 | -webkit-perspective: 700px;
940 | -moz-perspective: 700px;
941 | -ms-perspective: 700px;
942 | perspective: 700px;
943 | }
944 |
945 | .reveal.overview .slides section {
946 | padding: 20px 0;
947 | opacity: 1;
948 | cursor: pointer;
949 | background: rgba(0,0,0,0.1);
950 | }
951 | .reveal.overview .slides section .fragment {
952 | opacity: 1;
953 | }
954 | .reveal.overview .slides section:after,
955 | .reveal.overview .slides section:before {
956 | display: none !important;
957 | }
958 | .reveal.overview .slides section>section {
959 | opacity: 1;
960 | cursor: pointer;
961 | }
962 | .reveal.overview .slides section:hover {
963 | background: rgba(0,0,0,0.3);
964 | }
965 |
966 | .reveal.overview .slides section.present {
967 | background: rgba(0,0,0,0.3);
968 | }
969 | .reveal.overview .slides>section.stack {
970 | background: none;
971 | padding: 0;
972 | }
973 |
974 |
975 | /*********************************************
976 | * FALLBACK
977 | *********************************************/
978 |
979 | .no-transforms {
980 | overflow-y: auto;
981 | }
982 |
983 | .no-transforms .slides section {
984 | -webkit-transform: none;
985 | -moz-transform: none;
986 | -ms-transform: none;
987 | transform: none;
988 |
989 | display: block!important;
990 | opacity: 1!important;
991 | position: relative!important;
992 | }
993 |
994 |
995 | /*********************************************
996 | * DEFAULT STATES
997 | *********************************************/
998 |
999 | .state-background {
1000 | position: absolute;
1001 | width: 100%;
1002 | height: 100%;
1003 |
1004 | background: rgb(255,255,255); /* Old browsers */
1005 | background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%, rgba(242,242,242,1) 100%); /* FF3.6+ */
1006 | background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(242,242,242,1))); /* Chrome,Safari4+ */
1007 | background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(242,242,242,1) 100%); /* Chrome10+,Safari5.1+ */
1008 | background: -o-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(242,242,242,1) 100%); /* Opera 12+ */
1009 | background: -ms-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(242,242,242,1) 100%); /* IE10+ */
1010 | background: radial-gradient(ellipse at center, rgba(255,255,255,1) 0%,rgba(242,242,242,1) 100%); /* W3C */
1011 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#f2f2f2',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
1012 |
1013 | -webkit-transition: background 800ms ease;
1014 | -moz-transition: background 800ms ease;
1015 | -ms-transition: background 800ms ease;
1016 | -o-transition: background 800ms ease;
1017 | transition: background 800ms ease;
1018 | }
1019 | .alert .state-background {
1020 | background: rgba( 200, 50, 30, 0.6 );
1021 | }
1022 | .alert .reveal * {
1023 | color: #fff;
1024 | }
1025 | .soothe .state-background {
1026 | background: rgba( 50, 200, 90, 0.4 );
1027 | }
1028 | .soothe .reveal * {
1029 | color: #fff;
1030 | }
1031 | .blackout .state-background {
1032 | background: rgba( 0, 0, 0, 0.6 );
1033 | }
1034 | .blackout .reveal * {
1035 | color: #fff;
1036 | }
1037 | .bluesy .state-background {
1038 | background: rgba( 0, 0, 75, 0.5 );
1039 | }
1040 | .bluesy .reveal * {
1041 | color: #fff;
1042 | }
1043 |
1044 |
1045 |
1046 |
1047 |
--------------------------------------------------------------------------------
/css/print.css:
--------------------------------------------------------------------------------
1 | /* Default Print Stylesheet Template
2 | by Rob Glazebrook of CSSnewbie.com
3 | Last Updated: June 4, 2008
4 |
5 | Feel free (nay, compelled) to edit, append, and
6 | manipulate this file as you see fit. */
7 |
8 |
9 | /* SECTION 1: Set default width, margin, float, and
10 | background. This prevents elements from extending
11 | beyond the edge of the printed page, and prevents
12 | unnecessary background images from printing */
13 | body {
14 | background: #fff url(none);
15 | font-size: 13pt;
16 | width: auto;
17 | height: auto;
18 | border: 0;
19 | margin: 0 5%;
20 | padding: 0;
21 | float: none !important;
22 | overflow: visible;
23 | }
24 | html {
25 | background: #fff;
26 | width: auto;
27 | height: auto;
28 | overflow: visible;
29 | }
30 |
31 | /* SECTION 2: Remove any elements not needed in print.
32 | This would include navigation, ads, sidebars, etc. */
33 | .nestedarrow,
34 | .controls a,
35 | .reveal .progress span,
36 | .reveal.overview {
37 | display:none;
38 | }
39 |
40 | /* SECTION 3: Set body font face, size, and color.
41 | Consider using a serif font for readability. */
42 | body, p, td, li, div, a {
43 | font-size: 13pt;
44 | font-family: Georgia, "Times New Roman", Times, serif !important;
45 | color: #000;
46 | }
47 |
48 | /* SECTION 4: Set heading font face, sizes, and color.
49 | Diffrentiate your headings from your body text.
50 | Perhaps use a large sans-serif for distinction. */
51 | h1,h2,h3,h4,h5,h6 {
52 | color: #000!important;
53 | height: auto;
54 | line-height: normal;
55 | font-family: Georgia, "Times New Roman", Times, serif !important;
56 | text-shadow: 0 0 0 #000 !important;
57 | text-align: left;
58 | letter-spacing: normal;
59 | }
60 | /* Need to reduce the size of the fonts for printing */
61 | h1 { font-size: 26pt !important; }
62 | h2 { font-size: 22pt !important; }
63 | h3 { font-size: 20pt !important; }
64 | h4 { font-size: 20pt !important; font-variant: small-caps; }
65 | h5 { font-size: 19pt !important; }
66 | h6 { font-size: 18pt !important; font-style: italic; }
67 |
68 | /* SECTION 5: Make hyperlinks more usable.
69 | Ensure links are underlined, and consider appending
70 | the URL to the end of the link for usability. */
71 | a:link,
72 | a:visited {
73 | color: #000 !important;
74 | font-weight: bold;
75 | text-decoration: underline;
76 | }
77 | .reveal a:link:after,
78 | .reveal a:visited:after {
79 | content: " (" attr(href) ") ";
80 | color: #222 !important;
81 | font-size: 90%;
82 | }
83 |
84 |
85 | /* SECTION 6: more reveal.js specific additions by @skypanther */
86 | ul, ol, div, p {
87 | visibility: visible;
88 | position: static;
89 | width: auto;
90 | height: auto;
91 | display: block;
92 | overflow: visible;
93 | margin: auto;
94 | text-align: left !important;
95 | }
96 | .reveal .slides {
97 | position: static;
98 | width: auto;
99 | height: auto;
100 |
101 | left: auto;
102 | top: auto;
103 | margin-left: auto;
104 | margin-top: auto;
105 | padding: auto;
106 |
107 | overflow: visible;
108 | display: block;
109 |
110 | text-align: center;
111 | -webkit-perspective: none;
112 | -moz-perspective: none;
113 | -ms-perspective: none;
114 | perspective: none;
115 |
116 | -webkit-perspective-origin: 50% 50%; /* there isn't a none/auto value but 50-50 is the default */
117 | -moz-perspective-origin: 50% 50%;
118 | -ms-perspective-origin: 50% 50%;
119 | perspective-origin: 50% 50%;
120 | }
121 | .reveal .slides>section, .reveal .slides>section>section,
122 | .reveal .slides>section.past, .reveal .slides>section.future,
123 | .reveal.linear .slides>section, .reveal.linear .slides>section>section,
124 | .reveal.linear .slides>section.past, .reveal.linear .slides>section.future {
125 |
126 | visibility: visible;
127 | position: static;
128 | width: 90%;
129 | height: auto;
130 | display: block;
131 | overflow: visible;
132 |
133 | left: 0%;
134 | top: 0%;
135 | margin-left: 0px;
136 | margin-top: 0px;
137 | padding: 20px 0px;
138 |
139 | opacity: 1;
140 |
141 | -webkit-transform-style: flat;
142 | -moz-transform-style: flat;
143 | -ms-transform-style: flat;
144 | transform-style: flat;
145 |
146 | -webkit-transform: none;
147 | -moz-transform: none;
148 | -ms-transform: none;
149 | transform: none;
150 | }
151 | .reveal section {
152 | page-break-after: always !important;
153 | display: block !important;
154 | }
155 | .reveal section.stack {
156 | page-break-after: avoid !important;
157 | }
158 | .reveal section .fragment {
159 | opacity: 1 !important;
160 | }
161 | .reveal section img {
162 | display: block;
163 | margin: 15px 0px;
164 | background: rgba(255,255,255,1);
165 | border: 1px solid #666;
166 | box-shadow: none;
167 | }
--------------------------------------------------------------------------------
/css/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
49 |
50 |
51 | /* HTML5BP:
52 | These selection declarations have to be separate.
53 | No text-shadow: twitter.com/miketaylr/status/12228805301
54 | Also: hot pink. */
55 | ::-moz-selection{ background: #66B360; color:#fff; text-shadow: none; }
56 | ::selection { background:#66B360; color:#fff; text-shadow: none; }
57 |
58 |
--------------------------------------------------------------------------------
/css/webfonts/BryantWebMedium.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/css/webfonts/BryantWebMedium.eot
--------------------------------------------------------------------------------
/css/webfonts/BryantWebMedium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/css/webfonts/BryantWebMedium.ttf
--------------------------------------------------------------------------------
/css/webfonts/BryantWebMedium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/css/webfonts/BryantWebMedium.woff
--------------------------------------------------------------------------------
/examples/casper/drupal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Testing a demo of Drupal. Your test environment MUST exist at the URL in
4 | * the `host` property — The script will log in and check for various
5 | * features in Drupal core. This demo was inspired by a similar script for
6 | * a Wordpress site. The original script was written by Henrique Vicente.
7 | *
8 | * NOTE: this file assumes that Drupal's "clean urls" are enabled. If the
9 | * URLs are in a format such as "?q=node/add/page" instead of "/node/add/page"
10 | * the clean URLs need to be enabled before tests that match URLs can pass.
11 | *
12 | * @see https://github.com/henvic/phantom-casper-simple-talk/blob/master/wordpress.js
13 | */
14 |
15 | // Set up variables to visit a URL and log in.
16 | var config = {
17 | 'host': 'http://casper-drupal.test',
18 | 'form': {
19 | 'name': 'admin',
20 | 'pass': 'admin'
21 | }
22 | };
23 |
24 | // In one of the tests we want to set some content up, then test it in the
25 | // following block of code. Setting this content up as a global variable helps
26 | // keep the testing code DRY and reliable.
27 | var nodeContents = {
28 | 'title': 'Hello, World!',
29 | 'body[und][0][value]': 'This content was added by CasperJS!'
30 | };
31 |
32 | // Define the suite of tests and give it the following properties:
33 | // - Title, which shows up before any of the pass/fails.
34 | // - Number of tests, must be changed as you add tests.
35 | // - suite(), which contains all of your tests.
36 | //
37 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#begin
38 | casper.test.begin('Testing Drupal demo site', 8, function suite(test) {
39 |
40 | // casper.start() always wraps your first action. The first argument should
41 | // be the URL of the page you want to test. Instead of being hard-coded, ours
42 | // comes from the config object we defined above.
43 | //
44 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#start
45 | casper.start(config.host, function() {
46 |
47 | // casper.fill() allows you to populate and submit forms.
48 | //
49 | // The first argument is any selector that can uniquely identify your form.
50 | //
51 | // The second argument is an object containing the data you want to submit
52 | // using this form. Our config.form variable was defined at the beginning of
53 | // this file. Each property name of the object you supply should be identical
54 | // to the `name` attr on the form you're filling out.
55 | //
56 | // The third argument is a boolean that tells Casper whether the form should
57 | // be submitted automatically. There are other ways of submitting forms,
58 | // such as finding the submit button and running the click() operation on it.
59 | //
60 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#fill
61 | casper.fill('form#user-login-form', config.form, true);
62 |
63 | // You can output comments at any point in your script. Sometimes it's good
64 | // to add a message when you're attempting an operation that might take a
65 | // few moments to respond, such as this login attempt.
66 | test.comment('⌚️ Logging in...');
67 | });
68 |
69 | // casper.then() allows us to wait until previous tests and actions are
70 | // completed before moving on to the next steps. This is useful for many
71 | // situations and authenticated sessions are a prime candidate, since we
72 | // cannot perform any further actions if we failed to authenticate.
73 | //
74 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#then
75 | casper.then(function() {
76 |
77 | // test.assertHttpStatus() determines which HTTP response code was sent from
78 | // the server. All of the tests in this file are checking for 200, which is
79 | // a normal, successful response. But there's nothing stopping someone from
80 | // intentionally checking for 403, 404, or even 500 errors from the server.
81 | //
82 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#asserthttpstatus
83 | test.assertHttpStatus(200, "Authentication successful");
84 | })
85 |
86 | casper.then(function() {
87 | // Now that we're logged in, check for the `logged-in` class that is appended
88 | // to the
tag for all authenticated Drupal traffic.
89 | //
90 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertexists
91 | test.assertExists('body.logged-in', 'Drupal class for logged-in users was found.');
92 |
93 | // We want to browse the content list that is available to Drupal admins,
94 | // so we are clicking the Content link in the admin toolbar. The next series
95 | // of steps assumes that Overlay module is enabled, which is the default for
96 | // the Standard installation profile in Drupal 7.
97 | //
98 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#click
99 | this.click('#toolbar-link-admin-content');
100 |
101 | // Log the click to the console so we know why it's pausing momentarily.
102 | test.comment('⌚️ Clicking the Content admin link...');
103 | });
104 |
105 | // Now that we've authenticated and loaded the content page, it's time to look
106 | // for some content.
107 | casper.then(function() {
108 |
109 | // First we see if the Overlay updated the page title correctly. The first
110 | // argument is a regex that is run against tag. The second argument
111 | // is optional and can be used to provide a more descriptive test result.
112 | //
113 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#asserttitlematch
114 | test.assertTitleMatch(/^Content.*/, 'Overlay changed page title to begin with the word "Content"');
115 |
116 | // We also check that the URL is updated as expected. For science. Arguments
117 | // are the same as test.assertTitleMatch()
118 | //
119 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#asserturlmatch
120 | test.assertUrlMatch(/node#overlay=admin\/content/, 'Overlay updated the URL to /node#overlay=admin/content');
121 |
122 | // Instead of clicking another link, we want to load the node/add/page page
123 | // without our dear friend, the Overlay. casper.open() simply loads a new
124 | // URL. We put this at the end of the code block and use casper.then() to
125 | // ensure that the next tests are run after the page is finished opening.
126 | //
127 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#open
128 | this.open(config.host + '/node/add/page');
129 |
130 | // Log this action to the console as an informational courtesy to the user.
131 | test.comment('⌚️ Adding Basic page...');
132 | });
133 |
134 | // With our fresh node/add page, we want to populate and submit another form.
135 | casper.then(function() {
136 |
137 | // Check that the page loaded properly.
138 | test.assertHttpStatus(200, 'Opened the node/add/page page.');
139 |
140 | // Look for the node form that we want to populate.
141 | test.assertExists('form#page-node-form', 'Found the node/add/page form.');
142 |
143 | // Populate the form with the content we prepared at the beginning of this
144 | // file. As the notes above explain, we do not want to repeat the values we
145 | // submit here in the next test, because if we update these values later and
146 | // forget to change both simultaneously, then it will appear that the site
147 | // broke when it is actually our test that is broken.
148 | casper.fill('form#page-node-form', nodeContents, true);
149 |
150 | // Once again, report that we're saving the node to keep the user informed.
151 | test.comment('⌚️ Saving new node...');
152 | });
153 |
154 | // Drupal automatically redirects to the published node on success. With the
155 | // form submitted, we check to see what the published content looks like.
156 | casper.then(function() {
157 |
158 | // Check the page title of the published node. Since we don't want to hard-
159 | // code any of the test values, we use the `new RegExp` syntax which allows
160 | // the regex to contain variables.
161 | test.assertTitleMatch(new RegExp(nodeContents.title), 'Our custom title was found on the published page.');
162 |
163 | // Now we do some very light screen scraping to find the text that we added
164 | // to the body field of this node. Since Drupal 7 has jQuery 1.4.4 out of
165 | // the box, it is safe to rely on it to extract our text.
166 | //
167 | // test.assertEvalEquals() allows us to execute arbitrary code and compare
168 | // the results with a pre-defined value. The first argument is a function
169 | // containing our code that will be evaluated in the context of the testing
170 | // environment. The second argument (which is sourced from our variable at
171 | // the beginning of the file) is the expected result. The third argument is
172 | // optional and allows us to provide a more descriptive test result.
173 | //
174 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertevalequals
175 | test.assertEvalEquals(function () {
176 | return jQuery('.node-page .content p').text();
177 | }, nodeContents['body[und][0][value]'], 'Our custom text was found on the published page.');
178 | });
179 |
180 | // This code runs all the tests that we defined above.
181 | //
182 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#done
183 | casper.run(function () {
184 | test.done();
185 | });
186 | });
187 |
--------------------------------------------------------------------------------
/examples/casper/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "casperjs": {
6 | "version": "1.1.4",
7 | "resolved": "https://registry.npmjs.org/casperjs/-/casperjs-1.1.4.tgz",
8 | "integrity": "sha1-6wH07YWsUgqPTZMrTap00+d7x0Y="
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/casper/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "casperjs": "^1.1.4"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/casper/picturefill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Testing to see if Picturefill selects the right source at multiple
4 | * viewport sizes.
5 | */
6 |
7 | // Define the suite of tests and give it the following properties:
8 | // - Title, which shows up before any of the pass/fails.
9 | // - Number of tests, must be changed as you add tests.
10 | // - suite(), which contains all of your tests.
11 | //
12 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#begin
13 | casper.test.begin('Testing Picturefill', 5, function suite(test) {
14 | test.comment('⌚️ Opening https://scottjehl.github.io/picturefill/examples/demo-02.html');
15 |
16 | // casper.start() always wraps your first action. The first argument should
17 | // be the URL of the page you want to test.
18 | //
19 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#start
20 | casper.start('https://scottjehl.github.io/picturefill/examples/demo-02.html', function () {
21 |
22 | // First, we look for a element. The first argument is a query
23 | // selector, like jQuery or document.querySelectorAll(). Any tags
24 | // on the page are found by using the 'picture' selector.
25 | //
26 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertexists
27 | test.assertExists('picture', ' element found.');
28 |
29 | // Now verify that the tag has two tags. This is
30 | // another query selector. In a more complex test you will need to write
31 | // a more specific selector. This will find all tags within all
32 | // tags in your document. This demo only has one tag
33 | // so the simple selector works fine.
34 | //
35 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertelementcount
36 | test.assertElementCount('picture > source', 2);
37 |
38 | // Before running any viewport-specific tests, set the viewport to 320x480.
39 | // PhantomJS has a default viewport of 400x300 and CasperJS does not alter
40 | // this default, so it's always a good idea to explicitly set the viewport
41 | // to your desired dimensions.
42 | //
43 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#viewport
44 | this.viewport(320, 480);
45 | });
46 |
47 | // casper.then() allows us to wait until previous tests and actions are
48 | // completed before moving on to the next steps. This is useful for many
49 | // situations and viewport resizing is one of them, since scripts like
50 | // Picturefill have to respond to the resize.
51 | //
52 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#then
53 | casper.then(function() {
54 |
55 | // With the viewport resized, look for the src of the and make sure it
56 | // is set to the value we expect. In this example we are testing the smaller
57 | // viewport first, so medium.jpg should be found since the Picturefill markup
58 | // declares it as default.
59 | //
60 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertevalequals
61 | test.assertEvalEquals(function () {
62 | return document.querySelectorAll('picture img')[0].getAttribute('src').match('medium.jpg').toString();
63 | }, 'medium.jpg', 'medium.jpg found using 320x480 viewport.');
64 | });
65 |
66 | // Resize the viewport again
67 | casper.then(function() {
68 | this.viewport(960, 640);
69 | });
70 |
71 | // With the viewport resized to 960px, check and see if the large.jpg is
72 | // now contained within the src, since the demo markup specifies an
73 | // 800px breakpoint for this image.
74 | casper.then(function() {
75 | test.assertEvalEquals(function () {
76 | return document.querySelectorAll('picture img')[0].getAttribute('src').match('large.jpg').toString();
77 | }, 'large.jpg', 'large.jpg found using 960x640 viewport.');
78 | });
79 |
80 | // Resize the viewport again
81 | casper.then(function() {
82 | this.viewport(1280, 1024);
83 | });
84 |
85 | // Finally, with the 1280x1024 viewport, check to see if the is now
86 | // using the extralarge.jpg source which was specified for viewports larger
87 | // than 1000px wide.
88 | casper.then(function() {
89 | test.assertEvalEquals(function () {
90 | return document.querySelectorAll('picture img')[0].getAttribute('src').match('extralarge.jpg').toString();
91 | }, 'extralarge.jpg', 'extralarge.jpg found using 1280x1024 viewport.');
92 | });
93 |
94 | // This code runs all the tests that we defined above.
95 | //
96 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#done
97 | casper.run(function () {
98 | test.done();
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/examples/casper/test-js-code.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Using CasperJS to run tests on various libraries.
4 | */
5 |
6 | // Define the suite of tests and give it the following properties:
7 | // - Title, which shows up before any of the pass/fails.
8 | // - Number of tests, must be changed as you add tests.
9 | // - suite(), which contains all of your tests.
10 | //
11 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#begin
12 | casper.test.begin('Basic code tests for jQuery and Modernizr', 3, function suite(test) {
13 | // You can output comments at any point in your script. Sometimes it's good
14 | // to add a message when you're attempting an operation that might take a
15 | // few moments to respond, such as this page load.
16 | test.comment('⌚️ Opening https://rupl.github.io/frontend-testing/examples/targets/test-js-code.html');
17 |
18 | // casper.start() always wraps your first action. The first argument should
19 | // be the URL of the page you want to test.
20 | //
21 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#start
22 | casper.start('https://rupl.github.io/frontend-testing/examples/targets/test-js-code.html', function () {
23 |
24 | // Look at a specific property on the jQuery object to check its version.
25 | //
26 | // assertEvalEquals provides an easy way for us to test JavaScript variables
27 | // within the test environment. Any code within the assertEvalEquals() code
28 | // block is considered to be part of the web page, as if we are typing into
29 | // the JS console of the fully-loaded page.
30 | //
31 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertevalequals
32 | test.assertEvalEquals(function () {
33 | // In jQuery 3+ the version number is accompanied by some build info, so
34 | // our return value splits that extra data off.
35 | return jQuery.fn.jquery.split(' ')[0];
36 | }, '3.3.1', 'jQuery 3.3.1 was found.');
37 |
38 | // Look at a specific property on the Modernizr object to check its version.
39 | test.assertEvalEquals(function () {
40 | return Modernizr._version;
41 | }, '3.6.0', 'Modernizr 3.6.0 was found.');
42 |
43 | // Check for required Modernizr tests. This is NOT testing the output! We
44 | // are only testing whether Modernizr is working, not what its output is.
45 | test.assertEvalEquals(function () {
46 | // An array of strings. Each one represents a required test that must be
47 | // found within Modernizr. That value might be true or false, but as long
48 | // as the type is Boolean we ultimately do not care.
49 | var requiredTests = [
50 | 'csstransformslevel2',
51 | 'serviceworker',
52 | ];
53 |
54 | // confirmedTests will end up as a single boolean. First, we map the
55 | // requiredTests array and see if the global Modernizr object contains a
56 | // boolean for each value we entered into requiredTests.
57 | //
58 | // Note: we're not testing the value of the Modernizr test result, but
59 | // verifying that the test was present. Each browser will have a different
60 | // result, so it's not very useful to verify Modernizr's output in a test
61 | // environment.
62 | var confirmedTests = requiredTests.map(function(thisTest) {
63 | // This should return the type of data (boolean), not the boolean value.
64 | return typeof Modernizr[thisTest];
65 | }).every(function(thisType) {
66 | // Each data type will be verified to be boolean, and the result of the
67 | // .every() function will only be true if all the values resolved to true.
68 | // If even one of the values is not boolean, .every() returns false.
69 | return thisType === 'boolean';
70 | });
71 |
72 | // Return the result of our computation to Casper. The return value of
73 | // confirmedTests is ultimately what decides whether this test is reported
74 | // as PASS or FAIL. All of the other computation beforehand was internal.
75 | return confirmedTests;
76 | }, true, 'Required Modernizr tests are all present.');
77 | });
78 |
79 | // This code runs all the tests that we defined above.
80 | //
81 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#done
82 | casper.run(function () {
83 | test.done();
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/examples/casper/user-actions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Simulating user actions with CasperJS. This script explores the ability to
4 | * use Casper for navigation just like a user would: clicking the page and
5 | * entering text to submit a form.
6 | */
7 |
8 | // This object will hold all of the config/content that Casper needs to supply.
9 | var config = {
10 | url: 'https://rupl.github.io/frontend-testing/examples/targets/test-user-actions-p1.html',
11 | form: {
12 | "name": "Chris Ruppel",
13 | "email": "me@example.com",
14 | "project-title": "CasperJS Test Project",
15 | "project-desc": "CasperJS Test Project Description",
16 | "project-type": "freelance",
17 | }
18 | };
19 |
20 |
21 | // Define the suite of tests and give it the following properties:
22 | // - Title, which shows up before any of the pass/fails.
23 | // - Number of tests, must be changed as you add tests.
24 | // - suite(), which contains all of your tests.
25 | //
26 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#begin
27 | casper.test.begin('Testing navigation and forms', 7, function suite(test) {
28 | test.comment('⌚️ Loading ' + config.url);
29 |
30 | // casper.start() always wraps your first action. The first argument should
31 | // be the URL of the page you want to test. Instead of being hard-coded, ours
32 | // comes from the config object we defined above.
33 | //
34 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#start
35 | casper.start(config.url, function() {
36 |
37 | // casper.click() fires a click event on a particular element. In this case
38 | // we're clicking on the main logo of the site.
39 | //
40 | // The only argument needed is a selector. Be careful to be specific when
41 | // initiating an action like this. For instance, a selector such as plain
42 | // "a" would not be specific enough.
43 | //
44 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#click
45 | this.click('nav li:first-child a');
46 |
47 | // Log the click to the console so we know why it's pausing momentarily.
48 | test.comment('⌚️ Clicking the "Contact us" link...');
49 | });
50 |
51 | // casper.then() allows us to wait until previous tests and actions are
52 | // completed before moving on to the next steps. This is useful for many
53 | // situations and authenticated sessions are a prime candidate, since we
54 | // cannot perform any further actions if we failed to authenticate.
55 | //
56 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#then
57 | casper.then(function () {
58 | // test.assertUrlMatch() allows us to run a regular expression against the
59 | // current URL that Casper has loaded.
60 | //
61 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#asserturlmatch
62 | test.assertUrlMatch(/test-user-actions-p2/, 'New location is ' + this.getCurrentUrl());
63 |
64 | // Report that we're attempting to use keyboard nav.
65 | test.comment('⌚️ Using keyboard nav to visit contact page...');
66 |
67 | // casper.sendKeys() allows us to simulate pressing one or more keys on the
68 | // keyboard. You can use this to trigger a JS event listener, enter text
69 | // into an or element with `contenteditable` attribute, or use it
70 | // to test keyboard navigation.
71 | //
72 | // Our use-case is triggering the `accesskey` property on one of the menu
73 | // items, selecting the works just fine. If you want to test a
74 | // specific or editable element, the function can accept a more
75 | // specific selector.
76 | //
77 | // In this case we're pressing a combo: Ctrl+Alt+C, which is the way to use
78 | // keyboard navigation in PhantomJS. We do this passing the options object
79 | // to sendKeys() and specifying a `modifiers` value. You can find all the
80 | // possible modifier keys in the second docs link.
81 | //
82 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#sendkeys
83 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#options
84 | this.sendKeys('body', 'c', {modifiers: 'ctrl+alt'});
85 | });
86 |
87 | casper.then(function () {
88 | // Check the URL again to confirm navigation. Look earlier in this file for
89 | // explanation and docs link for test.assertUrlMatch().
90 | test.assertUrlMatch(/test-user-actions-p3/, 'New location is ' + this.getCurrentUrl());
91 |
92 | // casper.fill() allows us to quickly fill out a form with a minimal amount
93 | // of code. If you can write a JSON object, you already know how to fill
94 | // forms in Casper.
95 | //
96 | // @see http://casperjs.readthedocs.org/en/latest/modules/casper.html#fill
97 | casper.fill('#contact', config.form, false);
98 | });
99 |
100 | casper.then(function () {
101 | // Look for the information we just populated within the form.
102 | //
103 | // assertEvalEquals provides an easy way for us to test JavaScript within
104 | // the test environment. Any code within the assertEvalEquals() code block
105 | // is considered to be part of the web page, as if we are typing into the
106 | // browser's JS console of the fully-loaded page.
107 | //
108 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#assertevalequals
109 | test.assertEvalEquals(function () {
110 | return $('#contact [name="name"]').val();
111 | }, config.form.name, 'The name was filled out.');
112 |
113 | // Check the email.
114 | test.assertEvalEquals(function () {
115 | return $('#contact [name="email"]').val();
116 | }, config.form.email, 'The email was filled out.');
117 |
118 | // Check the project title.
119 | test.assertEvalEquals(function () {
120 | return $('#contact [name="project-title"]').val();
121 | }, config.form['project-title'], 'The project title was filled out.');
122 |
123 | // Check the project description.
124 | test.assertEvalEquals(function () {
125 | return $('#contact [name="project-desc"]').val();
126 | }, config.form['project-desc'], 'The project description was filled out.');
127 |
128 | // Check the project type.
129 | test.assertEvalEquals(function () {
130 | return $('#contact [name="project-type"]').val();
131 | }, config.form['project-type'], 'A project type was selected.');
132 | });
133 |
134 | // This code runs all the tests that we defined above.
135 | //
136 | // @see http://casperjs.readthedocs.org/en/latest/modules/tester.html#done
137 | casper.run(function () {
138 | test.done();
139 | });
140 | });
141 |
--------------------------------------------------------------------------------
/examples/grunt/devperf/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | grunt.initConfig({
3 | devperf: {
4 | options: {
5 | urls: [
6 | 'http://gruntjs.com'
7 | ],
8 | resultsFolder: './reports/'
9 | }
10 | }
11 | });
12 |
13 | grunt.loadNpmTasks('grunt-devperf');
14 | grunt.registerTask('default', ['devperf']);
15 | };
16 |
--------------------------------------------------------------------------------
/examples/grunt/devperf/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "grunt": "~0.4.4",
4 | "grunt-devperf": "~0.2.5"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/grunt/jshint/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // Project configuration.
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 |
7 | jshint: {
8 | files: {
9 | src: ['*.js']
10 | },
11 | options: {
12 | es3: true // Compatability for IE 6/7/8
13 | }
14 | },
15 |
16 | watch: {
17 | js: {
18 | files: ['*.js'],
19 | tasks: ['jshint']
20 | }
21 | }
22 | });
23 |
24 | grunt.loadNpmTasks('grunt-contrib-watch');
25 | grunt.loadNpmTasks('grunt-contrib-jshint');
26 |
27 | grunt.registerTask('default', ['watch']);
28 | };
29 |
--------------------------------------------------------------------------------
/examples/grunt/jshint/example.js:
--------------------------------------------------------------------------------
1 | // This file has syntax errors that will trigger feedback from JSHint
2 |
3 | $('.my-sample-selector').hide()
4 |
5 | if (my_var == false) {
6 | console.log('hello!');
7 | }
8 |
9 | var es3_object_broken = {
10 | key1: 'value1',
11 | key2: 'value2',
12 | }
13 |
--------------------------------------------------------------------------------
/examples/grunt/jshint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jshint-example",
3 | "version": "0.0.1",
4 | "devDependencies": {
5 | "grunt": "~0.4.1",
6 | "grunt-contrib-watch": "~0.5.3",
7 | "grunt-contrib-jshint": "~0.6.4"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/grunt/pagespeed/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // The settings var includes your PageSpeed API Key. To use yours, first go to
2 | // the following URL and generate/lookup your Google PageSpeed API key:
3 | //
4 | // @see https://code.google.com/apis/console/
5 | //
6 | // Now make a file called settings.json in the same dir as this Gruntfile and
7 | // include an object like this:
8 | //
9 | // {
10 | // "key": "your-google-pagespeed-api-key"
11 | // }
12 | var settings = require('./settings.json');
13 |
14 | // This is the normal Gruntfile
15 | module.exports = function(grunt) {
16 | grunt.initConfig({
17 | pkg: grunt.file.readJSON('package.json'),
18 |
19 | pagespeed: {
20 | desktop: {
21 | url: "http://gruntjs.com",
22 | locale: "en_US",
23 | strategy: "desktop",
24 | threshold: 85
25 | },
26 | mobile: {
27 | url: "http://gruntjs.com",
28 | locale: "en_US",
29 | strategy: "mobile",
30 | threshold: 85
31 | },
32 | options: {
33 | key: settings.key
34 | }
35 | }
36 | });
37 |
38 | grunt.loadNpmTasks('grunt-pagespeed');
39 |
40 | grunt.registerTask('default', ['pagespeed:desktop']);
41 | };
42 |
--------------------------------------------------------------------------------
/examples/grunt/pagespeed/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "grunt": "~0.4.1",
4 | "grunt-pagespeed": "0.1.3"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/grunt/perfbudget/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // The settings var includes your WebPageTest API Key. To use yours, first go to
2 | // the following URL to get a key:
3 | //
4 | // @see http://www.webpagetest.org/getkey.php
5 | //
6 | // Now make a file called settings.json in the same dir as this
7 | // Gruntfile and include an object like this:
8 | //
9 | // {
10 | // "key": "your-web-page-test-api-key"
11 | // }
12 | var settings = require('./settings.json');
13 |
14 | // This is the normal Gruntfile
15 | module.exports = function(grunt) {
16 | grunt.initConfig({
17 | pkg: grunt.file.readJSON('package.json'),
18 |
19 | perfbudget: {
20 | default: {
21 | // See all options here:
22 | // https://github.com/tkadlec/grunt-perfbudget#options
23 | options: {
24 | url: 'https://iamcarrico.com',
25 | key: settings.key,
26 | budget: {
27 | render: 800,
28 | requests: 10
29 | }
30 | }
31 | }
32 | }
33 | });
34 |
35 | grunt.loadNpmTasks('grunt-perfbudget');
36 |
37 | grunt.registerTask('default', ['perfbudget']);
38 | };
39 |
--------------------------------------------------------------------------------
/examples/grunt/perfbudget/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "grunt": "~0.4.1",
4 | "grunt-perfbudget": "~0.1.3"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/grunt/phantomas/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // Project configuration.
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 |
7 | phantomas: {
8 | default: {
9 | options: {
10 | indexPath: './reports/',
11 | options: {},
12 | url: 'http://gruntjs.com/'
13 | }
14 | },
15 | screenshot: {
16 | options: {
17 | indexPath: './reports/',
18 | options: {
19 | 'screenshot': 'screenshots/sample-' + Date.now() + '.png'
20 | },
21 | url: 'http://gruntjs.com/'
22 | }
23 | },
24 | requests: {
25 | options: {
26 | indexPath: './reports/',
27 | options: {
28 | 'assert-requests': 20
29 | },
30 | url: 'http://gruntjs.com/'
31 | }
32 | },
33 | }
34 | });
35 |
36 | grunt.loadNpmTasks('grunt-phantomas');
37 |
38 | grunt.registerTask('default', ['phantomas:default']);
39 | };
40 |
--------------------------------------------------------------------------------
/examples/grunt/phantomas/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "grunt": "~0.4.1",
4 | "grunt-phantomas": "~0.7.1"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/targets/README.md:
--------------------------------------------------------------------------------
1 | # Testing Targets for CasperJS
2 |
3 | This directory holds static assets that will be tested by CasperJS. If you're viewing this on GitHub, go one directory up and peer into the `casper` directory.
4 |
--------------------------------------------------------------------------------
/examples/targets/modernizr-3.6.0-custom.js:
--------------------------------------------------------------------------------
1 | /*! modernizr 3.6.0 (Custom Build) | MIT *
2 | * https://modernizr.com/download/?-csstransformslevel2-serviceworker-setclasses !*/
3 | !function(e,n,t){function r(e,n){return typeof e===n}function o(){var e,n,t,o,s,i,l;for(var a in w)if(w.hasOwnProperty(a)){if(e=[],n=w[a],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;td;d++)if(m=e[d],y=N.style[m],i(m,"-")&&(m=a(m)),N.style[m]!==t){if(s||r(o,"undefined"))return u(),"pfx"==n?m:!0;try{N.style[m]=o}catch(h){}if(N.style[m]!=y)return u(),"pfx"==n?m:!0}return u(),!1}function g(e,n,t,o,s){var i=e.charAt(0).toUpperCase()+e.slice(1),l=(e+" "+P.join(i+" ")+i).split(" ");return r(n,"string")||r(n,"undefined")?y(l,n,o,s):(l=(e+" "+z.join(i+" ")+i).split(" "),f(l,n,t))}function h(e,n,r){return g(e,t,t,n,r)}var C=[],w=[],S={_version:"3.6.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){w.push({name:e,fn:n,options:t})},addAsyncTest:function(e){w.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=S,Modernizr=new Modernizr;var _=n.documentElement,x="svg"===_.nodeName.toLowerCase();Modernizr.addTest("serviceworker","serviceWorker"in navigator);var b="Moz O ms Webkit",P=S._config.usePrefixes?b.split(" "):[];S._cssomPrefixes=P;var z=S._config.usePrefixes?b.toLowerCase().split(" "):[];S._domPrefixes=z;var E={elem:l("modernizr")};Modernizr._q.push(function(){delete E.elem});var N={style:E.elem.style};Modernizr._q.unshift(function(){delete N.style}),S.testAllProps=g,S.testAllProps=h,Modernizr.addTest("csstransformslevel2",function(){return h("translate","45px",!0)}),o(),s(C),delete S.addTest,delete S.addAsyncTest;for(var T=0;T
2 |
3 |
4 |
5 | Testing JS Code with CasperJS
6 |
16 |
17 |
18 | CasperJS — Testing JS code
19 | This page exists solely to offer a stable testing target for an example CasperJS script .
20 | Learn more about automated frontend testing .
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/targets/test-user-actions-p1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing JS Code with CasperJS
6 |
16 |
17 |
18 | CasperJS — Testing user actions
19 | This page exists to offer a stable testing target for an example CasperJS script .
20 | Learn more about automated frontend testing .
21 |
22 |
23 | Casper will use a click
event to follow the link in the navigation.
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/targets/test-user-actions-p2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing JS Code with CasperJS
6 |
16 |
17 |
18 | CasperJS — Testing user actions
19 | This page exists to offer a stable testing target for an example CasperJS script .
20 | Learn more about automated frontend testing .
21 |
22 |
23 | Casper will use the accesskey
instead of a click
event to follow this navigation link.
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/targets/test-user-actions-p3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Testing JS Code with CasperJS
6 |
35 |
36 |
37 | CasperJS — Testing user actions
38 | This page exists to offer a stable testing target for an example CasperJS script .
39 | Learn more about automated frontend testing .
40 |
41 |
42 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/examples/wraith/configs/demo.yaml:
--------------------------------------------------------------------------------
1 | #Headless browser option
2 | browser:
3 | webkit: "phantomjs"
4 | # gecko: "slimerjs"
5 |
6 | #If you want to have multiple snapping files, set the file name here
7 | snap_file: "javascript/snap.js"
8 |
9 | # Type the name of the directory that shots will be stored in
10 | directory:
11 | - 'shots'
12 |
13 | # Add only 2 domains, key will act as a label
14 | domains:
15 | live: "http://fourkitchens.com"
16 | dev: "http://localhost:4000"
17 |
18 | #Type screen widths below, here are a couple of examples
19 | screen_widths:
20 | - 320
21 | - 768
22 | - 1280
23 |
24 | #Type page URL paths below, here are a couple of examples
25 | paths:
26 | home: /
27 |
28 | # If you don't want to name the paths explicitly you can use a yaml
29 | # collection as follows, and names will be derived by replacing / with _
30 | #
31 | # paths:
32 | # - /imghp
33 | # - /maps
34 |
35 | #Amount of fuzz ImageMagick will use
36 | fuzz: '20%'
37 |
38 | #Set the number of days to keep the site spider file
39 | spider_days:
40 | - 10
41 |
--------------------------------------------------------------------------------
/examples/wraith/javascript/snap.js:
--------------------------------------------------------------------------------
1 | var system = require('system');
2 | var page = require('webpage').create();
3 | var fs = require('fs');
4 |
5 | if (system.args.length === 3) {
6 | console.log('Usage: snap.js ');
7 | phantom.exit();
8 | }
9 |
10 | var url = system.args[1];
11 | var image_name = system.args[3];
12 | var view_port_width = system.args[2];
13 | var current_requests = 0;
14 | var last_request_timeout;
15 | var final_timeout;
16 |
17 |
18 | page.viewportSize = { width: view_port_width, height: 1500};
19 | page.settings = { loadImages: true, javascriptEnabled: true };
20 |
21 | // If you want to use additional phantomjs commands, place them here
22 | page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.17';
23 |
24 | // You can place custom headers here, example below.
25 | // page.customHeaders = {
26 |
27 | // 'X-Candy-OVERRIDE': 'https://api.live.bbc.co.uk/'
28 |
29 | // };
30 |
31 | // If you want to set a cookie, just add your details below in the following way.
32 |
33 | // phantom.addCookie({
34 | // 'name': 'ckns_policy',
35 | // 'value': '111',
36 | // 'domain': '.bbc.co.uk'
37 | // });
38 | // phantom.addCookie({
39 | // 'name': 'locserv',
40 | // 'value': '1#l1#i=6691484:n=Oxford+Circus:h=e@w1#i=8:p=London@d1#1=l:2=e:3=e:4=2@n1#r=40',
41 | // 'domain': '.bbc.co.uk'
42 | // });
43 |
44 | page.onResourceRequested = function(req) {
45 | current_requests += 1;
46 | };
47 |
48 | page.onResourceReceived = function(res) {
49 | if (res.stage === 'end') {
50 | current_requests -= 1;
51 | debounced_render();
52 | }
53 | };
54 |
55 | page.open(url, function(status) {
56 | if (status !== 'success') {
57 | console.log('Error with page ' + url);
58 | phantom.exit();
59 | }
60 | });
61 |
62 |
63 | function debounced_render() {
64 | clearTimeout(last_request_timeout);
65 | clearTimeout(final_timeout);
66 |
67 | // If there's no more ongoing resource requests, wait for 1 second before
68 | // rendering, just in case the page kicks off another request
69 | if (current_requests < 1) {
70 | clearTimeout(final_timeout);
71 | last_request_timeout = setTimeout(function() {
72 | console.log('Snapping ' + url + ' at width ' + view_port_width);
73 | page.render(image_name);
74 | phantom.exit();
75 | }, 1000);
76 | }
77 |
78 | // Sometimes, straggling requests never make it back, in which
79 | // case, timeout after 5 seconds and render the page anyway
80 | final_timeout = setTimeout(function() {
81 | console.log('Snapping ' + url + ' at width ' + view_port_width);
82 | page.render(image_name);
83 | phantom.exit();
84 | }, 5000);
85 | }
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/favicon.ico
--------------------------------------------------------------------------------
/img/4k-logo-square-500px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/4k-logo-square-500px.png
--------------------------------------------------------------------------------
/img/browserstack-screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/browserstack-screenshots.png
--------------------------------------------------------------------------------
/img/delaypackage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/delaypackage.gif
--------------------------------------------------------------------------------
/img/grunt-devperf-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/grunt-devperf-graph.png
--------------------------------------------------------------------------------
/img/grunt-perfbudget-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/grunt-perfbudget-output.png
--------------------------------------------------------------------------------
/img/grunt-phantomas-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/grunt-phantomas-graph.png
--------------------------------------------------------------------------------
/img/jshint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/jshint.png
--------------------------------------------------------------------------------
/img/timing-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/timing-overview.png
--------------------------------------------------------------------------------
/img/visitor-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/visitor-flow.png
--------------------------------------------------------------------------------
/img/wraith-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rupl/frontend-testing/d565f924cca5aa51a76cb88a06b325cbb793768c/img/wraith-example.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Automated Frontend Testing
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Automated Frontend Testing
45 | Or: How to Automate the Process of detecting when you Break Things
46 |
50 |
51 |
52 |
53 | Why do I need frontend testing?
54 |
55 |
56 | There are untold number of subtle errors that can occur on the frontend.
57 |
58 |
59 | Minor CSS changes that throw things off
60 | Changes to JS files that break things
61 | Aggregates changing when not necessary
62 | Performance regressions
63 |
64 |
65 |
66 |
67 | Additionally, frontend development is becoming more critical as the trade matures.
68 |
69 | We need the same testing abilities that the backend has had for years.
70 |
71 |
72 |
73 |
74 | Testing page load times
75 | Testing render speeds
76 | Sticking to a performance budget
77 | Verifying visual changes
78 | Accountability for code changes
79 |
80 |
81 |
82 |
83 |
84 |
85 | workflow_alter()
86 |
87 | In order to deliver the best, fastest site possible, we have to change our development processes.
88 |
89 | Performance is not a checklist, it's a continuous process. — Ilya Grigorik
90 |
91 | …don’t take measures without measuring them. — Maximiliano Firtman
92 |
93 |
94 |
95 |
102 |
103 |
104 |
105 |
106 | Functional testing
107 |
108 |
109 | CasperJS
110 | Casper allows for scripted actions to be tested. It uses PhantomJS under the hood.
111 |
112 |
113 | Run the same test with multiple screen sizes.
114 | Test complex features or components.
115 | Automate complex user actions.
116 | Test content creation, transactions, other features.
117 | Keep an eye on problematic pages.
118 |
119 |
120 |
121 | Test frontend components
122 |
123 | # test the canonical picturefill demo
124 | $ casperjs test picturefill.js
125 |
126 | Read the blog post or watch a screencast describing this test in detail.
127 |
128 |
129 | Simulate user actions
130 |
131 | # test simple user actions like clicking,
132 | # keyboard navigation, filling forms
133 | $ casperjs test user-actions.js
134 |
135 | Read the blog post describing this test in detail.
136 |
137 |
138 | Test author workflow in Drupal
139 |
140 | # test a Drupal demo site, log in, add content
141 | $ casperjs test drupal.js
142 |
143 | Read the blog post or watch a video .
144 |
145 |
146 |
147 | More examples
148 |
149 | Keep checking our blog post series on the Four Kitchens blog to learn more about CasperJS .
150 |
151 |
152 |
153 |
154 |
155 |
156 | Performance Testing
157 |
158 |
159 | Automating PageSpeed
160 |
161 | Google has a service called PageSpeed Insights that grades your site and boils down tons of factors into a "speed index"
162 |
163 |
164 | Testing sites can be automated. Get your API key first .
165 |
166 |
167 |
168 |
169 | grunt-pagespeed
170 |
171 | PageSpeed API is documented quite thoroughly, but there's also a grunt plugin .
172 |
173 | # examples/grunt/pagespeed
174 | $ npm install
175 | $ grunt # runs default task
176 | $ grunt pagespeed:mobile # runs mobile task
177 |
178 | View code on GitHub
179 |
180 |
187 |
188 | Phantomas
189 |
190 | Phantomas is a PhantomJS-based web performance metrics tool
191 |
192 | It gives you loads of data about how the frontend of your website is performing.
193 |
194 | The usage guide is extensive.
195 |
196 |
197 |
198 | # run a basic report
199 | $ phantomas --url http://gruntjs.com
200 |
201 | # set viewport dimensions, generate images of rendering process
202 | $ phantomas --url http://gruntjs.com --viewport=320x480 --film-strip
203 |
204 | # assert a test for total number of requests
205 | $ phantomas --url http://gruntjs.com --assert-requests=20
206 |
207 |
208 |
209 | grunt-phantomas
210 |
211 | This grunt plugin is not just a wrapper for running the tool.
212 |
213 | It provides detailed reports that track your data over time, allowing you to identify trends using dynamic charts that update themselves each time you run the grunt task
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | # examples/grunt/phantomas
222 | $ grunt phantomas:default
223 |
224 | # run report and generate screenshot
225 | $ grunt phantomas:screenshot
226 |
227 | # test for certain values. this might cause failure!
228 | $ grunt phantomas:requests
229 |
230 | View code on GitHub
231 |
232 |
233 | Performance Budgets
234 |
235 | The idea is simple: performance budgets are just like a monthy expense budget. We should keep track of how fat our sites grow over time.
236 |
237 | grunt-phantomas has performance budget features that visualize over-budget metrics that you set.
238 |
239 |
240 | grunt-perfbudget
241 |
242 | Tim Kadlec, who first suggested performance budgets, released this tool to help teams meet their goals.
243 |
244 | grunt-perfbudget relies on the immensely useful WebPageTest API to enforce a budget.
245 |
246 | WebPageTest.org and its API are much more flexible than PhantomJS tools, because it can leverage multiple browsers, geographic locations, and network speeds.
247 |
248 |
249 | # examples/grunt/perfbudget
250 | $ npm install
251 |
252 | # run report
253 | $ grunt perfbudget
254 |
255 | View code on GitHub
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | CSS Regression testing
267 |
268 |
269 |
270 | CSS regressions? say it ain't so!
271 |
272 | Having no scope at all, CSS is the easiest thing to nudge out of place.
273 |
274 | It's also easier to prevent than you think.
275 |
276 |
277 | Wraith
278 | Wraith is the easiest way to take screenshots of two environments, producing a visual diff.
279 |
280 |
281 |
282 | Basic usage of Wraith
283 |
284 | # examples/wraith
285 | $ gem install wraith
286 |
287 | # run the capture process
288 | $ wraith capture demo
289 |
290 | # view results in the browser
291 | $ open shots/gallery.html
292 |
293 |
294 | Multiple tests
295 |
296 | Wraith handles one comparison per config file.
297 |
298 | However, it has support for multiple configs, so several config files in one repo allows for multiple comparisons.
299 |
300 | Read more on GitHub
301 |
302 |
303 |
304 |
305 |
306 | Automating Tasks with CI
307 |
308 |
309 | The Basics
310 |
311 | Everything outlined in this section requires two key ingredients:
312 |
313 |
314 | Continuous integration (CI)
315 | Git hooks
316 |
317 |
318 | Four Kitchens uses Jenkins and GitHub WebHooks in our workflow, and you can use whatever you wish.
319 |
320 |
321 |
322 |
323 |
324 | Trigger Jenkins builds by pushing to GitHub
325 |
326 | An oldie but goodie: check out our how-to from 2011
327 |
328 |
329 | You push to GitHub master branch (or merge PR)
330 | GitHub pings your CI server using post-receive hook
331 | "Yo Jenkins, the repository was updated!"
332 | CI server pulls the new code to your staging area
333 |
334 |
335 |
336 | Although fairly simple in application, the post illustrates the basic concepts underlying all tasks involving Jenkins.
337 |
338 |
339 | Use git hooks to test before pushing code
340 |
341 | Git can be configured to automatically run tasks before or after many of its operations.
342 |
343 | pre-commit hook that runs jshint on your JavaScript.
344 |
345 | pre-push hook that runs performance tests on your local. Helps enforce performance budgets.
346 |
347 |
348 | Travis CI
349 |
350 | Simple config via YAML file
351 | FREE for open-source projects
352 |
353 |
354 | Example using Four Kitchens' frontend performance training repo .Click the red × to see Travis log
355 |
356 |
357 |
358 |
359 |
360 | Automated Testing Services
361 |
362 |
363 | Ghost Inspector
364 | If you have a team willing to code up these examples, great! I like writing tests to complement code.
365 | If not, Ghost Inspector can record a user's actions as they browse normally, and turn those actions into Casper code.
366 | Ghost Inspector
367 |
368 |
369 |
370 |
371 |
372 | Further reading
373 |
374 |
380 |
381 |
382 |
392 |
393 |
394 |
395 |
396 | ◄
397 | ►
398 | ▲
399 | ▼
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
449 |
450 |
451 |
--------------------------------------------------------------------------------
/js/reveal.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js 1.4
3 | * http://lab.hakim.se/reveal-js
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2012 Hakim El Hattab, http://hakim.se
7 | */
8 | var Reveal = (function(){
9 |
10 | var HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
11 | VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
12 |
13 | IS_TOUCH_DEVICE = !!( 'ontouchstart' in window ),
14 |
15 | // The horizontal and verical index of the currently active slide
16 | indexh = 0,
17 | indexv = 0,
18 |
19 | // Configurations options, can be overridden at initialization time
20 | config = {
21 | controls: true,
22 | progress: false,
23 | history: false,
24 | loop: false,
25 | mouseWheel: true,
26 | rollingLinks: true,
27 | transition: 'default',
28 | theme: 'default'
29 | },
30 |
31 | // Slides may hold a data-state attribute which we pick up and apply
32 | // as a class to the body. This list contains the combined state of
33 | // all current slides.
34 | state = [],
35 |
36 | // Cached references to DOM elements
37 | dom = {},
38 |
39 | // Detect support for CSS 3D transforms
40 | supports3DTransforms = document.body.style['WebkitPerspective'] !== undefined ||
41 | document.body.style['MozPerspective'] !== undefined ||
42 | document.body.style['msPerspective'] !== undefined ||
43 | document.body.style['OPerspective'] !== undefined ||
44 | document.body.style['perspective'] !== undefined,
45 |
46 | supports2DTransforms = document.body.style['WebkitTransform'] !== undefined ||
47 | document.body.style['MozTransform'] !== undefined ||
48 | document.body.style['msTransform'] !== undefined ||
49 | document.body.style['OTransform'] !== undefined ||
50 | document.body.style['transform'] !== undefined,
51 |
52 | // Detect support for elem.classList
53 | supportsClassList = !!document.body.classList;
54 |
55 | // Throttles mouse wheel navigation
56 | mouseWheelTimeout = 0,
57 |
58 | // Delays updates to the URL due to a Chrome thumbnailer bug
59 | writeURLTimeout = 0,
60 |
61 | // Holds information about the currently ongoing touch input
62 | touch = {
63 | startX: 0,
64 | startY: 0,
65 | startSpan: 0,
66 | startCount: 0,
67 | handled: false,
68 | threshold: 40
69 | };
70 |
71 |
72 | /**
73 | * Starts up the slideshow by applying configuration
74 | * options and binding various events.
75 | */
76 | function initialize( options ) {
77 |
78 | if( ( !supports2DTransforms && !supports3DTransforms ) || !supportsClassList ) {
79 | document.body.setAttribute( 'class', 'no-transforms' );
80 |
81 | // If the browser doesn't support core features we won't be
82 | // using JavaScript to control the presentation
83 | return;
84 | }
85 |
86 | // Cache references to DOM elements
87 | dom.wrapper = document.querySelector( '.reveal' );
88 | dom.progress = document.querySelector( '.reveal .progress' );
89 | dom.progressbar = document.querySelector( '.reveal .progress span' );
90 |
91 | if ( config.controls ) {
92 | dom.controls = document.querySelector( '.reveal .controls' );
93 | dom.controlsLeft = document.querySelector( '.reveal .controls .left' );
94 | dom.controlsRight = document.querySelector( '.reveal .controls .right' );
95 | dom.controlsUp = document.querySelector( '.reveal .controls .up' );
96 | dom.controlsDown = document.querySelector( '.reveal .controls .down' );
97 | }
98 |
99 | addEventListeners();
100 |
101 | // Copy options over to our config object
102 | extend( config, options );
103 |
104 | // Updates the presentation to match the current configuration values
105 | configure();
106 |
107 | // Read the initial hash
108 | readURL();
109 |
110 | // Set up hiding of the browser address bar
111 | if( navigator.userAgent.match( /(iphone|ipod|android)/i ) ) {
112 | // Give the page some scrollable overflow
113 | document.documentElement.style.overflow = 'scroll';
114 | document.body.style.height = '120%';
115 |
116 | // Events that should trigger the address bar to hide
117 | window.addEventListener( 'load', removeAddressBar, false );
118 | window.addEventListener( 'orientationchange', removeAddressBar, false );
119 | }
120 |
121 | }
122 |
123 | function configure() {
124 | // Fall back on the 2D transform theme 'linear'
125 | if( supports3DTransforms === false ) {
126 | config.transition = 'linear';
127 | }
128 |
129 | if( config.controls && dom.controls ) {
130 | dom.controls.style.display = 'block';
131 | }
132 |
133 | if( config.progress ) {
134 | dom.progress.style.display = 'block';
135 | }
136 |
137 | if( config.transition !== 'default' ) {
138 | dom.wrapper.classList.add( config.transition );
139 | }
140 |
141 | if( config.theme !== 'default' ) {
142 | dom.wrapper.classList.add( config.theme );
143 | }
144 |
145 | if( config.mouseWheel ) {
146 | document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
147 | document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
148 | }
149 |
150 | if( config.rollingLinks ) {
151 | // Add some 3D magic to our anchors
152 | linkify();
153 | }
154 | }
155 |
156 | function addEventListeners() {
157 | document.addEventListener( 'keydown', onDocumentKeyDown, false );
158 | document.addEventListener( 'touchstart', onDocumentTouchStart, false );
159 | document.addEventListener( 'touchmove', onDocumentTouchMove, false );
160 | document.addEventListener( 'touchend', onDocumentTouchEnd, false );
161 | window.addEventListener( 'hashchange', onWindowHashChange, false );
162 |
163 | if ( config.controls && dom.controls ) {
164 | dom.controlsLeft.addEventListener( 'click', preventAndForward( navigateLeft ), false );
165 | dom.controlsRight.addEventListener( 'click', preventAndForward( navigateRight ), false );
166 | dom.controlsUp.addEventListener( 'click', preventAndForward( navigateUp ), false );
167 | dom.controlsDown.addEventListener( 'click', preventAndForward( navigateDown ), false );
168 | }
169 | }
170 |
171 | function removeEventListeners() {
172 | document.removeEventListener( 'keydown', onDocumentKeyDown, false );
173 | document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
174 | document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
175 | document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
176 | window.removeEventListener( 'hashchange', onWindowHashChange, false );
177 |
178 | if ( config.controls && dom.controls ) {
179 | dom.controlsLeft.removeEventListener( 'click', preventAndForward( navigateLeft ), false );
180 | dom.controlsRight.removeEventListener( 'click', preventAndForward( navigateRight ), false );
181 | dom.controlsUp.removeEventListener( 'click', preventAndForward( navigateUp ), false );
182 | dom.controlsDown.removeEventListener( 'click', preventAndForward( navigateDown ), false );
183 | }
184 | }
185 |
186 | /**
187 | * Extend object a with the properties of object b.
188 | * If there's a conflict, object b takes precedence.
189 | */
190 | function extend( a, b ) {
191 | for( var i in b ) {
192 | a[ i ] = b[ i ];
193 | }
194 | }
195 |
196 | /**
197 | * Measures the distance in pixels between point a
198 | * and point b.
199 | *
200 | * @param {Object} a point with x/y properties
201 | * @param {Object} b point with x/y properties
202 | */
203 | function distanceBetween( a, b ) {
204 | var dx = a.x - b.x,
205 | dy = a.y - b.y;
206 |
207 | return Math.sqrt( dx*dx + dy*dy );
208 | }
209 |
210 | /**
211 | * Prevents an events defaults behavior calls the
212 | * specified delegate.
213 | *
214 | * @param {Function} delegate The method to call
215 | * after the wrapper has been executed
216 | */
217 | function preventAndForward( delegate ) {
218 | return function( event ) {
219 | event.preventDefault();
220 | delegate.call();
221 | }
222 | }
223 |
224 | /**
225 | * Causes the address bar to hide on mobile devices,
226 | * more vertical space ftw.
227 | */
228 | function removeAddressBar() {
229 | setTimeout( function() {
230 | window.scrollTo( 0, 1 );
231 | }, 0 );
232 | }
233 |
234 | /**
235 | * Handler for the document level 'keydown' event.
236 | *
237 | * @param {Object} event
238 | */
239 | function onDocumentKeyDown( event ) {
240 | // FFT: Use document.querySelector( ':focus' ) === null
241 | // instead of checking contentEditable?
242 |
243 | // Disregard the event if the target is editable or a
244 | // modifier is present
245 | if ( event.target.contentEditable != 'inherit' || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
246 |
247 | var triggered = false;
248 |
249 | switch( event.keyCode ) {
250 | // p, page up
251 | case 80: case 33: navigatePrev(); triggered = true; break;
252 | // n, page down
253 | case 78: case 34: navigateNext(); triggered = true; break;
254 | // h, left
255 | case 72: case 37: navigateLeft(); triggered = true; break;
256 | // l, right
257 | case 76: case 39: navigateRight(); triggered = true; break;
258 | // k, up
259 | case 75: case 38: navigateUp(); triggered = true; break;
260 | // j, down
261 | case 74: case 40: navigateDown(); triggered = true; break;
262 | // home
263 | case 36: navigateTo( 0 ); triggered = true; break;
264 | // end
265 | case 35: navigateTo( Number.MAX_VALUE ); triggered = true; break;
266 | // space
267 | case 32: overviewIsActive() ? deactivateOverview() : navigateNext(); triggered = true; break;
268 | // return
269 | case 13: if( overviewIsActive() ) { deactivateOverview(); triggered = true; } break;
270 | }
271 |
272 | if( triggered ) {
273 | event.preventDefault();
274 | }
275 | else if ( event.keyCode === 27 && supports3DTransforms ) {
276 | if( overviewIsActive() ) {
277 | deactivateOverview();
278 | }
279 | else {
280 | activateOverview();
281 | }
282 |
283 | event.preventDefault();
284 | }
285 |
286 | }
287 |
288 | /**
289 | * Handler for the document level 'touchstart' event,
290 | * enables support for swipe and pinch gestures.
291 | */
292 | function onDocumentTouchStart( event ) {
293 | touch.startX = event.touches[0].clientX;
294 | touch.startY = event.touches[0].clientY;
295 | touch.startCount = event.touches.length;
296 |
297 | // If there's two touches we need to memorize the distance
298 | // between those two points to detect pinching
299 | if( event.touches.length === 2 ) {
300 | touch.startSpan = distanceBetween( {
301 | x: event.touches[1].clientX,
302 | y: event.touches[1].clientY
303 | }, {
304 | x: touch.startX,
305 | y: touch.startY
306 | } );
307 | }
308 | }
309 |
310 | /**
311 | * Handler for the document level 'touchmove' event.
312 | */
313 | function onDocumentTouchMove( event ) {
314 | // Each touch should only trigger one action
315 | if( !touch.handled ) {
316 | var currentX = event.touches[0].clientX;
317 | var currentY = event.touches[0].clientY;
318 |
319 | // If the touch started off with two points and still has
320 | // two active touches; test for the pinch gesture
321 | if( event.touches.length === 2 && touch.startCount === 2 ) {
322 |
323 | // The current distance in pixels between the two touch points
324 | var currentSpan = distanceBetween( {
325 | x: event.touches[1].clientX,
326 | y: event.touches[1].clientY
327 | }, {
328 | x: touch.startX,
329 | y: touch.startY
330 | } );
331 |
332 | // If the span is larger than the desire amount we've got
333 | // ourselves a pinch
334 | if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
335 | touch.handled = true;
336 |
337 | if( currentSpan < touch.startSpan ) {
338 | activateOverview();
339 | }
340 | else {
341 | deactivateOverview();
342 | }
343 | }
344 |
345 | }
346 | // There was only one touch point, look for a swipe
347 | else if( event.touches.length === 1 ) {
348 | var deltaX = currentX - touch.startX,
349 | deltaY = currentY - touch.startY;
350 |
351 | if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
352 | touch.handled = true;
353 | navigateLeft();
354 | }
355 | else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
356 | touch.handled = true;
357 | navigateRight();
358 | }
359 | else if( deltaY > touch.threshold ) {
360 | touch.handled = true;
361 | navigateUp();
362 | }
363 | else if( deltaY < -touch.threshold ) {
364 | touch.handled = true;
365 | navigateDown();
366 | }
367 | }
368 |
369 | event.preventDefault();
370 | }
371 | }
372 |
373 | /**
374 | * Handler for the document level 'touchend' event.
375 | */
376 | function onDocumentTouchEnd( event ) {
377 | touch.handled = false;
378 | }
379 |
380 | /**
381 | * Handles mouse wheel scrolling, throttled to avoid
382 | * skipping multiple slides.
383 | */
384 | function onDocumentMouseScroll( event ){
385 | clearTimeout( mouseWheelTimeout );
386 |
387 | mouseWheelTimeout = setTimeout( function() {
388 | var delta = event.detail || -event.wheelDelta;
389 | if( delta > 0 ) {
390 | navigateNext();
391 | }
392 | else {
393 | navigatePrev();
394 | }
395 | }, 100 );
396 | }
397 |
398 | /**
399 | * Handler for the window level 'hashchange' event.
400 | *
401 | * @param {Object} event
402 | */
403 | function onWindowHashChange( event ) {
404 | readURL();
405 | }
406 |
407 | /**
408 | * Wrap all links in 3D goodness.
409 | */
410 | function linkify() {
411 | if( supports3DTransforms ) {
412 | var nodes = document.querySelectorAll( '.reveal .slides section a:not(.image)' );
413 |
414 | for( var i = 0, len = nodes.length; i < len; i++ ) {
415 | var node = nodes[i];
416 |
417 | if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
418 | node.classList.add( 'roll' );
419 | node.innerHTML = '' + node.innerHTML + ' ';
420 | }
421 | };
422 | }
423 | }
424 |
425 | /**
426 | * Displays the overview of slides (quick nav) by
427 | * scaling down and arranging all slide elements.
428 | *
429 | * Experimental feature, might be dropped if perf
430 | * can't be improved.
431 | */
432 | function activateOverview() {
433 |
434 | dom.wrapper.classList.add( 'overview' );
435 |
436 | var horizontalSlides = Array.prototype.slice.call( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
437 |
438 | for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
439 | var hslide = horizontalSlides[i],
440 | htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)';
441 |
442 | hslide.setAttribute( 'data-index-h', i );
443 | hslide.style.display = 'block';
444 | hslide.style.WebkitTransform = htransform;
445 | hslide.style.MozTransform = htransform;
446 | hslide.style.msTransform = htransform;
447 | hslide.style.OTransform = htransform;
448 | hslide.style.transform = htransform;
449 |
450 | if( !hslide.classList.contains( 'stack' ) ) {
451 | // Navigate to this slide on click
452 | hslide.addEventListener( 'click', onOverviewSlideClicked, true );
453 | }
454 |
455 | var verticalSlides = Array.prototype.slice.call( hslide.querySelectorAll( 'section' ) );
456 |
457 | for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
458 | var vslide = verticalSlides[j],
459 | vtransform = 'translate(0%, ' + ( ( j - indexv ) * 105 ) + '%)';
460 |
461 | vslide.setAttribute( 'data-index-h', i );
462 | vslide.setAttribute( 'data-index-v', j );
463 | vslide.style.display = 'block';
464 | vslide.style.WebkitTransform = vtransform;
465 | vslide.style.MozTransform = vtransform;
466 | vslide.style.msTransform = vtransform;
467 | vslide.style.OTransform = vtransform;
468 | vslide.style.transform = vtransform;
469 |
470 | // Navigate to this slide on click
471 | vslide.addEventListener( 'click', onOverviewSlideClicked, true );
472 | }
473 |
474 | }
475 | }
476 |
477 | /**
478 | * Exits the slide overview and enters the currently
479 | * active slide.
480 | */
481 | function deactivateOverview() {
482 | dom.wrapper.classList.remove( 'overview' );
483 |
484 | var slides = Array.prototype.slice.call( document.querySelectorAll( '.reveal .slides section' ) );
485 |
486 | for( var i = 0, len = slides.length; i < len; i++ ) {
487 | var element = slides[i];
488 |
489 | // Resets all transforms to use the external styles
490 | element.style.WebkitTransform = '';
491 | element.style.MozTransform = '';
492 | element.style.msTransform = '';
493 | element.style.OTransform = '';
494 | element.style.transform = '';
495 |
496 | element.removeEventListener( 'click', onOverviewSlideClicked );
497 | }
498 |
499 | slide();
500 | }
501 |
502 | /**
503 | * Checks if the overview is currently active.
504 | *
505 | * @return {Boolean} true if the overview is active,
506 | * false otherwise
507 | */
508 | function overviewIsActive() {
509 | return dom.wrapper.classList.contains( 'overview' );
510 | }
511 |
512 | /**
513 | * Invoked when a slide is and we're in the overview.
514 | */
515 | function onOverviewSlideClicked( event ) {
516 | // TODO There's a bug here where the event listeners are not
517 | // removed after deactivating the overview.
518 | if( overviewIsActive() ) {
519 | event.preventDefault();
520 |
521 | deactivateOverview();
522 |
523 | indexh = this.getAttribute( 'data-index-h' );
524 | indexv = this.getAttribute( 'data-index-v' );
525 |
526 | slide();
527 | }
528 | }
529 |
530 | /**
531 | * Updates one dimension of slides by showing the slide
532 | * with the specified index.
533 | *
534 | * @param {String} selector A CSS selector that will fetch
535 | * the group of slides we are working with
536 | * @param {Number} index The index of the slide that should be
537 | * shown
538 | *
539 | * @return {Number} The index of the slide that is now shown,
540 | * might differ from the passed in index if it was out of
541 | * bounds.
542 | */
543 | function updateSlides( selector, index ) {
544 |
545 | // Select all slides and convert the NodeList result to
546 | // an array
547 | var slides = Array.prototype.slice.call( document.querySelectorAll( selector ) ),
548 | slidesLength = slides.length;
549 |
550 | if( slidesLength ) {
551 |
552 | // Should the index loop?
553 | if( config.loop ) {
554 | index %= slidesLength;
555 |
556 | if( index < 0 ) {
557 | index = slidesLength + index;
558 | }
559 | }
560 |
561 | // Enforce max and minimum index bounds
562 | index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
563 |
564 | for( var i = 0; i < slidesLength; i++ ) {
565 | var slide = slides[i];
566 |
567 | // Optimization; hide all slides that are three or more steps
568 | // away from the present slide
569 | if( overviewIsActive() === false ) {
570 | // The distance loops so that it measures 1 between the first
571 | // and last slides
572 | var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
573 |
574 | slide.style.display = distance > 3 ? 'none' : 'block';
575 | }
576 |
577 | slides[i].classList.remove( 'past' );
578 | slides[i].classList.remove( 'present' );
579 | slides[i].classList.remove( 'future' );
580 |
581 | if( i < index ) {
582 | // Any element previous to index is given the 'past' class
583 | slides[i].classList.add( 'past' );
584 | }
585 | else if( i > index ) {
586 | // Any element subsequent to index is given the 'future' class
587 | slides[i].classList.add( 'future' );
588 | }
589 |
590 | // If this element contains vertical slides
591 | if( slide.querySelector( 'section' ) ) {
592 | slides[i].classList.add( 'stack' );
593 | }
594 | }
595 |
596 | // Mark the current slide as present
597 | slides[index].classList.add( 'present' );
598 |
599 | // If this slide has a state associated with it, add it
600 | // onto the current state of the deck
601 | var slideState = slides[index].getAttribute( 'data-state' );
602 | if( slideState ) {
603 | state = state.concat( slideState.split( ' ' ) );
604 | }
605 | }
606 | else {
607 | // Since there are no slides we can't be anywhere beyond the
608 | // zeroth index
609 | index = 0;
610 | }
611 |
612 | return index;
613 |
614 | }
615 |
616 | /**
617 | * Updates the visual slides to represent the currently
618 | * set indices.
619 | */
620 | function slide( h, v ) {
621 | // Remember the state before this slide
622 | var stateBefore = state.concat();
623 |
624 | // Reset the state array
625 | state.length = 0;
626 |
627 | var indexhBefore = indexh,
628 | indexvBefore = indexv;
629 |
630 | // Activate and transition to the new slide
631 | indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
632 | indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
633 |
634 | // Apply the new state
635 | stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
636 | // Check if this state existed on the previous slide. If it
637 | // did, we will avoid adding it repeatedly.
638 | for( var j = 0; j < stateBefore.length; j++ ) {
639 | if( stateBefore[j] === state[i] ) {
640 | stateBefore.splice( j, 1 );
641 | continue stateLoop;
642 | }
643 | }
644 |
645 | document.documentElement.classList.add( state[i] );
646 |
647 | // Dispatch custom event matching the state's name
648 | dispatchEvent( state[i] );
649 | }
650 |
651 | // Clean up the remaints of the previous state
652 | while( stateBefore.length ) {
653 | document.documentElement.classList.remove( stateBefore.pop() );
654 | }
655 |
656 | // Update progress if enabled
657 | if( config.progress ) {
658 | dom.progressbar.style.width = ( indexh / ( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length - 1 ) ) * window.innerWidth + 'px';
659 | }
660 |
661 | // Close the overview if it's active
662 | if( overviewIsActive() ) {
663 | activateOverview();
664 | }
665 |
666 | updateControls();
667 |
668 | clearTimeout( writeURLTimeout );
669 | writeURLTimeout = setTimeout( writeURL, 1500 );
670 |
671 | // Only fire if the slide index is different from before
672 | if( indexh !== indexhBefore || indexv !== indexvBefore ) {
673 | // Query all horizontal slides in the deck
674 | var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
675 |
676 | // Find the previous and current horizontal slides
677 | var previousHorizontalSlide = horizontalSlides[ indexhBefore ],
678 | currentHorizontalSlide = horizontalSlides[ indexh ];
679 |
680 | // Query all vertical slides inside of the previous and current horizontal slides
681 | var previousVerticalSlides = previousHorizontalSlide.querySelectorAll( 'section' );
682 | currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
683 |
684 | // Dispatch an event notifying observers of the change in slide
685 | dispatchEvent( 'slidechanged', {
686 | // Include the current indices in the event
687 | 'indexh': indexh,
688 | 'indexv': indexv,
689 |
690 | // Passes direct references to the slide HTML elements, attempts to find
691 | // a vertical slide and falls back on the horizontal parent
692 | 'previousSlide': previousVerticalSlides[ indexvBefore ] || previousHorizontalSlide,
693 | 'currentSlide': currentVerticalSlides[ indexv ] || currentHorizontalSlide
694 | } );
695 | }
696 | }
697 |
698 | /**
699 | * Updates the state and link pointers of the controls.
700 | */
701 | function updateControls() {
702 | if ( !config.controls || !dom.controls ) {
703 | return;
704 | }
705 |
706 | var routes = availableRoutes();
707 |
708 | // Remove the 'enabled' class from all directions
709 | [ dom.controlsLeft, dom.controlsRight, dom.controlsUp, dom.controlsDown ].forEach( function( node ) {
710 | node.classList.remove( 'enabled' );
711 | } )
712 |
713 | if( routes.left ) dom.controlsLeft.classList.add( 'enabled' );
714 | if( routes.right ) dom.controlsRight.classList.add( 'enabled' );
715 | if( routes.up ) dom.controlsUp.classList.add( 'enabled' );
716 | if( routes.down ) dom.controlsDown.classList.add( 'enabled' );
717 | }
718 |
719 | /**
720 | * Determine what available routes there are for navigation.
721 | *
722 | * @return {Object} containing four booleans: left/right/up/down
723 | */
724 | function availableRoutes() {
725 | var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
726 | var verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
727 |
728 | return {
729 | left: indexh > 0,
730 | right: indexh < horizontalSlides.length - 1,
731 | up: indexv > 0,
732 | down: indexv < verticalSlides.length - 1
733 | };
734 | }
735 |
736 | /**
737 | * Reads the current URL (hash) and navigates accordingly.
738 | */
739 | function readURL() {
740 | // Break the hash down to separate components
741 | var bits = window.location.hash.slice(2).split('/');
742 |
743 | // Read the index components of the hash
744 | indexh = parseInt( bits[0] ) || 0 ;
745 | indexv = parseInt( bits[1] ) || 0 ;
746 |
747 | navigateTo( indexh, indexv );
748 | }
749 |
750 | /**
751 | * Updates the page URL (hash) to reflect the current
752 | * state.
753 | */
754 | function writeURL() {
755 | if( config.history ) {
756 | var url = '/';
757 |
758 | // Only include the minimum possible number of components in
759 | // the URL
760 | if( indexh > 0 || indexv > 0 ) url += indexh;
761 | if( indexv > 0 ) url += '/' + indexv;
762 |
763 | window.location.hash = url;
764 | }
765 | }
766 |
767 | /**
768 | * Dispatches an event of the specified type from the
769 | * reveal DOM element.
770 | */
771 | function dispatchEvent( type, properties ) {
772 | var event = document.createEvent( "HTMLEvents", 1, 2 );
773 | event.initEvent( type, true, true );
774 | extend( event, properties );
775 | dom.wrapper.dispatchEvent( event );
776 | }
777 |
778 | /**
779 | * Navigate to the next slide fragment.
780 | *
781 | * @return {Boolean} true if there was a next fragment,
782 | * false otherwise
783 | */
784 | function nextFragment() {
785 | // Vertical slides:
786 | if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
787 | var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
788 | if( verticalFragments.length ) {
789 | verticalFragments[0].classList.add( 'visible' );
790 |
791 | // Notify subscribers of the change
792 | dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } );
793 | return true;
794 | }
795 | }
796 | // Horizontal slides:
797 | else {
798 | var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
799 | if( horizontalFragments.length ) {
800 | horizontalFragments[0].classList.add( 'visible' );
801 |
802 | // Notify subscribers of the change
803 | dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
804 | return true;
805 | }
806 | }
807 |
808 | return false;
809 | }
810 |
811 | /**
812 | * Navigate to the previous slide fragment.
813 | *
814 | * @return {Boolean} true if there was a previous fragment,
815 | * false otherwise
816 | */
817 | function previousFragment() {
818 | // Vertical slides:
819 | if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
820 | var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
821 | if( verticalFragments.length ) {
822 | verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );
823 |
824 | // Notify subscribers of the change
825 | dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } );
826 | return true;
827 | }
828 | }
829 | // Horizontal slides:
830 | else {
831 | var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
832 | if( horizontalFragments.length ) {
833 | horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );
834 |
835 | // Notify subscribers of the change
836 | dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
837 | return true;
838 | }
839 | }
840 |
841 | return false;
842 | }
843 |
844 | /**
845 | * Triggers a navigation to the specified indices.
846 | *
847 | * @param {Number} h The horizontal index of the slide to show
848 | * @param {Number} v The vertical index of the slide to show
849 | */
850 | function navigateTo( h, v ) {
851 | slide( h, v );
852 | }
853 |
854 | function navigateLeft() {
855 | // Prioritize hiding fragments
856 | if( overviewIsActive() || previousFragment() === false ) {
857 | slide( indexh - 1, 0 );
858 | }
859 | }
860 | function navigateRight() {
861 | // Prioritize revealing fragments
862 | if( overviewIsActive() || nextFragment() === false ) {
863 | slide( indexh + 1, 0 );
864 | }
865 | }
866 | function navigateUp() {
867 | // Prioritize hiding fragments
868 | if( overviewIsActive() || previousFragment() === false ) {
869 | slide( indexh, indexv - 1 );
870 | }
871 | }
872 | function navigateDown() {
873 | // Prioritize revealing fragments
874 | if( overviewIsActive() || nextFragment() === false ) {
875 | slide( indexh, indexv + 1 );
876 | }
877 | }
878 |
879 | /**
880 | * Navigates backwards, prioritized in the following order:
881 | * 1) Previous fragment
882 | * 2) Previous vertical slide
883 | * 3) Previous horizontal slide
884 | */
885 | function navigatePrev() {
886 | // Prioritize revealing fragments
887 | if( previousFragment() === false ) {
888 | if( availableRoutes().up ) {
889 | navigateUp();
890 | }
891 | else {
892 | // Fetch the previous horizontal slide, if there is one
893 | var previousSlide = document.querySelector( '.reveal .slides>section.past:nth-child(' + indexh + ')' );
894 |
895 | if( previousSlide ) {
896 | indexv = ( previousSlide.querySelectorAll('section').length + 1 ) || 0;
897 | indexh --;
898 | slide();
899 | }
900 | }
901 | }
902 | }
903 |
904 | /**
905 | * Same as #navigatePrev() but navigates forwards.
906 | */
907 | function navigateNext() {
908 | // Prioritize revealing fragments
909 | if( nextFragment() === false ) {
910 | availableRoutes().down ? navigateDown() : navigateRight();
911 | }
912 | }
913 |
914 | /**
915 | * Toggles the slide overview mode on and off.
916 | */
917 | function toggleOverview() {
918 | if( overviewIsActive() ) {
919 | deactivateOverview();
920 | }
921 | else {
922 | activateOverview();
923 | }
924 | }
925 |
926 | // Expose some methods publicly
927 | return {
928 | initialize: initialize,
929 | navigateTo: navigateTo,
930 | navigateLeft: navigateLeft,
931 | navigateRight: navigateRight,
932 | navigateUp: navigateUp,
933 | navigateDown: navigateDown,
934 | navigatePrev: navigatePrev,
935 | navigateNext: navigateNext,
936 | toggleOverview: toggleOverview,
937 |
938 | addEventListeners: addEventListeners,
939 | removeEventListeners: removeEventListeners,
940 |
941 | // Forward event binding to the reveal DOM element
942 | addEventListener: function( type, listener, useCapture ) {
943 | ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
944 | },
945 | removeEventListener: function( type, listener, useCapture ) {
946 | ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
947 | }
948 | };
949 |
950 | })();
951 |
952 |
--------------------------------------------------------------------------------
/js/reveal.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js 1.4
3 | * http://lab.hakim.se/reveal-js
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2012 Hakim El Hattab, http://hakim.se
7 | */
8 | var Reveal=(function(){var j=".reveal .slides>section",a=".reveal .slides>section.present>section",e=!!("ontouchstart" in window),k=0,c=0,H={controls:true,progress:false,history:false,loop:false,mouseWheel:true,rollingLinks:true,transition:"default",theme:"default"},T=[],d={},J=document.body.style.WebkitPerspective!==undefined||document.body.style.MozPerspective!==undefined||document.body.style.msPerspective!==undefined||document.body.style.OPerspective!==undefined||document.body.style.perspective!==undefined,l=document.body.style.WebkitTransform!==undefined||document.body.style.MozTransform!==undefined||document.body.style.msTransform!==undefined||document.body.style.OTransform!==undefined||document.body.style.transform!==undefined,x=!!document.body.classList;
9 | mouseWheelTimeout=0,writeURLTimeout=0,touch={startX:0,startY:0,startSpan:0,startCount:0,handled:false,threshold:40};function h(V){if((!l&&!J)||!x){document.body.setAttribute("class","no-transforms");
10 | return;}d.wrapper=document.querySelector(".reveal");d.progress=document.querySelector(".reveal .progress");d.progressbar=document.querySelector(".reveal .progress span");
11 | if(H.controls){d.controls=document.querySelector(".reveal .controls");d.controlsLeft=document.querySelector(".reveal .controls .left");d.controlsRight=document.querySelector(".reveal .controls .right");
12 | d.controlsUp=document.querySelector(".reveal .controls .up");d.controlsDown=document.querySelector(".reveal .controls .down");}z();q(H,V);C();D();if(navigator.userAgent.match(/(iphone|ipod|android)/i)){document.documentElement.style.overflow="scroll";
13 | document.body.style.height="120%";window.addEventListener("load",P,false);window.addEventListener("orientationchange",P,false);}}function C(){if(J===false){H.transition="linear";
14 | }if(H.controls&&d.controls){d.controls.style.display="block";}if(H.progress){d.progress.style.display="block";}if(H.transition!=="default"){d.wrapper.classList.add(H.transition);
15 | }if(H.theme!=="default"){d.wrapper.classList.add(H.theme);}if(H.mouseWheel){document.addEventListener("DOMMouseScroll",m,false);document.addEventListener("mousewheel",m,false);
16 | }if(H.rollingLinks){E();}}function z(){document.addEventListener("keydown",S,false);document.addEventListener("touchstart",v,false);document.addEventListener("touchmove",R,false);
17 | document.addEventListener("touchend",L,false);window.addEventListener("hashchange",t,false);if(H.controls&&d.controls){d.controlsLeft.addEventListener("click",n(w),false);
18 | d.controlsRight.addEventListener("click",n(i),false);d.controlsUp.addEventListener("click",n(r),false);d.controlsDown.addEventListener("click",n(A),false);
19 | }}function K(){document.removeEventListener("keydown",S,false);document.removeEventListener("touchstart",v,false);document.removeEventListener("touchmove",R,false);
20 | document.removeEventListener("touchend",L,false);window.removeEventListener("hashchange",t,false);if(H.controls&&d.controls){d.controlsLeft.removeEventListener("click",n(w),false);
21 | d.controlsRight.removeEventListener("click",n(i),false);d.controlsUp.removeEventListener("click",n(r),false);d.controlsDown.removeEventListener("click",n(A),false);
22 | }}function q(W,V){for(var X in V){W[X]=V[X];}}function I(X,V){var Y=X.x-V.x,W=X.y-V.y;return Math.sqrt(Y*Y+W*W);}function n(V){return function(W){W.preventDefault();
23 | V.call();};}function P(){setTimeout(function(){window.scrollTo(0,1);},0);}function S(W){if(W.target.contentEditable!="inherit"||W.shiftKey||W.altKey||W.ctrlKey||W.metaKey){return;
24 | }var V=false;switch(W.keyCode){case 80:case 33:N();V=true;break;case 78:case 34:u();V=true;break;case 72:case 37:w();V=true;break;case 76:case 39:i();V=true;
25 | break;case 75:case 38:r();V=true;break;case 74:case 40:A();V=true;break;case 36:F(0);V=true;break;case 35:F(Number.MAX_VALUE);V=true;break;case 32:O()?Q():u();
26 | V=true;break;case 13:if(O()){Q();V=true;}break;}if(V){W.preventDefault();}else{if(W.keyCode===27&&J){if(O()){Q();}else{B();}W.preventDefault();}}}function v(V){touch.startX=V.touches[0].clientX;
27 | touch.startY=V.touches[0].clientY;touch.startCount=V.touches.length;if(V.touches.length===2){touch.startSpan=I({x:V.touches[1].clientX,y:V.touches[1].clientY},{x:touch.startX,y:touch.startY});
28 | }}function R(aa){if(!touch.handled){var Y=aa.touches[0].clientX;var X=aa.touches[0].clientY;if(aa.touches.length===2&&touch.startCount===2){var Z=I({x:aa.touches[1].clientX,y:aa.touches[1].clientY},{x:touch.startX,y:touch.startY});
29 | if(Math.abs(touch.startSpan-Z)>touch.threshold){touch.handled=true;if(Ztouch.threshold&&Math.abs(W)>Math.abs(V)){touch.handled=true;w();}else{if(W<-touch.threshold&&Math.abs(W)>Math.abs(V)){touch.handled=true;i();}else{if(V>touch.threshold){touch.handled=true;
31 | r();}else{if(V<-touch.threshold){touch.handled=true;A();}}}}}}aa.preventDefault();}}function L(V){touch.handled=false;}function m(V){clearTimeout(mouseWheelTimeout);
32 | mouseWheelTimeout=setTimeout(function(){var W=V.detail||-V.wheelDelta;if(W>0){u();}else{N();}},100);}function t(V){D();}function E(){if(J){var W=document.querySelectorAll(".reveal .slides section a:not(.image)");
33 | for(var X=0,V=W.length;X'+Y.innerHTML+"";}}}}function B(){d.wrapper.classList.add("overview");var V=Array.prototype.slice.call(document.querySelectorAll(j));
35 | for(var aa=0,Y=V.length;aa3?"none":"block";
44 | }aa[Z].classList.remove("past");aa[Z].classList.remove("present");aa[Z].classList.remove("future");if(ZY){aa[Z].classList.add("future");
45 | }}if(V.querySelector("section")){aa[Z].classList.add("stack");}}aa[Y].classList.add("present");var X=aa[Y].getAttribute("data-state");if(X){T=T.concat(X.split(" "));
46 | }}else{Y=0;}return Y;}function b(ab,ag){var Y=T.concat();T.length=0;var af=k,W=c;k=U(j,ab===undefined?k:ab);c=U(a,ag===undefined?c:ag);stateLoop:for(var Z=0,ad=T.length;
47 | Z0,right:k0,down:c0||c>0){V+=k;}if(c>0){V+="/"+c;}window.location.hash=V;}}function o(W,V){var X=document.createEvent("HTMLEvents",1,2);
55 | X.initEvent(W,true,true);q(X,V);d.wrapper.dispatchEvent(X);}function s(){if(document.querySelector(a+".present")){var W=document.querySelectorAll(a+".present .fragment:not(.visible)");
56 | if(W.length){W[0].classList.add("visible");o("fragmentshown",{fragment:W[0]});return true;}}else{var V=document.querySelectorAll(j+".present .fragment:not(.visible)");
57 | if(V.length){V[0].classList.add("visible");o("fragmentshown",{fragment:V[0]});return true;}}return false;}function G(){if(document.querySelector(a+".present")){var W=document.querySelectorAll(a+".present .fragment.visible");
58 | if(W.length){W[W.length-1].classList.remove("visible");o("fragmenthidden",{fragment:W[0]});return true;}}else{var V=document.querySelectorAll(j+".present .fragment.visible");
59 | if(V.length){V[V.length-1].classList.remove("visible");o("fragmenthidden",{fragment:V[0]});return true;}}return false;}function F(W,V){b(W,V);}function w(){if(O()||G()===false){b(k-1,0);
60 | }}function i(){if(O()||s()===false){b(k+1,0);}}function r(){if(O()||G()===false){b(k,c-1);}}function A(){if(O()||s()===false){b(k,c+1);}}function N(){if(G()===false){if(f().up){r();
61 | }else{var V=document.querySelector(".reveal .slides>section.past:nth-child("+k+")");if(V){c=(V.querySelectorAll("section").length+1)||0;k--;b();}}}}function u(){if(s()===false){f().down?A():i();
62 | }}function M(){if(O()){Q();}else{B();}}return{initialize:h,navigateTo:F,navigateLeft:w,navigateRight:i,navigateUp:r,navigateDown:A,navigatePrev:N,navigateNext:u,toggleOverview:M,addEventListeners:z,removeEventListeners:K,addEventListener:function(W,X,V){(d.wrapper||document.querySelector(".reveal")).addEventListener(W,X,V);
63 | },removeEventListener:function(W,X,V){(d.wrapper||document.querySelector(".reveal")).removeEventListener(W,X,V);}};})();
--------------------------------------------------------------------------------
/lib/classList.js:
--------------------------------------------------------------------------------
1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
2 | if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p
4 |
5 | */
6 |
7 | pre code {
8 | display: block; padding: 0.5em;
9 | color: #000;
10 | background: #f8f8ff
11 | }
12 |
13 | pre .comment,
14 | pre .template_comment,
15 | pre .diff .header,
16 | pre .javadoc {
17 | color: #998;
18 | font-style: italic
19 | }
20 |
21 | pre .keyword,
22 | pre .css .rule .keyword,
23 | pre .winutils,
24 | pre .javascript .title,
25 | pre .lisp .title,
26 | pre .nginx .title,
27 | pre .subst,
28 | pre .request,
29 | pre .status {
30 | color: #000;
31 | font-weight: bold
32 | }
33 |
34 | pre .number,
35 | pre .hexcolor {
36 | color: #40a070
37 | }
38 |
39 | pre .string,
40 | pre .tag .value,
41 | pre .phpdoc,
42 | pre .tex .formula {
43 | color: #d14
44 | }
45 |
46 | pre .title,
47 | pre .id {
48 | color: #900;
49 | font-weight: bold
50 | }
51 |
52 | pre .javascript .title,
53 | pre .lisp .title,
54 | pre .subst {
55 | font-weight: normal
56 | }
57 |
58 | pre .class .title,
59 | pre .haskell .type,
60 | pre .vhdl .literal,
61 | pre .tex .command {
62 | color: #458;
63 | font-weight: bold
64 | }
65 |
66 | pre .tag,
67 | pre .tag .title,
68 | pre .rules .property,
69 | pre .django .tag .keyword {
70 | color: #000080;
71 | font-weight: normal
72 | }
73 |
74 | pre .attribute,
75 | pre .variable,
76 | pre .instancevar,
77 | pre .lisp .body {
78 | color: #008080
79 | }
80 |
81 | pre .regexp {
82 | color: #009926
83 | }
84 |
85 | pre .class {
86 | color: #458;
87 | font-weight: bold
88 | }
89 |
90 | pre .symbol,
91 | pre .ruby .symbol .string,
92 | pre .ruby .symbol .keyword,
93 | pre .ruby .symbol .keymethods,
94 | pre .lisp .keyword,
95 | pre .tex .special,
96 | pre .input_number {
97 | color: #990073
98 | }
99 |
100 | pre .builtin,
101 | pre .built_in,
102 | pre .lisp .title {
103 | color: #0086b3
104 | }
105 |
106 | pre .preprocessor,
107 | pre .pi,
108 | pre .doctype,
109 | pre .shebang,
110 | pre .cdata {
111 | color: #999;
112 | font-weight: bold
113 | }
114 |
115 | pre .deletion {
116 | background: #fdd
117 | }
118 |
119 | pre .addition {
120 | background: #dfd
121 | }
122 |
123 | pre .diff .change {
124 | background: #0086b3
125 | }
126 |
127 | pre .chunk {
128 | color: #aaa
129 | }
130 |
131 | pre .tex .formula {
132 | opacity: 0.5;
133 | }
134 |
--------------------------------------------------------------------------------
/lib/prism.css:
--------------------------------------------------------------------------------
1 | /**
2 | * okaidia theme for JavaScript, CSS and HTML
3 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/
4 | * @author ocodia
5 | */
6 |
7 | code[class*="language-"],
8 | pre[class*="language-"] {
9 | color: #f8f8f2;
10 | text-shadow: 0 1px rgba(0,0,0,0.3);
11 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
12 | direction: ltr;
13 | text-align: left;
14 | white-space: pre;
15 | word-spacing: normal;
16 |
17 | -moz-tab-size: 4;
18 | -o-tab-size: 4;
19 | tab-size: 4;
20 |
21 | -webkit-hyphens: none;
22 | -moz-hyphens: none;
23 | -ms-hyphens: none;
24 | hyphens: none;
25 | }
26 |
27 | /* Code blocks */
28 | pre[class*="language-"] {
29 | padding: 1em;
30 | margin: .5em 0;
31 | overflow: auto;
32 | border-radius: 0.3em;
33 | }
34 |
35 | :not(pre) > code[class*="language-"],
36 | pre[class*="language-"] {
37 | background: #272822;
38 | }
39 |
40 | /* Inline code */
41 | :not(pre) > code[class*="language-"] {
42 | padding: .1em;
43 | border-radius: .3em;
44 | }
45 |
46 | .token.comment,
47 | .token.prolog,
48 | .token.doctype,
49 | .token.cdata {
50 | color: slategray;
51 | }
52 |
53 | .token.punctuation {
54 | color: #f8f8f2;
55 | }
56 |
57 | .namespace {
58 | opacity: .7;
59 | }
60 |
61 | .token.property,
62 | .token.tag {
63 | color: #f92672;
64 | }
65 |
66 | .token.boolean,
67 | .token.number{
68 | color: #ae81ff;
69 | }
70 |
71 | .token.selector,
72 | .token.attr-name,
73 | .token.string {
74 | color: #a6e22e;
75 | }
76 |
77 |
78 | .token.operator,
79 | .token.entity,
80 | .token.url,
81 | .language-css .token.string,
82 | .style .token.string {
83 | color: #f8f8f2;
84 | }
85 |
86 | .token.atrule,
87 | .token.attr-value
88 | {
89 | color: #e6db74;
90 | }
91 |
92 |
93 | .token.keyword{
94 | color: #66d9ef;
95 | }
96 |
97 | .token.regex,
98 | .token.important {
99 | color: #fd971f;
100 | }
101 |
102 | .token.important {
103 | font-weight: bold;
104 | }
105 |
106 | .token.entity {
107 | cursor: help;
108 | }
109 |
--------------------------------------------------------------------------------
/lib/prism.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prism: Lightweight, robust, elegant syntax highlighting
3 | * MIT license http://www.opensource.org/licenses/mit-license.php/
4 | * @author Lea Verou http://lea.verou.me
5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""+s.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
6 | Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});;
7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*{))/gi,inside:{punctuation:/[;:]/g}},url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/g,property:/(\b|\B)[\w-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}});;
8 | Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};
9 | ;
10 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});
11 | ;
12 | Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|extends|private|protected|parent|static|throw|null|echo|print|trait|namespace|use|final|yield|goto|instanceof|finally|try|catch)\b/ig, constant:/\b[A-Z0-9_]{2,}\b/g});Prism.languages.insertBefore("php","keyword",{delimiter:/(\?>|<\?php|<\?)/ig,variable:/(\$\w+)\b/ig,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/g,lookbehind:!0,inside:{punctuation:/\\/}}});Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/g,lookbehind:!0}}); Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(a){"php"===a.language&&(a.tokenStack=[],a.code=a.code.replace(/(?:<\?php|<\?|<\?php|<\?)[\w\W]*?(?:\?>|\?>)/ig,function(b){a.tokenStack.push(b);return"{{{PHP"+a.tokenStack.length+"}}}"}))}),Prism.hooks.add("after-highlight",function(a){if("php"===a.language){for(var b=0,c;c=a.tokenStack[b];b++)a.highlightedCode=a.highlightedCode.replace("{{{PHP"+(b+1)+"}}}",Prism.highlight(c,a.grammar,"php"));a.element.innerHTML=a.highlightedCode}}), Prism.hooks.add("wrap",function(a){"php"===a.language&&"markup"===a.type&&(a.content=a.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g,'$1 '))}),Prism.languages.insertBefore("php","comment",{markup:{pattern:/(<|<)[^?]\/?(.*?)(>|>)/g,inside:Prism.languages.markup},php:/\{\{\{PHP[0-9]+\}\}\}/g}));;
13 | Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|\/\/.*?(\r?\n|$))/g,lookbehind:!0},atrule:/@[\w-]+(?=\s+(\(|\{|;))/gi,url:/([-a-z]+-)*url(?=\()/gi,selector:/([^@;\{\}\(\)]?([^@;\{\}\(\)]|&|\#\{\$[-_\w]+\})+)(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/gm});Prism.languages.insertBefore("scss","atrule",{keyword:/@(if|else if|else|for|each|while|import|extend|debug|warn|mixin|include|function|return)|(?=@for\s+\$[-_\w]+\s)+from/i});Prism.languages.insertBefore("scss","property",{variable:/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i});Prism.languages.insertBefore("scss","ignore",{placeholder:/%[-_\w]+/i,statement:/\B!(default|optional)\b/gi,"boolean":/\b(true|false)\b/g,"null":/\b(null)\b/g,operator:/\s+([-+]{1,2}|={1,2}|!=|\|?\||\?|\*|\/|\%)\s+/g});
14 | ;
15 | Prism.languages.bash=Prism.languages.extend("clike",{comment:{pattern:/(^|[^"{\\])(#.*?(\r?\n|$))/g,lookbehind:!0},string:{pattern:/("|')(\\?[\s\S])*?\1/g,inside:{property:/\$([a-zA-Z0-9_#\?\-\*!@]+|\{[^\}]+\})/g}},keyword:/\b(if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)\b/g});Prism.languages.insertBefore("bash","keyword",{property:/\$([a-zA-Z0-9_#\?\-\*!@]+|\{[^}]+\})/g});Prism.languages.insertBefore("bash","comment",{important:/(^#!\s*\/bin\/bash)|(^#!\s*\/bin\/sh)/g});
16 | ;
17 |
--------------------------------------------------------------------------------
/lib/zenburn.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | pre code {
9 | display: block; padding: 0.5em;
10 | background: #3F3F3F;
11 | color: #DCDCDC;
12 | }
13 |
14 | pre .keyword,
15 | pre .tag,
16 | pre .django .tag,
17 | pre .django .keyword,
18 | pre .css .class,
19 | pre .css .id,
20 | pre .lisp .title {
21 | color: #E3CEAB;
22 | }
23 |
24 | pre .django .template_tag,
25 | pre .django .variable,
26 | pre .django .filter .argument {
27 | color: #DCDCDC;
28 | }
29 |
30 | pre .number,
31 | pre .date {
32 | color: #8CD0D3;
33 | }
34 |
35 | pre .dos .envvar,
36 | pre .dos .stream,
37 | pre .variable,
38 | pre .apache .sqbracket {
39 | color: #EFDCBC;
40 | }
41 |
42 | pre .dos .flow,
43 | pre .diff .change,
44 | pre .python .exception,
45 | pre .python .built_in,
46 | pre .literal,
47 | pre .tex .special {
48 | color: #EFEFAF;
49 | }
50 |
51 | pre .diff .chunk,
52 | pre .ruby .subst {
53 | color: #8F8F8F;
54 | }
55 |
56 | pre .dos .keyword,
57 | pre .python .decorator,
58 | pre .class .title,
59 | pre .haskell .label,
60 | pre .function .title,
61 | pre .ini .title,
62 | pre .diff .header,
63 | pre .ruby .class .parent,
64 | pre .apache .tag,
65 | pre .nginx .built_in,
66 | pre .tex .command,
67 | pre .input_number {
68 | color: #efef8f;
69 | }
70 |
71 | pre .dos .winutils,
72 | pre .ruby .symbol,
73 | pre .ruby .symbol .string,
74 | pre .ruby .symbol .keyword,
75 | pre .ruby .symbol .keymethods,
76 | pre .ruby .string,
77 | pre .ruby .instancevar {
78 | color: #DCA3A3;
79 | }
80 |
81 | pre .diff .deletion,
82 | pre .string,
83 | pre .tag .value,
84 | pre .preprocessor,
85 | pre .built_in,
86 | pre .sql .aggregate,
87 | pre .javadoc,
88 | pre .smalltalk .class,
89 | pre .smalltalk .localvars,
90 | pre .smalltalk .array,
91 | pre .css .rules .value,
92 | pre .attr_selector,
93 | pre .pseudo,
94 | pre .apache .cbracket,
95 | pre .tex .formula {
96 | color: #CC9393;
97 | }
98 |
99 | pre .shebang,
100 | pre .diff .addition,
101 | pre .comment,
102 | pre .java .annotation,
103 | pre .template_comment,
104 | pre .pi,
105 | pre .doctype {
106 | color: #7F9F7F;
107 | }
108 |
109 | pre .xml .css,
110 | pre .xml .javascript,
111 | pre .xml .vbscript,
112 | pre .tex .formula {
113 | opacity: 0.5;
114 | }
115 |
116 |
--------------------------------------------------------------------------------