├── .bowerrc
├── .gitignore
├── .gitmodules
├── .nojekyll
├── README.md
├── app.js
├── browser-stats.js
├── component.json
├── components
├── jets
│ ├── .bower.json
│ ├── .gitignore
│ ├── .travis.yml
│ ├── LICENSE
│ ├── README.md
│ ├── bower.json
│ ├── jets.js
│ ├── jets.min.js
│ ├── package.json
│ └── test
│ │ ├── index.html
│ │ └── test.js
├── jquery
│ ├── component.json
│ ├── composer.json
│ ├── jquery.js
│ └── jquery.min.js
└── underscore
│ ├── .gitignore
│ ├── .npmignore
│ ├── CNAME
│ ├── CONTRIBUTING.md
│ ├── LICENSE
│ ├── README.md
│ ├── Rakefile
│ ├── component.json
│ ├── docs
│ ├── docco.css
│ ├── favicon.ico
│ ├── images
│ │ ├── background.png
│ │ └── underscore.png
│ └── underscore.html
│ ├── favicon.ico
│ ├── index.html
│ ├── index.js
│ ├── package.json
│ ├── test
│ ├── arrays.js
│ ├── chaining.js
│ ├── collections.js
│ ├── functions.js
│ ├── index.html
│ ├── objects.js
│ ├── speed.js
│ ├── utility.js
│ └── vendor
│ │ ├── jquery.js
│ │ ├── jslitmus.js
│ │ ├── qunit.css
│ │ └── qunit.js
│ ├── underscore-min.js
│ └── underscore.js
├── data.json
├── favicon-32x32.png
├── favicon.ico
├── images
├── darkdenim3.png
└── lightpaperfibers.png
├── index.html
└── wfa192.png
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "components"
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | .DS_Store
3 | bower_components/
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "caniuse"]
2 | path = caniuse
3 | url = https://github.com/Fyrd/caniuse.git
4 | branch = main
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | web-feature-distribution
2 | ============
3 |
4 | i hacked up https://github.com/PaulKinlan/iwanttouse.com real good.
5 |
6 | ### dev
7 |
8 | run a local server and open index.html
9 |
10 |
11 | #### ~update data~ (not needed anymore)
12 |
13 | I used to serve the data locally, but now i just hotlink the data from unpkg which always keeps it up to date.
14 |
15 | ```sh
16 | git -C caniuse pull origin main
17 | cp caniuse/fulldata-json/data-1.0.json data.json
18 | ```
19 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | /* bling.js */
2 | window.$ = document.querySelector.bind(document);
3 | window.$$ = document.querySelectorAll.bind(document);
4 | Node.prototype.on = window.on = function (name, fn) {
5 | this.addEventListener(name, fn);
6 | };
7 | NodeList.prototype.__proto__ = Array.prototype;
8 | NodeList.prototype.on = NodeList.prototype.addEventListener = function (name, fn) {
9 | this.forEach(function (elem, i) {
10 | elem.on(name, fn);
11 | });
12 | };
13 |
14 | // honestly i couldn't tell you how much of this code is from me over the past 5 years and how much is kinlan's fault (https://github.com/PaulKinlan/iwanttouse.com)
15 | // but i can tell you that the code isn't elegant
16 | // so, i'm sorry.
17 |
18 | //
19 | // app
20 | //
21 |
22 | var bindFeatureDataList = function (features, required, onItem, sumCurrent) {
23 | return Object.keys(features).map(function (key) {
24 | return {
25 | id: key,
26 | title: BrowserStats.browsers.getFeature(key).title,
27 | };
28 | });
29 | };
30 |
31 |
32 | /**
33 | * Computes the weighted-average of the score of the list of items.
34 | * stolen from lighthouse
35 | * @param {Array<{score: number|null, weight: number}>} items
36 | * @return {number|null}
37 | */
38 | function arithmeticMean(items) {
39 | // Filter down to just the items with a weight as they have no effect on score
40 | items = items.filter(item => item.weight > 0);
41 | // If there is 1 null score, return a null average
42 | if (items.some(item => item.score === null)) return null;
43 |
44 | const results = items.reduce(
45 | (result, item) => {
46 | const score = item.score;
47 | const weight = item.weight;
48 |
49 | return {
50 | weight: result.weight + weight,
51 | sum: result.sum + /** @type {number} */ (score) * weight,
52 | };
53 | },
54 | {weight: 0, sum: 0}
55 | );
56 |
57 | return results.sum / results.weight || 0;
58 | }
59 |
60 |
61 | document.on('DOMContentLoaded', function () {
62 | var deviceType = 'all';
63 |
64 | BrowserStats.load(deviceType, function (browsers) {
65 | var features = browsers.features;
66 |
67 | updateDates(browsers);
68 |
69 | // Sum total usage per agent, cuz it comes broken down by version
70 | const agents = browsers.origCaniuseData.agents;
71 |
72 | Object.values(browsers.origCaniuseData.agents).forEach(agent => {
73 | agent.lite = Object.values(browsers.liteAgents).find(lA => lA.E === agent.browser);
74 | const thing = Object.entries(agent.lite.F).map(([key, date]) => {
75 | const versStr = browsers.liteBrowserVersions[key];
76 | return [versStr, new Date(date * 1000)];
77 | });
78 | agent.versionDates = Object.fromEntries(thing);
79 | });
80 |
81 | Object.values(agents).forEach(agent => {
82 | // just a sum.
83 | const totalUsageForAgent = Object.values(agent.usage_global).reduce((prev, curr) => {
84 | return prev + curr;
85 | }, 0)
86 | if (!isFinite(totalUsageForAgent)) {
87 | console.warn('invalid totalUsageForAgent', totalUsageForAgent, agent);
88 | }
89 | agent.usage_global_total = totalUsageForAgent;
90 | });
91 |
92 | _.each(features, function (itm, idx) {
93 | itm.id = idx;
94 | });
95 |
96 |
97 | /**
98 | * Most recent versions first
99 | * TODO: computationally this is very innefficient.
100 | */
101 | function sortBrowserVersions(agentStats, agentName) {
102 | // this LUT provides a canonical order for all version ids, incl safari tech preview..
103 | const agentVersions = agents[agentName].versions;
104 |
105 | return Object.entries(agentStats).sort(([versAStr, suppA], [versBStr, suppB]) => {
106 | const aIndex = agentVersions.findIndex(v => v === versAStr);
107 | const bIndex = agentVersions.findIndex(v => v === versBStr);
108 | return bIndex - aIndex;
109 | });
110 | }
111 |
112 | function getNewlySupportedRecency(feat) {
113 | const allBrowserStats = Object.entries(feat.stats);
114 | const recencyPerAgent = allBrowserStats.map(([agentName, agentStats]) => {
115 | // If global share is under 1% round down to zero. this is cuz fyrd has a shortcut for old browsers (like android webview)
116 | // where he just marks latest version as supporting and theres no real history behind it.
117 | // this is kinda unfair but improves data quality.
118 | const weightViaBrowserShare = agents[agentName].usage_global_total < 1 ? 0 : agents[agentName].usage_global_total
119 |
120 | const sorted = sortBrowserVersions(agentStats, agentName);
121 | const allAreSupported = sorted.every(([vers, res]) => res.startsWith('y'));
122 | if (allAreSupported) {
123 | // It's always been supported. uninteresting. eg. 'ttf'
124 | return {
125 | agentName,
126 | weight: weightViaBrowserShare,
127 | score: 0,
128 | };
129 | }
130 | let isPartialBump = false;
131 |
132 | const newlySupportedVersions = sorted.filter(([vers, res], i) => {
133 | const nextOlderVers = sorted[i + 1];
134 | if (!nextOlderVers) return false;
135 | const nextOlderVerSupp = nextOlderVers[1];
136 | if (nextOlderVerSupp.startsWith('n') && res.startsWith('y')) return true;
137 | const partialBump = nextOlderVerSupp.startsWith('a') && res.startsWith('y') ||
138 | nextOlderVerSupp.startsWith('n') && res.startsWith('a');
139 | if (partialBump && isPartialBump === false) {
140 | isPartialBump = true;
141 | }
142 | return isPartialBump;
143 | });
144 | if (newlySupportedVersions.length > 1) {
145 | // Theres only 7 instances in the full dataset where this happens.
146 | // This == they added support. then removed it. then added it back.
147 | // They all are pretty old and uninteresting (shared web workers in safari, some opera stuff.. etc)
148 | // So ill simplify and just take the most recent.
149 | }
150 | const mostRecentVersionThatSupports = newlySupportedVersions.at(0)?.at(0);
151 |
152 | // If it's unsupported then then we dont include in the weighted avg
153 | if (!mostRecentVersionThatSupports) {
154 | return {
155 | agentName,
156 | weight: 0,
157 | score: 0,
158 | }
159 | }
160 |
161 | const mostRecentVersionThatSupportsRls = agents[agentName].versionDates[mostRecentVersionThatSupports];
162 |
163 | const tenYearsAgo = new Date(new Date() - 1000 * 60 * 60 * 24 * 365 * 10);
164 | let pctRecentInLastTenYears = (mostRecentVersionThatSupportsRls - tenYearsAgo) / (new Date() - tenYearsAgo);
165 | // Dumb but w/e. w/o it text-indent is at the top cuz some recent safari fixed a minor bug.
166 | if (isPartialBump && pctRecentInLastTenYears > 0) {
167 | pctRecentInLastTenYears /= 2;
168 | }
169 | const recencyPct = pctRecentInLastTenYears;
170 | return {
171 | agentName,
172 | score: recencyPct,
173 | weight: weightViaBrowserShare,
174 | mostRecentVersionThatSupports,
175 | mostRecentVersionThatSupportsRls,
176 | }
177 | });
178 | const maxScore = Math.max(... recencyPerAgent.map(a => a.weight ? a.score : 0));
179 | const mostRecent = recencyPerAgent.find(a => a.score === maxScore);
180 | const mean = arithmeticMean(recencyPerAgent);
181 | return {mean, mostRecent};
182 | }
183 |
184 | function updateShare(requiredFeatures) {
185 | if (!!requiredFeatures === false) return;
186 | return bindFeatureDataList(features, requiredFeatures, undefined, 100);
187 | }
188 |
189 | var urlFeats = getFeatureArrayFromString('');
190 | var shareResults = updateShare(urlFeats);
191 | shareResults = _.groupBy(shareResults, function (f) {
192 | return f.id;
193 | });
194 |
195 | // merge a few categories together
196 | browsers.featureCats.CSS = [...browsers.featureCats.CSS, ...browsers.featureCats.CSS3];
197 | delete browsers.featureCats.CSS3;
198 | browsers.featureCats.Other = [...browsers.featureCats.Other, ...browsers.featureCats.PNG];
199 | delete browsers.featureCats.PNG;
200 |
201 | let totalFeatures = 0;
202 | const hasParamAll = new URL(location.href).searchParams.has('all');
203 | const hasParam99 = new URL(location.href).searchParams.has('99');
204 |
205 | var categories = Object.keys(browsers.featureCats).sort();
206 |
207 | const featureToCat = new Map();
208 | Object.entries(browsers.featureCats).forEach(([catId, feats]) => {
209 | for (const feat of feats) featureToCat.set(feat, catId);
210 | });
211 | const allFeatures = Array.from(featureToCat.keys());
212 |
213 |
214 | var allHTML = allFeatures.map(function (feat) {
215 | feat.share = shareResults[feat.id][0];
216 | feat.totalSupport = feat.usage_perc_a + feat.usage_perc_y;
217 | const {mean, mostRecent} = getNewlySupportedRecency(feat);
218 | feat.avgRecency = mean;
219 | feat.mostRecent = mostRecent;
220 |
221 | return feat;
222 | })
223 | .filter(feat => {
224 | // Previously i filtered for features with a chrome_id.. (as a proxy for recent stuff?) but it misses out on some stuff...
225 | // now we filter for obviously supported stuff (but allowlist 'JS' stuff because _developit said so.)
226 | if (hasParamAll) return true;
227 |
228 | return feat.avgRecency > 0.01; // manually picked this threshold
229 | })
230 | .sort(function (a, b) {
231 | return b.avgRecency - a.avgRecency;
232 | // return b.totalSupport - a.totalSupport;
233 | })
234 | .map(function (feat) {
235 | var adjustedHue = adjustHue(feat.totalSupport);
236 | var color = `hsla(${adjustedHue}, 100%, 42%, 1)`;
237 |
238 | var partialColor = `hsla(${adjustedHue}, 90%, 39.6%, 1)`;
239 | const fullSupportPct = feat.usage_perc_y;
240 | const partialSupportPct = feat.usage_perc_a;
241 |
242 | var pct = `${escape(
243 | feat.totalSupport.toLocaleString(undefined, {maximumFractionDigits: 1})
244 | )}%`;
245 | var title = feat.title
246 | .replace(`CSS3 `, ``)
247 | .replace(`CSS `, ``)
248 | .replace(`(rounded corners)`, ``);
249 |
250 | totalFeatures++;
251 | allfeats.push(feat);
252 |
253 | // const feats = browsers.featureCats[cat];
254 |
255 | // const renamedCat = {'JS API': 'Browser APIs'}[cat] || cat;
256 | // const titleHTML = `
${renamedCat}
`;
257 | const cat = featureToCat.get(feat);
258 |
259 | const recentChangeDateStr = feat.mostRecent.mostRecentVersionThatSupportsRls?.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric'});
260 | const recentChangeStr = !recentChangeDateStr ? '' : `
261 | ${recentChangeDateStr}: ${agents[feat.mostRecent.agentName].browser} ${feat.mostRecent.mostRecentVersionThatSupports} improved support.
262 | `.trim();
263 |
264 | return `
265 | -
266 |
270 |
271 | ${pct}
272 | FAR: ${feat.avgRecency.toLocaleString()}
273 | ${recentChangeStr}
274 |
275 |
277 |
286 |
287 | `;
288 | })
289 | .join('');
290 |
291 |
292 | $('#features').innerHTML = ``;
293 |
294 | $('#search').placeholder = `Filter across ${totalFeatures.toLocaleString()} features…`;
295 |
296 | setupSearch();
297 | });
298 | });
299 |
300 | window.allfeats = [];
301 |
302 | function escape(str) {
303 | return str.replace(/'/g, '');
304 | }
305 |
306 | var getFeatureArrayFromString = function (str) {
307 | var feats = str.split(',');
308 | if (feats.length == 1 && feats[0] === '') return [];
309 | return feats;
310 | };
311 |
312 | function updateDates(browsers) {
313 | const dateOptions = {year: 'numeric', month: 'long', day: 'numeric'};
314 | $('#caniuse-date').textContent = new Date(browsers.updated * 1000).toLocaleDateString(
315 | undefined,
316 | dateOptions
317 | );
318 | }
319 |
320 | function setupSearch() {
321 | var searchInput = document.getElementById('search');
322 |
323 | searchInput.addEventListener('input', function () {
324 | document.body.classList.toggle('is-searching', searchInput.value);
325 | });
326 |
327 | new Jets({
328 | searchTag: '#search',
329 | contentTag: '#features > ul',
330 | });
331 | }
332 |
333 | function adjustHue(hue) {
334 | // what math this is i have NO idea
335 | return Math.pow(hue, 3) / 10000;
336 | }
337 |
--------------------------------------------------------------------------------
/browser-stats.js:
--------------------------------------------------------------------------------
1 | var BrowserStats = (function () {
2 | var Browser = function (key, agent) {
3 |
4 | };
5 |
6 | var Browsers = function () {
7 | var _agents = {};
8 | var _features = {};
9 |
10 | this.__defineGetter__('features', function () {
11 | return _features;
12 | });
13 |
14 | this.addBrowser = function (a, agent) {
15 | _agents[a] = new Browser(a, agent);
16 | };
17 |
18 |
19 | this.addFeature = function (feature, versions) {
20 | _features[feature] = versions;
21 | };
22 |
23 |
24 | this.getFeature = function (featureName) {
25 | return _features[featureName];
26 | };
27 | };
28 |
29 | var load = async function (type, callback) {
30 | callback = callback || function () {};
31 |
32 | const cP = fetch('https://unpkg.com/caniuse-db/fulldata-json/data-1.0.json').then(d => d.json());
33 | const aP = fetch('https://unpkg.com/caniuse-lite/data/agents.js').then(r => r.text()).then(agentsTxt => {
34 | // lol hack because its CJS.
35 | window.module = window.module || {};
36 | eval(agentsTxt)
37 | return window.module.exports;
38 | });
39 | const bvP = fetch('https://unpkg.com/caniuse-lite/data/browserVersions.js').then(r => r.text()).then(bvText => {
40 | // lol hack because its CJS.
41 | window.module = window.module || {};
42 | eval(bvText)
43 | return window.module.exports;
44 | });
45 | await Promise.all([cP, aP, bvP]).then(([data, agents, browserVersions]) => {
46 | data.liteAgents = agents;
47 | data.liteBrowserVersions = browserVersions;
48 | parse(type, data, callback);
49 | })
50 | };
51 |
52 | var browsers = new Browsers();
53 |
54 | var parse = function (type, data, callback) {
55 | var validAgents = {};
56 | for (var a in data.agents) {
57 | if (type == 'all' || type == data.agents[a].type) {
58 | browsers.addBrowser(a, data.agents[a]);
59 | validAgents[a] = true;
60 | }
61 | }
62 |
63 | for (var i in data.data) {
64 | // Remove agents that are not part of the viewed set.
65 | var feature = data.data[i];
66 | for (var a in feature.stats) {
67 | if (!!validAgents[a] == false) {
68 | feature.stats[a] = undefined;
69 | }
70 | }
71 | browsers.addFeature(i, feature);
72 | }
73 | browsers.featureCats = _.groupBy(browsers.features, function (t) {
74 | return t.categories[0];
75 | });
76 |
77 | browsers.updated = data.updated;
78 | browsers.origCaniuseData = data;
79 | browsers.liteAgents = data.liteAgents;
80 | browsers.liteBrowserVersions = data.liteBrowserVersions;
81 |
82 | callback(browsers);
83 | };
84 |
85 |
86 | var returnObject = {
87 | load: load,
88 | browsers: function (type) {
89 | if (!!type == false) return browsers;
90 | else return;
91 | },
92 | };
93 |
94 | Object.defineProperty(returnObject, 'browsers', {
95 | get: function () {
96 | return browsers;
97 | },
98 | });
99 |
100 | return returnObject;
101 | })();
102 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "What Can I Use",
3 | "version": "0.0.0.1",
4 | "dependencies": {
5 | "jquery": "*",
6 | "underscore": "*",
7 | "jets": "^0.11.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/components/jets/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jets",
3 | "main": "jets.min.js",
4 | "homepage": "https://github.com/NeXTs/Jets.js",
5 | "authors": [
6 | "Denis Lukov "
7 | ],
8 | "description": "Native CSS search engine",
9 | "moduleType": [
10 | "amd",
11 | "globals",
12 | "commonjs"
13 | ],
14 | "keywords": [
15 | "css",
16 | "search",
17 | "list",
18 | "jets"
19 | ],
20 | "license": "MIT",
21 | "ignore": [
22 | "/node_modules",
23 | "npm-debug.log"
24 | ],
25 | "version": "0.11.0",
26 | "_release": "0.11.0",
27 | "_resolution": {
28 | "type": "version",
29 | "tag": "v0.11.0",
30 | "commit": "f58478dae65427cc34d1cdedd2e3c28986a3687c"
31 | },
32 | "_source": "https://github.com/NeXTs/Jets.js.git",
33 | "_target": "^0.11.0",
34 | "_originalSource": "jets"
35 | }
--------------------------------------------------------------------------------
/components/jets/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | npm-debug.log
--------------------------------------------------------------------------------
/components/jets/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
--------------------------------------------------------------------------------
/components/jets/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Denis Lukov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/components/jets/README.md:
--------------------------------------------------------------------------------
1 | # Jets.js
2 | [](https://www.npmjs.com/package/jets)
3 | [](http://bower.io/search/?q=jets)
4 | [](https://travis-ci.org/NeXTs/Jets.js)
5 | [](http://packagequality.com/#?package=jets)
6 | [](https://gitter.im/NeXTs/Jets.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 | > Native CSS search engine
9 |
10 | [Demo, usage, etc…](https://jets.js.org/)
11 |
12 | [](https://jets.js.org/)
13 |
--------------------------------------------------------------------------------
/components/jets/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jets",
3 | "main": "jets.min.js",
4 | "homepage": "https://github.com/NeXTs/Jets.js",
5 | "authors": [
6 | "Denis Lukov "
7 | ],
8 | "description": "Native CSS search engine",
9 | "moduleType": [
10 | "amd",
11 | "globals",
12 | "commonjs"
13 | ],
14 | "keywords": [
15 | "css",
16 | "search",
17 | "list",
18 | "jets"
19 | ],
20 | "license": "MIT",
21 | "ignore": ["/node_modules", "npm-debug.log"]
22 | }
23 |
--------------------------------------------------------------------------------
/components/jets/jets.js:
--------------------------------------------------------------------------------
1 | /*! Jets.js - v0.11.0 - 2016-07-30
2 | * http://NeXTs.github.com/Jets.js/
3 | * Copyright (c) 2015 Denis Lukov; Licensed MIT */
4 |
5 | ;(function(root, definition) {
6 | if (typeof module != 'undefined') module.exports = definition();
7 | else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
8 | else root['Jets'] = definition();
9 | }(this, function() {
10 | "use strict"
11 |
12 | function Jets(opts) {
13 | if( ! (this instanceof Jets)) {
14 | return new Jets(opts);
15 | }
16 | var self = this;
17 | ['searchTag', 'contentTag'].forEach(function(param) {
18 | var name = param.replace('Tag', ''),
19 | queryMethod = 'querySelector' + (param == 'contentTag' ? 'All' : '');
20 | self[name + '_tag'] = document[queryMethod](opts[param]);
21 | self[name + '_param'] = opts[param];
22 | if( ! self[name + '_tag']) {
23 | throw new Error('Error! Could not find ' + param + ' element');
24 | }
25 | });
26 |
27 | var defaults = {
28 | searchSelector: '*AND',
29 | hideBy: 'display:none',
30 | diacriticsMap: {}
31 | }
32 |
33 | self.options = {};
34 | ['columns', 'addImportant', 'searchSelector', 'hideBy', 'manualContentHandling', 'callSearchManually', 'diacriticsMap', 'didSearch', 'invert'].forEach(function(name) {
35 | self.options[name] = opts[name] || defaults[name];
36 | });
37 | if(this.options.searchSelector.length > 1) {
38 | var searchSelector = self.options['searchSelector'].trim();
39 | self.options.searchSelector = searchSelector.substr(0, 1);
40 | self.options.searchSelectorMode = searchSelector.substr(1).toUpperCase();
41 | }
42 |
43 | var last_search_query;
44 | self.search = function() {
45 | if(last_search_query == (last_search_query = self.search_tag.value)) return;
46 | self._applyCSS();
47 | self.options.didSearch && self.options.didSearch(self.search_tag.value);
48 | };
49 | self._onSearch = function(event) {
50 | if(event.type == 'keydown')
51 | return setTimeout(self.search, 0);
52 | self.search();
53 | };
54 | self.destroy = function() {
55 | if( ! self.options.callSearchManually) self._processEventListeners('remove');
56 | self._destroy();
57 | };
58 |
59 | if( ! self.options.callSearchManually) self._processEventListeners('add');
60 | self._addStyleTag();
61 | self._setJets();
62 | self._applyCSS();
63 | }
64 |
65 | Jets.prototype = {
66 | constructor: Jets,
67 | _processEventListeners: function(action) {
68 | ['input', 'keydown', 'change'].forEach(function(event_type) {
69 | this.search_tag[action + 'EventListener'](event_type, this._onSearch);
70 | }.bind(this));
71 | },
72 | _applyCSS: function() {
73 | var options = this.options,
74 | search_phrase = this.replaceDiacritics(this.search_tag.value.trim().toLowerCase().replace(/\s\s+/g, ' ')).replace(/\\/g, '\\\\'),
75 | words = options.searchSelectorMode
76 | ? search_phrase.split(' ').filter(function(item, pos, arr) { return arr.indexOf(item) == pos; })
77 | : [search_phrase],
78 | is_strict_selector = options.searchSelectorMode == 'AND',
79 | selectors = [];
80 | for(var i = 0, ii = words.length; i < ii; i++) {
81 | selectors.push((is_strict_selector ? this.content_param + '>' : '') + (options.invert ? '' : ':not(') + '[data-jets' +
82 | options.searchSelector + '="' + words[i] + '"]' + (options.invert ? '' : ')'));
83 | }
84 | var hide_rules = options.hideBy.split(';').filter(Boolean).map(function(rule) { return rule + (options.addImportant ? '!important' : '') });
85 | var css_rule = (is_strict_selector ? '' : this.content_param + '>') + selectors.join(is_strict_selector ? ',' : '') + '{' + hide_rules.join(';') + '}';
86 | this.styleTag.innerHTML = search_phrase.length ? css_rule : '';
87 | },
88 | _addStyleTag: function() {
89 | this.styleTag = document.createElement('style');
90 | document.head.appendChild(this.styleTag);
91 | },
92 | _getText: function(tag) {
93 | return tag && (tag.textContent || tag.innerText) || '';
94 | },
95 | _getContentTags: function(query) {
96 | return Array.prototype.slice.call(this.content_tag).reduce(function(all, elem) {
97 | return all.concat(Array.prototype.slice.call(elem.querySelectorAll(query || ':scope > *')));
98 | }, []);
99 | },
100 | _setJets: function(query, force) {
101 | var self = this,
102 | tags = self._getContentTags(force ? '' : query), text;
103 | for(var i = 0, tag; tag = tags[i]; i++) {
104 | if(tag.hasAttribute('data-jets') && ! force) continue;
105 | text = this.options.manualContentHandling
106 | ? this.options.manualContentHandling(tag)
107 | : self.options.columns && self.options.columns.length
108 | ? self.options.columns.map(function(column) {
109 | return self._getText(tag.children[column]);
110 | }).join(' ')
111 | : self._getText(tag);
112 | tag.setAttribute('data-jets', self.replaceDiacritics(text.trim().replace(/\s+/g, ' ').toLowerCase()));
113 | };
114 | },
115 | replaceDiacritics: function(text) {
116 | var diacritics = this.options.diacriticsMap;
117 | for(var letter in diacritics) if(diacritics.hasOwnProperty(letter)) {
118 | for(var i = 0, ii = diacritics[letter].length; i < ii; i++) {
119 | text = text.replace(new RegExp(diacritics[letter][i], 'g'), letter);
120 | }
121 | }
122 | return text;
123 | },
124 | update: function(force) {
125 | this._setJets(':scope > :not([data-jets])', force);
126 | },
127 | _destroy: function() {
128 | this.styleTag.parentNode && document.head.removeChild(this.styleTag);
129 | var tags = this._getContentTags();
130 | for(var i = 0, tag; tag = tags[i]; i++) {
131 | tag.removeAttribute('data-jets');
132 | }
133 | }
134 | }
135 |
136 | // :scope polyfill
137 | // http://stackoverflow.com/a/17989803/1221082
138 | ;(function(doc, proto) {
139 | try {
140 | doc.querySelector(':scope body');
141 | } catch (err) {
142 | ['querySelector', 'querySelectorAll'].forEach(function(method) {
143 | var nativ = proto[method];
144 | proto[method] = function(selectors) {
145 | if (/(^|,)\s*:scope/.test(selectors)) {
146 | var id = this.id;
147 | this.id = 'ID_' + Date.now();
148 | selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.id);
149 | var result = doc[method](selectors);
150 | this.id = id;
151 | return result;
152 | } else {
153 | return nativ.call(this, selectors);
154 | }
155 | }
156 | });
157 | }
158 | })(window.document, Element.prototype);
159 |
160 | return Jets;
161 | }));
162 |
--------------------------------------------------------------------------------
/components/jets/jets.min.js:
--------------------------------------------------------------------------------
1 | /*! Jets.js - v0.11.0 - 2016-07-30
2 | * http://NeXTs.github.com/Jets.js/
3 | * Copyright (c) 2015 Denis Lukov; Licensed MIT */
4 |
5 | ;(function(g,b){"undefined"!=typeof module?module.exports=b():"function"==typeof define&&"object"==typeof define.amd?define(b):g.Jets=b()})(this,function(){function g(b){if(!(this instanceof g))return new g(b);var a=this;["searchTag","contentTag"].forEach(function(c){var d=c.replace("Tag","");a[d+"_tag"]=document["querySelector"+("contentTag"==c?"All":"")](b[c]);a[d+"_param"]=b[c];if(!a[d+"_tag"])throw Error("Error! Could not find "+c+" element");});var c={searchSelector:"*AND",hideBy:"display:none",
6 | diacriticsMap:{}};a.options={};"columns addImportant searchSelector hideBy manualContentHandling callSearchManually diacriticsMap didSearch invert".split(" ").forEach(function(d){a.options[d]=b[d]||c[d]});if(1":"")+(b.invert?"":":not(")+"[data-jets"+b.searchSelector+'="'+c[h]+'"]'+(b.invert?"":")"));c=b.hideBy.split(";").filter(Boolean).map(function(a){return a+(b.addImportant?"!important":"")});d=(d?"":
9 | this.content_param+">")+e.join(d?",":"")+"{"+c.join(";")+"}";this.styleTag.innerHTML=a.length?d:""},_addStyleTag:function(){this.styleTag=document.createElement("style");document.head.appendChild(this.styleTag)},_getText:function(b){return b&&(b.textContent||b.innerText)||""},_getContentTags:function(b){return Array.prototype.slice.call(this.content_tag).reduce(function(a,c){return a.concat(Array.prototype.slice.call(c.querySelectorAll(b||":scope > *")))},[])},_setJets:function(b,a){for(var c=this,
10 | d=c._getContentTags(a?"":b),e,h=0,f;f=d[h];h++)if(!f.hasAttribute("data-jets")||a)e=this.options.manualContentHandling?this.options.manualContentHandling(f):c.options.columns&&c.options.columns.length?c.options.columns.map(function(a){return c._getText(f.children[a])}).join(" "):c._getText(f),f.setAttribute("data-jets",c.replaceDiacritics(e.trim().replace(/\s+/g," ").toLowerCase()))},replaceDiacritics:function(b){var a=this.options.diacriticsMap,c;for(c in a)if(a.hasOwnProperty(c))for(var d=0,e=a[c].length;d<
11 | e;d++)b=b.replace(new RegExp(a[c][d],"g"),c);return b},update:function(b){this._setJets(":scope > :not([data-jets])",b)},_destroy:function(){this.styleTag.parentNode&&document.head.removeChild(this.styleTag);for(var b=this._getContentTags(),a=0,c;c=b[a];a++)c.removeAttribute("data-jets")}};(function(b,a){try{b.querySelector(":scope body")}catch(c){["querySelector","querySelectorAll"].forEach(function(c){var e=a[c];a[c]=function(a){if(/(^|,)\s*:scope/.test(a)){var f=this.id;this.id="ID_"+Date.now();
12 | a=a.replace(/((^|,)\s*):scope/g,"$1#"+this.id);a=b[c](a);this.id=f;return a}return e.call(this,a)}})}})(window.document,Element.prototype);return g});
--------------------------------------------------------------------------------
/components/jets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jets",
3 | "version": "0.11.0",
4 | "description": "Native CSS search engine",
5 | "main": "jets.min.js",
6 | "scripts": {
7 | "test": "mocha-phantomjs ./test/index.html"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git@github.com:NeXTs/Jets.js.git"
12 | },
13 | "keywords": [
14 | "css",
15 | "search",
16 | "list",
17 | "jets"
18 | ],
19 | "author": "Denis Lukov (https://github.com/NeXTs)",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "chai": "^3.2.0",
23 | "jquery": "^3.0.0",
24 | "mocha": "^2.3.2",
25 | "mocha-phantomjs": "^4.0.2",
26 | "phantomjs": "2.1.7"
27 | },
28 | "bugs": {
29 | "url": "https://github.com/NeXTs/Jets.js/issues"
30 | },
31 | "homepage": "https://github.com/NeXTs/Jets.js"
32 | }
33 |
--------------------------------------------------------------------------------
/components/jets/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jets.js tests
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
29 |
30 |
--------------------------------------------------------------------------------
/components/jets/test/test.js:
--------------------------------------------------------------------------------
1 | describe('Jets', function() {
2 |
3 | var people = ['John Doe', 'Mike Doe', 'Alex Smith', 'Alice Vazovsky', 'Denis Koen'],
4 | $search = $('#jetsSearch'),
5 | searchNode = $search.get(0),
6 | $content = $('#jetsContent');
7 |
8 |
9 | /*
10 | * Helper functions
11 | */
12 | function trigger(ev, _elem) {
13 | elem = _elem || searchNode
14 | if ("createEvent" in document) {
15 | var evt = document.createEvent("HTMLEvents");
16 | evt.initEvent(ev, false, true);
17 | elem.dispatchEvent(evt);
18 | } else elem.fireEvent("on" + ev);
19 | }
20 |
21 | function ext(options) {
22 | return $.extend({
23 | searchTag: '#jetsSearch',
24 | contentTag: '#jetsContent'
25 | }, options || {});
26 | }
27 |
28 | function make(action) {
29 | action();
30 | trigger('change');
31 | }
32 |
33 |
34 | /*
35 | * Mocha helpers
36 | */
37 | before(function() {
38 | $content.html($.map(people, function(val, i){
39 | var reversed = val.split('').reverse().join('');
40 | return '
' + val + ' | ' + i + ' |
'
41 | }).join(''));
42 | })
43 |
44 | afterEach(function() {
45 | jet && jet.destroy();
46 | $search.val('');
47 | trigger('change', searchNode);
48 | })
49 |
50 | after(function() {
51 | jet && jet.destroy();
52 | $search.val('');
53 | $content.find('[data-custom]').removeAttr('data-custom');
54 | })
55 |
56 |
57 | /*
58 | * Specs
59 | */
60 |
61 | it('Should hide inappropriate rows', function() {
62 | jet = new Jets(ext({}));
63 | make(function() {
64 | $search.val(people[0]);
65 | })
66 | assert.lengthOf($content.children(':visible'), 1);
67 | })
68 |
69 | it('Should show all rows if search query not specified', function() {
70 | jet = new Jets(ext({}));
71 | make(function() {
72 | $search.val('');
73 | })
74 | assert.lengthOf($content.children(':visible'), people.length);
75 | })
76 |
77 | it('Should hide all rows if no suitable results', function() {
78 | jet = new Jets(ext({}));
79 | make(function() {
80 | $search.val('404');
81 | })
82 | assert.lengthOf($content.children(':visible'), 0);
83 | })
84 |
85 | describe('On init', function() {
86 |
87 | it('Shoud add style tag after init', function() {
88 | jet = new Jets(ext({}));
89 | assert.lengthOf($('head').find(jet.styleTag), 1);
90 | })
91 |
92 | it('Should add "data-jets" attribute', function() {
93 | jet = new Jets(ext({
94 | columns: [0]
95 | }));
96 | var $firstRow = $content.children(':first');
97 | assert.equal($firstRow.attr('data-jets'), $firstRow.children(':first').text().trim().toLowerCase());
98 | })
99 |
100 | })
101 |
102 | describe('On destroy', function() {
103 |
104 | it('Shoud remove style tag after destroy', function() {
105 | jet = new Jets(ext({}));
106 | jet.destroy();
107 | assert.lengthOf($('head').find(jet.styleTag), 0);
108 | })
109 |
110 | it('Should remove "data-jets" attribute', function() {
111 | jet = new Jets(ext({}));
112 | jet.destroy()
113 | assert.isUndefined($content.children(':first').attr('data-jets'));
114 | })
115 |
116 | })
117 |
118 | describe('Columns option', function() {
119 |
120 | it('Should search by any column', function() {
121 | jet = new Jets(ext({}));
122 | make(function() {
123 | $search.val('1');
124 | })
125 | assert.lengthOf($content.children(':visible'), 1);
126 | })
127 |
128 | it('Should find one row by first column', function() {
129 | jet = new Jets(ext({
130 | columns: [0]
131 | }));
132 | make(function() {
133 | $search.val(people[0]);
134 | })
135 | assert.lengthOf($content.children(':visible'), 1);
136 | })
137 |
138 | it('Should avoid second column', function() {
139 | jet = new Jets(ext({
140 | columns: [0]
141 | }));
142 | make(function() {
143 | $search.val('1');
144 | })
145 | assert.lengthOf($content.children(':visible'), 0);
146 | })
147 |
148 | })
149 |
150 | describe('addImportant option', function() {
151 |
152 | it('Should avoid !important by default', function() {
153 | jet = new Jets(ext({}));
154 | make(function() {
155 | $search.val(people[0]);
156 | })
157 | assert.notInclude($(jet.styleTag).html(), '!important');
158 | })
159 |
160 | it('Should add !important', function() {
161 | jet = new Jets(ext({
162 | addImportant: true
163 | }));
164 | make(function() {
165 | $search.val(people[0]);
166 | })
167 | assert.include($(jet.styleTag).html(), '!important');
168 | })
169 |
170 | })
171 |
172 | describe('searchSelector option', function() {
173 |
174 | function execute(selector) {
175 | it('Should use custom selector: ' + selector, function() {
176 | jet = new Jets(ext({
177 | searchSelector: selector
178 | }));
179 | make(function() {
180 | $search.val(people[0]);
181 | })
182 | assert.include($(jet.styleTag).html(), selector);
183 | })
184 | }
185 |
186 | var selectors = ['^', '$', '~', '|'];
187 | for(var i = 0, ii = selectors.length; i < ii; i++) {
188 | execute(selectors[i]);
189 | }
190 |
191 | it('Should find one result with * selector', function() {
192 |
193 | jet = new Jets(ext({
194 | searchSelector: '*'
195 | }));
196 | make(function() {
197 | $search.val('John Doe');
198 | })
199 | assert.lengthOf($content.children(':visible'), 1);
200 |
201 | })
202 |
203 | it('Should hide all results with * selector and mixed words', function() {
204 |
205 | jet = new Jets(ext({
206 | searchSelector: '*'
207 | }));
208 | make(function() {
209 | $search.val('Doe John');
210 | })
211 | assert.lengthOf($content.children(':visible'), 0);
212 |
213 | })
214 |
215 | it('Should find one result with *AND selector and mixed words', function() {
216 |
217 | jet = new Jets(ext({
218 | searchSelector: '*AND'
219 | }));
220 | make(function() {
221 | $search.val('Doe John');
222 | })
223 | assert.lengthOf($content.children(':visible'), 1);
224 |
225 | })
226 |
227 | it('Should find one result with *AND selector and part of word', function() {
228 |
229 | jet = new Jets(ext({
230 | searchSelector: '*AND'
231 | }));
232 | make(function() {
233 | $search.val('John oe');
234 | })
235 | assert.lengthOf($content.children(':visible'), 1);
236 |
237 | })
238 |
239 | it('Should find three results with *OR selector and part of word', function() {
240 |
241 | jet = new Jets(ext({
242 | searchSelector: '*OR'
243 | }));
244 | make(function() {
245 | $search.val('John oe');
246 | })
247 | assert.lengthOf($content.children(':visible'), 3);
248 |
249 | })
250 |
251 | })
252 |
253 | describe('manualContentHandling option', function() {
254 |
255 | it('Should fetch content by custom rule', function() {
256 | jet = new Jets(ext({
257 | manualContentHandling: function(tag) {
258 | return $(tag).children(0).attr('data-custom')
259 | }
260 | }));
261 | make(function() {
262 | $search.val(people[0].split('').reverse().join(''));
263 | })
264 | assert.lengthOf($content.children(':visible'), 1);
265 | })
266 |
267 | })
268 |
269 | describe('invert option', function() {
270 |
271 | it('Should invert results', function() {
272 | jet = new Jets(ext({
273 | invert: true
274 | }));
275 | make(function() {
276 | $search.val(people[0]);
277 | })
278 | assert.lengthOf($content.children(':visible'), 3);
279 | })
280 |
281 | })
282 |
283 | describe('callSearchManually option', function() {
284 |
285 | it('Should not call search on typing', function() {
286 | jet = new Jets(ext({
287 | callSearchManually: true
288 | }));
289 | make(function() {
290 | $search.val(people[0]);
291 | })
292 | assert.lengthOf($content.children(':visible'), 5);
293 | })
294 |
295 | it('Should call search by manual calling .search', function() {
296 | jet = new Jets(ext({
297 | callSearchManually: true
298 | }));
299 | make(function() {
300 | $search.val(people[0]);
301 | })
302 | jet.search();
303 | assert.lengthOf($content.children(':visible'), 1);
304 | })
305 |
306 | })
307 |
308 | })
--------------------------------------------------------------------------------
/components/jquery/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery",
3 | "version": "1.8.3",
4 | "main": "./jquery.js",
5 | "dependencies": {},
6 | "gitHead": "7d6149dea8a6cdd0d1fbeca8a189f4d464d6f0cd",
7 | "_id": "jquery@1.8.3",
8 | "readme": "ERROR: No README.md file found!",
9 | "description": "ERROR: No README.md file found!",
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/components/jquery.git"
13 | }
14 | }
--------------------------------------------------------------------------------
/components/jquery/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "components/jquery",
3 | "description": "jQuery JavaScript Library",
4 | "type": "component",
5 | "homepage": "http://jquery.com",
6 | "license": "MIT",
7 | "support": {
8 | "irc": "irc://irc.freenode.org/jquery",
9 | "issues": "http://bugs.jquery.com",
10 | "forum": "http://forum.jquery.com",
11 | "wiki": "http://docs.jquery.com/",
12 | "source": "https://github.com/jquery/jquery"
13 | },
14 | "authors": [
15 | {
16 | "name": "John Resig",
17 | "email": "jeresig@gmail.com"
18 | }
19 | ],
20 | "extra": {
21 | "js": "jquery.js"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/components/underscore/.gitignore:
--------------------------------------------------------------------------------
1 | raw
--------------------------------------------------------------------------------
/components/underscore/.npmignore:
--------------------------------------------------------------------------------
1 | test/
2 | Rakefile
3 | docs/
--------------------------------------------------------------------------------
/components/underscore/CNAME:
--------------------------------------------------------------------------------
1 | underscorejs.org
2 |
--------------------------------------------------------------------------------
/components/underscore/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to contribute to Underscore.js
2 |
3 | * Before you open a ticket or send a pull request, [search](https://github.com/documentcloud/underscore/issues) for previous discussions about the same feature or issue. Add to the earlier ticket if you find one.
4 |
5 | * Before sending a pull request for a feature, be sure to have [tests](http://underscorejs.org/test/test.html).
6 |
7 | * Use the same coding style as the rest of the [codebase](https://github.com/documentcloud/underscore/blob/master/underscore.js).
8 |
9 | * In your pull request, do not add documentation or re-build the minified `underscore-min.js` file. We'll do those things before cutting a new release.
--------------------------------------------------------------------------------
/components/underscore/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009-2012 Jeremy Ashkenas, DocumentCloud
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/components/underscore/README.md:
--------------------------------------------------------------------------------
1 | __
2 | /\ \ __
3 | __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____
4 | /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\
5 | \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\
6 | \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/
7 | \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/
8 | \ \____/
9 | \/___/
10 |
11 | Underscore.js is a utility-belt library for JavaScript that provides
12 | support for the usual functional suspects (each, map, reduce, filter...)
13 | without extending any core JavaScript objects.
14 |
15 | For Docs, License, Tests, and pre-packed downloads, see:
16 | http://underscorejs.org
17 |
18 | Many thanks to our contributors:
19 | https://github.com/documentcloud/underscore/contributors
20 |
--------------------------------------------------------------------------------
/components/underscore/Rakefile:
--------------------------------------------------------------------------------
1 | desc "Use Uglify JS to compress Underscore.js"
2 | task :build do
3 | sh "uglifyjs underscore.js -c -m -o underscore-min.js"
4 | end
5 |
6 | desc "Build the docco documentation"
7 | task :doc do
8 | sh "docco underscore.js"
9 | end
10 |
11 |
--------------------------------------------------------------------------------
/components/underscore/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "underscore",
3 | "version": "1.4.3",
4 | "repository": {
5 | "type": "git",
6 | "url": "git://github.com/documentcloud/underscore.git"
7 | }
8 | }
--------------------------------------------------------------------------------
/components/underscore/docs/docco.css:
--------------------------------------------------------------------------------
1 | /*--------------------- Layout and Typography ----------------------------*/
2 | body {
3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
4 | font-size: 15px;
5 | line-height: 22px;
6 | color: #252519;
7 | margin: 0; padding: 0;
8 | }
9 | a {
10 | color: #261a3b;
11 | }
12 | a:visited {
13 | color: #261a3b;
14 | }
15 | p {
16 | margin: 0 0 15px 0;
17 | }
18 | h1, h2, h3, h4, h5, h6 {
19 | margin: 0px 0 15px 0;
20 | }
21 | h1 {
22 | margin-top: 40px;
23 | }
24 | hr {
25 | border: 0 none;
26 | border-top: 1px solid #e5e5ee;
27 | height: 1px;
28 | margin: 20px 0;
29 | }
30 | #container {
31 | position: relative;
32 | }
33 | #background {
34 | position: fixed;
35 | top: 0; left: 525px; right: 0; bottom: 0;
36 | background: #f5f5ff;
37 | border-left: 1px solid #e5e5ee;
38 | z-index: -1;
39 | }
40 | #jump_to, #jump_page {
41 | background: white;
42 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
43 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
44 | font: 10px Arial;
45 | text-transform: uppercase;
46 | cursor: pointer;
47 | text-align: right;
48 | }
49 | #jump_to, #jump_wrapper {
50 | position: fixed;
51 | right: 0; top: 0;
52 | padding: 5px 10px;
53 | }
54 | #jump_wrapper {
55 | padding: 0;
56 | display: none;
57 | }
58 | #jump_to:hover #jump_wrapper {
59 | display: block;
60 | }
61 | #jump_page {
62 | padding: 5px 0 3px;
63 | margin: 0 0 25px 25px;
64 | }
65 | #jump_page .source {
66 | display: block;
67 | padding: 5px 10px;
68 | text-decoration: none;
69 | border-top: 1px solid #eee;
70 | }
71 | #jump_page .source:hover {
72 | background: #f5f5ff;
73 | }
74 | #jump_page .source:first-child {
75 | }
76 | table td {
77 | border: 0;
78 | outline: 0;
79 | }
80 | td.docs, th.docs {
81 | max-width: 450px;
82 | min-width: 450px;
83 | min-height: 5px;
84 | padding: 10px 25px 1px 50px;
85 | overflow-x: hidden;
86 | vertical-align: top;
87 | text-align: left;
88 | }
89 | .docs pre {
90 | margin: 15px 0 15px;
91 | padding-left: 15px;
92 | }
93 | .docs p tt, .docs p code {
94 | background: #f8f8ff;
95 | border: 1px solid #dedede;
96 | font-size: 12px;
97 | padding: 0 0.2em;
98 | }
99 | .pilwrap {
100 | position: relative;
101 | }
102 | .pilcrow {
103 | font: 12px Arial;
104 | text-decoration: none;
105 | color: #454545;
106 | position: absolute;
107 | top: 3px; left: -20px;
108 | padding: 1px 2px;
109 | opacity: 0;
110 | -webkit-transition: opacity 0.2s linear;
111 | }
112 | td.docs:hover .pilcrow {
113 | opacity: 1;
114 | }
115 | td.code, th.code {
116 | padding: 14px 15px 16px 25px;
117 | width: 100%;
118 | vertical-align: top;
119 | background: #f5f5ff;
120 | border-left: 1px solid #e5e5ee;
121 | }
122 | pre, tt, code {
123 | font-size: 12px; line-height: 18px;
124 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
125 | margin: 0; padding: 0;
126 | }
127 |
128 |
129 | /*---------------------- Syntax Highlighting -----------------------------*/
130 | td.linenos { background-color: #f0f0f0; padding-right: 10px; }
131 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
132 | body .hll { background-color: #ffffcc }
133 | body .c { color: #408080; font-style: italic } /* Comment */
134 | body .err { border: 1px solid #FF0000 } /* Error */
135 | body .k { color: #954121 } /* Keyword */
136 | body .o { color: #666666 } /* Operator */
137 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
138 | body .cp { color: #BC7A00 } /* Comment.Preproc */
139 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */
140 | body .cs { color: #408080; font-style: italic } /* Comment.Special */
141 | body .gd { color: #A00000 } /* Generic.Deleted */
142 | body .ge { font-style: italic } /* Generic.Emph */
143 | body .gr { color: #FF0000 } /* Generic.Error */
144 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
145 | body .gi { color: #00A000 } /* Generic.Inserted */
146 | body .go { color: #808080 } /* Generic.Output */
147 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
148 | body .gs { font-weight: bold } /* Generic.Strong */
149 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
150 | body .gt { color: #0040D0 } /* Generic.Traceback */
151 | body .kc { color: #954121 } /* Keyword.Constant */
152 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
153 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
154 | body .kp { color: #954121 } /* Keyword.Pseudo */
155 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
156 | body .kt { color: #B00040 } /* Keyword.Type */
157 | body .m { color: #666666 } /* Literal.Number */
158 | body .s { color: #219161 } /* Literal.String */
159 | body .na { color: #7D9029 } /* Name.Attribute */
160 | body .nb { color: #954121 } /* Name.Builtin */
161 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
162 | body .no { color: #880000 } /* Name.Constant */
163 | body .nd { color: #AA22FF } /* Name.Decorator */
164 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */
165 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
166 | body .nf { color: #0000FF } /* Name.Function */
167 | body .nl { color: #A0A000 } /* Name.Label */
168 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
169 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */
170 | body .nv { color: #19469D } /* Name.Variable */
171 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
172 | body .w { color: #bbbbbb } /* Text.Whitespace */
173 | body .mf { color: #666666 } /* Literal.Number.Float */
174 | body .mh { color: #666666 } /* Literal.Number.Hex */
175 | body .mi { color: #666666 } /* Literal.Number.Integer */
176 | body .mo { color: #666666 } /* Literal.Number.Oct */
177 | body .sb { color: #219161 } /* Literal.String.Backtick */
178 | body .sc { color: #219161 } /* Literal.String.Char */
179 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
180 | body .s2 { color: #219161 } /* Literal.String.Double */
181 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
182 | body .sh { color: #219161 } /* Literal.String.Heredoc */
183 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
184 | body .sx { color: #954121 } /* Literal.String.Other */
185 | body .sr { color: #BB6688 } /* Literal.String.Regex */
186 | body .s1 { color: #219161 } /* Literal.String.Single */
187 | body .ss { color: #19469D } /* Literal.String.Symbol */
188 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */
189 | body .vc { color: #19469D } /* Name.Variable.Class */
190 | body .vg { color: #19469D } /* Name.Variable.Global */
191 | body .vi { color: #19469D } /* Name.Variable.Instance */
192 | body .il { color: #666666 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/components/underscore/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paulirish/web-feature-availability/f9ed3a1890ed0546c927548028765ea937691afc/components/underscore/docs/favicon.ico
--------------------------------------------------------------------------------
/components/underscore/docs/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paulirish/web-feature-availability/f9ed3a1890ed0546c927548028765ea937691afc/components/underscore/docs/images/background.png
--------------------------------------------------------------------------------
/components/underscore/docs/images/underscore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paulirish/web-feature-availability/f9ed3a1890ed0546c927548028765ea937691afc/components/underscore/docs/images/underscore.png
--------------------------------------------------------------------------------
/components/underscore/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paulirish/web-feature-availability/f9ed3a1890ed0546c927548028765ea937691afc/components/underscore/favicon.ico
--------------------------------------------------------------------------------
/components/underscore/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./underscore');
2 |
--------------------------------------------------------------------------------
/components/underscore/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "underscore",
3 | "description" : "JavaScript's functional programming helper library.",
4 | "homepage" : "http://underscorejs.org",
5 | "keywords" : ["util", "functional", "server", "client", "browser"],
6 | "author" : "Jeremy Ashkenas ",
7 | "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"},
8 | "main" : "underscore.js",
9 | "version" : "1.4.3"
10 | }
11 |
--------------------------------------------------------------------------------
/components/underscore/test/arrays.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | module("Arrays");
4 |
5 | test("first", function() {
6 | equal(_.first([1,2,3]), 1, 'can pull out the first element of an array');
7 | equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
8 | equal(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first');
9 | equal(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first');
10 | equal(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first');
11 | var result = (function(){ return _.first(arguments); })(4, 3, 2, 1);
12 | equal(result, 4, 'works on an arguments object.');
13 | result = _.map([[1,2,3],[1,2,3]], _.first);
14 | equal(result.join(','), '1,1', 'works well with _.map');
15 | result = (function() { return _.take([1,2,3], 2); })();
16 | equal(result.join(','), '1,2', 'aliased as take');
17 |
18 | equal(_.first(null), undefined, 'handles nulls');
19 | });
20 |
21 | test("rest", function() {
22 | var numbers = [1, 2, 3, 4];
23 | equal(_.rest(numbers).join(", "), "2, 3, 4", 'working rest()');
24 | equal(_.rest(numbers, 0).join(", "), "1, 2, 3, 4", 'working rest(0)');
25 | equal(_.rest(numbers, 2).join(', '), '3, 4', 'rest can take an index');
26 | var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4);
27 | equal(result.join(', '), '2, 3, 4', 'aliased as tail and works on arguments object');
28 | result = _.map([[1,2,3],[1,2,3]], _.rest);
29 | equal(_.flatten(result).join(','), '2,3,2,3', 'works well with _.map');
30 | result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
31 | equal(result.join(', '), '2, 3, 4', 'aliased as drop and works on arguments object');
32 | });
33 |
34 | test("initial", function() {
35 | equal(_.initial([1,2,3,4,5]).join(", "), "1, 2, 3, 4", 'working initial()');
36 | equal(_.initial([1,2,3,4],2).join(", "), "1, 2", 'initial can take an index');
37 | var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4);
38 | equal(result.join(", "), "1, 2, 3", 'initial works on arguments object');
39 | result = _.map([[1,2,3],[1,2,3]], _.initial);
40 | equal(_.flatten(result).join(','), '1,2,1,2', 'initial works with _.map');
41 | });
42 |
43 | test("last", function() {
44 | equal(_.last([1,2,3]), 3, 'can pull out the last element of an array');
45 | equal(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last');
46 | equal(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last');
47 | equal(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last');
48 | var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4);
49 | equal(result, 4, 'works on an arguments object');
50 | result = _.map([[1,2,3],[1,2,3]], _.last);
51 | equal(result.join(','), '3,3', 'works well with _.map');
52 |
53 | equal(_.last(null), undefined, 'handles nulls');
54 | });
55 |
56 | test("compact", function() {
57 | equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
58 | var result = (function(){ return _.compact(arguments).length; })(0, 1, false, 2, false, 3);
59 | equal(result, 3, 'works on an arguments object');
60 | });
61 |
62 | test("flatten", function() {
63 | var list = [1, [2], [3, [[[4]]]]];
64 | deepEqual(_.flatten(list), [1,2,3,4], 'can flatten nested arrays');
65 | deepEqual(_.flatten(list, true), [1,2,3,[[[4]]]], 'can shallowly flatten nested arrays');
66 | var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]);
67 | deepEqual(result, [1,2,3,4], 'works on an arguments object');
68 | });
69 |
70 | test("without", function() {
71 | var list = [1, 2, 1, 0, 3, 1, 4];
72 | equal(_.without(list, 0, 1).join(', '), '2, 3, 4', 'can remove all instances of an object');
73 | var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4);
74 | equal(result.join(', '), '2, 3, 4', 'works on an arguments object');
75 |
76 | var list = [{one : 1}, {two : 2}];
77 | ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.');
78 | ok(_.without(list, list[0]).length == 1, 'ditto.');
79 | });
80 |
81 | test("uniq", function() {
82 | var list = [1, 2, 1, 3, 1, 4];
83 | equal(_.uniq(list).join(', '), '1, 2, 3, 4', 'can find the unique values of an unsorted array');
84 |
85 | var list = [1, 1, 1, 2, 2, 3];
86 | equal(_.uniq(list, true).join(', '), '1, 2, 3', 'can find the unique values of a sorted array faster');
87 |
88 | var list = [{name:'moe'}, {name:'curly'}, {name:'larry'}, {name:'curly'}];
89 | var iterator = function(value) { return value.name; };
90 | equal(_.map(_.uniq(list, false, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator');
91 |
92 | equal(_.map(_.uniq(list, iterator), iterator).join(', '), 'moe, curly, larry', 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
93 |
94 | var iterator = function(value) { return value +1; };
95 | var list = [1, 2, 2, 3, 4, 4];
96 | equal(_.uniq(list, true, iterator).join(', '), '1, 2, 3, 4', 'iterator works with sorted array');
97 |
98 | var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4);
99 | equal(result.join(', '), '1, 2, 3, 4', 'works on an arguments object');
100 | });
101 |
102 | test("intersection", function() {
103 | var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
104 | equal(_.intersection(stooges, leaders).join(''), 'moe', 'can take the set intersection of two arrays');
105 | equal(_(stooges).intersection(leaders).join(''), 'moe', 'can perform an OO-style intersection');
106 | var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry');
107 | equal(result.join(''), 'moe', 'works on an arguments object');
108 | });
109 |
110 | test("union", function() {
111 | var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
112 | equal(result.join(' '), '1 2 3 30 40', 'takes the union of a list of arrays');
113 |
114 | var result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]);
115 | equal(result.join(' '), '1 2 3 30 40 1', 'takes the union of a list of nested arrays');
116 | });
117 |
118 | test("difference", function() {
119 | var result = _.difference([1, 2, 3], [2, 30, 40]);
120 | equal(result.join(' '), '1 3', 'takes the difference of two arrays');
121 |
122 | var result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]);
123 | equal(result.join(' '), '3 4', 'takes the difference of three arrays');
124 | });
125 |
126 | test('zip', function() {
127 | var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
128 | var stooges = _.zip(names, ages, leaders);
129 | equal(String(stooges), 'moe,30,true,larry,40,,curly,50,', 'zipped together arrays of different lengths');
130 | });
131 |
132 | test('object', function() {
133 | var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
134 | var shouldBe = {moe: 30, larry: 40, curly: 50};
135 | ok(_.isEqual(result, shouldBe), 'two arrays zipped together into an object');
136 |
137 | result = _.object([['one', 1], ['two', 2], ['three', 3]]);
138 | shouldBe = {one: 1, two: 2, three: 3};
139 | ok(_.isEqual(result, shouldBe), 'an array of pairs zipped together into an object');
140 |
141 | var stooges = {moe: 30, larry: 40, curly: 50};
142 | ok(_.isEqual(_.object(_.pairs(stooges)), stooges), 'an object converted to pairs and back to an object');
143 |
144 | ok(_.isEqual(_.object(null), {}), 'handles nulls');
145 | });
146 |
147 | test("indexOf", function() {
148 | var numbers = [1, 2, 3];
149 | numbers.indexOf = null;
150 | equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
151 | var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
152 | equal(result, 1, 'works on an arguments object');
153 | equal(_.indexOf(null, 2), -1, 'handles nulls properly');
154 |
155 | var numbers = [10, 20, 30, 40, 50], num = 35;
156 | var index = _.indexOf(numbers, num, true);
157 | equal(index, -1, '35 is not in the list');
158 |
159 | numbers = [10, 20, 30, 40, 50]; num = 40;
160 | index = _.indexOf(numbers, num, true);
161 | equal(index, 3, '40 is in the list');
162 |
163 | numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
164 | index = _.indexOf(numbers, num, true);
165 | equal(index, 1, '40 is in the list');
166 |
167 | numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
168 | index = _.indexOf(numbers, 2, 5);
169 | equal(index, 7, 'supports the fromIndex argument');
170 | });
171 |
172 | test("lastIndexOf", function() {
173 | var numbers = [1, 0, 1];
174 | equal(_.lastIndexOf(numbers, 1), 2);
175 |
176 | numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
177 | numbers.lastIndexOf = null;
178 | equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
179 | equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
180 | var result = (function(){ return _.lastIndexOf(arguments, 1); })(1, 0, 1, 0, 0, 1, 0, 0, 0);
181 | equal(result, 5, 'works on an arguments object');
182 | equal(_.indexOf(null, 2), -1, 'handles nulls properly');
183 |
184 | numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
185 | index = _.lastIndexOf(numbers, 2, 2);
186 | equal(index, 1, 'supports the fromIndex argument');
187 | });
188 |
189 | test("range", function() {
190 | equal(_.range(0).join(''), '', 'range with 0 as a first argument generates an empty array');
191 | equal(_.range(4).join(' '), '0 1 2 3', 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
192 | equal(_.range(5, 8).join(' '), '5 6 7', 'range with two arguments a & b, a<b generates an array of elements a,a+1,a+2,...,b-2,b-1');
193 | equal(_.range(8, 5).join(''), '', 'range with two arguments a & b, b<a generates an empty array');
194 | equal(_.range(3, 10, 3).join(' '), '3 6 9', 'range with three arguments a & b & c, c < b-a, a < b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) < c');
195 | equal(_.range(3, 10, 15).join(''), '3', 'range with three arguments a & b & c, c > b-a, a < b generates an array with a single element, equal to a');
196 | equal(_.range(12, 7, -2).join(' '), '12 10 8', 'range with three arguments a & b & c, a > b, c < 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
197 | equal(_.range(0, -10, -1).join(' '), '0 -1 -2 -3 -4 -5 -6 -7 -8 -9', 'final example in the Python docs');
198 | });
199 |
200 | });
201 |
--------------------------------------------------------------------------------
/components/underscore/test/chaining.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | module("Chaining");
4 |
5 | test("map/flatten/reduce", function() {
6 | var lyrics = [
7 | "I'm a lumberjack and I'm okay",
8 | "I sleep all night and I work all day",
9 | "He's a lumberjack and he's okay",
10 | "He sleeps all night and he works all day"
11 | ];
12 | var counts = _(lyrics).chain()
13 | .map(function(line) { return line.split(''); })
14 | .flatten()
15 | .reduce(function(hash, l) {
16 | hash[l] = hash[l] || 0;
17 | hash[l]++;
18 | return hash;
19 | }, {}).value();
20 | ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
21 | });
22 |
23 | test("select/reject/sortBy", function() {
24 | var numbers = [1,2,3,4,5,6,7,8,9,10];
25 | numbers = _(numbers).chain().select(function(n) {
26 | return n % 2 == 0;
27 | }).reject(function(n) {
28 | return n % 4 == 0;
29 | }).sortBy(function(n) {
30 | return -n;
31 | }).value();
32 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
33 | });
34 |
35 | test("select/reject/sortBy in functional style", function() {
36 | var numbers = [1,2,3,4,5,6,7,8,9,10];
37 | numbers = _.chain(numbers).select(function(n) {
38 | return n % 2 == 0;
39 | }).reject(function(n) {
40 | return n % 4 == 0;
41 | }).sortBy(function(n) {
42 | return -n;
43 | }).value();
44 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
45 | });
46 |
47 | test("reverse/concat/unshift/pop/map", function() {
48 | var numbers = [1,2,3,4,5];
49 | numbers = _(numbers).chain()
50 | .reverse()
51 | .concat([5, 5, 5])
52 | .unshift(17)
53 | .pop()
54 | .map(function(n){ return n * 2; })
55 | .value();
56 | equal(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.');
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/components/underscore/test/collections.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | module("Collections");
4 |
5 | test("each", function() {
6 | _.each([1, 2, 3], function(num, i) {
7 | equal(num, i + 1, 'each iterators provide value and iteration count');
8 | });
9 |
10 | var answers = [];
11 | _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier);}, {multiplier : 5});
12 | equal(answers.join(', '), '5, 10, 15', 'context object property accessed');
13 |
14 | answers = [];
15 | _.forEach([1, 2, 3], function(num){ answers.push(num); });
16 | equal(answers.join(', '), '1, 2, 3', 'aliased as "forEach"');
17 |
18 | answers = [];
19 | var obj = {one : 1, two : 2, three : 3};
20 | obj.constructor.prototype.four = 4;
21 | _.each(obj, function(value, key){ answers.push(key); });
22 | equal(answers.join(", "), 'one, two, three', 'iterating over objects works, and ignores the object prototype.');
23 | delete obj.constructor.prototype.four;
24 |
25 | answer = null;
26 | _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
27 | ok(answer, 'can reference the original collection from inside the iterator');
28 |
29 | answers = 0;
30 | _.each(null, function(){ ++answers; });
31 | equal(answers, 0, 'handles a null properly');
32 | });
33 |
34 | test('map', function() {
35 | var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
36 | equal(doubled.join(', '), '2, 4, 6', 'doubled numbers');
37 |
38 | doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
39 | equal(doubled.join(', '), '2, 4, 6', 'aliased as "collect"');
40 |
41 | var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
42 | equal(tripled.join(', '), '3, 6, 9', 'tripled numbers with context');
43 |
44 | var doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
45 | equal(doubled.join(', '), '2, 4, 6', 'OO-style doubled numbers');
46 |
47 | if (document.querySelectorAll) {
48 | var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; });
49 | deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
50 | }
51 |
52 | var ids = _.map($('#map-test').children(), function(n){ return n.id; });
53 | deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on jQuery Array-likes.');
54 |
55 | var ids = _.map(document.images, function(n){ return n.id; });
56 | ok(ids[0] == 'chart_image', 'can use collection methods on HTMLCollections');
57 |
58 | var ifnull = _.map(null, function(){});
59 | ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
60 | });
61 |
62 | test('reduce', function() {
63 | var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }, 0);
64 | equal(sum, 6, 'can sum up an array');
65 |
66 | var context = {multiplier : 3};
67 | sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num * this.multiplier; }, 0, context);
68 | equal(sum, 18, 'can reduce with a context object');
69 |
70 | sum = _.inject([1, 2, 3], function(sum, num){ return sum + num; }, 0);
71 | equal(sum, 6, 'aliased as "inject"');
72 |
73 | sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
74 | equal(sum, 6, 'OO-style reduce');
75 |
76 | var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
77 | equal(sum, 6, 'default initial value');
78 |
79 | var ifnull;
80 | try {
81 | _.reduce(null, function(){});
82 | } catch (ex) {
83 | ifnull = ex;
84 | }
85 | ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
86 |
87 | ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
88 | equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
89 | raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
90 | });
91 |
92 | test('reduceRight', function() {
93 | var list = _.reduceRight(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
94 | equal(list, 'bazbarfoo', 'can perform right folds');
95 |
96 | var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; }, '');
97 | equal(list, 'bazbarfoo', 'aliased as "foldr"');
98 |
99 | var list = _.foldr(["foo", "bar", "baz"], function(memo, str){ return memo + str; });
100 | equal(list, 'bazbarfoo', 'default initial value');
101 |
102 | var ifnull;
103 | try {
104 | _.reduceRight(null, function(){});
105 | } catch (ex) {
106 | ifnull = ex;
107 | }
108 | ok(ifnull instanceof TypeError, 'handles a null (without inital value) properly');
109 |
110 | var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; });
111 | equal(sum, 6, 'default initial value on object');
112 |
113 | ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
114 |
115 | equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
116 | raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
117 |
118 | // Assert that the correct arguments are being passed.
119 |
120 | var args,
121 | memo = {},
122 | object = {a: 1, b: 2},
123 | lastKey = _.keys(object).pop();
124 |
125 | var expected = lastKey == 'a'
126 | ? [memo, 1, 'a', object]
127 | : [memo, 2, 'b', object];
128 |
129 | _.reduceRight(object, function() {
130 | args || (args = _.toArray(arguments));
131 | }, memo);
132 |
133 | deepEqual(args, expected);
134 |
135 | // And again, with numeric keys.
136 |
137 | object = {'2': 'a', '1': 'b'};
138 | lastKey = _.keys(object).pop();
139 | args = null;
140 |
141 | expected = lastKey == '2'
142 | ? [memo, 'a', '2', object]
143 | : [memo, 'b', '1', object];
144 |
145 | _.reduceRight(object, function() {
146 | args || (args = _.toArray(arguments));
147 | }, memo);
148 |
149 | deepEqual(args, expected);
150 | });
151 |
152 | test('find', function() {
153 | var array = [1, 2, 3, 4];
154 | strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
155 | strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
156 | });
157 |
158 | test('detect', function() {
159 | var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
160 | equal(result, 2, 'found the first "2" and broke the loop');
161 | });
162 |
163 | test('select', function() {
164 | var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
165 | equal(evens.join(', '), '2, 4, 6', 'selected each even number');
166 |
167 | evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
168 | equal(evens.join(', '), '2, 4, 6', 'aliased as "filter"');
169 | });
170 |
171 | test('reject', function() {
172 | var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
173 | equal(odds.join(', '), '1, 3, 5', 'rejected each even number');
174 |
175 | var context = "obj";
176 |
177 | var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
178 | equal(context, "obj");
179 | return num % 2 != 0;
180 | }, context);
181 | equal(evens.join(', '), '2, 4, 6', 'rejected each odd number');
182 | });
183 |
184 | test('all', function() {
185 | ok(_.all([], _.identity), 'the empty set');
186 | ok(_.all([true, true, true], _.identity), 'all true values');
187 | ok(!_.all([true, false, true], _.identity), 'one false value');
188 | ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers');
189 | ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number');
190 | ok(_.all([1], _.identity) === true, 'cast to boolean - true');
191 | ok(_.all([0], _.identity) === false, 'cast to boolean - false');
192 | ok(_.every([true, true, true], _.identity), 'aliased as "every"');
193 | ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
194 | });
195 |
196 | test('any', function() {
197 | var nativeSome = Array.prototype.some;
198 | Array.prototype.some = null;
199 | ok(!_.any([]), 'the empty set');
200 | ok(!_.any([false, false, false]), 'all false values');
201 | ok(_.any([false, false, true]), 'one true value');
202 | ok(_.any([null, 0, 'yes', false]), 'a string');
203 | ok(!_.any([null, 0, '', false]), 'falsy values');
204 | ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers');
205 | ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number');
206 | ok(_.any([1], _.identity) === true, 'cast to boolean - true');
207 | ok(_.any([0], _.identity) === false, 'cast to boolean - false');
208 | ok(_.some([false, false, true]), 'aliased as "some"');
209 | Array.prototype.some = nativeSome;
210 | });
211 |
212 | test('include', function() {
213 | ok(_.include([1,2,3], 2), 'two is in the array');
214 | ok(!_.include([1,3,9], 2), 'two is not in the array');
215 | ok(_.contains({moe:1, larry:3, curly:9}, 3) === true, '_.include on objects checks their values');
216 | ok(_([1,2,3]).include(2), 'OO-style include');
217 | });
218 |
219 | test('invoke', function() {
220 | var list = [[5, 1, 7], [3, 2, 1]];
221 | var result = _.invoke(list, 'sort');
222 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
223 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
224 | });
225 |
226 | test('invoke w/ function reference', function() {
227 | var list = [[5, 1, 7], [3, 2, 1]];
228 | var result = _.invoke(list, Array.prototype.sort);
229 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
230 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
231 | });
232 |
233 | // Relevant when using ClojureScript
234 | test('invoke when strings have a call method', function() {
235 | String.prototype.call = function() {
236 | return 42;
237 | };
238 | var list = [[5, 1, 7], [3, 2, 1]];
239 | var s = "foo";
240 | equal(s.call(), 42, "call function exists");
241 | var result = _.invoke(list, 'sort');
242 | equal(result[0].join(', '), '1, 5, 7', 'first array sorted');
243 | equal(result[1].join(', '), '1, 2, 3', 'second array sorted');
244 | delete String.prototype.call;
245 | equal(s.call, undefined, "call function removed");
246 | });
247 |
248 | test('pluck', function() {
249 | var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
250 | equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'pulls names out of objects');
251 | });
252 |
253 | test('where', function() {
254 | var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
255 | var result = _.where(list, {a: 1});
256 | equal(result.length, 3);
257 | equal(result[result.length - 1].b, 4);
258 | result = _.where(list, {b: 2});
259 | equal(result.length, 2);
260 | equal(result[0].a, 1);
261 | });
262 |
263 | test('max', function() {
264 | equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
265 |
266 | var neg = _.max([1, 2, 3], function(num){ return -num; });
267 | equal(neg, 1, 'can perform a computation-based max');
268 |
269 | equal(-Infinity, _.max({}), 'Maximum value of an empty object');
270 | equal(-Infinity, _.max([]), 'Maximum value of an empty array');
271 | equal(_.max({'a': 'a'}), -Infinity, 'Maximum value of a non-numeric collection');
272 |
273 | equal(299999, _.max(_.range(1,300000)), "Maximum value of a too-big array");
274 | });
275 |
276 | test('min', function() {
277 | equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
278 |
279 | var neg = _.min([1, 2, 3], function(num){ return -num; });
280 | equal(neg, 3, 'can perform a computation-based min');
281 |
282 | equal(Infinity, _.min({}), 'Minimum value of an empty object');
283 | equal(Infinity, _.min([]), 'Minimum value of an empty array');
284 | equal(_.min({'a': 'a'}), Infinity, 'Minimum value of a non-numeric collection');
285 |
286 | var now = new Date(9999999999);
287 | var then = new Date(0);
288 | equal(_.min([now, then]), then);
289 |
290 | equal(1, _.min(_.range(1,300000)), "Minimum value of a too-big array");
291 | });
292 |
293 | test('sortBy', function() {
294 | var people = [{name : 'curly', age : 50}, {name : 'moe', age : 30}];
295 | people = _.sortBy(people, function(person){ return person.age; });
296 | equal(_.pluck(people, 'name').join(', '), 'moe, curly', 'stooges sorted by age');
297 |
298 | var list = [undefined, 4, 1, undefined, 3, 2];
299 | equal(_.sortBy(list, _.identity).join(','), '1,2,3,4,,', 'sortBy with undefined values');
300 |
301 | var list = ["one", "two", "three", "four", "five"];
302 | var sorted = _.sortBy(list, 'length');
303 | equal(sorted.join(' '), 'one two four five three', 'sorted by length');
304 |
305 | function Pair(x, y) {
306 | this.x = x;
307 | this.y = y;
308 | }
309 |
310 | var collection = [
311 | new Pair(1, 1), new Pair(1, 2),
312 | new Pair(1, 3), new Pair(1, 4),
313 | new Pair(1, 5), new Pair(1, 6),
314 | new Pair(2, 1), new Pair(2, 2),
315 | new Pair(2, 3), new Pair(2, 4),
316 | new Pair(2, 5), new Pair(2, 6),
317 | new Pair(undefined, 1), new Pair(undefined, 2),
318 | new Pair(undefined, 3), new Pair(undefined, 4),
319 | new Pair(undefined, 5), new Pair(undefined, 6)
320 | ];
321 |
322 | var actual = _.sortBy(collection, function(pair) {
323 | return pair.x;
324 | });
325 |
326 | deepEqual(actual, collection, 'sortBy should be stable');
327 | });
328 |
329 | test('groupBy', function() {
330 | var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
331 | ok('0' in parity && '1' in parity, 'created a group for each value');
332 | equal(parity[0].join(', '), '2, 4, 6', 'put each even number in the right group');
333 |
334 | var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
335 | var grouped = _.groupBy(list, 'length');
336 | equal(grouped['3'].join(' '), 'one two six ten');
337 | equal(grouped['4'].join(' '), 'four five nine');
338 | equal(grouped['5'].join(' '), 'three seven eight');
339 |
340 | var context = {};
341 | _.groupBy([{}], function(){ ok(this === context); }, context);
342 |
343 | grouped = _.groupBy([4.2, 6.1, 6.4], function(num) {
344 | return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
345 | });
346 | equal(grouped.constructor.length, 1);
347 | equal(grouped.hasOwnProperty.length, 2);
348 |
349 | var array = [{}];
350 | _.groupBy(array, function(value, index, obj){ ok(obj === array); });
351 |
352 | var array = [1, 2, 1, 2, 3];
353 | var grouped = _.groupBy(array);
354 | equal(grouped['1'].length, 2);
355 | equal(grouped['3'].length, 1);
356 | });
357 |
358 | test('countBy', function() {
359 | var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
360 | equal(parity['true'], 2);
361 | equal(parity['false'], 3);
362 |
363 | var list = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"];
364 | var grouped = _.countBy(list, 'length');
365 | equal(grouped['3'], 4);
366 | equal(grouped['4'], 3);
367 | equal(grouped['5'], 3);
368 |
369 | var context = {};
370 | _.countBy([{}], function(){ ok(this === context); }, context);
371 |
372 | grouped = _.countBy([4.2, 6.1, 6.4], function(num) {
373 | return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
374 | });
375 | equal(grouped.constructor, 1);
376 | equal(grouped.hasOwnProperty, 2);
377 |
378 | var array = [{}];
379 | _.countBy(array, function(value, index, obj){ ok(obj === array); });
380 |
381 | var array = [1, 2, 1, 2, 3];
382 | var grouped = _.countBy(array);
383 | equal(grouped['1'], 2);
384 | equal(grouped['3'], 1);
385 | });
386 |
387 | test('sortedIndex', function() {
388 | var numbers = [10, 20, 30, 40, 50], num = 35;
389 | var indexForNum = _.sortedIndex(numbers, num);
390 | equal(indexForNum, 3, '35 should be inserted at index 3');
391 |
392 | var indexFor30 = _.sortedIndex(numbers, 30);
393 | equal(indexFor30, 2, '30 should be inserted at index 2');
394 |
395 | var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}];
396 | var iterator = function(obj){ return obj.x; };
397 | strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2);
398 | strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3);
399 |
400 | var context = {1: 2, 2: 3, 3: 4};
401 | iterator = function(obj){ return this[obj]; };
402 | strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1);
403 | });
404 |
405 | test('shuffle', function() {
406 | var numbers = _.range(10);
407 | var shuffled = _.shuffle(numbers).sort();
408 | notStrictEqual(numbers, shuffled, 'original object is unmodified');
409 | equal(shuffled.join(','), numbers.join(','), 'contains the same members before and after shuffle');
410 | });
411 |
412 | test('toArray', function() {
413 | ok(!_.isArray(arguments), 'arguments object is not an array');
414 | ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
415 | var a = [1,2,3];
416 | ok(_.toArray(a) !== a, 'array is cloned');
417 | equal(_.toArray(a).join(', '), '1, 2, 3', 'cloned array contains same elements');
418 |
419 | var numbers = _.toArray({one : 1, two : 2, three : 3});
420 | equal(numbers.join(', '), '1, 2, 3', 'object flattened into array');
421 |
422 | // test in IE < 9
423 | try {
424 | var actual = _.toArray(document.childNodes);
425 | } catch(ex) { }
426 |
427 | ok(_.isArray(actual), 'should not throw converting a node list');
428 | });
429 |
430 | test('size', function() {
431 | equal(_.size({one : 1, two : 2, three : 3}), 3, 'can compute the size of an object');
432 | equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
433 |
434 | var func = function() {
435 | return _.size(arguments);
436 | };
437 |
438 | equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
439 |
440 | equal(_.size('hello'), 5, 'can compute the size of a string');
441 |
442 | equal(_.size(null), 0, 'handles nulls');
443 | });
444 |
445 | });
446 |
--------------------------------------------------------------------------------
/components/underscore/test/functions.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | module("Functions");
4 |
5 | test("bind", function() {
6 | var context = {name : 'moe'};
7 | var func = function(arg) { return "name: " + (this.name || arg); };
8 | var bound = _.bind(func, context);
9 | equal(bound(), 'name: moe', 'can bind a function to a context');
10 |
11 | bound = _(func).bind(context);
12 | equal(bound(), 'name: moe', 'can do OO-style binding');
13 |
14 | bound = _.bind(func, null, 'curly');
15 | equal(bound(), 'name: curly', 'can bind without specifying a context');
16 |
17 | func = function(salutation, name) { return salutation + ': ' + name; };
18 | func = _.bind(func, this, 'hello');
19 | equal(func('moe'), 'hello: moe', 'the function was partially applied in advance');
20 |
21 | var func = _.bind(func, this, 'curly');
22 | equal(func(), 'hello: curly', 'the function was completely applied in advance');
23 |
24 | var func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
25 | func = _.bind(func, this, 'hello', 'moe', 'curly');
26 | equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
27 |
28 | func = function(context, message) { equal(this, context, message); };
29 | _.bind(func, 0, 0, 'can bind a function to `0`')();
30 | _.bind(func, '', '', 'can bind a function to an empty string')();
31 | _.bind(func, false, false, 'can bind a function to `false`')();
32 |
33 | // These tests are only meaningful when using a browser without a native bind function
34 | // To test this with a modern browser, set underscore's nativeBind to undefined
35 | var F = function () { return this; };
36 | var Boundf = _.bind(F, {hello: "moe curly"});
37 | var newBoundf = new Boundf();
38 | equal(newBoundf.hello, undefined, "function should not be bound to the context, to comply with ECMAScript 5");
39 | equal(Boundf().hello, "moe curly", "When called without the new operator, it's OK to be bound to the context");
40 | ok(newBoundf instanceof F, "a bound instance is an instance of the original function");
41 | });
42 |
43 | test("bindAll", function() {
44 | var curly = {name : 'curly'}, moe = {
45 | name : 'moe',
46 | getName : function() { return 'name: ' + this.name; },
47 | sayHi : function() { return 'hi: ' + this.name; }
48 | };
49 | curly.getName = moe.getName;
50 | _.bindAll(moe, 'getName', 'sayHi');
51 | curly.sayHi = moe.sayHi;
52 | equal(curly.getName(), 'name: curly', 'unbound function is bound to current object');
53 | equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
54 |
55 | curly = {name : 'curly'};
56 | moe = {
57 | name : 'moe',
58 | getName : function() { return 'name: ' + this.name; },
59 | sayHi : function() { return 'hi: ' + this.name; }
60 | };
61 | _.bindAll(moe);
62 | curly.sayHi = moe.sayHi;
63 | equal(curly.sayHi(), 'hi: moe', 'calling bindAll with no arguments binds all functions to the object');
64 | });
65 |
66 | test("memoize", function() {
67 | var fib = function(n) {
68 | return n < 2 ? n : fib(n - 1) + fib(n - 2);
69 | };
70 | var fastFib = _.memoize(fib);
71 | equal(fib(10), 55, 'a memoized version of fibonacci produces identical results');
72 | equal(fastFib(10), 55, 'a memoized version of fibonacci produces identical results');
73 |
74 | var o = function(str) {
75 | return str;
76 | };
77 | var fastO = _.memoize(o);
78 | equal(o('toString'), 'toString', 'checks hasOwnProperty');
79 | equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
80 | });
81 |
82 | asyncTest("delay", 2, function() {
83 | var delayed = false;
84 | _.delay(function(){ delayed = true; }, 100);
85 | setTimeout(function(){ ok(!delayed, "didn't delay the function quite yet"); }, 50);
86 | setTimeout(function(){ ok(delayed, 'delayed the function'); start(); }, 150);
87 | });
88 |
89 | asyncTest("defer", 1, function() {
90 | var deferred = false;
91 | _.defer(function(bool){ deferred = bool; }, true);
92 | _.delay(function(){ ok(deferred, "deferred the function"); start(); }, 50);
93 | });
94 |
95 | asyncTest("throttle", 2, function() {
96 | var counter = 0;
97 | var incr = function(){ counter++; };
98 | var throttledIncr = _.throttle(incr, 100);
99 | throttledIncr(); throttledIncr(); throttledIncr();
100 | setTimeout(throttledIncr, 70);
101 | setTimeout(throttledIncr, 120);
102 | setTimeout(throttledIncr, 140);
103 | setTimeout(throttledIncr, 190);
104 | setTimeout(throttledIncr, 220);
105 | setTimeout(throttledIncr, 240);
106 | _.delay(function(){ equal(counter, 1, "incr was called immediately"); }, 30);
107 | _.delay(function(){ equal(counter, 4, "incr was throttled"); start(); }, 400);
108 | });
109 |
110 | asyncTest("throttle arguments", 2, function() {
111 | var value = 0;
112 | var update = function(val){ value = val; };
113 | var throttledUpdate = _.throttle(update, 100);
114 | throttledUpdate(1); throttledUpdate(2); throttledUpdate(3);
115 | setTimeout(function(){ throttledUpdate(4); }, 120);
116 | setTimeout(function(){ throttledUpdate(5); }, 140);
117 | setTimeout(function(){ throttledUpdate(6); }, 250);
118 | _.delay(function(){ equal(value, 1, "updated to latest value"); }, 40);
119 | _.delay(function(){ equal(value, 6, "updated to latest value"); start(); }, 400);
120 | });
121 |
122 | asyncTest("throttle once", 2, function() {
123 | var counter = 0;
124 | var incr = function(){ return ++counter; };
125 | var throttledIncr = _.throttle(incr, 100);
126 | var result = throttledIncr();
127 | _.delay(function(){
128 | equal(result, 1, "throttled functions return their value");
129 | equal(counter, 1, "incr was called once"); start();
130 | }, 220);
131 | });
132 |
133 | asyncTest("throttle twice", 1, function() {
134 | var counter = 0;
135 | var incr = function(){ counter++; };
136 | var throttledIncr = _.throttle(incr, 100);
137 | throttledIncr(); throttledIncr();
138 | _.delay(function(){ equal(counter, 2, "incr was called twice"); start(); }, 220);
139 | });
140 |
141 | asyncTest("throttle repeatedly with results", 9, function() {
142 | var counter = 0;
143 | var incr = function(){ return ++counter; };
144 | var throttledIncr = _.throttle(incr, 100);
145 | var results = [];
146 | var saveResult = function() { results.push(throttledIncr()); };
147 | saveResult(); saveResult(); saveResult();
148 | setTimeout(saveResult, 70);
149 | setTimeout(saveResult, 120);
150 | setTimeout(saveResult, 140);
151 | setTimeout(saveResult, 190);
152 | setTimeout(saveResult, 240);
153 | setTimeout(saveResult, 260);
154 | _.delay(function() {
155 | equal(results[0], 1, "incr was called once");
156 | equal(results[1], 1, "incr was throttled");
157 | equal(results[2], 1, "incr was throttled");
158 | equal(results[3], 1, "incr was throttled");
159 | equal(results[4], 2, "incr was called twice");
160 | equal(results[5], 2, "incr was throttled");
161 | equal(results[6], 2, "incr was throttled");
162 | equal(results[7], 3, "incr was called thrice");
163 | equal(results[8], 3, "incr was throttled");
164 | start();
165 | }, 400);
166 | });
167 |
168 | asyncTest("throttle triggers trailing call after repeatedly invoked", 2, function() {
169 | var actual;
170 | var counter = 0;
171 | var limit = 80;
172 | var incr = function(){ counter++; };
173 | var throttledIncr = _.throttle(incr, 32);
174 |
175 | var stamp = new Date;
176 | while ((new Date - stamp) < limit) {
177 | throttledIncr();
178 | }
179 | _.delay(function() {
180 | actual = counter + 2;
181 | throttledIncr();
182 | throttledIncr();
183 | }, 64);
184 |
185 | _.delay(function() {
186 | equal(counter, actual);
187 | start();
188 | }, 128);
189 |
190 | ok(counter > 1);
191 | });
192 |
193 | asyncTest("debounce", 1, function() {
194 | var counter = 0;
195 | var incr = function(){ counter++; };
196 | var debouncedIncr = _.debounce(incr, 50);
197 | debouncedIncr(); debouncedIncr(); debouncedIncr();
198 | setTimeout(debouncedIncr, 30);
199 | setTimeout(debouncedIncr, 60);
200 | setTimeout(debouncedIncr, 90);
201 | setTimeout(debouncedIncr, 120);
202 | setTimeout(debouncedIncr, 150);
203 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
204 | });
205 |
206 | asyncTest("debounce asap", 5, function() {
207 | var a, b, c;
208 | var counter = 0;
209 | var incr = function(){ return ++counter; };
210 | var debouncedIncr = _.debounce(incr, 50, true);
211 | a = debouncedIncr();
212 | b = debouncedIncr();
213 | c = debouncedIncr();
214 | equal(a, 1);
215 | equal(b, 1);
216 | equal(c, 1);
217 | equal(counter, 1, 'incr was called immediately');
218 | setTimeout(debouncedIncr, 30);
219 | setTimeout(debouncedIncr, 60);
220 | setTimeout(debouncedIncr, 90);
221 | setTimeout(debouncedIncr, 120);
222 | setTimeout(debouncedIncr, 150);
223 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 220);
224 | });
225 |
226 | asyncTest("debounce asap recursively", 2, function() {
227 | var counter = 0;
228 | var debouncedIncr = _.debounce(function(){
229 | counter++;
230 | if (counter < 5) debouncedIncr();
231 | }, 50, true);
232 | debouncedIncr();
233 | equal(counter, 1, 'incr was called immediately');
234 | _.delay(function(){ equal(counter, 1, "incr was debounced"); start(); }, 70);
235 | });
236 |
237 | test("once", function() {
238 | var num = 0;
239 | var increment = _.once(function(){ num++; });
240 | increment();
241 | increment();
242 | equal(num, 1);
243 | });
244 |
245 | test("wrap", function() {
246 | var greet = function(name){ return "hi: " + name; };
247 | var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
248 | equal(backwards('moe'), 'hi: moe eom', 'wrapped the saluation function');
249 |
250 | var inner = function(){ return "Hello "; };
251 | var obj = {name : "Moe"};
252 | obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; });
253 | equal(obj.hi(), "Hello Moe");
254 |
255 | var noop = function(){};
256 | var wrapped = _.wrap(noop, function(fn){ return Array.prototype.slice.call(arguments, 0); });
257 | var ret = wrapped(['whats', 'your'], 'vector', 'victor');
258 | deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
259 | });
260 |
261 | test("compose", function() {
262 | var greet = function(name){ return "hi: " + name; };
263 | var exclaim = function(sentence){ return sentence + '!'; };
264 | var composed = _.compose(exclaim, greet);
265 | equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
266 |
267 | composed = _.compose(greet, exclaim);
268 | equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
269 | });
270 |
271 | test("after", function() {
272 | var testAfter = function(afterAmount, timesCalled) {
273 | var afterCalled = 0;
274 | var after = _.after(afterAmount, function() {
275 | afterCalled++;
276 | });
277 | while (timesCalled--) after();
278 | return afterCalled;
279 | };
280 |
281 | equal(testAfter(5, 5), 1, "after(N) should fire after being called N times");
282 | equal(testAfter(5, 4), 0, "after(N) should not fire unless called N times");
283 | equal(testAfter(0, 0), 1, "after(0) should fire immediately");
284 | });
285 |
286 | });
287 |
--------------------------------------------------------------------------------
/components/underscore/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Underscore Test Suite
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 | A representative sample of the functions are benchmarked here, to provide
31 | a sense of how fast they might run in different browsers.
32 | Each iteration runs on an array of 1000 elements.
33 | For example, the 'intersection' test measures the number of times you can
34 | find the intersection of two thousand-element arrays in one second.
35 |
36 |
37 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/components/underscore/test/objects.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | module("Objects");
4 |
5 | test("keys", function() {
6 | equal(_.keys({one : 1, two : 2}).join(', '), 'one, two', 'can extract the keys from an object');
7 | // the test above is not safe because it relies on for-in enumeration order
8 | var a = []; a[1] = 0;
9 | equal(_.keys(a).join(', '), '1', 'is not fooled by sparse arrays; see issue #95');
10 | raises(function() { _.keys(null); }, TypeError, 'throws an error for `null` values');
11 | raises(function() { _.keys(void 0); }, TypeError, 'throws an error for `undefined` values');
12 | raises(function() { _.keys(1); }, TypeError, 'throws an error for number primitives');
13 | raises(function() { _.keys('a'); }, TypeError, 'throws an error for string primitives');
14 | raises(function() { _.keys(true); }, TypeError, 'throws an error for boolean primitives');
15 | });
16 |
17 | test("values", function() {
18 | equal(_.values({one: 1, two: 2}).join(', '), '1, 2', 'can extract the values from an object');
19 | equal(_.values({one: 1, two: 2, length: 3}).join(', '), '1, 2, 3', '... even when one of them is "length"');
20 | });
21 |
22 | test("pairs", function() {
23 | deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs');
24 | deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"');
25 | });
26 |
27 | test("invert", function() {
28 | var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
29 | equal(_.keys(_.invert(obj)).join(' '), 'Moe Larry Curly', 'can invert an object');
30 | ok(_.isEqual(_.invert(_.invert(obj)), obj), 'two inverts gets you back where you started');
31 |
32 | var obj = {length: 3};
33 | ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"')
34 | });
35 |
36 | test("functions", function() {
37 | var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce};
38 | ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object');
39 |
40 | var Animal = function(){};
41 | Animal.prototype.run = function(){};
42 | equal(_.functions(new Animal).join(''), 'run', 'also looks up functions on the prototype');
43 | });
44 |
45 | test("extend", function() {
46 | var result;
47 | equal(_.extend({}, {a:'b'}).a, 'b', 'can extend an object with the attributes of another');
48 | equal(_.extend({a:'x'}, {a:'b'}).a, 'b', 'properties in source override destination');
49 | equal(_.extend({x:'x'}, {a:'b'}).x, 'x', 'properties not in source dont get overriden');
50 | result = _.extend({x:'x'}, {a:'a'}, {b:'b'});
51 | ok(_.isEqual(result, {x:'x', a:'a', b:'b'}), 'can extend from multiple source objects');
52 | result = _.extend({x:'x'}, {a:'a', x:2}, {a:'b'});
53 | ok(_.isEqual(result, {x:2, a:'b'}), 'extending from multiple source objects last property trumps');
54 | result = _.extend({}, {a: void 0, b: null});
55 | equal(_.keys(result).join(''), 'ab', 'extend does not copy undefined values');
56 |
57 | try {
58 | result = {};
59 | _.extend(result, null, undefined, {a:1});
60 | } catch(ex) {}
61 |
62 | equal(result.a, 1, 'should not error on `null` or `undefined` sources');
63 | });
64 |
65 | test("pick", function() {
66 | var result;
67 | result = _.pick({a:1, b:2, c:3}, 'a', 'c');
68 | ok(_.isEqual(result, {a:1, c:3}), 'can restrict properties to those named');
69 | result = _.pick({a:1, b:2, c:3}, ['b', 'c']);
70 | ok(_.isEqual(result, {b:2, c:3}), 'can restrict properties to those named in an array');
71 | result = _.pick({a:1, b:2, c:3}, ['a'], 'b');
72 | ok(_.isEqual(result, {a:1, b:2}), 'can restrict properties to those named in mixed args');
73 |
74 | var Obj = function(){};
75 | Obj.prototype = {a: 1, b: 2, c: 3};
76 | ok(_.isEqual(_.pick(new Obj, 'a', 'c'), {a:1, c: 3}), 'include prototype props');
77 | });
78 |
79 | test("omit", function() {
80 | var result;
81 | result = _.omit({a:1, b:2, c:3}, 'b');
82 | ok(_.isEqual(result, {a:1, c:3}), 'can omit a single named property');
83 | result = _.omit({a:1, b:2, c:3}, 'a', 'c');
84 | ok(_.isEqual(result, {b:2}), 'can omit several named properties');
85 | result = _.omit({a:1, b:2, c:3}, ['b', 'c']);
86 | ok(_.isEqual(result, {a:1}), 'can omit properties named in an array');
87 |
88 | var Obj = function(){};
89 | Obj.prototype = {a: 1, b: 2, c: 3};
90 | ok(_.isEqual(_.omit(new Obj, 'b'), {a:1, c: 3}), 'include prototype props');
91 | });
92 |
93 | test("defaults", function() {
94 | var result;
95 | var options = {zero: 0, one: 1, empty: "", nan: NaN, string: "string"};
96 |
97 | _.defaults(options, {zero: 1, one: 10, twenty: 20});
98 | equal(options.zero, 0, 'value exists');
99 | equal(options.one, 1, 'value exists');
100 | equal(options.twenty, 20, 'default applied');
101 |
102 | _.defaults(options, {empty: "full"}, {nan: "nan"}, {word: "word"}, {word: "dog"});
103 | equal(options.empty, "", 'value exists');
104 | ok(_.isNaN(options.nan), "NaN isn't overridden");
105 | equal(options.word, "word", 'new value is added, first one wins');
106 |
107 | try {
108 | options = {};
109 | _.defaults(options, null, undefined, {a:1});
110 | } catch(ex) {}
111 |
112 | equal(options.a, 1, 'should not error on `null` or `undefined` sources');
113 | });
114 |
115 | test("clone", function() {
116 | var moe = {name : 'moe', lucky : [13, 27, 34]};
117 | var clone = _.clone(moe);
118 | equal(clone.name, 'moe', 'the clone as the attributes of the original');
119 |
120 | clone.name = 'curly';
121 | ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original');
122 |
123 | clone.lucky.push(101);
124 | equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
125 |
126 | equal(_.clone(undefined), void 0, 'non objects should not be changed by clone');
127 | equal(_.clone(1), 1, 'non objects should not be changed by clone');
128 | equal(_.clone(null), null, 'non objects should not be changed by clone');
129 | });
130 |
131 | test("isEqual", function() {
132 | function First() {
133 | this.value = 1;
134 | }
135 | First.prototype.value = 1;
136 | function Second() {
137 | this.value = 1;
138 | }
139 | Second.prototype.value = 2;
140 |
141 | // Basic equality and identity comparisons.
142 | ok(_.isEqual(null, null), "`null` is equal to `null`");
143 | ok(_.isEqual(), "`undefined` is equal to `undefined`");
144 |
145 | ok(!_.isEqual(0, -0), "`0` is not equal to `-0`");
146 | ok(!_.isEqual(-0, 0), "Commutative equality is implemented for `0` and `-0`");
147 | ok(!_.isEqual(null, undefined), "`null` is not equal to `undefined`");
148 | ok(!_.isEqual(undefined, null), "Commutative equality is implemented for `null` and `undefined`");
149 |
150 | // String object and primitive comparisons.
151 | ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal");
152 | ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal");
153 | ok(_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are equal");
154 | ok(_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives");
155 |
156 | ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal");
157 | ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal");
158 | ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal");
159 |
160 | // Number object and primitive comparisons.
161 | ok(_.isEqual(75, 75), "Identical number primitives are equal");
162 | ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal");
163 | ok(_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are equal");
164 | ok(_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives");
165 | ok(!_.isEqual(new Number(0), -0), "`new Number(0)` and `-0` are not equal");
166 | ok(!_.isEqual(0, new Number(-0)), "Commutative equality is implemented for `new Number(0)` and `-0`");
167 |
168 | ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal");
169 | ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal");
170 |
171 | // Comparisons involving `NaN`.
172 | ok(_.isEqual(NaN, NaN), "`NaN` is equal to `NaN`");
173 | ok(!_.isEqual(61, NaN), "A number primitive is not equal to `NaN`");
174 | ok(!_.isEqual(new Number(79), NaN), "A number object is not equal to `NaN`");
175 | ok(!_.isEqual(Infinity, NaN), "`Infinity` is not equal to `NaN`");
176 |
177 | // Boolean object and primitive comparisons.
178 | ok(_.isEqual(true, true), "Identical boolean primitives are equal");
179 | ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal");
180 | ok(_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are equal");
181 | ok(_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans");
182 | ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal");
183 |
184 | // Common type coercions.
185 | ok(!_.isEqual(true, new Boolean(false)), "Boolean objects are not equal to the boolean primitive `true`");
186 | ok(!_.isEqual("75", 75), "String and number primitives with like values are not equal");
187 | ok(!_.isEqual(new Number(63), new String(63)), "String and number objects with like values are not equal");
188 | ok(!_.isEqual(75, "75"), "Commutative equality is implemented for like string and number values");
189 | ok(!_.isEqual(0, ""), "Number and string primitives with like values are not equal");
190 | ok(!_.isEqual(1, true), "Number and boolean primitives with like values are not equal");
191 | ok(!_.isEqual(new Boolean(false), new Number(0)), "Boolean and number objects with like values are not equal");
192 | ok(!_.isEqual(false, new String("")), "Boolean primitives and string objects with like values are not equal");
193 | ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), "Dates and their corresponding numeric primitive values are not equal");
194 |
195 | // Dates.
196 | ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), "Date objects referencing identical times are equal");
197 | ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), "Date objects referencing different times are not equal");
198 | ok(!_.isEqual(new Date(2009, 11, 13), {
199 | getTime: function(){
200 | return 12606876e5;
201 | }
202 | }), "Date objects and objects with a `getTime` method are not equal");
203 | ok(!_.isEqual(new Date("Curly"), new Date("Curly")), "Invalid dates are not equal");
204 |
205 | // Functions.
206 | ok(!_.isEqual(First, Second), "Different functions with identical bodies and source code representations are not equal");
207 |
208 | // RegExps.
209 | ok(_.isEqual(/(?:)/gim, /(?:)/gim), "RegExps with equivalent patterns and flags are equal");
210 | ok(!_.isEqual(/(?:)/g, /(?:)/gi), "RegExps with equivalent patterns and different flags are not equal");
211 | ok(!_.isEqual(/Moe/gim, /Curly/gim), "RegExps with different patterns and equivalent flags are not equal");
212 | ok(!_.isEqual(/(?:)/gi, /(?:)/g), "Commutative equality is implemented for RegExps");
213 | ok(!_.isEqual(/Curly/g, {source: "Larry", global: true, ignoreCase: false, multiline: false}), "RegExps and RegExp-like objects are not equal");
214 |
215 | // Empty arrays, array-like objects, and object literals.
216 | ok(_.isEqual({}, {}), "Empty object literals are equal");
217 | ok(_.isEqual([], []), "Empty array literals are equal");
218 | ok(_.isEqual([{}], [{}]), "Empty nested arrays and objects are equal");
219 | ok(!_.isEqual({length: 0}, []), "Array-like objects and arrays are not equal.");
220 | ok(!_.isEqual([], {length: 0}), "Commutative equality is implemented for array-like objects");
221 |
222 | ok(!_.isEqual({}, []), "Object literals and array literals are not equal");
223 | ok(!_.isEqual([], {}), "Commutative equality is implemented for objects and arrays");
224 |
225 | // Arrays with primitive and object values.
226 | ok(_.isEqual([1, "Larry", true], [1, "Larry", true]), "Arrays containing identical primitives are equal");
227 | ok(_.isEqual([(/Moe/g), new Date(2009, 9, 25)], [(/Moe/g), new Date(2009, 9, 25)]), "Arrays containing equivalent elements are equal");
228 |
229 | // Multi-dimensional arrays.
230 | var a = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
231 | var b = [new Number(47), false, "Larry", /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
232 | ok(_.isEqual(a, b), "Arrays containing nested arrays and objects are recursively compared");
233 |
234 | // Overwrite the methods defined in ES 5.1 section 15.4.4.
235 | a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null;
236 | b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null;
237 |
238 | // Array elements and properties.
239 | ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal");
240 | a.push("White Rocks");
241 | ok(!_.isEqual(a, b), "Arrays of different lengths are not equal");
242 | a.push("East Boulder");
243 | b.push("Gunbarrel Ranch", "Teller Farm");
244 | ok(!_.isEqual(a, b), "Arrays of identical lengths containing different elements are not equal");
245 |
246 | // Sparse arrays.
247 | ok(_.isEqual(Array(3), Array(3)), "Sparse arrays of identical lengths are equal");
248 | ok(!_.isEqual(Array(3), Array(6)), "Sparse arrays of different lengths are not equal when both are empty");
249 |
250 | // Simple objects.
251 | ok(_.isEqual({a: "Curly", b: 1, c: true}, {a: "Curly", b: 1, c: true}), "Objects containing identical primitives are equal");
252 | ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), "Objects containing equivalent members are equal");
253 | ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), "Objects of identical sizes with different values are not equal");
254 | ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), "Objects of identical sizes with different property names are not equal");
255 | ok(!_.isEqual({a: 1, b: 2}, {a: 1}), "Objects of different sizes are not equal");
256 | ok(!_.isEqual({a: 1}, {a: 1, b: 2}), "Commutative equality is implemented for objects");
257 | ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), "Objects with identical keys and different values are not equivalent");
258 |
259 | // `A` contains nested objects and arrays.
260 | a = {
261 | name: new String("Moe Howard"),
262 | age: new Number(77),
263 | stooge: true,
264 | hobbies: ["acting"],
265 | film: {
266 | name: "Sing a Song of Six Pants",
267 | release: new Date(1947, 9, 30),
268 | stars: [new String("Larry Fine"), "Shemp Howard"],
269 | minutes: new Number(16),
270 | seconds: 54
271 | }
272 | };
273 |
274 | // `B` contains equivalent nested objects and arrays.
275 | b = {
276 | name: new String("Moe Howard"),
277 | age: new Number(77),
278 | stooge: true,
279 | hobbies: ["acting"],
280 | film: {
281 | name: "Sing a Song of Six Pants",
282 | release: new Date(1947, 9, 30),
283 | stars: [new String("Larry Fine"), "Shemp Howard"],
284 | minutes: new Number(16),
285 | seconds: 54
286 | }
287 | };
288 | ok(_.isEqual(a, b), "Objects with nested equivalent members are recursively compared");
289 |
290 | // Instances.
291 | ok(_.isEqual(new First, new First), "Object instances are equal");
292 | ok(!_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are not equal");
293 | ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not equal");
294 | ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined");
295 |
296 | // Circular Arrays.
297 | (a = []).push(a);
298 | (b = []).push(b);
299 | ok(_.isEqual(a, b), "Arrays containing circular references are equal");
300 | a.push(new String("Larry"));
301 | b.push(new String("Larry"));
302 | ok(_.isEqual(a, b), "Arrays containing circular references and equivalent properties are equal");
303 | a.push("Shemp");
304 | b.push("Curly");
305 | ok(!_.isEqual(a, b), "Arrays containing circular references and different properties are not equal");
306 |
307 | // More circular arrays #767.
308 | a = ["everything is checked but", "this", "is not"];
309 | a[1] = a;
310 | b = ["everything is checked but", ["this", "array"], "is not"];
311 | ok(!_.isEqual(a, b), "Comparison of circular references with non-circular references are not equal");
312 |
313 | // Circular Objects.
314 | a = {abc: null};
315 | b = {abc: null};
316 | a.abc = a;
317 | b.abc = b;
318 | ok(_.isEqual(a, b), "Objects containing circular references are equal");
319 | a.def = 75;
320 | b.def = 75;
321 | ok(_.isEqual(a, b), "Objects containing circular references and equivalent properties are equal");
322 | a.def = new Number(75);
323 | b.def = new Number(63);
324 | ok(!_.isEqual(a, b), "Objects containing circular references and different properties are not equal");
325 |
326 | // More circular objects #767.
327 | a = {everything: "is checked", but: "this", is: "not"};
328 | a.but = a;
329 | b = {everything: "is checked", but: {that:"object"}, is: "not"};
330 | ok(!_.isEqual(a, b), "Comparison of circular references with non-circular object references are not equal");
331 |
332 | // Cyclic Structures.
333 | a = [{abc: null}];
334 | b = [{abc: null}];
335 | (a[0].abc = a).push(a);
336 | (b[0].abc = b).push(b);
337 | ok(_.isEqual(a, b), "Cyclic structures are equal");
338 | a[0].def = "Larry";
339 | b[0].def = "Larry";
340 | ok(_.isEqual(a, b), "Cyclic structures containing equivalent properties are equal");
341 | a[0].def = new String("Larry");
342 | b[0].def = new String("Curly");
343 | ok(!_.isEqual(a, b), "Cyclic structures containing different properties are not equal");
344 |
345 | // Complex Circular References.
346 | a = {foo: {b: {foo: {c: {foo: null}}}}};
347 | b = {foo: {b: {foo: {c: {foo: null}}}}};
348 | a.foo.b.foo.c.foo = a;
349 | b.foo.b.foo.c.foo = b;
350 | ok(_.isEqual(a, b), "Cyclic structures with nested and identically-named properties are equal");
351 |
352 | // Chaining.
353 | ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
354 |
355 | a = _({x: 1, y: 2}).chain();
356 | b = _({x: 1, y: 2}).chain();
357 | equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained');
358 |
359 | // Objects from another frame.
360 | ok(_.isEqual({}, iObject));
361 | });
362 |
363 | test("isEmpty", function() {
364 | ok(!_([1]).isEmpty(), '[1] is not empty');
365 | ok(_.isEmpty([]), '[] is empty');
366 | ok(!_.isEmpty({one : 1}), '{one : 1} is not empty');
367 | ok(_.isEmpty({}), '{} is empty');
368 | ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty');
369 | ok(_.isEmpty(null), 'null is empty');
370 | ok(_.isEmpty(), 'undefined is empty');
371 | ok(_.isEmpty(''), 'the empty string is empty');
372 | ok(!_.isEmpty('moe'), 'but other strings are not');
373 |
374 | var obj = {one : 1};
375 | delete obj.one;
376 | ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
377 | });
378 |
379 | // Setup remote variables for iFrame tests.
380 | var iframe = document.createElement('iframe');
381 | jQuery(iframe).appendTo(document.body);
382 | var iDoc = iframe.contentDocument || iframe.contentWindow.document;
383 | iDoc.write(
384 | ""
399 | );
400 | iDoc.close();
401 |
402 | test("isElement", function() {
403 | ok(!_.isElement('div'), 'strings are not dom elements');
404 | ok(_.isElement($('html')[0]), 'the html tag is a DOM element');
405 | ok(_.isElement(iElement), 'even from another frame');
406 | });
407 |
408 | test("isArguments", function() {
409 | var args = (function(){ return arguments; })(1, 2, 3);
410 | ok(!_.isArguments('string'), 'a string is not an arguments object');
411 | ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
412 | ok(_.isArguments(args), 'but the arguments object is an arguments object');
413 | ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array');
414 | ok(!_.isArguments([1,2,3]), 'and not vanilla arrays.');
415 | ok(_.isArguments(iArguments), 'even from another frame');
416 | });
417 |
418 | test("isObject", function() {
419 | ok(_.isObject(arguments), 'the arguments object is object');
420 | ok(_.isObject([1, 2, 3]), 'and arrays');
421 | ok(_.isObject($('html')[0]), 'and DOM element');
422 | ok(_.isObject(iElement), 'even from another frame');
423 | ok(_.isObject(function () {}), 'and functions');
424 | ok(_.isObject(iFunction), 'even from another frame');
425 | ok(!_.isObject(null), 'but not null');
426 | ok(!_.isObject(undefined), 'and not undefined');
427 | ok(!_.isObject('string'), 'and not string');
428 | ok(!_.isObject(12), 'and not number');
429 | ok(!_.isObject(true), 'and not boolean');
430 | ok(_.isObject(new String('string')), 'but new String()');
431 | });
432 |
433 | test("isArray", function() {
434 | ok(!_.isArray(arguments), 'the arguments object is not an array');
435 | ok(_.isArray([1, 2, 3]), 'but arrays are');
436 | ok(_.isArray(iArray), 'even from another frame');
437 | });
438 |
439 | test("isString", function() {
440 | ok(!_.isString(document.body), 'the document body is not a string');
441 | ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
442 | ok(_.isString(iString), 'even from another frame');
443 | });
444 |
445 | test("isNumber", function() {
446 | ok(!_.isNumber('string'), 'a string is not a number');
447 | ok(!_.isNumber(arguments), 'the arguments object is not a number');
448 | ok(!_.isNumber(undefined), 'undefined is not a number');
449 | ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are');
450 | ok(_.isNumber(NaN), 'NaN *is* a number');
451 | ok(_.isNumber(Infinity), 'Infinity is a number');
452 | ok(_.isNumber(iNumber), 'even from another frame');
453 | ok(!_.isNumber('1'), 'numeric strings are not numbers');
454 | });
455 |
456 | test("isBoolean", function() {
457 | ok(!_.isBoolean(2), 'a number is not a boolean');
458 | ok(!_.isBoolean("string"), 'a string is not a boolean');
459 | ok(!_.isBoolean("false"), 'the string "false" is not a boolean');
460 | ok(!_.isBoolean("true"), 'the string "true" is not a boolean');
461 | ok(!_.isBoolean(arguments), 'the arguments object is not a boolean');
462 | ok(!_.isBoolean(undefined), 'undefined is not a boolean');
463 | ok(!_.isBoolean(NaN), 'NaN is not a boolean');
464 | ok(!_.isBoolean(null), 'null is not a boolean');
465 | ok(_.isBoolean(true), 'but true is');
466 | ok(_.isBoolean(false), 'and so is false');
467 | ok(_.isBoolean(iBoolean), 'even from another frame');
468 | });
469 |
470 | test("isFunction", function() {
471 | ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
472 | ok(!_.isFunction('moe'), 'strings are not functions');
473 | ok(_.isFunction(_.isFunction), 'but functions are');
474 | ok(_.isFunction(iFunction), 'even from another frame');
475 | });
476 |
477 | test("isDate", function() {
478 | ok(!_.isDate(100), 'numbers are not dates');
479 | ok(!_.isDate({}), 'objects are not dates');
480 | ok(_.isDate(new Date()), 'but dates are');
481 | ok(_.isDate(iDate), 'even from another frame');
482 | });
483 |
484 | test("isRegExp", function() {
485 | ok(!_.isRegExp(_.identity), 'functions are not RegExps');
486 | ok(_.isRegExp(/identity/), 'but RegExps are');
487 | ok(_.isRegExp(iRegExp), 'even from another frame');
488 | });
489 |
490 | test("isFinite", function() {
491 | ok(!_.isFinite(undefined), 'undefined is not Finite');
492 | ok(!_.isFinite(null), 'null is not Finite');
493 | ok(!_.isFinite(NaN), 'NaN is not Finite');
494 | ok(!_.isFinite(Infinity), 'Infinity is not Finite');
495 | ok(!_.isFinite(-Infinity), '-Infinity is not Finite');
496 | ok(_.isFinite('12'), 'Numeric strings are numbers');
497 | ok(!_.isFinite('1a'), 'Non numeric strings are not numbers');
498 | ok(!_.isFinite(''), 'Empty strings are not numbers');
499 | var obj = new Number(5);
500 | ok(_.isFinite(obj), 'Number instances can be finite');
501 | ok(_.isFinite(0), '0 is Finite');
502 | ok(_.isFinite(123), 'Ints are Finite');
503 | ok(_.isFinite(-12.44), 'Floats are Finite');
504 | });
505 |
506 | test("isNaN", function() {
507 | ok(!_.isNaN(undefined), 'undefined is not NaN');
508 | ok(!_.isNaN(null), 'null is not NaN');
509 | ok(!_.isNaN(0), '0 is not NaN');
510 | ok(_.isNaN(NaN), 'but NaN is');
511 | ok(_.isNaN(iNaN), 'even from another frame');
512 | ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
513 | });
514 |
515 | test("isNull", function() {
516 | ok(!_.isNull(undefined), 'undefined is not null');
517 | ok(!_.isNull(NaN), 'NaN is not null');
518 | ok(_.isNull(null), 'but null is');
519 | ok(_.isNull(iNull), 'even from another frame');
520 | });
521 |
522 | test("isUndefined", function() {
523 | ok(!_.isUndefined(1), 'numbers are defined');
524 | ok(!_.isUndefined(null), 'null is defined');
525 | ok(!_.isUndefined(false), 'false is defined');
526 | ok(!_.isUndefined(NaN), 'NaN is defined');
527 | ok(_.isUndefined(), 'nothing is undefined');
528 | ok(_.isUndefined(undefined), 'undefined is undefined');
529 | ok(_.isUndefined(iUndefined), 'even from another frame');
530 | });
531 |
532 | if (window.ActiveXObject) {
533 | test("IE host objects", function() {
534 | var xml = new ActiveXObject("Msxml2.DOMDocument.3.0");
535 | ok(!_.isNumber(xml));
536 | ok(!_.isBoolean(xml));
537 | ok(!_.isNaN(xml));
538 | ok(!_.isFunction(xml));
539 | ok(!_.isNull(xml));
540 | ok(!_.isUndefined(xml));
541 | });
542 | }
543 |
544 | test("tap", function() {
545 | var intercepted = null;
546 | var interceptor = function(obj) { intercepted = obj; };
547 | var returned = _.tap(1, interceptor);
548 | equal(intercepted, 1, "passes tapped object to interceptor");
549 | equal(returned, 1, "returns tapped object");
550 |
551 | returned = _([1,2,3]).chain().
552 | map(function(n){ return n * 2; }).
553 | max().
554 | tap(interceptor).
555 | value();
556 | ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain');
557 | });
558 | });
559 |
--------------------------------------------------------------------------------
/components/underscore/test/speed.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var numbers = [];
4 | for (var i=0; i<1000; i++) numbers.push(i);
5 | var objects = _.map(numbers, function(n){ return {num : n}; });
6 | var randomized = _.sortBy(numbers, function(){ return Math.random(); });
7 | var deep = _.map(_.range(100), function() { return _.range(1000); });
8 |
9 | JSLitmus.test('_.each()', function() {
10 | var timesTwo = [];
11 | _.each(numbers, function(num){ timesTwo.push(num * 2); });
12 | return timesTwo;
13 | });
14 |
15 | JSLitmus.test('_(list).each()', function() {
16 | var timesTwo = [];
17 | _(numbers).each(function(num){ timesTwo.push(num * 2); });
18 | return timesTwo;
19 | });
20 |
21 | JSLitmus.test('jQuery.each()', function() {
22 | var timesTwo = [];
23 | jQuery.each(numbers, function(){ timesTwo.push(this * 2); });
24 | return timesTwo;
25 | });
26 |
27 | JSLitmus.test('_.map()', function() {
28 | return _.map(objects, function(obj){ return obj.num; });
29 | });
30 |
31 | JSLitmus.test('jQuery.map()', function() {
32 | return jQuery.map(objects, function(obj){ return obj.num; });
33 | });
34 |
35 | JSLitmus.test('_.pluck()', function() {
36 | return _.pluck(objects, 'num');
37 | });
38 |
39 | JSLitmus.test('_.uniq()', function() {
40 | return _.uniq(randomized);
41 | });
42 |
43 | JSLitmus.test('_.uniq() (sorted)', function() {
44 | return _.uniq(numbers, true);
45 | });
46 |
47 | JSLitmus.test('_.sortBy()', function() {
48 | return _.sortBy(numbers, function(num){ return -num; });
49 | });
50 |
51 | JSLitmus.test('_.isEqual()', function() {
52 | return _.isEqual(numbers, randomized);
53 | });
54 |
55 | JSLitmus.test('_.keys()', function() {
56 | return _.keys(objects);
57 | });
58 |
59 | JSLitmus.test('_.values()', function() {
60 | return _.values(objects);
61 | });
62 |
63 | JSLitmus.test('_.intersection()', function() {
64 | return _.intersection(numbers, randomized);
65 | });
66 |
67 | JSLitmus.test('_.range()', function() {
68 | return _.range(1000);
69 | });
70 |
71 | JSLitmus.test('_.flatten()', function() {
72 | return _.flatten(deep);
73 | });
74 |
75 | })();
76 |
--------------------------------------------------------------------------------
/components/underscore/test/utility.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | var templateSettings;
4 |
5 | module("Utility", {
6 |
7 | setup: function() {
8 | templateSettings = _.clone(_.templateSettings);
9 | },
10 |
11 | teardown: function() {
12 | _.templateSettings = templateSettings;
13 | }
14 |
15 | });
16 |
17 | test("#750 - Return _ instance.", 2, function() {
18 | var instance = _([]);
19 | ok(_(instance) === instance);
20 | ok(new _(instance) === instance);
21 | });
22 |
23 | test("identity", function() {
24 | var moe = {name : 'moe'};
25 | equal(_.identity(moe), moe, 'moe is the same as his identity');
26 | });
27 |
28 | test("uniqueId", function() {
29 | var ids = [], i = 0;
30 | while(i++ < 100) ids.push(_.uniqueId());
31 | equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
32 | });
33 |
34 | test("times", function() {
35 | var vals = [];
36 | _.times(3, function (i) { vals.push(i); });
37 | ok(_.isEqual(vals, [0,1,2]), "is 0 indexed");
38 | //
39 | vals = [];
40 | _(3).times(function(i) { vals.push(i); });
41 | ok(_.isEqual(vals, [0,1,2]), "works as a wrapper");
42 | // collects return values
43 | ok(_.isEqual([0, 1, 2], _.times(3, function(i) { return i; })), "collects return values");
44 | });
45 |
46 | test("mixin", function() {
47 | _.mixin({
48 | myReverse: function(string) {
49 | return string.split('').reverse().join('');
50 | }
51 | });
52 | equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _');
53 | equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
54 | });
55 |
56 | test("_.escape", function() {
57 | equal(_.escape("Curly & Moe"), "Curly & Moe");
58 | equal(_.escape("Curly & Moe"), "Curly & Moe");
59 | equal(_.escape(null), '');
60 | });
61 |
62 | test("_.unescape", function() {
63 | var string = "Curly & Moe";
64 | equal(_.unescape("Curly & Moe"), string);
65 | equal(_.unescape("Curly & Moe"), "Curly & Moe");
66 | equal(_.unescape(null), '');
67 | equal(_.unescape(_.escape(string)), string);
68 | });
69 |
70 | test("template", function() {
71 | var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
72 | var result = basicTemplate({thing : 'This'});
73 | equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
74 |
75 | var sansSemicolonTemplate = _.template("A <% this %> B");
76 | equal(sansSemicolonTemplate(), "A B");
77 |
78 | var backslashTemplate = _.template("<%= thing %> is \\ridanculous");
79 | equal(backslashTemplate({thing: 'This'}), "This is \\ridanculous");
80 |
81 | var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
82 | equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
83 |
84 | var fancyTemplate = _.template("<% \
85 | for (key in people) { \
86 | %>- <%= people[key] %>
<% } %>
");
87 | result = fancyTemplate({people : {moe : "Moe", larry : "Larry", curly : "Curly"}});
88 | equal(result, "", 'can run arbitrary javascript in templates');
89 |
90 | var escapedCharsInJavascriptTemplate = _.template("<% _.each(numbers.split('\\n'), function(item) { %>- <%= item %>
<% }) %>
");
91 | result = escapedCharsInJavascriptTemplate({numbers: "one\ntwo\nthree\nfour"});
92 | equal(result, "", 'Can use escaped characters (e.g. \\n) in Javascript');
93 |
94 | var namespaceCollisionTemplate = _.template("<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %>\">
<% }); %>");
95 | result = namespaceCollisionTemplate({
96 | pageCount: 3,
97 | thumbnails: {
98 | 1: "p1-thumbnail.gif",
99 | 2: "p2-thumbnail.gif",
100 | 3: "p3-thumbnail.gif"
101 | }
102 | });
103 | equal(result, "3 p3-thumbnail.gif ");
104 |
105 | var noInterpolateTemplate = _.template("Just some text. Hey, I know this is silly but it aids consistency.
");
106 | result = noInterpolateTemplate();
107 | equal(result, "Just some text. Hey, I know this is silly but it aids consistency.
");
108 |
109 | var quoteTemplate = _.template("It's its, not it's");
110 | equal(quoteTemplate({}), "It's its, not it's");
111 |
112 | var quoteInStatementAndBody = _.template("<%\
113 | if(foo == 'bar'){ \
114 | %>Statement quotes and 'quotes'.<% } %>");
115 | equal(quoteInStatementAndBody({foo: "bar"}), "Statement quotes and 'quotes'.");
116 |
117 | var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
118 | equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
119 |
120 | var template = _.template("<%- value %>");
121 | var result = template({value: "
233 | -->
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |