` in case of VML enabled browser)
31 | that wrap each of the **slider** and **picker** gradient rectangles have set `width` and `height` to `100%` which
32 | means that the color picker components (slider and picker) adjust themselfs automatically to the dimensions of the **slider** and **picker**
33 | HTML elements.
34 |
35 |
36 | API
37 | ===
38 |
39 | **`ColorPicker(colorPickerElement, function(hex, hsv, rgb) { /*do something when the color changes */ })`**
40 |
41 | This is the no-hassle form of creating the color picker. This is the recommended call.
42 |
43 | Example:
44 |
45 |
46 |
51 |
52 |
53 | **`ColorPicker.prototype.setHsv(hsv)`**
54 |
55 | Sets HSV value.
56 |
57 | Example:
58 |
59 | var cp = ColorPicker(document.getElementById('mycolorpicker'), function() {});
60 | cp.setHsv({ h: 180, s: .2, v: .7 });
61 |
62 | **`ColorPicker.prototype.setRgb(rgb)`**
63 |
64 | Sets RGB value.
65 |
66 | Example:
67 |
68 | var cp = ColorPicker(document.getElementById('mycolorpicker'), function() {});
69 | cp.setRgb({ r: 120, g: 205, b: 18 });
70 |
71 |
72 | **`ColorPicker.prototype.setHex(hex)`**
73 |
74 | Sets HEX value.
75 |
76 | Example:
77 |
78 | var cp = ColorPicker(document.getElementById('mycolorpicker'), function() {});
79 | cp.setHex('#AB12FE');
80 |
81 |
82 | **`ColorPicker.positionIndicators(sliderIndicator, pickerIndicator, sliderCoordinate, pickerCoordinate)`**
83 |
84 | Positions indicators in the slider and the picker. This is a helper function that is supposed to be called
85 | in the callback function passed as the fourth argument to the `ColorPicker` function. The only thing it does is
86 | setting the `top` and `left` CSS coordinate on the `sliderIndicator` and `pickerIndicator` HTML elements.
87 | If you use the no-hassle form (see above), you don't have to deal with this function at all.
88 |
89 | Example:
90 |
91 | ColorPicker(
92 | document.getElementById('slider'),
93 | document.getElementById('picker'),
94 |
95 | function(hex, hsv, rgb, pickerCoordinate, sliderCoordinate) {
96 |
97 | ColorPicker.positionIndicators(
98 | document.getElementById('slider-indicator'),
99 | document.getElementById('picker-indicator'),
100 | sliderCoordinate, pickerCoordinate
101 | );
102 |
103 | document.body.style.backgroundColor = hex;
104 | });
105 |
106 |
107 | **`ColorPicker.fixIndicators(sliderIndicator, pickerIndicator)`**
108 |
109 | This helper function just sets the `pointer-events` CSS property to `'none'` on both the `sliderIndicator` and
110 | `pickerIndicator`. This is necessary otherwise any mouse event (click, mousedown, mousemove) triggered
111 | on the `sliderIndicator` or `pickerIndicator` HTML elements would be cought instead of bypassed to
112 | the slider and the picker area, hence preventing the color picker to catch these UI events in order to change
113 | color. As `pointer-events` CSS property is not supported in all browsers, this function might workaround
114 | this issue in the future. At this time, setting `pointer-events: none` in CSS on the slider and picker indicators
115 | is equivalent.
116 |
117 | Again, if you use the no-hassle form (see above), you don't have to deal with this function at all.
118 |
119 |
120 | Examples
121 | ========
122 |
123 | The basic example demonstrates the minimalism of the FlexiColorPicker. More useful examples follow.
124 |
125 | Basic
126 | -----
127 |
128 |
129 |
130 |
131 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
155 |
156 |
157 |
158 |
159 | Note that you can set arbitrary dimensions, position, border and other CSS properties on the slider and picker
160 | elements as you would do with any other HTML element on the page.
161 |
162 |
163 | Advanced
164 | --------
165 |
166 | This is an advanced example showing how to work with custom indicators.
167 |
168 |
169 |
170 |
171 |
196 |
197 |
198 |
199 |
203 |
207 |
208 |
230 |
231 |
232 |
233 |
234 |
235 | Note how the indicators work. There is no built-in indicators in FlexiColorPicker, instead, the user
236 | has a freedom to set their own indicators as normal HTML elements styled in CSS (or use one of the ready-to-use themes packaged with FlexiColorPicker).
237 |
238 |
239 | No hassle
240 | ---------
241 |
242 | If you don't want to deal with any of the above mentioned details and you're just looking for a copy-paste
243 | (one function call-like) color picker, see this example.
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
267 |
268 |
269 |
270 |
271 | The ColorPicker function has recognized only two arguments which means that it builds the HTML needed for you
272 | and also fixes and positions indicators automatically.
273 |
274 |
275 | License
276 | ========
277 |
278 | FlexiColorPicker is licensed under the MIT license:
279 |
280 | Copyright (c) 2011 - 2012 David Durman
281 |
282 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
283 |
284 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
285 |
286 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
287 |
288 |
289 | [](https://bitdeli.com/free "Bitdeli Badge")
290 |
291 |
--------------------------------------------------------------------------------
/colorpicker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ColorPicker - pure JavaScript color picker without using images, external CSS or 1px divs.
3 | * Copyright © 2011 David Durman, All rights reserved.
4 | */
5 | (function(window, document, undefined) {
6 |
7 | var type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML"),
8 | picker, slide, hueOffset = 15, svgNS = 'http://www.w3.org/2000/svg';
9 |
10 | // This HTML snippet is inserted into the innerHTML property of the passed color picker element
11 | // when the no-hassle call to ColorPicker() is used, i.e. ColorPicker(function(hex, hsv, rgb) { ... });
12 |
13 | var colorpickerHTMLSnippet = [
14 |
15 | '
',
16 | '
',
17 | '
',
18 | '
',
19 | '
',
20 | '
',
21 | '
',
22 | '
'
23 |
24 | ].join('');
25 |
26 | /**
27 | * Return mouse position relative to the element el.
28 | */
29 | function mousePosition(evt) {
30 | // IE:
31 | if (window.event && window.event.contentOverflow !== undefined) {
32 | return { x: window.event.offsetX, y: window.event.offsetY };
33 | }
34 | // Webkit:
35 | if (evt.offsetX !== undefined && evt.offsetY !== undefined) {
36 | return { x: evt.offsetX, y: evt.offsetY };
37 | }
38 | // Firefox:
39 | var wrapper = evt.target.parentNode.parentNode;
40 | return { x: evt.layerX - wrapper.offsetLeft, y: evt.layerY - wrapper.offsetTop };
41 | }
42 |
43 | /**
44 | * Create SVG element.
45 | */
46 | function $(el, attrs, children) {
47 | el = document.createElementNS(svgNS, el);
48 | for (var key in attrs)
49 | el.setAttribute(key, attrs[key]);
50 | if (Object.prototype.toString.call(children) != '[object Array]') children = [children];
51 | var i = 0, len = (children[0] && children.length) || 0;
52 | for (; i < len; i++)
53 | el.appendChild(children[i]);
54 | return el;
55 | }
56 |
57 | /**
58 | * Create slide and picker markup depending on the supported technology.
59 | */
60 | if (type == 'SVG') {
61 |
62 | slide = $('svg', { xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '100%', height: '100%' },
63 | [
64 | $('defs', {},
65 | $('linearGradient', { id: 'gradient-hsv', x1: '0%', y1: '100%', x2: '0%', y2: '0%'},
66 | [
67 | $('stop', { offset: '0%', 'stop-color': '#FF0000', 'stop-opacity': '1' }),
68 | $('stop', { offset: '13%', 'stop-color': '#FF00FF', 'stop-opacity': '1' }),
69 | $('stop', { offset: '25%', 'stop-color': '#8000FF', 'stop-opacity': '1' }),
70 | $('stop', { offset: '38%', 'stop-color': '#0040FF', 'stop-opacity': '1' }),
71 | $('stop', { offset: '50%', 'stop-color': '#00FFFF', 'stop-opacity': '1' }),
72 | $('stop', { offset: '63%', 'stop-color': '#00FF40', 'stop-opacity': '1' }),
73 | $('stop', { offset: '75%', 'stop-color': '#0BED00', 'stop-opacity': '1' }),
74 | $('stop', { offset: '88%', 'stop-color': '#FFFF00', 'stop-opacity': '1' }),
75 | $('stop', { offset: '100%', 'stop-color': '#FF0000', 'stop-opacity': '1' })
76 | ]
77 | )
78 | ),
79 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-hsv)'})
80 | ]
81 | );
82 |
83 | picker = $('svg', { xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '100%', height: '100%' },
84 | [
85 | $('defs', {},
86 | [
87 | $('linearGradient', { id: 'gradient-black', x1: '0%', y1: '100%', x2: '0%', y2: '0%'},
88 | [
89 | $('stop', { offset: '0%', 'stop-color': '#000000', 'stop-opacity': '1' }),
90 | $('stop', { offset: '100%', 'stop-color': '#CC9A81', 'stop-opacity': '0' })
91 | ]
92 | ),
93 | $('linearGradient', { id: 'gradient-white', x1: '0%', y1: '100%', x2: '100%', y2: '100%'},
94 | [
95 | $('stop', { offset: '0%', 'stop-color': '#FFFFFF', 'stop-opacity': '1' }),
96 | $('stop', { offset: '100%', 'stop-color': '#CC9A81', 'stop-opacity': '0' })
97 | ]
98 | )
99 | ]
100 | ),
101 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-white)'}),
102 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-black)'})
103 | ]
104 | );
105 |
106 | } else if (type == 'VML') {
107 | slide = [
108 | '
',
109 | '',
110 | ' ',
111 | ' ',
112 | '
'
113 | ].join('');
114 |
115 | picker = [
116 | '
',
117 | '',
118 | ' ',
119 | ' ',
120 | '',
121 | ' ',
122 | ' ',
123 | '
'
124 | ].join('');
125 |
126 | if (!document.namespaces['v'])
127 | document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
128 | }
129 |
130 | /**
131 | * Convert HSV representation to RGB HEX string.
132 | * Credits to http://www.raphaeljs.com
133 | */
134 | function hsv2rgb(hsv) {
135 | var R, G, B, X, C;
136 | var h = (hsv.h % 360) / 60;
137 |
138 | C = hsv.v * hsv.s;
139 | X = C * (1 - Math.abs(h % 2 - 1));
140 | R = G = B = hsv.v - C;
141 |
142 | h = ~~h;
143 | R += [C, X, 0, 0, X, C][h];
144 | G += [X, C, C, X, 0, 0][h];
145 | B += [0, 0, X, C, C, X][h];
146 |
147 | var r = Math.floor(R * 255);
148 | var g = Math.floor(G * 255);
149 | var b = Math.floor(B * 255);
150 | return { r: r, g: g, b: b, hex: "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) };
151 | }
152 |
153 | /**
154 | * Convert RGB representation to HSV.
155 | * r, g, b can be either in <0,1> range or <0,255> range.
156 | * Credits to http://www.raphaeljs.com
157 | */
158 | function rgb2hsv(rgb) {
159 |
160 | var r = rgb.r;
161 | var g = rgb.g;
162 | var b = rgb.b;
163 |
164 | if (rgb.r > 1 || rgb.g > 1 || rgb.b > 1) {
165 | r /= 255;
166 | g /= 255;
167 | b /= 255;
168 | }
169 |
170 | var H, S, V, C;
171 | V = Math.max(r, g, b);
172 | C = V - Math.min(r, g, b);
173 | H = (C == 0 ? null :
174 | V == r ? (g - b) / C + (g < b ? 6 : 0) :
175 | V == g ? (b - r) / C + 2 :
176 | (r - g) / C + 4);
177 | H = (H % 6) * 60;
178 | S = C == 0 ? 0 : C / V;
179 | return { h: H, s: S, v: V };
180 | }
181 |
182 | /**
183 | * Return click event handler for the slider.
184 | * Sets picker background color and calls ctx.callback if provided.
185 | */
186 | function slideListener(ctx, slideElement, pickerElement) {
187 | return function(evt) {
188 | evt = evt || window.event;
189 | var mouse = mousePosition(evt);
190 | ctx.h = mouse.y / slideElement.offsetHeight * 360 + hueOffset;
191 | var pickerColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 });
192 | var c = hsv2rgb({ h: ctx.h, s: ctx.s, v: ctx.v });
193 | pickerElement.style.backgroundColor = pickerColor.hex;
194 | ctx.callback && ctx.callback(c.hex, { h: ctx.h - hueOffset, s: ctx.s, v: ctx.v }, { r: c.r, g: c.g, b: c.b }, undefined, mouse);
195 | }
196 | };
197 |
198 | /**
199 | * Return click event handler for the picker.
200 | * Calls ctx.callback if provided.
201 | */
202 | function pickerListener(ctx, pickerElement) {
203 | return function(evt) {
204 | evt = evt || window.event;
205 | var mouse = mousePosition(evt),
206 | width = pickerElement.offsetWidth,
207 | height = pickerElement.offsetHeight;
208 |
209 | ctx.s = mouse.x / width;
210 | ctx.v = (height - mouse.y) / height;
211 | var c = hsv2rgb(ctx);
212 | ctx.callback && ctx.callback(c.hex, { h: ctx.h - hueOffset, s: ctx.s, v: ctx.v }, { r: c.r, g: c.g, b: c.b }, mouse);
213 | }
214 | };
215 |
216 | var uniqID = 0;
217 |
218 | /**
219 | * ColorPicker.
220 | * @param {DOMElement} slideElement HSV slide element.
221 | * @param {DOMElement} pickerElement HSV picker element.
222 | * @param {Function} callback Called whenever the color is changed provided chosen color in RGB HEX format as the only argument.
223 | */
224 | function ColorPicker(slideElement, pickerElement, callback) {
225 |
226 | if (!(this instanceof ColorPicker)) return new ColorPicker(slideElement, pickerElement, callback);
227 |
228 | this.h = 0;
229 | this.s = 1;
230 | this.v = 1;
231 |
232 | if (!callback) {
233 | // call of the form ColorPicker(element, funtion(hex, hsv, rgb) { ... }), i.e. the no-hassle call.
234 |
235 | var element = slideElement;
236 | element.innerHTML = colorpickerHTMLSnippet;
237 |
238 | this.slideElement = element.getElementsByClassName('slide')[0];
239 | this.pickerElement = element.getElementsByClassName('picker')[0];
240 | var slideIndicator = element.getElementsByClassName('slide-indicator')[0];
241 | var pickerIndicator = element.getElementsByClassName('picker-indicator')[0];
242 |
243 | ColorPicker.fixIndicators(slideIndicator, pickerIndicator);
244 |
245 | this.callback = function(hex, hsv, rgb, pickerCoordinate, slideCoordinate) {
246 |
247 | ColorPicker.positionIndicators(slideIndicator, pickerIndicator, slideCoordinate, pickerCoordinate);
248 |
249 | pickerElement(hex, hsv, rgb);
250 | };
251 |
252 | } else {
253 |
254 | this.callback = callback;
255 | this.pickerElement = pickerElement;
256 | this.slideElement = slideElement;
257 | }
258 |
259 | if (type == 'SVG') {
260 |
261 | // Generate uniq IDs for linearGradients so that we don't have the same IDs within one document.
262 | // Then reference those gradients in the associated rectangles.
263 |
264 | var slideClone = slide.cloneNode(true);
265 | var pickerClone = picker.cloneNode(true);
266 |
267 | var hsvGradient = slideClone.getElementsByTagName('linearGradient')[0];
268 |
269 | var hsvRect = slideClone.getElementsByTagName('rect')[0];
270 |
271 | hsvGradient.id = 'gradient-hsv-' + uniqID;
272 | hsvRect.setAttribute('fill', 'url(#' + hsvGradient.id + ')');
273 |
274 | var blackAndWhiteGradients = [pickerClone.getElementsByTagName('linearGradient')[0], pickerClone.getElementsByTagName('linearGradient')[1]];
275 | var whiteAndBlackRects = pickerClone.getElementsByTagName('rect');
276 |
277 | blackAndWhiteGradients[0].id = 'gradient-black-' + uniqID;
278 | blackAndWhiteGradients[1].id = 'gradient-white-' + uniqID;
279 |
280 | whiteAndBlackRects[0].setAttribute('fill', 'url(#' + blackAndWhiteGradients[1].id + ')');
281 | whiteAndBlackRects[1].setAttribute('fill', 'url(#' + blackAndWhiteGradients[0].id + ')');
282 |
283 | this.slideElement.appendChild(slideClone);
284 | this.pickerElement.appendChild(pickerClone);
285 |
286 | uniqID++;
287 |
288 | } else {
289 |
290 | this.slideElement.innerHTML = slide;
291 | this.pickerElement.innerHTML = picker;
292 | }
293 |
294 | addEventListener(this.slideElement, 'click', slideListener(this, this.slideElement, this.pickerElement));
295 | addEventListener(this.pickerElement, 'click', pickerListener(this, this.pickerElement));
296 |
297 | enableDragging(this, this.slideElement, slideListener(this, this.slideElement, this.pickerElement));
298 | enableDragging(this, this.pickerElement, pickerListener(this, this.pickerElement));
299 | };
300 |
301 | function addEventListener(element, event, listener) {
302 |
303 | if (element.attachEvent) {
304 |
305 | element.attachEvent('on' + event, listener);
306 |
307 | } else if (element.addEventListener) {
308 |
309 | element.addEventListener(event, listener, false);
310 | }
311 | }
312 |
313 | /**
314 | * Enable drag&drop color selection.
315 | * @param {object} ctx ColorPicker instance.
316 | * @param {DOMElement} element HSV slide element or HSV picker element.
317 | * @param {Function} listener Function that will be called whenever mouse is dragged over the element with event object as argument.
318 | */
319 | function enableDragging(ctx, element, listener) {
320 |
321 | var mousedown = false;
322 |
323 | addEventListener(element, 'mousedown', function(evt) { mousedown = true; });
324 | addEventListener(element, 'mouseup', function(evt) { mousedown = false; });
325 | addEventListener(element, 'mouseout', function(evt) { mousedown = false; });
326 | addEventListener(element, 'mousemove', function(evt) {
327 |
328 | if (mousedown) {
329 |
330 | listener(evt);
331 | }
332 | });
333 | }
334 |
335 |
336 | ColorPicker.hsv2rgb = function(hsv) {
337 | var rgbHex = hsv2rgb(hsv);
338 | delete rgbHex.hex;
339 | return rgbHex;
340 | };
341 |
342 | ColorPicker.hsv2hex = function(hsv) {
343 | return hsv2rgb(hsv).hex;
344 | };
345 |
346 | ColorPicker.rgb2hsv = rgb2hsv;
347 |
348 | ColorPicker.rgb2hex = function(rgb) {
349 | return hsv2rgb(rgb2hsv(rgb)).hex;
350 | };
351 |
352 | ColorPicker.hex2hsv = function(hex) {
353 | return rgb2hsv(ColorPicker.hex2rgb(hex));
354 | };
355 |
356 | ColorPicker.hex2rgb = function(hex) {
357 | return { r: parseInt(hex.substr(1, 2), 16), g: parseInt(hex.substr(3, 2), 16), b: parseInt(hex.substr(5, 2), 16) };
358 | };
359 |
360 | /**
361 | * Sets color of the picker in hsv/rgb/hex format.
362 | * @param {object} ctx ColorPicker instance.
363 | * @param {object} hsv Object of the form: { h:
, s: , v: }.
364 | * @param {object} rgb Object of the form: { r: , g: , b: }.
365 | * @param {string} hex String of the form: #RRGGBB.
366 | */
367 | function setColor(ctx, hsv, rgb, hex) {
368 | ctx.h = hsv.h % 360;
369 | ctx.s = hsv.s;
370 | ctx.v = hsv.v;
371 |
372 | var c = hsv2rgb(ctx);
373 |
374 | var mouseSlide = {
375 | y: (ctx.h * ctx.slideElement.offsetHeight) / 360,
376 | x: 0 // not important
377 | };
378 |
379 | var pickerHeight = ctx.pickerElement.offsetHeight;
380 |
381 | var mousePicker = {
382 | x: ctx.s * ctx.pickerElement.offsetWidth,
383 | y: pickerHeight - ctx.v * pickerHeight
384 | };
385 |
386 | ctx.pickerElement.style.backgroundColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 }).hex;
387 | ctx.callback && ctx.callback(hex || c.hex, { h: ctx.h, s: ctx.s, v: ctx.v }, rgb || { r: c.r, g: c.g, b: c.b }, mousePicker, mouseSlide);
388 |
389 | return ctx;
390 | };
391 |
392 | /**
393 | * Sets color of the picker in hsv format.
394 | * @param {object} hsv Object of the form: { h: , s: , v: }.
395 | */
396 | ColorPicker.prototype.setHsv = function(hsv) {
397 | return setColor(this, hsv);
398 | };
399 |
400 | /**
401 | * Sets color of the picker in rgb format.
402 | * @param {object} rgb Object of the form: { r: , g: , b: }.
403 | */
404 | ColorPicker.prototype.setRgb = function(rgb) {
405 | return setColor(this, rgb2hsv(rgb), rgb);
406 | };
407 |
408 | /**
409 | * Sets color of the picker in hex format.
410 | * @param {string} hex Hex color format #RRGGBB.
411 | */
412 | ColorPicker.prototype.setHex = function(hex) {
413 | return setColor(this, ColorPicker.hex2hsv(hex), undefined, hex);
414 | };
415 |
416 | /**
417 | * Helper to position indicators.
418 | * @param {HTMLElement} slideIndicator DOM element representing the indicator of the slide area.
419 | * @param {HTMLElement} pickerIndicator DOM element representing the indicator of the picker area.
420 | * @param {object} mouseSlide Coordinates of the mouse cursor in the slide area.
421 | * @param {object} mousePicker Coordinates of the mouse cursor in the picker area.
422 | */
423 | ColorPicker.positionIndicators = function(slideIndicator, pickerIndicator, mouseSlide, mousePicker) {
424 |
425 | if (mouseSlide) {
426 | slideIndicator.style.top = (mouseSlide.y - slideIndicator.offsetHeight/2) + 'px';
427 | }
428 | if (mousePicker) {
429 | pickerIndicator.style.top = (mousePicker.y - pickerIndicator.offsetHeight/2) + 'px';
430 | pickerIndicator.style.left = (mousePicker.x - pickerIndicator.offsetWidth/2) + 'px';
431 | }
432 | };
433 |
434 | /**
435 | * Helper to fix indicators - this is recommended (and needed) for dragable color selection (see enabledDragging()).
436 | */
437 | ColorPicker.fixIndicators = function(slideIndicator, pickerIndicator) {
438 |
439 | pickerIndicator.style.pointerEvents = 'none';
440 | slideIndicator.style.pointerEvents = 'none';
441 | };
442 |
443 | window.ColorPicker = ColorPicker;
444 |
445 | })(window, window.document);
446 |
--------------------------------------------------------------------------------
/test/qunit/qunit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.10.0pre - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright 2012 jQuery Foundation and other contributors
7 | * Dual licensed under the MIT or GPL Version 2 licenses.
8 | * http://jquery.org/license
9 | */
10 |
11 | (function( window ) {
12 |
13 | var QUnit,
14 | config,
15 | onErrorFnPrev,
16 | testId = 0,
17 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
18 | toString = Object.prototype.toString,
19 | hasOwn = Object.prototype.hasOwnProperty,
20 | // Keep a local reference to Date (GH-283)
21 | Date = window.Date,
22 | defined = {
23 | setTimeout: typeof window.setTimeout !== "undefined",
24 | sessionStorage: (function() {
25 | var x = "qunit-test-string";
26 | try {
27 | sessionStorage.setItem( x, x );
28 | sessionStorage.removeItem( x );
29 | return true;
30 | } catch( e ) {
31 | return false;
32 | }
33 | }())
34 | };
35 |
36 | function Test( settings ) {
37 | extend( this, settings );
38 | this.assertions = [];
39 | this.testNumber = ++Test.count;
40 | }
41 |
42 | Test.count = 0;
43 |
44 | Test.prototype = {
45 | init: function() {
46 | var a, b, li,
47 | tests = id( "qunit-tests" );
48 |
49 | if ( tests ) {
50 | b = document.createElement( "strong" );
51 | b.innerHTML = this.name;
52 |
53 | // `a` initialized at top of scope
54 | a = document.createElement( "a" );
55 | a.innerHTML = "Rerun";
56 | a.href = QUnit.url({ testNumber: this.testNumber });
57 |
58 | li = document.createElement( "li" );
59 | li.appendChild( b );
60 | li.appendChild( a );
61 | li.className = "running";
62 | li.id = this.id = "qunit-test-output" + testId++;
63 |
64 | tests.appendChild( li );
65 | }
66 | },
67 | setup: function() {
68 | if ( this.module !== config.previousModule ) {
69 | if ( config.previousModule ) {
70 | runLoggingCallbacks( "moduleDone", QUnit, {
71 | name: config.previousModule,
72 | failed: config.moduleStats.bad,
73 | passed: config.moduleStats.all - config.moduleStats.bad,
74 | total: config.moduleStats.all
75 | });
76 | }
77 | config.previousModule = this.module;
78 | config.moduleStats = { all: 0, bad: 0 };
79 | runLoggingCallbacks( "moduleStart", QUnit, {
80 | name: this.module
81 | });
82 | } else if ( config.autorun ) {
83 | runLoggingCallbacks( "moduleStart", QUnit, {
84 | name: this.module
85 | });
86 | }
87 |
88 | config.current = this;
89 |
90 | this.testEnvironment = extend({
91 | setup: function() {},
92 | teardown: function() {}
93 | }, this.moduleTestEnvironment );
94 |
95 | runLoggingCallbacks( "testStart", QUnit, {
96 | name: this.testName,
97 | module: this.module
98 | });
99 |
100 | // allow utility functions to access the current test environment
101 | // TODO why??
102 | QUnit.current_testEnvironment = this.testEnvironment;
103 |
104 | if ( !config.pollution ) {
105 | saveGlobal();
106 | }
107 | if ( config.notrycatch ) {
108 | this.testEnvironment.setup.call( this.testEnvironment );
109 | return;
110 | }
111 | try {
112 | this.testEnvironment.setup.call( this.testEnvironment );
113 | } catch( e ) {
114 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
115 | }
116 | },
117 | run: function() {
118 | config.current = this;
119 |
120 | var running = id( "qunit-testresult" );
121 |
122 | if ( running ) {
123 | running.innerHTML = "Running: " + this.name;
124 | }
125 |
126 | if ( this.async ) {
127 | QUnit.stop();
128 | }
129 |
130 | if ( config.notrycatch ) {
131 | this.callback.call( this.testEnvironment, QUnit.assert );
132 | return;
133 | }
134 |
135 | try {
136 | this.callback.call( this.testEnvironment, QUnit.assert );
137 | } catch( e ) {
138 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
139 | // else next test will carry the responsibility
140 | saveGlobal();
141 |
142 | // Restart the tests if they're blocking
143 | if ( config.blocking ) {
144 | QUnit.start();
145 | }
146 | }
147 | },
148 | teardown: function() {
149 | config.current = this;
150 | if ( config.notrycatch ) {
151 | this.testEnvironment.teardown.call( this.testEnvironment );
152 | return;
153 | } else {
154 | try {
155 | this.testEnvironment.teardown.call( this.testEnvironment );
156 | } catch( e ) {
157 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
158 | }
159 | }
160 | checkPollution();
161 | },
162 | finish: function() {
163 | config.current = this;
164 | if ( config.requireExpects && this.expected == null ) {
165 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
166 | } else if ( this.expected != null && this.expected != this.assertions.length ) {
167 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
168 | } else if ( this.expected == null && !this.assertions.length ) {
169 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
170 | }
171 |
172 | var assertion, a, b, i, li, ol,
173 | test = this,
174 | good = 0,
175 | bad = 0,
176 | tests = id( "qunit-tests" );
177 |
178 | config.stats.all += this.assertions.length;
179 | config.moduleStats.all += this.assertions.length;
180 |
181 | if ( tests ) {
182 | ol = document.createElement( "ol" );
183 |
184 | for ( i = 0; i < this.assertions.length; i++ ) {
185 | assertion = this.assertions[i];
186 |
187 | li = document.createElement( "li" );
188 | li.className = assertion.result ? "pass" : "fail";
189 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
190 | ol.appendChild( li );
191 |
192 | if ( assertion.result ) {
193 | good++;
194 | } else {
195 | bad++;
196 | config.stats.bad++;
197 | config.moduleStats.bad++;
198 | }
199 | }
200 |
201 | // store result when possible
202 | if ( QUnit.config.reorder && defined.sessionStorage ) {
203 | if ( bad ) {
204 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
205 | } else {
206 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
207 | }
208 | }
209 |
210 | if ( bad === 0 ) {
211 | ol.style.display = "none";
212 | }
213 |
214 | // `b` initialized at top of scope
215 | b = document.createElement( "strong" );
216 | b.innerHTML = this.name + " (" + bad + " , " + good + " , " + this.assertions.length + ") ";
217 |
218 | addEvent(b, "click", function() {
219 | var next = b.nextSibling.nextSibling,
220 | display = next.style.display;
221 | next.style.display = display === "none" ? "block" : "none";
222 | });
223 |
224 | addEvent(b, "dblclick", function( e ) {
225 | var target = e && e.target ? e.target : window.event.srcElement;
226 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
227 | target = target.parentNode;
228 | }
229 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
230 | window.location = QUnit.url({ testNumber: test.testNumber });
231 | }
232 | });
233 |
234 | // `li` initialized at top of scope
235 | li = id( this.id );
236 | li.className = bad ? "fail" : "pass";
237 | li.removeChild( li.firstChild );
238 | a = li.firstChild;
239 | li.appendChild( b );
240 | li.appendChild ( a );
241 | li.appendChild( ol );
242 |
243 | } else {
244 | for ( i = 0; i < this.assertions.length; i++ ) {
245 | if ( !this.assertions[i].result ) {
246 | bad++;
247 | config.stats.bad++;
248 | config.moduleStats.bad++;
249 | }
250 | }
251 | }
252 |
253 | runLoggingCallbacks( "testDone", QUnit, {
254 | name: this.testName,
255 | module: this.module,
256 | failed: bad,
257 | passed: this.assertions.length - bad,
258 | total: this.assertions.length
259 | });
260 |
261 | QUnit.reset();
262 |
263 | config.current = undefined;
264 | },
265 |
266 | queue: function() {
267 | var bad,
268 | test = this;
269 |
270 | synchronize(function() {
271 | test.init();
272 | });
273 | function run() {
274 | // each of these can by async
275 | synchronize(function() {
276 | test.setup();
277 | });
278 | synchronize(function() {
279 | test.run();
280 | });
281 | synchronize(function() {
282 | test.teardown();
283 | });
284 | synchronize(function() {
285 | test.finish();
286 | });
287 | }
288 |
289 | // `bad` initialized at top of scope
290 | // defer when previous test run passed, if storage is available
291 | bad = QUnit.config.reorder && defined.sessionStorage &&
292 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
293 |
294 | if ( bad ) {
295 | run();
296 | } else {
297 | synchronize( run, true );
298 | }
299 | }
300 | };
301 |
302 | // Root QUnit object.
303 | // `QUnit` initialized at top of scope
304 | QUnit = {
305 |
306 | // call on start of module test to prepend name to all tests
307 | module: function( name, testEnvironment ) {
308 | config.currentModule = name;
309 | config.currentModuleTestEnviroment = testEnvironment;
310 | },
311 |
312 | asyncTest: function( testName, expected, callback ) {
313 | if ( arguments.length === 2 ) {
314 | callback = expected;
315 | expected = null;
316 | }
317 |
318 | QUnit.test( testName, expected, callback, true );
319 | },
320 |
321 | test: function( testName, expected, callback, async ) {
322 | var test,
323 | name = "" + escapeInnerText( testName ) + " ";
324 |
325 | if ( arguments.length === 2 ) {
326 | callback = expected;
327 | expected = null;
328 | }
329 |
330 | if ( config.currentModule ) {
331 | name = "" + config.currentModule + " : " + name;
332 | }
333 |
334 | test = new Test({
335 | name: name,
336 | testName: testName,
337 | expected: expected,
338 | async: async,
339 | callback: callback,
340 | module: config.currentModule,
341 | moduleTestEnvironment: config.currentModuleTestEnviroment,
342 | stack: sourceFromStacktrace( 2 )
343 | });
344 |
345 | if ( !validTest( test ) ) {
346 | return;
347 | }
348 |
349 | test.queue();
350 | },
351 |
352 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
353 | expect: function( asserts ) {
354 | config.current.expected = asserts;
355 | },
356 |
357 | start: function( count ) {
358 | config.semaphore -= count || 1;
359 | // don't start until equal number of stop-calls
360 | if ( config.semaphore > 0 ) {
361 | return;
362 | }
363 | // ignore if start is called more often then stop
364 | if ( config.semaphore < 0 ) {
365 | config.semaphore = 0;
366 | }
367 | // A slight delay, to avoid any current callbacks
368 | if ( defined.setTimeout ) {
369 | window.setTimeout(function() {
370 | if ( config.semaphore > 0 ) {
371 | return;
372 | }
373 | if ( config.timeout ) {
374 | clearTimeout( config.timeout );
375 | }
376 |
377 | config.blocking = false;
378 | process( true );
379 | }, 13);
380 | } else {
381 | config.blocking = false;
382 | process( true );
383 | }
384 | },
385 |
386 | stop: function( count ) {
387 | config.semaphore += count || 1;
388 | config.blocking = true;
389 |
390 | if ( config.testTimeout && defined.setTimeout ) {
391 | clearTimeout( config.timeout );
392 | config.timeout = window.setTimeout(function() {
393 | QUnit.ok( false, "Test timed out" );
394 | config.semaphore = 1;
395 | QUnit.start();
396 | }, config.testTimeout );
397 | }
398 | }
399 | };
400 |
401 | // Asssert helpers
402 | // All of these must call either QUnit.push() or manually do:
403 | // - runLoggingCallbacks( "log", .. );
404 | // - config.current.assertions.push({ .. });
405 | QUnit.assert = {
406 | /**
407 | * Asserts rough true-ish result.
408 | * @name ok
409 | * @function
410 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
411 | */
412 | ok: function( result, msg ) {
413 | if ( !config.current ) {
414 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
415 | }
416 | result = !!result;
417 |
418 | var source,
419 | details = {
420 | result: result,
421 | message: msg
422 | };
423 |
424 | msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
425 | msg = "" + msg + " ";
426 |
427 | if ( !result ) {
428 | source = sourceFromStacktrace( 2 );
429 | if ( source ) {
430 | details.source = source;
431 | msg += "Source: " + escapeInnerText( source ) + "
";
432 | }
433 | }
434 | runLoggingCallbacks( "log", QUnit, details );
435 | config.current.assertions.push({
436 | result: result,
437 | message: msg
438 | });
439 | },
440 |
441 | /**
442 | * Assert that the first two arguments are equal, with an optional message.
443 | * Prints out both actual and expected values.
444 | * @name equal
445 | * @function
446 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
447 | */
448 | equal: function( actual, expected, message ) {
449 | QUnit.push( expected == actual, actual, expected, message );
450 | },
451 |
452 | /**
453 | * @name notEqual
454 | * @function
455 | */
456 | notEqual: function( actual, expected, message ) {
457 | QUnit.push( expected != actual, actual, expected, message );
458 | },
459 |
460 | /**
461 | * @name deepEqual
462 | * @function
463 | */
464 | deepEqual: function( actual, expected, message ) {
465 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
466 | },
467 |
468 | /**
469 | * @name notDeepEqual
470 | * @function
471 | */
472 | notDeepEqual: function( actual, expected, message ) {
473 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
474 | },
475 |
476 | /**
477 | * @name strictEqual
478 | * @function
479 | */
480 | strictEqual: function( actual, expected, message ) {
481 | QUnit.push( expected === actual, actual, expected, message );
482 | },
483 |
484 | /**
485 | * @name notStrictEqual
486 | * @function
487 | */
488 | notStrictEqual: function( actual, expected, message ) {
489 | QUnit.push( expected !== actual, actual, expected, message );
490 | },
491 |
492 | throws: function( block, expected, message ) {
493 | var actual,
494 | ok = false;
495 |
496 | // 'expected' is optional
497 | if ( typeof expected === "string" ) {
498 | message = expected;
499 | expected = null;
500 | }
501 |
502 | config.current.ignoreGlobalErrors = true;
503 | try {
504 | block.call( config.current.testEnvironment );
505 | } catch (e) {
506 | actual = e;
507 | }
508 | config.current.ignoreGlobalErrors = false;
509 |
510 | if ( actual ) {
511 | // we don't want to validate thrown error
512 | if ( !expected ) {
513 | ok = true;
514 | // expected is a regexp
515 | } else if ( QUnit.objectType( expected ) === "regexp" ) {
516 | ok = expected.test( actual );
517 | // expected is a constructor
518 | } else if ( actual instanceof expected ) {
519 | ok = true;
520 | // expected is a validation function which returns true is validation passed
521 | } else if ( expected.call( {}, actual ) === true ) {
522 | ok = true;
523 | }
524 |
525 | QUnit.push( ok, actual, null, message );
526 | } else {
527 | QUnit.pushFailure( message, null, 'No exception was thrown.' );
528 | }
529 | }
530 | };
531 |
532 | /**
533 | * @deprecate since 1.8.0
534 | * Kept assertion helpers in root for backwards compatibility
535 | */
536 | extend( QUnit, QUnit.assert );
537 |
538 | /**
539 | * @deprecated since 1.9.0
540 | * Kept global "raises()" for backwards compatibility
541 | */
542 | QUnit.raises = QUnit.assert.throws;
543 |
544 | /**
545 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
546 | * Kept to avoid TypeErrors for undefined methods.
547 | */
548 | QUnit.equals = function() {
549 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
550 | };
551 | QUnit.same = function() {
552 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
553 | };
554 |
555 | // We want access to the constructor's prototype
556 | (function() {
557 | function F() {}
558 | F.prototype = QUnit;
559 | QUnit = new F();
560 | // Make F QUnit's constructor so that we can add to the prototype later
561 | QUnit.constructor = F;
562 | }());
563 |
564 | /**
565 | * Config object: Maintain internal state
566 | * Later exposed as QUnit.config
567 | * `config` initialized at top of scope
568 | */
569 | config = {
570 | // The queue of tests to run
571 | queue: [],
572 |
573 | // block until document ready
574 | blocking: true,
575 |
576 | // when enabled, show only failing tests
577 | // gets persisted through sessionStorage and can be changed in UI via checkbox
578 | hidepassed: false,
579 |
580 | // by default, run previously failed tests first
581 | // very useful in combination with "Hide passed tests" checked
582 | reorder: true,
583 |
584 | // by default, modify document.title when suite is done
585 | altertitle: true,
586 |
587 | // when enabled, all tests must call expect()
588 | requireExpects: false,
589 |
590 | // add checkboxes that are persisted in the query-string
591 | // when enabled, the id is set to `true` as a `QUnit.config` property
592 | urlConfig: [
593 | {
594 | id: "noglobals",
595 | label: "Check for Globals",
596 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
597 | },
598 | {
599 | id: "notrycatch",
600 | label: "No try-catch",
601 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
602 | }
603 | ],
604 |
605 | // logging callback queues
606 | begin: [],
607 | done: [],
608 | log: [],
609 | testStart: [],
610 | testDone: [],
611 | moduleStart: [],
612 | moduleDone: []
613 | };
614 |
615 | // Initialize more QUnit.config and QUnit.urlParams
616 | (function() {
617 | var i,
618 | location = window.location || { search: "", protocol: "file:" },
619 | params = location.search.slice( 1 ).split( "&" ),
620 | length = params.length,
621 | urlParams = {},
622 | current;
623 |
624 | if ( params[ 0 ] ) {
625 | for ( i = 0; i < length; i++ ) {
626 | current = params[ i ].split( "=" );
627 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
628 | // allow just a key to turn on a flag, e.g., test.html?noglobals
629 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
630 | urlParams[ current[ 0 ] ] = current[ 1 ];
631 | }
632 | }
633 |
634 | QUnit.urlParams = urlParams;
635 |
636 | // String search anywhere in moduleName+testName
637 | config.filter = urlParams.filter;
638 |
639 | // Exact match of the module name
640 | config.module = urlParams.module;
641 |
642 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
643 |
644 | // Figure out if we're running the tests from a server or not
645 | QUnit.isLocal = location.protocol === "file:";
646 | }());
647 |
648 | // Export global variables, unless an 'exports' object exists,
649 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script)
650 | if ( typeof exports === "undefined" ) {
651 | extend( window, QUnit );
652 |
653 | // Expose QUnit object
654 | window.QUnit = QUnit;
655 | }
656 |
657 | // Extend QUnit object,
658 | // these after set here because they should not be exposed as global functions
659 | extend( QUnit, {
660 | config: config,
661 |
662 | // Initialize the configuration options
663 | init: function() {
664 | extend( config, {
665 | stats: { all: 0, bad: 0 },
666 | moduleStats: { all: 0, bad: 0 },
667 | started: +new Date(),
668 | updateRate: 1000,
669 | blocking: false,
670 | autostart: true,
671 | autorun: false,
672 | filter: "",
673 | queue: [],
674 | semaphore: 0
675 | });
676 |
677 | var tests, banner, result,
678 | qunit = id( "qunit" );
679 |
680 | if ( qunit ) {
681 | qunit.innerHTML =
682 | "" +
683 | " " +
684 | "
" +
685 | " " +
686 | " ";
687 | }
688 |
689 | tests = id( "qunit-tests" );
690 | banner = id( "qunit-banner" );
691 | result = id( "qunit-testresult" );
692 |
693 | if ( tests ) {
694 | tests.innerHTML = "";
695 | }
696 |
697 | if ( banner ) {
698 | banner.className = "";
699 | }
700 |
701 | if ( result ) {
702 | result.parentNode.removeChild( result );
703 | }
704 |
705 | if ( tests ) {
706 | result = document.createElement( "p" );
707 | result.id = "qunit-testresult";
708 | result.className = "result";
709 | tests.parentNode.insertBefore( result, tests );
710 | result.innerHTML = "Running... ";
711 | }
712 | },
713 |
714 | // Resets the test setup. Useful for tests that modify the DOM.
715 | // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
716 | reset: function() {
717 | var fixture;
718 |
719 | if ( window.jQuery ) {
720 | jQuery( "#qunit-fixture" ).html( config.fixture );
721 | } else {
722 | fixture = id( "qunit-fixture" );
723 | if ( fixture ) {
724 | fixture.innerHTML = config.fixture;
725 | }
726 | }
727 | },
728 |
729 | // Trigger an event on an element.
730 | // @example triggerEvent( document.body, "click" );
731 | triggerEvent: function( elem, type, event ) {
732 | if ( document.createEvent ) {
733 | event = document.createEvent( "MouseEvents" );
734 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
735 | 0, 0, 0, 0, 0, false, false, false, false, 0, null);
736 |
737 | elem.dispatchEvent( event );
738 | } else if ( elem.fireEvent ) {
739 | elem.fireEvent( "on" + type );
740 | }
741 | },
742 |
743 | // Safe object type checking
744 | is: function( type, obj ) {
745 | return QUnit.objectType( obj ) == type;
746 | },
747 |
748 | objectType: function( obj ) {
749 | if ( typeof obj === "undefined" ) {
750 | return "undefined";
751 | // consider: typeof null === object
752 | }
753 | if ( obj === null ) {
754 | return "null";
755 | }
756 |
757 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
758 |
759 | switch ( type ) {
760 | case "Number":
761 | if ( isNaN(obj) ) {
762 | return "nan";
763 | }
764 | return "number";
765 | case "String":
766 | case "Boolean":
767 | case "Array":
768 | case "Date":
769 | case "RegExp":
770 | case "Function":
771 | return type.toLowerCase();
772 | }
773 | if ( typeof obj === "object" ) {
774 | return "object";
775 | }
776 | return undefined;
777 | },
778 |
779 | push: function( result, actual, expected, message ) {
780 | if ( !config.current ) {
781 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
782 | }
783 |
784 | var output, source,
785 | details = {
786 | result: result,
787 | message: message,
788 | actual: actual,
789 | expected: expected
790 | };
791 |
792 | message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
793 | message = "" + message + " ";
794 | output = message;
795 |
796 | if ( !result ) {
797 | expected = escapeInnerText( QUnit.jsDump.parse(expected) );
798 | actual = escapeInnerText( QUnit.jsDump.parse(actual) );
799 | output += "Expected: " + expected + " ";
800 |
801 | if ( actual != expected ) {
802 | output += "Result: " + actual + " ";
803 | output += "Diff: " + QUnit.diff( expected, actual ) + " ";
804 | }
805 |
806 | source = sourceFromStacktrace();
807 |
808 | if ( source ) {
809 | details.source = source;
810 | output += "Source: " + escapeInnerText( source ) + " ";
811 | }
812 |
813 | output += "
";
814 | }
815 |
816 | runLoggingCallbacks( "log", QUnit, details );
817 |
818 | config.current.assertions.push({
819 | result: !!result,
820 | message: output
821 | });
822 | },
823 |
824 | pushFailure: function( message, source, actual ) {
825 | if ( !config.current ) {
826 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
827 | }
828 |
829 | var output,
830 | details = {
831 | result: false,
832 | message: message
833 | };
834 |
835 | message = escapeInnerText( message ) || "error";
836 | message = "" + message + " ";
837 | output = message;
838 |
839 | output += "";
840 |
841 | if ( actual ) {
842 | output += "Result: " + escapeInnerText( actual ) + " ";
843 | }
844 |
845 | if ( source ) {
846 | details.source = source;
847 | output += "Source: " + escapeInnerText( source ) + " ";
848 | }
849 |
850 | output += "
";
851 |
852 | runLoggingCallbacks( "log", QUnit, details );
853 |
854 | config.current.assertions.push({
855 | result: false,
856 | message: output
857 | });
858 | },
859 |
860 | url: function( params ) {
861 | params = extend( extend( {}, QUnit.urlParams ), params );
862 | var key,
863 | querystring = "?";
864 |
865 | for ( key in params ) {
866 | if ( !hasOwn.call( params, key ) ) {
867 | continue;
868 | }
869 | querystring += encodeURIComponent( key ) + "=" +
870 | encodeURIComponent( params[ key ] ) + "&";
871 | }
872 | return window.location.pathname + querystring.slice( 0, -1 );
873 | },
874 |
875 | extend: extend,
876 | id: id,
877 | addEvent: addEvent
878 | // load, equiv, jsDump, diff: Attached later
879 | });
880 |
881 | /**
882 | * @deprecated: Created for backwards compatibility with test runner that set the hook function
883 | * into QUnit.{hook}, instead of invoking it and passing the hook function.
884 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
885 | * Doing this allows us to tell if the following methods have been overwritten on the actual
886 | * QUnit object.
887 | */
888 | extend( QUnit.constructor.prototype, {
889 |
890 | // Logging callbacks; all receive a single argument with the listed properties
891 | // run test/logs.html for any related changes
892 | begin: registerLoggingCallback( "begin" ),
893 |
894 | // done: { failed, passed, total, runtime }
895 | done: registerLoggingCallback( "done" ),
896 |
897 | // log: { result, actual, expected, message }
898 | log: registerLoggingCallback( "log" ),
899 |
900 | // testStart: { name }
901 | testStart: registerLoggingCallback( "testStart" ),
902 |
903 | // testDone: { name, failed, passed, total }
904 | testDone: registerLoggingCallback( "testDone" ),
905 |
906 | // moduleStart: { name }
907 | moduleStart: registerLoggingCallback( "moduleStart" ),
908 |
909 | // moduleDone: { name, failed, passed, total }
910 | moduleDone: registerLoggingCallback( "moduleDone" )
911 | });
912 |
913 | if ( typeof document === "undefined" || document.readyState === "complete" ) {
914 | config.autorun = true;
915 | }
916 |
917 | QUnit.load = function() {
918 | runLoggingCallbacks( "begin", QUnit, {} );
919 |
920 | // Initialize the config, saving the execution queue
921 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes,
922 | urlConfigHtml = "",
923 | oldconfig = extend( {}, config );
924 |
925 | QUnit.init();
926 | extend(config, oldconfig);
927 |
928 | config.blocking = false;
929 |
930 | len = config.urlConfig.length;
931 |
932 | for ( i = 0; i < len; i++ ) {
933 | val = config.urlConfig[i];
934 | if ( typeof val === "string" ) {
935 | val = {
936 | id: val,
937 | label: val,
938 | tooltip: "[no tooltip available]"
939 | };
940 | }
941 | config[ val.id ] = QUnit.urlParams[ val.id ];
942 | urlConfigHtml += "" + val.label + " ";
943 | }
944 |
945 | // `userAgent` initialized at top of scope
946 | userAgent = id( "qunit-userAgent" );
947 | if ( userAgent ) {
948 | userAgent.innerHTML = navigator.userAgent;
949 | }
950 |
951 | // `banner` initialized at top of scope
952 | banner = id( "qunit-header" );
953 | if ( banner ) {
954 | banner.innerHTML = "" + banner.innerHTML + " ";
955 | }
956 |
957 | // `toolbar` initialized at top of scope
958 | toolbar = id( "qunit-testrunner-toolbar" );
959 | if ( toolbar ) {
960 | // `filter` initialized at top of scope
961 | filter = document.createElement( "input" );
962 | filter.type = "checkbox";
963 | filter.id = "qunit-filter-pass";
964 |
965 | addEvent( filter, "click", function() {
966 | var tmp,
967 | ol = document.getElementById( "qunit-tests" );
968 |
969 | if ( filter.checked ) {
970 | ol.className = ol.className + " hidepass";
971 | } else {
972 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
973 | ol.className = tmp.replace( / hidepass /, " " );
974 | }
975 | if ( defined.sessionStorage ) {
976 | if (filter.checked) {
977 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
978 | } else {
979 | sessionStorage.removeItem( "qunit-filter-passed-tests" );
980 | }
981 | }
982 | });
983 |
984 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
985 | filter.checked = true;
986 | // `ol` initialized at top of scope
987 | ol = document.getElementById( "qunit-tests" );
988 | ol.className = ol.className + " hidepass";
989 | }
990 | toolbar.appendChild( filter );
991 |
992 | // `label` initialized at top of scope
993 | label = document.createElement( "label" );
994 | label.setAttribute( "for", "qunit-filter-pass" );
995 | label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
996 | label.innerHTML = "Hide passed tests";
997 | toolbar.appendChild( label );
998 |
999 | urlConfigCheckboxes = document.createElement( 'span' );
1000 | urlConfigCheckboxes.innerHTML = urlConfigHtml;
1001 | addEvent( urlConfigCheckboxes, "change", function( event ) {
1002 | var params = {};
1003 | params[ event.target.name ] = event.target.checked ? true : undefined;
1004 | window.location = QUnit.url( params );
1005 | });
1006 | toolbar.appendChild( urlConfigCheckboxes );
1007 | }
1008 |
1009 | // `main` initialized at top of scope
1010 | main = id( "qunit-fixture" );
1011 | if ( main ) {
1012 | config.fixture = main.innerHTML;
1013 | }
1014 |
1015 | if ( config.autostart ) {
1016 | QUnit.start();
1017 | }
1018 | };
1019 |
1020 | addEvent( window, "load", QUnit.load );
1021 |
1022 | // `onErrorFnPrev` initialized at top of scope
1023 | // Preserve other handlers
1024 | onErrorFnPrev = window.onerror;
1025 |
1026 | // Cover uncaught exceptions
1027 | // Returning true will surpress the default browser handler,
1028 | // returning false will let it run.
1029 | window.onerror = function ( error, filePath, linerNr ) {
1030 | var ret = false;
1031 | if ( onErrorFnPrev ) {
1032 | ret = onErrorFnPrev( error, filePath, linerNr );
1033 | }
1034 |
1035 | // Treat return value as window.onerror itself does,
1036 | // Only do our handling if not surpressed.
1037 | if ( ret !== true ) {
1038 | if ( QUnit.config.current ) {
1039 | if ( QUnit.config.current.ignoreGlobalErrors ) {
1040 | return true;
1041 | }
1042 | QUnit.pushFailure( error, filePath + ":" + linerNr );
1043 | } else {
1044 | QUnit.test( "global failure", function() {
1045 | QUnit.pushFailure( error, filePath + ":" + linerNr );
1046 | });
1047 | }
1048 | return false;
1049 | }
1050 |
1051 | return ret;
1052 | };
1053 |
1054 | function done() {
1055 | config.autorun = true;
1056 |
1057 | // Log the last module results
1058 | if ( config.currentModule ) {
1059 | runLoggingCallbacks( "moduleDone", QUnit, {
1060 | name: config.currentModule,
1061 | failed: config.moduleStats.bad,
1062 | passed: config.moduleStats.all - config.moduleStats.bad,
1063 | total: config.moduleStats.all
1064 | });
1065 | }
1066 |
1067 | var i, key,
1068 | banner = id( "qunit-banner" ),
1069 | tests = id( "qunit-tests" ),
1070 | runtime = +new Date() - config.started,
1071 | passed = config.stats.all - config.stats.bad,
1072 | html = [
1073 | "Tests completed in ",
1074 | runtime,
1075 | " milliseconds. ",
1076 | "",
1077 | passed,
1078 | " tests of ",
1079 | config.stats.all,
1080 | " passed, ",
1081 | config.stats.bad,
1082 | " failed."
1083 | ].join( "" );
1084 |
1085 | if ( banner ) {
1086 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
1087 | }
1088 |
1089 | if ( tests ) {
1090 | id( "qunit-testresult" ).innerHTML = html;
1091 | }
1092 |
1093 | if ( config.altertitle && typeof document !== "undefined" && document.title ) {
1094 | // show ✖ for good, ✔ for bad suite result in title
1095 | // use escape sequences in case file gets loaded with non-utf-8-charset
1096 | document.title = [
1097 | ( config.stats.bad ? "\u2716" : "\u2714" ),
1098 | document.title.replace( /^[\u2714\u2716] /i, "" )
1099 | ].join( " " );
1100 | }
1101 |
1102 | // clear own sessionStorage items if all tests passed
1103 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
1104 | // `key` & `i` initialized at top of scope
1105 | for ( i = 0; i < sessionStorage.length; i++ ) {
1106 | key = sessionStorage.key( i++ );
1107 | if ( key.indexOf( "qunit-test-" ) === 0 ) {
1108 | sessionStorage.removeItem( key );
1109 | }
1110 | }
1111 | }
1112 |
1113 | runLoggingCallbacks( "done", QUnit, {
1114 | failed: config.stats.bad,
1115 | passed: passed,
1116 | total: config.stats.all,
1117 | runtime: runtime
1118 | });
1119 | }
1120 |
1121 | /** @return Boolean: true if this test should be ran */
1122 | function validTest( test ) {
1123 | var include,
1124 | filter = config.filter && config.filter.toLowerCase(),
1125 | module = config.module && config.module.toLowerCase(),
1126 | fullName = (test.module + ": " + test.testName).toLowerCase();
1127 |
1128 | if ( config.testNumber ) {
1129 | return test.testNumber === config.testNumber;
1130 | }
1131 |
1132 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
1133 | return false;
1134 | }
1135 |
1136 | if ( !filter ) {
1137 | return true;
1138 | }
1139 |
1140 | include = filter.charAt( 0 ) !== "!";
1141 | if ( !include ) {
1142 | filter = filter.slice( 1 );
1143 | }
1144 |
1145 | // If the filter matches, we need to honour include
1146 | if ( fullName.indexOf( filter ) !== -1 ) {
1147 | return include;
1148 | }
1149 |
1150 | // Otherwise, do the opposite
1151 | return !include;
1152 | }
1153 |
1154 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
1155 | // Later Safari and IE10 are supposed to support error.stack as well
1156 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1157 | function extractStacktrace( e, offset ) {
1158 | offset = offset === undefined ? 3 : offset;
1159 |
1160 | var stack, include, i, regex;
1161 |
1162 | if ( e.stacktrace ) {
1163 | // Opera
1164 | return e.stacktrace.split( "\n" )[ offset + 3 ];
1165 | } else if ( e.stack ) {
1166 | // Firefox, Chrome
1167 | stack = e.stack.split( "\n" );
1168 | if (/^error$/i.test( stack[0] ) ) {
1169 | stack.shift();
1170 | }
1171 | if ( fileName ) {
1172 | include = [];
1173 | for ( i = offset; i < stack.length; i++ ) {
1174 | if ( stack[ i ].indexOf( fileName ) != -1 ) {
1175 | break;
1176 | }
1177 | include.push( stack[ i ] );
1178 | }
1179 | if ( include.length ) {
1180 | return include.join( "\n" );
1181 | }
1182 | }
1183 | return stack[ offset ];
1184 | } else if ( e.sourceURL ) {
1185 | // Safari, PhantomJS
1186 | // hopefully one day Safari provides actual stacktraces
1187 | // exclude useless self-reference for generated Error objects
1188 | if ( /qunit.js$/.test( e.sourceURL ) ) {
1189 | return;
1190 | }
1191 | // for actual exceptions, this is useful
1192 | return e.sourceURL + ":" + e.line;
1193 | }
1194 | }
1195 | function sourceFromStacktrace( offset ) {
1196 | try {
1197 | throw new Error();
1198 | } catch ( e ) {
1199 | return extractStacktrace( e, offset );
1200 | }
1201 | }
1202 |
1203 | function escapeInnerText( s ) {
1204 | if ( !s ) {
1205 | return "";
1206 | }
1207 | s = s + "";
1208 | return s.replace( /[\&<>]/g, function( s ) {
1209 | switch( s ) {
1210 | case "&": return "&";
1211 | case "<": return "<";
1212 | case ">": return ">";
1213 | default: return s;
1214 | }
1215 | });
1216 | }
1217 |
1218 | function synchronize( callback, last ) {
1219 | config.queue.push( callback );
1220 |
1221 | if ( config.autorun && !config.blocking ) {
1222 | process( last );
1223 | }
1224 | }
1225 |
1226 | function process( last ) {
1227 | function next() {
1228 | process( last );
1229 | }
1230 | var start = new Date().getTime();
1231 | config.depth = config.depth ? config.depth + 1 : 1;
1232 |
1233 | while ( config.queue.length && !config.blocking ) {
1234 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
1235 | config.queue.shift()();
1236 | } else {
1237 | window.setTimeout( next, 13 );
1238 | break;
1239 | }
1240 | }
1241 | config.depth--;
1242 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
1243 | done();
1244 | }
1245 | }
1246 |
1247 | function saveGlobal() {
1248 | config.pollution = [];
1249 |
1250 | if ( config.noglobals ) {
1251 | for ( var key in window ) {
1252 | // in Opera sometimes DOM element ids show up here, ignore them
1253 | if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
1254 | continue;
1255 | }
1256 | config.pollution.push( key );
1257 | }
1258 | }
1259 | }
1260 |
1261 | function checkPollution( name ) {
1262 | var newGlobals,
1263 | deletedGlobals,
1264 | old = config.pollution;
1265 |
1266 | saveGlobal();
1267 |
1268 | newGlobals = diff( config.pollution, old );
1269 | if ( newGlobals.length > 0 ) {
1270 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
1271 | }
1272 |
1273 | deletedGlobals = diff( old, config.pollution );
1274 | if ( deletedGlobals.length > 0 ) {
1275 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
1276 | }
1277 | }
1278 |
1279 | // returns a new Array with the elements that are in a but not in b
1280 | function diff( a, b ) {
1281 | var i, j,
1282 | result = a.slice();
1283 |
1284 | for ( i = 0; i < result.length; i++ ) {
1285 | for ( j = 0; j < b.length; j++ ) {
1286 | if ( result[i] === b[j] ) {
1287 | result.splice( i, 1 );
1288 | i--;
1289 | break;
1290 | }
1291 | }
1292 | }
1293 | return result;
1294 | }
1295 |
1296 | function extend( a, b ) {
1297 | for ( var prop in b ) {
1298 | if ( b[ prop ] === undefined ) {
1299 | delete a[ prop ];
1300 |
1301 | // Avoid "Member not found" error in IE8 caused by setting window.constructor
1302 | } else if ( prop !== "constructor" || a !== window ) {
1303 | a[ prop ] = b[ prop ];
1304 | }
1305 | }
1306 |
1307 | return a;
1308 | }
1309 |
1310 | function addEvent( elem, type, fn ) {
1311 | if ( elem.addEventListener ) {
1312 | elem.addEventListener( type, fn, false );
1313 | } else if ( elem.attachEvent ) {
1314 | elem.attachEvent( "on" + type, fn );
1315 | } else {
1316 | fn();
1317 | }
1318 | }
1319 |
1320 | function id( name ) {
1321 | return !!( typeof document !== "undefined" && document && document.getElementById ) &&
1322 | document.getElementById( name );
1323 | }
1324 |
1325 | function registerLoggingCallback( key ) {
1326 | return function( callback ) {
1327 | config[key].push( callback );
1328 | };
1329 | }
1330 |
1331 | // Supports deprecated method of completely overwriting logging callbacks
1332 | function runLoggingCallbacks( key, scope, args ) {
1333 | //debugger;
1334 | var i, callbacks;
1335 | if ( QUnit.hasOwnProperty( key ) ) {
1336 | QUnit[ key ].call(scope, args );
1337 | } else {
1338 | callbacks = config[ key ];
1339 | for ( i = 0; i < callbacks.length; i++ ) {
1340 | callbacks[ i ].call( scope, args );
1341 | }
1342 | }
1343 | }
1344 |
1345 | // Test for equality any JavaScript type.
1346 | // Author: Philippe Rathé
1347 | QUnit.equiv = (function() {
1348 |
1349 | // Call the o related callback with the given arguments.
1350 | function bindCallbacks( o, callbacks, args ) {
1351 | var prop = QUnit.objectType( o );
1352 | if ( prop ) {
1353 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1354 | return callbacks[ prop ].apply( callbacks, args );
1355 | } else {
1356 | return callbacks[ prop ]; // or undefined
1357 | }
1358 | }
1359 | }
1360 |
1361 | // the real equiv function
1362 | var innerEquiv,
1363 | // stack to decide between skip/abort functions
1364 | callers = [],
1365 | // stack to avoiding loops from circular referencing
1366 | parents = [],
1367 |
1368 | getProto = Object.getPrototypeOf || function ( obj ) {
1369 | return obj.__proto__;
1370 | },
1371 | callbacks = (function () {
1372 |
1373 | // for string, boolean, number and null
1374 | function useStrictEquality( b, a ) {
1375 | if ( b instanceof a.constructor || a instanceof b.constructor ) {
1376 | // to catch short annotaion VS 'new' annotation of a
1377 | // declaration
1378 | // e.g. var i = 1;
1379 | // var j = new Number(1);
1380 | return a == b;
1381 | } else {
1382 | return a === b;
1383 | }
1384 | }
1385 |
1386 | return {
1387 | "string": useStrictEquality,
1388 | "boolean": useStrictEquality,
1389 | "number": useStrictEquality,
1390 | "null": useStrictEquality,
1391 | "undefined": useStrictEquality,
1392 |
1393 | "nan": function( b ) {
1394 | return isNaN( b );
1395 | },
1396 |
1397 | "date": function( b, a ) {
1398 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1399 | },
1400 |
1401 | "regexp": function( b, a ) {
1402 | return QUnit.objectType( b ) === "regexp" &&
1403 | // the regex itself
1404 | a.source === b.source &&
1405 | // and its modifers
1406 | a.global === b.global &&
1407 | // (gmi) ...
1408 | a.ignoreCase === b.ignoreCase &&
1409 | a.multiline === b.multiline;
1410 | },
1411 |
1412 | // - skip when the property is a method of an instance (OOP)
1413 | // - abort otherwise,
1414 | // initial === would have catch identical references anyway
1415 | "function": function() {
1416 | var caller = callers[callers.length - 1];
1417 | return caller !== Object && typeof caller !== "undefined";
1418 | },
1419 |
1420 | "array": function( b, a ) {
1421 | var i, j, len, loop;
1422 |
1423 | // b could be an object literal here
1424 | if ( QUnit.objectType( b ) !== "array" ) {
1425 | return false;
1426 | }
1427 |
1428 | len = a.length;
1429 | if ( len !== b.length ) {
1430 | // safe and faster
1431 | return false;
1432 | }
1433 |
1434 | // track reference to avoid circular references
1435 | parents.push( a );
1436 | for ( i = 0; i < len; i++ ) {
1437 | loop = false;
1438 | for ( j = 0; j < parents.length; j++ ) {
1439 | if ( parents[j] === a[i] ) {
1440 | loop = true;// dont rewalk array
1441 | }
1442 | }
1443 | if ( !loop && !innerEquiv(a[i], b[i]) ) {
1444 | parents.pop();
1445 | return false;
1446 | }
1447 | }
1448 | parents.pop();
1449 | return true;
1450 | },
1451 |
1452 | "object": function( b, a ) {
1453 | var i, j, loop,
1454 | // Default to true
1455 | eq = true,
1456 | aProperties = [],
1457 | bProperties = [];
1458 |
1459 | // comparing constructors is more strict than using
1460 | // instanceof
1461 | if ( a.constructor !== b.constructor ) {
1462 | // Allow objects with no prototype to be equivalent to
1463 | // objects with Object as their constructor.
1464 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
1465 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
1466 | return false;
1467 | }
1468 | }
1469 |
1470 | // stack constructor before traversing properties
1471 | callers.push( a.constructor );
1472 | // track reference to avoid circular references
1473 | parents.push( a );
1474 |
1475 | for ( i in a ) { // be strict: don't ensures hasOwnProperty
1476 | // and go deep
1477 | loop = false;
1478 | for ( j = 0; j < parents.length; j++ ) {
1479 | if ( parents[j] === a[i] ) {
1480 | // don't go down the same path twice
1481 | loop = true;
1482 | }
1483 | }
1484 | aProperties.push(i); // collect a's properties
1485 |
1486 | if (!loop && !innerEquiv( a[i], b[i] ) ) {
1487 | eq = false;
1488 | break;
1489 | }
1490 | }
1491 |
1492 | callers.pop(); // unstack, we are done
1493 | parents.pop();
1494 |
1495 | for ( i in b ) {
1496 | bProperties.push( i ); // collect b's properties
1497 | }
1498 |
1499 | // Ensures identical properties name
1500 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1501 | }
1502 | };
1503 | }());
1504 |
1505 | innerEquiv = function() { // can take multiple arguments
1506 | var args = [].slice.apply( arguments );
1507 | if ( args.length < 2 ) {
1508 | return true; // end transition
1509 | }
1510 |
1511 | return (function( a, b ) {
1512 | if ( a === b ) {
1513 | return true; // catch the most you can
1514 | } else if ( a === null || b === null || typeof a === "undefined" ||
1515 | typeof b === "undefined" ||
1516 | QUnit.objectType(a) !== QUnit.objectType(b) ) {
1517 | return false; // don't lose time with error prone cases
1518 | } else {
1519 | return bindCallbacks(a, callbacks, [ b, a ]);
1520 | }
1521 |
1522 | // apply transition with (1..n) arguments
1523 | }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
1524 | };
1525 |
1526 | return innerEquiv;
1527 | }());
1528 |
1529 | /**
1530 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1531 | * http://flesler.blogspot.com Licensed under BSD
1532 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1533 | *
1534 | * @projectDescription Advanced and extensible data dumping for Javascript.
1535 | * @version 1.0.0
1536 | * @author Ariel Flesler
1537 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1538 | */
1539 | QUnit.jsDump = (function() {
1540 | function quote( str ) {
1541 | return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
1542 | }
1543 | function literal( o ) {
1544 | return o + "";
1545 | }
1546 | function join( pre, arr, post ) {
1547 | var s = jsDump.separator(),
1548 | base = jsDump.indent(),
1549 | inner = jsDump.indent(1);
1550 | if ( arr.join ) {
1551 | arr = arr.join( "," + s + inner );
1552 | }
1553 | if ( !arr ) {
1554 | return pre + post;
1555 | }
1556 | return [ pre, inner + arr, base + post ].join(s);
1557 | }
1558 | function array( arr, stack ) {
1559 | var i = arr.length, ret = new Array(i);
1560 | this.up();
1561 | while ( i-- ) {
1562 | ret[i] = this.parse( arr[i] , undefined , stack);
1563 | }
1564 | this.down();
1565 | return join( "[", ret, "]" );
1566 | }
1567 |
1568 | var reName = /^function (\w+)/,
1569 | jsDump = {
1570 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1571 | stack = stack || [ ];
1572 | var inStack, res,
1573 | parser = this.parsers[ type || this.typeOf(obj) ];
1574 |
1575 | type = typeof parser;
1576 | inStack = inArray( obj, stack );
1577 |
1578 | if ( inStack != -1 ) {
1579 | return "recursion(" + (inStack - stack.length) + ")";
1580 | }
1581 | //else
1582 | if ( type == "function" ) {
1583 | stack.push( obj );
1584 | res = parser.call( this, obj, stack );
1585 | stack.pop();
1586 | return res;
1587 | }
1588 | // else
1589 | return ( type == "string" ) ? parser : this.parsers.error;
1590 | },
1591 | typeOf: function( obj ) {
1592 | var type;
1593 | if ( obj === null ) {
1594 | type = "null";
1595 | } else if ( typeof obj === "undefined" ) {
1596 | type = "undefined";
1597 | } else if ( QUnit.is( "regexp", obj) ) {
1598 | type = "regexp";
1599 | } else if ( QUnit.is( "date", obj) ) {
1600 | type = "date";
1601 | } else if ( QUnit.is( "function", obj) ) {
1602 | type = "function";
1603 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
1604 | type = "window";
1605 | } else if ( obj.nodeType === 9 ) {
1606 | type = "document";
1607 | } else if ( obj.nodeType ) {
1608 | type = "node";
1609 | } else if (
1610 | // native arrays
1611 | toString.call( obj ) === "[object Array]" ||
1612 | // NodeList objects
1613 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1614 | ) {
1615 | type = "array";
1616 | } else {
1617 | type = typeof obj;
1618 | }
1619 | return type;
1620 | },
1621 | separator: function() {
1622 | return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
1623 | },
1624 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1625 | if ( !this.multiline ) {
1626 | return "";
1627 | }
1628 | var chr = this.indentChar;
1629 | if ( this.HTML ) {
1630 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
1631 | }
1632 | return new Array( this._depth_ + (extra||0) ).join(chr);
1633 | },
1634 | up: function( a ) {
1635 | this._depth_ += a || 1;
1636 | },
1637 | down: function( a ) {
1638 | this._depth_ -= a || 1;
1639 | },
1640 | setParser: function( name, parser ) {
1641 | this.parsers[name] = parser;
1642 | },
1643 | // The next 3 are exposed so you can use them
1644 | quote: quote,
1645 | literal: literal,
1646 | join: join,
1647 | //
1648 | _depth_: 1,
1649 | // This is the list of parsers, to modify them, use jsDump.setParser
1650 | parsers: {
1651 | window: "[Window]",
1652 | document: "[Document]",
1653 | error: "[ERROR]", //when no parser is found, shouldn"t happen
1654 | unknown: "[Unknown]",
1655 | "null": "null",
1656 | "undefined": "undefined",
1657 | "function": function( fn ) {
1658 | var ret = "function",
1659 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
1660 |
1661 | if ( name ) {
1662 | ret += " " + name;
1663 | }
1664 | ret += "( ";
1665 |
1666 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
1667 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
1668 | },
1669 | array: array,
1670 | nodelist: array,
1671 | "arguments": array,
1672 | object: function( map, stack ) {
1673 | var ret = [ ], keys, key, val, i;
1674 | QUnit.jsDump.up();
1675 | if ( Object.keys ) {
1676 | keys = Object.keys( map );
1677 | } else {
1678 | keys = [];
1679 | for ( key in map ) {
1680 | keys.push( key );
1681 | }
1682 | }
1683 | keys.sort();
1684 | for ( i = 0; i < keys.length; i++ ) {
1685 | key = keys[ i ];
1686 | val = map[ key ];
1687 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
1688 | }
1689 | QUnit.jsDump.down();
1690 | return join( "{", ret, "}" );
1691 | },
1692 | node: function( node ) {
1693 | var a, val,
1694 | open = QUnit.jsDump.HTML ? "<" : "<",
1695 | close = QUnit.jsDump.HTML ? ">" : ">",
1696 | tag = node.nodeName.toLowerCase(),
1697 | ret = open + tag;
1698 |
1699 | for ( a in QUnit.jsDump.DOMAttrs ) {
1700 | val = node[ QUnit.jsDump.DOMAttrs[a] ];
1701 | if ( val ) {
1702 | ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
1703 | }
1704 | }
1705 | return ret + close + open + "/" + tag + close;
1706 | },
1707 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
1708 | var args,
1709 | l = fn.length;
1710 |
1711 | if ( !l ) {
1712 | return "";
1713 | }
1714 |
1715 | args = new Array(l);
1716 | while ( l-- ) {
1717 | args[l] = String.fromCharCode(97+l);//97 is 'a'
1718 | }
1719 | return " " + args.join( ", " ) + " ";
1720 | },
1721 | key: quote, //object calls it internally, the key part of an item in a map
1722 | functionCode: "[code]", //function calls it internally, it's the content of the function
1723 | attribute: quote, //node calls it internally, it's an html attribute value
1724 | string: quote,
1725 | date: quote,
1726 | regexp: literal, //regex
1727 | number: literal,
1728 | "boolean": literal
1729 | },
1730 | DOMAttrs: {
1731 | //attributes to dump from nodes, name=>realName
1732 | id: "id",
1733 | name: "name",
1734 | "class": "className"
1735 | },
1736 | HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
1737 | indentChar: " ",//indentation unit
1738 | multiline: true //if true, items in a collection, are separated by a \n, else just a space.
1739 | };
1740 |
1741 | return jsDump;
1742 | }());
1743 |
1744 | // from Sizzle.js
1745 | function getText( elems ) {
1746 | var i, elem,
1747 | ret = "";
1748 |
1749 | for ( i = 0; elems[i]; i++ ) {
1750 | elem = elems[i];
1751 |
1752 | // Get the text from text nodes and CDATA nodes
1753 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1754 | ret += elem.nodeValue;
1755 |
1756 | // Traverse everything else, except comment nodes
1757 | } else if ( elem.nodeType !== 8 ) {
1758 | ret += getText( elem.childNodes );
1759 | }
1760 | }
1761 |
1762 | return ret;
1763 | }
1764 |
1765 | // from jquery.js
1766 | function inArray( elem, array ) {
1767 | if ( array.indexOf ) {
1768 | return array.indexOf( elem );
1769 | }
1770 |
1771 | for ( var i = 0, length = array.length; i < length; i++ ) {
1772 | if ( array[ i ] === elem ) {
1773 | return i;
1774 | }
1775 | }
1776 |
1777 | return -1;
1778 | }
1779 |
1780 | /*
1781 | * Javascript Diff Algorithm
1782 | * By John Resig (http://ejohn.org/)
1783 | * Modified by Chu Alan "sprite"
1784 | *
1785 | * Released under the MIT license.
1786 | *
1787 | * More Info:
1788 | * http://ejohn.org/projects/javascript-diff-algorithm/
1789 | *
1790 | * Usage: QUnit.diff(expected, actual)
1791 | *
1792 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
1793 | */
1794 | QUnit.diff = (function() {
1795 | function diff( o, n ) {
1796 | var i,
1797 | ns = {},
1798 | os = {};
1799 |
1800 | for ( i = 0; i < n.length; i++ ) {
1801 | if ( ns[ n[i] ] == null ) {
1802 | ns[ n[i] ] = {
1803 | rows: [],
1804 | o: null
1805 | };
1806 | }
1807 | ns[ n[i] ].rows.push( i );
1808 | }
1809 |
1810 | for ( i = 0; i < o.length; i++ ) {
1811 | if ( os[ o[i] ] == null ) {
1812 | os[ o[i] ] = {
1813 | rows: [],
1814 | n: null
1815 | };
1816 | }
1817 | os[ o[i] ].rows.push( i );
1818 | }
1819 |
1820 | for ( i in ns ) {
1821 | if ( !hasOwn.call( ns, i ) ) {
1822 | continue;
1823 | }
1824 | if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
1825 | n[ ns[i].rows[0] ] = {
1826 | text: n[ ns[i].rows[0] ],
1827 | row: os[i].rows[0]
1828 | };
1829 | o[ os[i].rows[0] ] = {
1830 | text: o[ os[i].rows[0] ],
1831 | row: ns[i].rows[0]
1832 | };
1833 | }
1834 | }
1835 |
1836 | for ( i = 0; i < n.length - 1; i++ ) {
1837 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
1838 | n[ i + 1 ] == o[ n[i].row + 1 ] ) {
1839 |
1840 | n[ i + 1 ] = {
1841 | text: n[ i + 1 ],
1842 | row: n[i].row + 1
1843 | };
1844 | o[ n[i].row + 1 ] = {
1845 | text: o[ n[i].row + 1 ],
1846 | row: i + 1
1847 | };
1848 | }
1849 | }
1850 |
1851 | for ( i = n.length - 1; i > 0; i-- ) {
1852 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
1853 | n[ i - 1 ] == o[ n[i].row - 1 ]) {
1854 |
1855 | n[ i - 1 ] = {
1856 | text: n[ i - 1 ],
1857 | row: n[i].row - 1
1858 | };
1859 | o[ n[i].row - 1 ] = {
1860 | text: o[ n[i].row - 1 ],
1861 | row: i - 1
1862 | };
1863 | }
1864 | }
1865 |
1866 | return {
1867 | o: o,
1868 | n: n
1869 | };
1870 | }
1871 |
1872 | return function( o, n ) {
1873 | o = o.replace( /\s+$/, "" );
1874 | n = n.replace( /\s+$/, "" );
1875 |
1876 | var i, pre,
1877 | str = "",
1878 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
1879 | oSpace = o.match(/\s+/g),
1880 | nSpace = n.match(/\s+/g);
1881 |
1882 | if ( oSpace == null ) {
1883 | oSpace = [ " " ];
1884 | }
1885 | else {
1886 | oSpace.push( " " );
1887 | }
1888 |
1889 | if ( nSpace == null ) {
1890 | nSpace = [ " " ];
1891 | }
1892 | else {
1893 | nSpace.push( " " );
1894 | }
1895 |
1896 | if ( out.n.length === 0 ) {
1897 | for ( i = 0; i < out.o.length; i++ ) {
1898 | str += "" + out.o[i] + oSpace[i] + "";
1899 | }
1900 | }
1901 | else {
1902 | if ( out.n[0].text == null ) {
1903 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
1904 | str += "" + out.o[n] + oSpace[n] + "";
1905 | }
1906 | }
1907 |
1908 | for ( i = 0; i < out.n.length; i++ ) {
1909 | if (out.n[i].text == null) {
1910 | str += "" + out.n[i] + nSpace[i] + " ";
1911 | }
1912 | else {
1913 | // `pre` initialized at top of scope
1914 | pre = "";
1915 |
1916 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
1917 | pre += "" + out.o[n] + oSpace[n] + "";
1918 | }
1919 | str += " " + out.n[i].text + nSpace[i] + pre;
1920 | }
1921 | }
1922 | }
1923 |
1924 | return str;
1925 | };
1926 | }());
1927 |
1928 | // for CommonJS enviroments, export everything
1929 | if ( typeof exports !== "undefined" ) {
1930 | extend(exports, QUnit);
1931 | }
1932 |
1933 | // get at whatever the global object is, like window in browsers
1934 | }( (function() {return this;}.call()) ));
1935 |
--------------------------------------------------------------------------------