diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 70c22ad0..00000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.idea -projectFilesBackup -extension.zip - diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c361ff9c..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "extension/assets/css-selector"] - path = extension/assets/css-selector - url = https://github.com/martinsbalodis/css-selector.git diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 02bbb60b..00000000 --- a/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file diff --git a/README.md b/README.md index 5a64886a..c12c5d7a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,27 @@ -# Web Scraper -Web Scraper is a chrome browser extension built for data extraction from web +# Web Scraper Plus +Web Scraper Plus is a chrome browser extension built for data extraction from web pages. Using this extension you can create a plan (sitemap) how a web site should be traversed and what should be extracted. Using these sitemaps the Web Scraper will navigate the site accordingly and extract all data. Scraped data later can be exported as CSV. -Install the extension from [Chrome store] [chrome-store] +Install the extension from [chrome-store] -### Features +Document for new features: [wiki] + +#### This tool is forked form [Web-Scraper] with many more features + +### New Features + 1. [CLI Support]: Start scraping from CMD/Terminal + 2. [MySQL Support]: Support MySQL database (v5.7+) + 3. [Anti Lazy-Loading]: Anti Lazy-Loading feature on pages + 4. [Data Filter]: Support user defined JS code for data preprocess and much more + 5. [Distinct]: Remove dulplicate data before the end of every task. + 6. [Custom Columns]: Define the columns you want to display, please use this feature together with [Data Filter] + 7. [Easy Scrape]: Create & scrape sitemap in a more easily way. (Based on https://github.com/aagiss) + 8. Random Interval: Add a random delay between requests. (Provided by https://github.com/Euphorbium) + +### Features(Forked from original work) 1. Scrape multiple pages 2. Sitemaps and scraped data are stored in browsers local storage or in CouchDB @@ -20,11 +34,9 @@ Install the extension from [Chrome store] [chrome-store] ### Help - Documentation and tutorials are available on [webscraper.io] [webscraper.io] + Basic documentation and tutorials are available on [webscraper.io] - Ask for help, submit bugs, suggest features on [google groups] [google-groups] - - Submit bugs and suggest features on [bug tracker] [github-issues] + Submit bugs and suggest features on [github-issues] #### Bugs When submitting a bug please attach an exported sitemap if possible. @@ -32,30 +44,21 @@ When submitting a bug please attach an exported sitemap if possible. ## License LGPLv3 -## Changelog - -### v0.2 - * Added Element click selector - * Added Element scroll down selector - * Added Link popup selector - * Improved table selector to work with any html markup - * Added Image download - * Added keyboard shortcuts when selecting elements - * Added configurable delay before using selector - * Added configurable delay between page visiting - * Added multiple start url configuration - * Added form field validation - * Fixed a lot of bugs - -### v0.1.3 - * Added Table selector - * Added HTML selector - * Added HTML attribute selector - * Added data preview - * Added ranged start urls - * Fixed bug which made selector tree not to show on some operating systems - - [chrome-store]: https://chrome.google.com/webstore/detail/web-scraper/jnhgnonknehpejjnehehllkliplmbmhn + [Web-Scraper]: https://github.com/martinsbalodis/web-scraper-chrome-extension + [chrome-store]: https://chrome.google.com/webstore/detail/pbbfbmlnpackgeofecdfncmmdbodkhma [webscraper.io]: http://webscraper.io/ - [google-groups]: https://groups.google.com/forum/#!forum/web-scraper - [github-issues]: https://github.com/martinsbalodis/web-scraper-chrome-extension/issues + [github-issues]: https://github.com/hejiheji001/web-scraper-chrome-extension/issues + [wiki]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki +[MySQL Support]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/MySQL-Support + +[CLI Support]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/CLI-Support + +[Anti Lazy-Loading]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/Anti-Lazy-Loading + +[Data Filter]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/Data-Filter + +[Distinct]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/Distinct + +[Custom Columns]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/Custom-Columns + +[Easy Scrape]: https://github.com/hejiheji001/web-scraper-chrome-extension/wiki/Easy-Scrape diff --git a/docs/wiki/Anti LazyLoading.png b/docs/wiki/Anti LazyLoading.png new file mode 100644 index 00000000..ce14d465 Binary files /dev/null and b/docs/wiki/Anti LazyLoading.png differ diff --git a/docs/wiki/CustomColumns1.png b/docs/wiki/CustomColumns1.png new file mode 100644 index 00000000..cb5879a5 Binary files /dev/null and b/docs/wiki/CustomColumns1.png differ diff --git a/docs/wiki/CustomColumns2.png b/docs/wiki/CustomColumns2.png new file mode 100644 index 00000000..c227853f Binary files /dev/null and b/docs/wiki/CustomColumns2.png differ diff --git a/docs/wiki/DataFilter1.png b/docs/wiki/DataFilter1.png new file mode 100644 index 00000000..501e1f15 Binary files /dev/null and b/docs/wiki/DataFilter1.png differ diff --git a/docs/wiki/DataFilter2.png b/docs/wiki/DataFilter2.png new file mode 100644 index 00000000..938fca0e Binary files /dev/null and b/docs/wiki/DataFilter2.png differ diff --git a/docs/wiki/DataFilter3.png b/docs/wiki/DataFilter3.png new file mode 100644 index 00000000..4ceb5d69 Binary files /dev/null and b/docs/wiki/DataFilter3.png differ diff --git a/docs/wiki/DataFilter4.png b/docs/wiki/DataFilter4.png new file mode 100644 index 00000000..936b6da5 Binary files /dev/null and b/docs/wiki/DataFilter4.png differ diff --git a/docs/wiki/Distinct.png b/docs/wiki/Distinct.png new file mode 100644 index 00000000..3d8016cb Binary files /dev/null and b/docs/wiki/Distinct.png differ diff --git a/docs/wiki/MySQL Support.png b/docs/wiki/MySQL Support.png new file mode 100644 index 00000000..6831c29d Binary files /dev/null and b/docs/wiki/MySQL Support.png differ diff --git a/docs/wiki/Selector.png b/docs/wiki/Selector.png new file mode 100644 index 00000000..e66b0d8d Binary files /dev/null and b/docs/wiki/Selector.png differ diff --git a/extension/assets/images/1400.png b/extension/assets/images/1400.png new file mode 100644 index 00000000..51365998 Binary files /dev/null and b/extension/assets/images/1400.png differ diff --git a/extension/assets/images/440.png b/extension/assets/images/440.png new file mode 100644 index 00000000..51365998 Binary files /dev/null and b/extension/assets/images/440.png differ diff --git a/extension/assets/images/920g.png b/extension/assets/images/920g.png new file mode 100644 index 00000000..47d60c7a Binary files /dev/null and b/extension/assets/images/920g.png differ diff --git a/extension/assets/images/icon128.png b/extension/assets/images/icon128.png index 559d72d2..05d2e673 100644 Binary files a/extension/assets/images/icon128.png and b/extension/assets/images/icon128.png differ diff --git a/extension/assets/images/icon16.png b/extension/assets/images/icon16.png index 6e6cdd18..88cb5086 100644 Binary files a/extension/assets/images/icon16.png and b/extension/assets/images/icon16.png differ diff --git a/extension/assets/images/icon19.png b/extension/assets/images/icon19.png index 49debcd9..bb1d610d 100644 Binary files a/extension/assets/images/icon19.png and b/extension/assets/images/icon19.png differ diff --git a/extension/assets/images/icon38.png b/extension/assets/images/icon38.png index 70027344..31905993 100644 Binary files a/extension/assets/images/icon38.png and b/extension/assets/images/icon38.png differ diff --git a/extension/assets/images/icon48.png b/extension/assets/images/icon48.png index 65e46c34..876b8656 100644 Binary files a/extension/assets/images/icon48.png and b/extension/assets/images/icon48.png differ diff --git a/extension/background_page/background_script.js b/extension/background_page/background_script.js index 480287e8..d6e51dfd 100644 --- a/extension/background_page/background_script.js +++ b/extension/background_page/background_script.js @@ -27,12 +27,22 @@ var sendToActiveTab = function(request, callback) { }); }; +var currentChildURLs=null; + chrome.runtime.onMessage.addListener( function (request, sender, sendResponse) { console.log("chrome.runtime.onMessage", request); - if (request.createSitemap) { + if (request.setCurrentChildURLs) { + currentChildURLs=request.urls; + sendResponse(request); + return true; + }else if (request.getCurrentChildURLs) { + request.urls=currentChildURLs; + sendResponse(request); + return true; + }else if (request.createSitemap) { store.createSitemap(request.sitemap, sendResponse); return true; } @@ -51,16 +61,22 @@ chrome.runtime.onMessage.addListener( else if (request.sitemapExists) { store.sitemapExists(request.sitemapId, sendResponse); return true; + }else if (request.findSitemap) { + store.findSitemap(request.sitemapId, sendResponse); + return true; } else if (request.getSitemapData) { + store.getSitemapData(new Sitemap(request.sitemap), sendResponse); store.getSitemapData(new Sitemap(request.sitemap), sendResponse); return true; } - else if (request.scrapeSitemap) { + else if (request.scrapeSitemap) {//TODO var sitemap = new Sitemap(request.sitemap); var queue = new Queue(); var browser = new ChromePopupBrowser({ - pageLoadDelay: request.pageLoadDelay + pageLoadDelay: request.pageLoadDelay, + scrollToBottom: request.scrollToBottom, + urls: sitemap.getStartUrls() }); var scraper = new Scraper({ @@ -72,8 +88,8 @@ chrome.runtime.onMessage.addListener( }); try { - scraper.run(function () { - browser.close(); + const callback = function(){ + //browser.close(); var notification = chrome.notifications.create("scraping-finished", { type: 'basic', iconUrl: 'assets/images/icon128.png', @@ -83,7 +99,14 @@ chrome.runtime.onMessage.addListener( // notification showed }); sendResponse(); - }); + } + + const finalize = function(finalResult){ + store.removeDuplicate(sitemap._id, request.distinct, finalResult); + callback(); + } + + scraper.run(finalize); } catch (e) { console.log("Scraper execution cancelled".e); diff --git a/extension/content_script/content_script.js b/extension/content_script/content_script.js index 7bd00b4b..d021d11b 100644 --- a/extension/content_script/content_script.js +++ b/extension/content_script/content_script.js @@ -17,6 +17,7 @@ chrome.runtime.onMessage.addListener( console.log("received data-preview extraction request", request); var extractor = new DataExtractor(request); var deferredData = extractor.getSingleSelectorData(request.parentSelectorIds, request.selectorId); + deferredData.done(function(data){ console.log("dataextractor data", data); sendResponse(data); @@ -36,6 +37,9 @@ chrome.runtime.onMessage.addListener( }); return true; + }else if(request.notice){ + console.log("Notification: ", request.message); + } } ); \ No newline at end of file diff --git a/extension/content_script/scrollToBottom.js b/extension/content_script/scrollToBottom.js new file mode 100644 index 00000000..a5c4b0c7 --- /dev/null +++ b/extension/content_script/scrollToBottom.js @@ -0,0 +1,13 @@ +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + if(message.run){ + var i = 0; + var x = setInterval(function(){ + window.scrollTo(0, i * 100); + i++; + if(i * 100 >= document.body.scrollHeight){ + clearInterval(x); + chrome.runtime.sendMessage({antilazyloading: true}); + } + }, 100); + } +}); \ No newline at end of file diff --git a/extension/devtools/devtools_init_page.js b/extension/devtools/devtools_init_page.js index 933883fd..3de62f61 100644 --- a/extension/devtools/devtools_init_page.js +++ b/extension/devtools/devtools_init_page.js @@ -1 +1 @@ -chrome.devtools.panels.create("Web Scraper", "../assets/images/icon48.png", "devtools/devtools_scraper_panel.html"); \ No newline at end of file +chrome.devtools.panels.create("Web Scraper Plus", "../assets/images/icon48.png", "devtools/devtools_scraper_panel.html"); \ No newline at end of file diff --git a/extension/devtools/devtools_scraper_panel.html b/extension/devtools/devtools_scraper_panel.html index 6f7c4e30..e1ca172f 100644 --- a/extension/devtools/devtools_scraper_panel.html +++ b/extension/devtools/devtools_scraper_panel.html @@ -34,6 +34,8 @@ + + \ No newline at end of file diff --git a/extension/devtools/views/SelectorEdit.html b/extension/devtools/views/SelectorEdit.html index 04928fc9..d306c04a 100644 --- a/extension/devtools/views/SelectorEdit.html +++ b/extension/devtools/views/SelectorEdit.html @@ -151,6 +151,22 @@ +
+ + +
+ +
+
+ +
+ + +
+ +
+
+
diff --git a/extension/devtools/views/SitemapListItem.html b/extension/devtools/views/SitemapListItem.html index cdbe1a47..6a3f4e3f 100644 --- a/extension/devtools/views/SitemapListItem.html +++ b/extension/devtools/views/SitemapListItem.html @@ -1,6 +1,6 @@ {{_id}} - +
{{#startUrl.push}} {{#startUrl}} {{.}}, @@ -9,6 +9,7 @@ {{^startUrl.push}} {{startUrl}} {{/startUrl.push}} +
diff --git a/extension/devtools/views/SitemapScrapeConfig.html b/extension/devtools/views/SitemapScrapeConfig.html index 3ea00f90..0515e443 100644 --- a/extension/devtools/views/SitemapScrapeConfig.html +++ b/extension/devtools/views/SitemapScrapeConfig.html @@ -1,3 +1,6 @@ +
@@ -6,12 +9,40 @@
+
+ +
+ +

A Math.random() * RandomInterval will be added to the RequestInterval attribute.

+
+
+
+ +
+ +

Page will SLOWLY scroll to bottom in order to force the page load all resources.

+
+
+
+ +
+ +

Web Scraper Plus will try to discard duplicate data. ONLY works with ArrestDB + JSON now.

+
+
+
- \ No newline at end of file + diff --git a/extension/devtools/views/Viewport.html b/extension/devtools/views/Viewport.html index c9ac4b9b..19875b18 100644 --- a/extension/devtools/views/Viewport.html +++ b/extension/devtools/views/Viewport.html @@ -1,7 +1,7 @@
-

Move developer tools to the bottom of your browser to start using Web Scraper.

+

Move developer tools to the bottom of your browser to start using Web Scraper Plus.

diff --git a/extension/extension.zip b/extension/extension.zip new file mode 100644 index 00000000..d76b2ca0 Binary files /dev/null and b/extension/extension.zip differ diff --git a/extension/manifest.json b/extension/manifest.json index 38191c95..c502f359 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,9 +1,9 @@ { "manifest_version": 2, - "version": "0.2.0.11", - "name": "Web Scraper", - "short_name": "Web Scraper", - "description": "Tool for data extraction from websites", + "version": "0.4.1", + "name": "Web Scraper Plus", + "short_name": "Web Scraper Plus", + "description": "Tool for data extraction from websites. Based on `Web Scraper`, now support MySQL/Anti Lazy-Loading/CMD/Terminal/JS filter and more", "permissions": ["", "tabs", "notifications", "storage", "unlimitedStorage", "downloads"], "icons": { "16": "assets/images/icon16.png", @@ -15,7 +15,7 @@ "19": "assets/images/icon19.png", "38": "assets/images/icon38.png" }, - "default_title": "Web Scraper", + "default_title": "Web Scraper Plus", "default_popup": "popup.html" }, "options_page": "options_page/options.html", @@ -52,6 +52,9 @@ "background_page/background_script.js" ] }, + "externally_connectable": { + "matches": ["*://*.jd.com/*", "*://*.jd.hk/*"] + }, "web_accessible_resources": [ "assets/images/icon16.png", "assets/images/icon48.png", @@ -88,7 +91,8 @@ "scripts/Sitemap.js", "scripts/ContentScript.js", "scripts/BackgroundScript.js", - "content_script/content_script.js" + "content_script/content_script.js", + "content_script/scrollToBottom.js" ], "css": [ "content_script/content_script.css" diff --git a/extension/options_page/options.html b/extension/options_page/options.html index beffd433..3fc8164f 100644 --- a/extension/options_page/options.html +++ b/extension/options_page/options.html @@ -1,7 +1,7 @@ - Web Scraper + Web Scraper Plus @@ -10,19 +10,18 @@
-

Web Scraper

+

Web Scraper Plus

Options page


- -
Storage settings -
+
@@ -43,7 +42,28 @@

Web Scraper

-
+ Default Sitemap (Selectors) +
+
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
diff --git a/extension/options_page/options_page.js b/extension/options_page/options_page.js index 5d28b493..b1d38f35 100644 --- a/extension/options_page/options_page.js +++ b/extension/options_page/options_page.js @@ -23,16 +23,33 @@ $(function () { $(this).popover('hide'); }); + $("#mysqlDB") + .popover({ + title: 'Database for scraped data', + html: true, + content: "Restful MySQL Server.
", + placement: 'bottom' + }) + .blur(function () { + $(this).popover('hide'); + }); + + $("#mysqlSitemap") + .popover({ + title: 'Table for Sitemap', + html: true, + content: "Sitemap for MySQL Database
", + placement: 'bottom' + }) + .blur(function () { + $(this).popover('hide'); + }); + // switch between configuration types $("select[name=storageType]").change(function () { var type = $(this).val(); - - if (type === 'couchdb') { - $(".form-group.couchdb").show(); - } - else { - $(".form-group.couchdb").hide(); - } + $(".form-group").not(".controller").hide(); + $(".form-group." + type).show(); }); // Extension configuration @@ -40,11 +57,17 @@ $(function () { // load previously synced data config.loadConfiguration(function () { - - $("#storageType").val(config.storageType); - $("#sitemapDb").val(config.sitemapDb); - $("#dataDb").val(config.dataDb); - + var type = config.storageType; + $("#storageType").val(type); + + if(type === 'mysql'){ + $("#mysqlDB").val(config.mysqlDB); + $("#mysqlSitemap").val(config.mysqlSitemap); + }else{ + $("#dataDb").val(config.dataDb); + $("#sitemapDb").val(config.sitemapDb); + } + $("#defaultSitemap").val(JSON.stringify(JSON.parse(config.defaultSitemap),null,'\t')); $("select[name=storageType]").change(); }); @@ -54,6 +77,9 @@ $(function () { var sitemapDb = $("#sitemapDb").val(); var dataDb = $("#dataDb").val(); var storageType = $("#storageType").val(); + var defaultSitemap = JSON.stringify(JSON.parse($("#defaultSitemap").val())); + var mysqlDB = $("#mysqlDB").val(); + var mysqlSitemap = $("#mysqlSitemap").val(); var newConfig; @@ -61,17 +87,26 @@ $(function () { newConfig = { storageType: storageType, sitemapDb: ' ', - dataDb: ' ' + dataDb: ' ', + defaultSitemap: defaultSitemap } } - else { + else if(storageType === 'mysql'){ + newConfig = { + storageType: storageType, + mysqlDB: mysqlDB, + mysqlSitemap: mysqlSitemap, + defaultSitemap: defaultSitemap + } + }else if(storageType === 'couchdb'){ newConfig = { storageType: storageType, sitemapDb: sitemapDb, - dataDb: dataDb + dataDb: dataDb, + defaultSitemap: defaultSitemap } } - + console.log(newConfig); config.updateConfiguration(newConfig); return false; }); diff --git a/extension/popup.html b/extension/popup.html index 9fb65a30..b602c6f2 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -7,10 +7,54 @@ font-size:12px; } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID:
URL:
+ + +
  • +

    - Open Developer tools where you will find Web Scraper tab: + Open Developer tools where you will find Web Scraper Plus tab:

    • Windows, Linux: Ctrl+Shift+I or F12 @@ -25,6 +69,7 @@

      Documentation is available on webscraper.io + Documentation for new features on Wiki

      \ No newline at end of file diff --git a/extension/scripts/App.js b/extension/scripts/App.js index b6ec293e..be6f6534 100644 --- a/extension/scripts/App.js +++ b/extension/scripts/App.js @@ -4,7 +4,7 @@ $(function () { $(".alert").alert(); var store = new StoreDevtools(); - new SitemapController({ + window.controller = new SitemapController({ store: store, templateDir: 'views/' }); diff --git a/extension/scripts/ChromePopupBrowser.js b/extension/scripts/ChromePopupBrowser.js index 0999f07a..38e12887 100644 --- a/extension/scripts/ChromePopupBrowser.js +++ b/extension/scripts/ChromePopupBrowser.js @@ -1,8 +1,7 @@ var ChromePopupBrowser = function (options) { - this.pageLoadDelay = options.pageLoadDelay; - - // @TODO somehow handle the closed window + this.scrollToBottom = options.scrollToBottom; + this.urls = options.urls; }; ChromePopupBrowser.prototype = { @@ -34,25 +33,42 @@ ChromePopupBrowser.prototype = { }, loadUrl: function (url, callback) { - var tab = this.tab; var tabLoadListener = function (tabId, changeInfo, tab) { if(tabId === this.tab.id) { if (changeInfo.status === 'complete') { - // @TODO check url ? maybe it would be bad because some sites might use redirects // remove event listener chrome.tabs.onUpdated.removeListener(tabLoadListener); // callback tab is loaded after page load delay - setTimeout(callback, this.pageLoadDelay); + + + if(this.scrollToBottom == "true"){ + var scrollListenser = function(request, sender, sendResponse) { + if (request.antilazyloading){ + chrome.tabs.sendMessage(tab.id, "Anti-LazyLoad finished"); + setTimeout(callback, this.pageLoadDelay); + } + } + + chrome.tabs.sendMessage(tab.id, "Anti-LazyLoad running"); + chrome.runtime.onMessage.addListener(scrollListenser); + try{ + chrome.tabs.executeScript(tab.id, {file: 'scrollToBottom.js'}); + }catch(e){ + alert(e); + } + chrome.tabs.sendMessage(tab.id, {run: true}); + }else{ + setTimeout(callback, this.pageLoadDelay); + } } } }.bind(this); chrome.tabs.onUpdated.addListener(tabLoadListener); - chrome.tabs.update(tab.id, {url: url}); }, @@ -61,25 +77,35 @@ ChromePopupBrowser.prototype = { }, fetchData: function (url, sitemap, parentSelectorId, callback, scope) { - var browser = this; - + let u = this.urls.pop(); this._initPopupWindow(function () { var tab = browser.tab; - browser.loadUrl(url, function () { - - var message = { - extractData: true, - sitemap: JSON.parse(JSON.stringify(sitemap)), - parentSelectorId: parentSelectorId - }; - - chrome.tabs.sendMessage(tab.id, message, function (data) { - console.log("extracted data from web page", data); - callback.call(scope, data); - }); - }.bind(this)); + if(u === url){ + u = ""; + var message = { + extractData: true, + sitemap: JSON.parse(JSON.stringify(sitemap)), + parentSelectorId: parentSelectorId + }; + chrome.tabs.sendMessage(tab.id, message, function (data) { + console.log("extracted data from web page", data);//TODO + callback.call(scope, data); + }); + } + }.bind(this, u)); }, this); + }, + + sendNotification: function(msg){ + chrome.tabs.sendMessage(this.tab.id, {message: msg, notice: true }); + var notification = chrome.notifications.create("Notification", { + type: 'basic', + iconUrl: 'assets/images/icon128.png', + title: 'Saving to DB', + message: 'Records:' + msg + }, function(id) { + }); } }; \ No newline at end of file diff --git a/extension/scripts/Config.js b/extension/scripts/Config.js index 2da39ad7..f1a31870 100644 --- a/extension/scripts/Config.js +++ b/extension/scripts/Config.js @@ -6,6 +6,8 @@ Config.prototype = { sitemapDb: '', dataDb: '', + mysqlDB: '', + defaultSitemap: '', defaults: { storageType: "local", @@ -13,7 +15,10 @@ Config.prototype = { sitemapDb: "scraper-sitemaps", // this is where scraped data is stored. // empty for local storage - dataDb: "" + dataDb: "", + defaultSitemap:'{"selectors": [{"id": "title","parentSelectors": ["_root"],"type": "SelectorText","multiple": false,"selector": "h1" }, { "id": "photo", "parentSelectors": ["_root"], "type": "SelectorImage", "multiple": false, "selector": "img" }, { "id": "date", "parentSelectors": ["_root"], "type": "SelectorText", "multiple": false,"selector": "time" }, { "id": "descr", "parentSelectors": ["_root"], "type": "SelectorText", "multiple": true, "selector": "p"}]}', + mysqlDB: "http://", + mysqlSitemap: "sitemap" }, /** @@ -21,16 +26,24 @@ Config.prototype = { */ loadConfiguration: function (callback) { - chrome.storage.sync.get(['sitemapDb', 'dataDb', 'storageType'], function (items) { - + chrome.storage.sync.get(['sitemapDb', 'dataDb', 'storageType', 'mysqlDB', 'defaultSitemap'], function (items) { this.storageType = items.storageType || this.defaults.storageType; + this.defaultSitemap= items.defaultSitemap || this.defaults.defaultSitemap; if (this.storageType === 'local') { this.sitemapDb = this.defaults.sitemapDb; this.dataDb = this.defaults.dataDb; } - else { + else if(this.storageType === 'couchdb') { + this.sitemapDb = items.sitemapDb || this.defaults.sitemapDb; + this.dataDb = items.dataDb || this.defaults.dataDb; + }else if(this.storageType === 'mysql'){ + this.mysqlDB = items.mysqlDB || this.defaults.mysqlDB; + this.dataDb = this.defaults.dataDb; + this.mysqlSitemap = items.mysqlSitemap || this.defaults.mysqlSitemap; + }else { this.sitemapDb = items.sitemapDb || this.defaults.sitemapDb; this.dataDb = items.dataDb || this.defaults.dataDb; + this.mysqlSitemap = items.mysqlSitemap || this.defaults.mysqlSitemap; } callback(); diff --git a/extension/scripts/ContentSelector.js b/extension/scripts/ContentSelector.js index d5f9981e..b2c45479 100644 --- a/extension/scripts/ContentSelector.js +++ b/extension/scripts/ContentSelector.js @@ -142,7 +142,12 @@ ContentSelector.prototype = { if(this.selectedElements.indexOf(element) === -1) { this.selectedElements.push(element); } - this.highlightSelectedElements(); + if(!$("#-selector-toolbar [name=diferentElementSelection]").prop("checked")) { + this.selectionFinished(); + }else{ + this.highlightSelectedElements(); + } + // Cancel all other events return false; @@ -389,7 +394,7 @@ ContentSelector.prototype = { selectionFinished: function () { var resultCssSelector = this.getCurrentCSSSelector(); - + if($("#-selector-toolbar [name=diferentElementSelection]").prop("checked")) resultCssSelector=prompt('selector',resultCssSelector); this.deferredCSSSelectorResponse.resolve({ CSSSelector: resultCssSelector }); diff --git a/extension/scripts/Controller.js b/extension/scripts/Controller.js index 3caa4cab..ded78df5 100644 --- a/extension/scripts/Controller.js +++ b/extension/scripts/Controller.js @@ -415,6 +415,7 @@ SitemapController.prototype = { $sitemapListPanel.find("tbody").append($sitemap); }); $("#viewport").html($sitemapListPanel); + window.runTask(); }); }, @@ -488,8 +489,7 @@ SitemapController.prototype = { if(sitemapExists) { var validator = this.getFormValidator(); validator.updateStatus('_id', 'INVALID', 'callback'); - } - else { + }else { this.store.createSitemap(sitemap, function (sitemap) { this._editSitemap(sitemap, ['_root']); }.bind(this, sitemap)); @@ -556,7 +556,6 @@ SitemapController.prototype = { * Callback when sitemap edit button is clicked in sitemap grid */ editSitemap: function (tr) { - var sitemap = $(tr).data("sitemap"); this._editSitemap(sitemap); }, @@ -567,7 +566,6 @@ SitemapController.prototype = { this.showSitemapSelectorList(); }, showSitemapSelectorList: function () { - this.setActiveNavigationButton('sitemap-selector-list'); var sitemap = this.state.currentSitemap; @@ -835,11 +833,9 @@ SitemapController.prototype = { if(!this.isValidForm()) { return false; } - // cancel possible element selection this.contentScript.removeCurrentContentSelector().done(function(){ sitemap.updateSelector(selector, newSelector); - this.store.saveSitemap(sitemap, function () { this.showSitemapSelectorList(); }.bind(this)); @@ -863,6 +859,8 @@ SitemapController.prototype = { var clickPopup = $("#edit-selector [name=clickPopup]").is(":checked"); var regex = $("#edit-selector [name=regex]").val(); var delay = $("#edit-selector [name=delay]").val(); + var datafilter = $("#edit-selector [name=datafilter]").val(); + var customColumns = $("#edit-selector [name=customColumns]").val(); var extractAttribute = $("#edit-selector [name=extractAttribute]").val(); var parentSelectors = $("#edit-selector [name=parentSelectors]").val(); var columns = []; @@ -896,6 +894,8 @@ SitemapController.prototype = { clickPopup: clickPopup, regex: regex, extractAttribute:extractAttribute, + datafilter:datafilter, + customColumns: customColumns, parentSelectors: parentSelectors, columns:columns, delay:delay @@ -969,6 +969,16 @@ SitemapController.prototype = { } } }, + "requestIntervalRandomness": { + validators: { + notEmpty: { + message: 'The request interval randomness is required and cannot be empty' + }, + numeric: { + message: 'The request interval randomness must be numeric' + }, + } + }, "pageLoadDelay": { validators: { notEmpty: { @@ -989,7 +999,6 @@ SitemapController.prototype = { }); }, showScrapeSitemapConfigPanel: function() { - this.setActiveNavigationButton('sitemap-scrape'); var scrapeConfigPanel = ich.SitemapScrapeConfig(); $("#viewport").html(scrapeConfigPanel); @@ -1001,16 +1010,21 @@ SitemapController.prototype = { if(!this.isValidForm()) { return false; } - var requestInterval = $("input[name=requestInterval]").val(); var pageLoadDelay = $("input[name=pageLoadDelay]").val(); + var scrollToBottom = $("input[name=scrollToBottom]").val(); + var distinct = $("input[name=distinct]").val(); + var nonEmpty = $("input[name=nonEmpty]").val(); var sitemap = this.state.currentSitemap; var request = { scrapeSitemap: true, sitemap: JSON.parse(JSON.stringify(sitemap)), requestInterval: requestInterval, - pageLoadDelay: pageLoadDelay + pageLoadDelay: pageLoadDelay, + scrollToBottom: scrollToBottom, + distinct: distinct, + nonEmpty: nonEmpty }; // show sitemap scraping panel @@ -1033,8 +1047,10 @@ SitemapController.prototype = { this.setActiveNavigationButton('sitemap-browse'); var sitemap = this.state.currentSitemap; this.store.getSitemapData(sitemap, function (data) { - var dataColumns = sitemap.getDataColumns(); + if(sitemap.selectors[0].columns){ + dataColumns = JSON.parse(sitemap.selectors[0].columns); + } var dataPanel = ich.SitemapBrowseData({ columns: dataColumns @@ -1042,7 +1058,7 @@ SitemapController.prototype = { $("#viewport").html(dataPanel); // display data - // Doing this the long way so there aren't xss vulnerubilites + // Doing this the long way so there aren't xss vulnerubilites // while working with data or with the selector titles var $tbody = $("#sitemap-data tbody"); data.forEach(function (row) { @@ -1342,13 +1358,13 @@ SitemapController.prototype = { parentSelectorIds: parentSelectorIds, selectorId: selectorId }; + chrome.runtime.sendMessage(request, function (response) { if (response.length === 0) { return } var dataColumns = Object.keys(response[0]); - console.log(dataColumns); var $dataPreviewPanel = ich.DataPreview({ @@ -1365,9 +1381,15 @@ SitemapController.prototype = { dataColumns.forEach(function (column) { var $td = $(""); var cellData = row[column]; + + if(typeof func == "function"){ + cellData = func(cellData); + } + if (typeof cellData === 'object') { cellData = JSON.stringify(cellData); } + $td.text(cellData); $tr.append($td); }); @@ -1407,7 +1429,7 @@ SitemapController.prototype = { // remove from validator var validator = this.getFormValidator(); validator.removeField($block.find("input")); - + $block.remove(); } } diff --git a/extension/scripts/DataExtractor.js b/extension/scripts/DataExtractor.js index 6dc29fea..07c8307a 100644 --- a/extension/scripts/DataExtractor.js +++ b/extension/scripts/DataExtractor.js @@ -110,10 +110,11 @@ DataExtractor.prototype = { } }, - getSelectorTreeCommonData: function (selectors, parentSelectorId, parentElement) { + getSelectorTreeCommonData: function (selectors, parentSelectorId, parentElement) {//Grouped var childSelectors = selectors.getDirectChildSelectors(parentSelectorId); var deferredDataCalls = []; + childSelectors.forEach(function (selector) { if (!selectors.willReturnMultipleRecords(selector.id)) { deferredDataCalls.push(this.getSelectorCommonData.bind(this,selectors, selector, parentElement)); @@ -122,9 +123,8 @@ DataExtractor.prototype = { var deferredResponse = $.Deferred(); $.whenCallSequentially(deferredDataCalls).done(function(responses) { - var commonData = {}; - responses.forEach(function(data) { + responses.forEach(function(data) { commonData = Object.merge(commonData, data); }); deferredResponse.resolve(commonData); @@ -133,11 +133,16 @@ DataExtractor.prototype = { return deferredResponse; }, - getSelectorCommonData: function(selectors, selector, parentElement) { + getSelectorCommonData: function(selectors, selector, parentElement) {//TODO var d = $.Deferred(); var deferredData = selector.getData(parentElement); deferredData.done(function(data) { + var func = ""; + + if(selector.datafilter){ + func = new Function("data", "return " + selector.datafilter); + } if (selector.willReturnElements()) { var newParentElement = data[0]; @@ -147,7 +152,18 @@ DataExtractor.prototype = { }); } else { - d.resolve(data[0]); + if(typeof func === "function"){ + try{ + var dt = data[0]; + var key = Object.keys(dt); + key.forEach(k => dt[k] = func(dt[k])) + d.resolve(dt); + }catch(e){ + d.resolve(data[0]); + } + }else{ + d.resolve(data[0]); + } } }.bind(this)); @@ -163,6 +179,11 @@ DataExtractor.prototype = { // if the selector is not an Element selector then its fetched data is the result. if (!selector.willReturnElements()) { + var func = ""; + + if(selector.datafilter){ + func = new Function("data", "return " + selector.datafilter); + } var deferredData = selector.getData(parentElement); deferredData.done(function(selectorData) { @@ -174,7 +195,16 @@ DataExtractor.prototype = { resultData.push(record); }.bind(this)); - deferredResponse.resolve(resultData); + if(typeof func === "function"){ + try{ + deferredResponse.resolve(func(resultData)); + }catch(e){ + deferredResponse.resolve(resultData); + } + }else{ + deferredResponse.resolve(resultData); + } + }.bind(this)); } @@ -245,7 +275,6 @@ DataExtractor.prototype = { deferredResponse.resolve([]); } else { - deferredResponse.resolve([commonData]); } } @@ -259,15 +288,21 @@ DataExtractor.prototype = { return deferredResponse; }, - getData: function () { + getData: function () {//TODO var selectorTrees = this.findSelectorTrees(); var dataDeferredCalls = []; - + var commonFunc = ""; + selectorTrees.forEach(function (selectorTree) { - var deferredTreeDataCall = this.getSelectorTreeData.bind(this, selectorTree, this.parentSelectorId, this.parentElement, {}); dataDeferredCalls.push(deferredTreeDataCall); + + selectorTree.forEach((selector) => { + if(selector.datafilter && selector.willReturnElements()){ + commonFunc = new Function("data", "return " + selector.datafilter); + } + }); }.bind(this)); var responseDeferred = $.Deferred(); @@ -276,7 +311,17 @@ DataExtractor.prototype = { responses.forEach(function(dataResults) { results = results.concat(dataResults); }.bind(this)); - responseDeferred.resolve(results); + + if(typeof commonFunc == "function"){ + try{ + let data = commonFunc(results); + responseDeferred.resolve(data); + }catch(e){ + responseDeferred.resolve(results); + } + }else{ + responseDeferred.resolve(results); + } }.bind(this)); return responseDeferred; }, diff --git a/extension/scripts/Job.js b/extension/scripts/Job.js index 2f06a71c..24b6e9c7 100644 --- a/extension/scripts/Job.js +++ b/extension/scripts/Job.js @@ -54,24 +54,26 @@ Job.prototype = { }, execute: function (browser, callback, scope) { - var sitemap = this.scraper.sitemap; var job = this; browser.fetchData(this.url, sitemap, this.parentSelector, function (results) { // merge data with data from initialization - for (var i in results) { - var result = results[i]; - for (var key in this.baseData) { - if(!(key in result)) { - result[key] = this.baseData[key]; + if(results){ + for (var i in results) { + var result = results[i]; + for (var key in this.baseData) { + if(!(key in result)) { + result[key] = this.baseData[key]; + } } + this.dataItems.push(result); } - this.dataItems.push(result); + console.log(job); + callback(job); } - console.log(job); - callback(job); }.bind(this), this); }, + getResults: function () { return this.dataItems; } diff --git a/extension/scripts/PopupApp.js b/extension/scripts/PopupApp.js new file mode 100644 index 00000000..de8c5b4d --- /dev/null +++ b/extension/scripts/PopupApp.js @@ -0,0 +1,357 @@ +chrome.tabs.query({ + active: true, // Select active tabs + lastFocusedWindow: true // In the current window +}, function(array_of_Tabs) { + var tab = array_of_Tabs[0]; + chrome.extension.getBackgroundPage().console.log(tab); + chrome.tabs.executeScript(tab.id, { + file: "assets/jquery-2.0.3.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "assets/jquery.whencallsequentially.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "assets/sugar-1.4.1.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "assets/base64.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/ElementQuery.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Sitemap.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorElement.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorGroup.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorLink.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorPopupLink.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorText.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorImage.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorHTML.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorElementAttribute.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorTable.js", + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorElementScroll.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/Selector/SelectorElementClick.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/SelectorList.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: "scripts/DataExtractor.js" + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './content_script/content_script.js' + }, function(res) { + popup_main(tab); + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) + }) +}); + +function popup_selector_click() { + selector_id = $(this).attr('rel'); + sitemap_id = $(this).attr('data-sitemap_id'); + chrome.tabs.query({ + active: true, // Select active tabs + lastFocusedWindow: true // In the current window + }, function(array_of_Tabs) { + var tab = array_of_Tabs[0]; + chrome.tabs.insertCSS(tab.id, { + file: './content_script/content_script.css' + }); + chrome.tabs.executeScript(tab.id, { + file: './scripts/BackgroundScript.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/ContentScript.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/ContentSelector.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './assets/d3.v3.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/Config.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/SelectorGraph.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/SelectorGraphv2.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/StoreDevTools.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './assets/css-selector/lib/CssSelector.js', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + code: 'var selector_id="' + selector_id + '";var sitemap_id="' + sitemap_id + '";', + }, function(res) { + chrome.tabs.executeScript(tab.id, { + file: './scripts/PopupClickRow.js', + }, function(res) { + window.close(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); +} + +function popup_view_update(store, sitemap_exists, sitemap_id, filter) { + $(".data-preview-modal").hide(); + var general = $("#general").html(); + if (!sitemap_exists) { + $('#grab_link').css('display', 'block'); + $('#grab_list').css('display', 'block'); + $('#ungrab').css('display', 'none'); + $('#selectors').html(general); + return; + } + $('#grab_link').css('display', 'none'); + $('#grab_list').css('display', 'none'); + if (filter == 'l2k'){ + $('#ungrab').css('display', 'none'); + }else{ + $('#ungrab').css('display', 'block'); + store.findSitemap(sitemap_id, function(sitemap) { + html = ""; + for (i in sitemap.selectors) { + var selector = sitemap.selectors[i]; + if (selector.parentSelectors[0] != filter && filter != '*') continue; + html += ''; + html += ''; + html += ''; + html += ''; + } + html += '
      ' + selector['id'] + '
      '; + html += '
      ' + selector['selector'] + '
      ...
      '; + $('#selectors').html(html); + var selected = null; + $('.selector').mouseover(function() { + $('.selector').css('background', 'white'); + $(this).css('background', 'yellow'); + }); + $('.selector').click(popup_selector_click); + for (i in sitemap.selectors) { + var selector = sitemap.selectors[i]; + if (selector.parentSelectors[0] != filter && filter != '*') continue; + var f = function(selector) { + var request = { + previewSelectorData: true, + sitemap: JSON.parse(JSON.stringify(sitemap)), + parentSelectorIds: [filter], + selectorId: selector.id + }; + chrome.runtime.sendMessage(request, function(response) { + if (!response || response.length === 0) { + return; + } + if (selector.type == 'SelectorLink') { + html = ''; + var request = { + setCurrentChildURLs: true, + urls: urls + }; + chrome.runtime.sendMessage(request, function(res) {}); + $('#sel_data_' + selector.id).html(html); + $('#' + selector.id + '-regexp').click(function(e) { + event.preventDefault(); + return false; + + }); + $('#' + selector.id + '-regexp').change(function() { + selector.regex = $(this).val(); + store.saveSitemap(sitemap, function() { + window.location.reload(); + }); + return true; + }); + + } else if (selector.type == 'SelectorImage') { + $('#sel_data_' + selector.id).html(''); + } else { + html = ''; + for (var i in response) { + html += response[i][selector.id] + ' '; + } + $('#sel_data_' + selector.id).html(html); + } + }); + }; + f(selector); + } + }); + } +} + +function showLoading() { + $(".data-preview-modal").show(); +} + +function popup_main(tab) { + showLoading(); + config = new Config(); + config.loadConfiguration(function() { + var store = new Store(config); + var request = { + getCurrentChildURLs: true + }; + chrome.runtime.sendMessage(request, function(res) { + var url = tab.url; + var sitemap_id = url.replace(/https?:/, '').replace(/[^0123456789qwertyuiopljkjhgfdsazxcvbnm]+/ig, '_').replace(/^_+/, '').replace(/_+$/, ''); + store.sitemapExists('t' + sitemap_id, function(exists) { + if (exists) { + popup_main2(config, store, url, sitemap_id, '_root'); + } else { + if (res.urls && res.urls[tab.url]) { + popup_main2(config, store, url, res.urls[url].replace(/^t/, ''), 'l2k'); + } else { + popup_main2(config, store, url, sitemap_id, '_root'); + } + }; + }); + }); + }); +} + + +function popup_main2(config, store, url, sitemap_id, filter) { + if (filter == '*') { + $('#_id').css('display', 'none'); + $('#_url').css('display', 'none'); + } else { + $('#_id').css('display', 'block'); + $('#_url').css('display', 'block'); + } + $('#_id').val(sitemap_id); + $('#_url').val(url); + store.sitemapExists('t' + sitemap_id, function(exists) { + if (exists) { + popup_view_update(store, exists, 't' + sitemap_id, filter); + } else { + store.sitemapExists('k' + sitemap_id, function(exists) { + popup_view_update(store, exists, 'k' + sitemap_id, filter); + }); + } + + }); + $('#ungrab').click(function() { + store.deleteSitemap({ + '_id': 't' + sitemap_id + }, function() {}); + store.deleteSitemap({ + '_id': 'k' + sitemap_id + }, function() {}); + popup_view_update(store, false, sitemap_id, filter); + }); + $('#grab_link').click(function() { + var _id = 'k' + sitemap_id; + store.findSimilar(_id, function(s) { + if (s) + sitemap = { + selectors: s.selectors + }; + else + sitemap = { + selectors: JSON.parse(config.defaultSitemap).selectors + } + sitemap['_id'] = _id; + sitemap['startUrl'] = url; + store.createSitemap(sitemap, function(s) { + popup_view_update(store, true, _id, filter); + }); + }); + }); + $('#grab_list').click(function() { + var _id = sitemap_id; + store.findSimilar('k' + _id, function(s) { + sitemap = { + selectors: JSON.parse(config.defaultSitemap).selectors + } + for (var i in sitemap.selectors) { + if (s) { + for (var j in s.selectors) { + if (s.selectors[j].id == sitemap.selectors[i]) { + sitemap.selectors[i] = s.selectors[j].id; + } + } + } + sitemap.selectors[i].parentSelectors[0] = 'l2k'; + } + sitemap.selectors.unshift({ + "type": "SelectorLink", + "multiple": true, + "id": "l2k", + "regex": ".*", + "selector": "a", + "delay": "", + "parentSelectors": ["_root"] + }); + sitemap['_id'] = 't' + _id; + sitemap['startUrl'] = url; + showLoading(); + store.createSitemap(sitemap, function(s) { + popup_view_update(store, true, 't' + _id, filter); + }); + }); + }); +} \ No newline at end of file diff --git a/extension/scripts/PopupClickRow.js b/extension/scripts/PopupClickRow.js new file mode 100644 index 00000000..20fa0016 --- /dev/null +++ b/extension/scripts/PopupClickRow.js @@ -0,0 +1,47 @@ +$(function (){ + var store=new StoreDevtools(); + var foundsitemap=function (sitemap){ + if(sitemap===false){ + alert('Could not find site (id:'+sitemap_id+')... maybe grab first'); + return; + } + var selectorList=new SelectorList(); + var aselector=null; + for(i in sitemap.selectors){ + var selector=sitemap.selectors[i]; + aselector=new Selector(selector); + selectorList.push(aselector); + if(selector.id==selector_id) break; + aselector=null; + + } + if(aselector){ + var currentStateParentSelectorIds = ['_root']; + var parentCSSSelector = selectorList.getParentCSSSelectorWithinOnePage(['_root']); + var contentScript=getContentScript('ContentScript'); + var deferredSelector = contentScript.selectSelector({ + parentCSSSelector: parentCSSSelector, + allowedElements: aselector.getItemCSSSelector() + }); + + deferredSelector.done(function(result) { + if(!result) return; + selector['selector']=result.CSSSelector; + store.saveSitemap(sitemap); + }.bind(this)); + } + }; + store.findSitemap(sitemap_id,function(sitemap){ + if(sitemap!==false){ + foundsitemap(sitemap); + }else{ + store.findSitemap('t'+sitemap_id,function(sitemap){ + if(sitemap!==false){ + foundsitemap(sitemap); + }else{ + store.findSitemap('k'+sitemap_id,foundsitemap); + } + }); + } + }); +}); \ No newline at end of file diff --git a/extension/scripts/Queue.js b/extension/scripts/Queue.js index a45065b8..712ed0b1 100644 --- a/extension/scripts/Queue.js +++ b/extension/scripts/Queue.js @@ -44,6 +44,10 @@ Queue.prototype = { this.scrapedUrls[url] = true; }, + getJobs: function(){ + return this.jobs; + }, + getNextJob: function () { // @TODO test this diff --git a/extension/scripts/RunTask.js b/extension/scripts/RunTask.js new file mode 100644 index 00000000..26a937fa --- /dev/null +++ b/extension/scripts/RunTask.js @@ -0,0 +1,23 @@ +window.runTask = function () { + var query = location.search; + if (query.indexOf("run") > -1) { + var id = getQueryString("run"); + var anti = getQueryString("anti"); + var distinct = getQueryString("distinct"); + var nonEmpty = getQueryString("nonempty"); + var dom = $($("#sitemaps").find("td").get().filter(s => s.innerHTML == id)); + dom.click(); + controller.showScrapeSitemapConfigPanel(); + $("#scrollToBottom").val(anti); + $("#distinct").val(distinct); + $("#nonEmpty").val(nonEmpty); + controller.scrapeSitemap(); + } +} + +function getQueryString(name) { + var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); + var r = window.location.search.substr(1).match(reg); + if (r != null) return unescape(r[2]); + return null; +} \ No newline at end of file diff --git a/extension/scripts/Scraper.js b/extension/scripts/Scraper.js index 2a5f25b9..67ec87ff 100644 --- a/extension/scripts/Scraper.js +++ b/extension/scripts/Scraper.js @@ -2,10 +2,16 @@ Scraper = function (options) { this.queue = options.queue; this.sitemap = options.sitemap; this.store = options.store; + this.store.browser = options.browser; this.browser = options.browser; this.resultWriter = null; // db instance for scraped data writing this.requestInterval = parseInt(options.requestInterval); + this.requestIntervalRandomness = parseInt(options.requestIntervalRandomness); this.pageLoadDelay = parseInt(options.pageLoadDelay); + this.size = 0; + this.current = 0; + this.resultLength = 0; + this.urls = []; }; Scraper.prototype = { @@ -17,17 +23,15 @@ Scraper.prototype = { _timeNextScrapeAvailable: 0, initFirstJobs: function () { - - var urls = this.sitemap.getStartUrls(); - - urls.forEach(function (url) { + this.urls = this.sitemap.getStartUrls(); + this.size = this.urls.length; + this.urls.forEach(function (url) { var firstJob = new Job(url, "_root", this); this.queue.add(firstJob); }.bind(this)); }, run: function (executionCallback) { - var scraper = this; // callback when scraping is finished @@ -134,72 +138,94 @@ Scraper.prototype = { }, // @TODO remove recursion and add an iterative way to run these jobs. - _run: function () { - - var job = this.queue.getNextJob(); - if (job === false) { - console.log("Scraper execution is finished"); - this.executionCallback(); - return; - } - - job.execute(this.browser, function (job) { - - var scrapedRecords = []; - var deferredDatamanipulations = []; - - var records = job.getResults(); - records.forEach(function (record) { - //var record = JSON.parse(JSON.stringify(rec)); - - deferredDatamanipulations.push(this.saveImages.bind(this, record)); - - // @TODO refactor job exstraction to a seperate method - if (this.recordCanHaveChildJobs(record)) { - var followSelectorId = record._followSelectorId; - var followURL = record['_follow']; - var followSelectorId = record['_followSelectorId']; - delete record['_follow']; - delete record['_followSelectorId']; - var newJob = new Job(followURL, followSelectorId, this, job, record); - if (this.queue.canBeAdded(newJob)) { - this.queue.add(newJob); - } - // store already scraped links - else { - console.log("Ignoring next") - console.log(record); -// scrapedRecords.push(record); - } - } - else { - if (record._follow !== undefined) { - delete record['_follow']; - delete record['_followSelectorId']; - } - scrapedRecords.push(record); - } - - }.bind(this)); - - $.whenCallSequentially(deferredDatamanipulations).done(function() { - this.resultWriter.writeDocs(scrapedRecords, function () { + _run: function (tempDoc) { + var jobs = this.queue.getJobs(); + var job = jobs.pop(); + if(job && this.urls.indexOf(job.url) > -1 && this.current == this.size - jobs.length - 1){ + job.execute(this.browser, function (job) { + var scrapedRecords = []; + var deferredDatamanipulations = []; + + var records = job.getResults(); + if(records.length > 0){ + records.forEach(function (record) { + deferredDatamanipulations.push(this.saveImages.bind(this, record)); + if (this.recordCanHaveChildJobs(record)) { + var followSelectorId = record._followSelectorId; + var followURL = record['_follow']; + var followSelectorId = record['_followSelectorId']; + delete record['_follow']; + delete record['_followSelectorId']; + var newJob = new Job(followURL, followSelectorId, this, job, record); + if (this.queue.canBeAdded(newJob)) { + this.queue.add(newJob); + } + else { + console.log("Ignoring next") + console.log(record); + } + } + else { + if (record._follow !== undefined) { + delete record['_follow']; + delete record['_followSelectorId']; + } + scrapedRecords.push(record); + } + }.bind(this)); + $.whenCallSequentially(deferredDatamanipulations).done(function() { + this.resultWriter.writeDocs(scrapedRecords, function (tempDoc) { + const toSortedJSON = function(obj) { + return JSON.stringify(typeof obj == "object" ? Object.keys(obj).sort().reduce((o, key) => (o[key] = toSortedJSON(obj[key]), o), {}) : obj); + } + + const uniq = function(xs) { + let seen = {}; + return xs.filter(x => (k = (toSortedJSON || JSON.stringify)(x), !(k in seen) && (seen[k] = 1))); + } + + try{ + tempDoc = uniq(tempDoc); + }catch(e){ + console.log(e); + } + if(this.resultLength == 0 || tempDoc.length > this.resultLength){ + this.resultLength = tempDoc ? tempDoc.length : 0; + this.current++; + var now = (new Date()).getTime(); + this._timeNextScrapeAvailable = now + this.requestInterval + Math.random()*this.requestIntervalRandomness; + if(now >= this._timeNextScrapeAvailable) { + this._run(tempDoc); + }else{ + var delay = this._timeNextScrapeAvailable - now; + setTimeout(function() { + this._run(tempDoc); + }.bind(this), delay); + } + } + }.bind(this)); + }.bind(this)); + }else{ + this.current++; var now = (new Date()).getTime(); - // delay next job if needed - this._timeNextScrapeAvailable = now + this.requestInterval; - if(now >= this._timeNextScrapeAvailable) { - this._run(); - } - else { - var delay = this._timeNextScrapeAvailable - now; - setTimeout(function() { - this._run(); - }.bind(this), delay); - } - }.bind(this)); + this._timeNextScrapeAvailable = now + this.requestInterval + Math.random()*this.requestIntervalRandomness; + if(now >= this._timeNextScrapeAvailable) { + this._run(tempDoc); + }else{ + var delay = this._timeNextScrapeAvailable - now; + setTimeout(function() { + this._run(tempDoc); + }.bind(this), delay); + } + } }.bind(this)); - - }.bind(this)); + }else if (this.current === this.size) { + this.browser.sendNotification(tempDoc.length); + this.executionCallback(tempDoc); + return; + }else{ + this._run(tempDoc); + } } -}; \ No newline at end of file +}; diff --git a/extension/scripts/Selector.js b/extension/scripts/Selector.js index ac5aad28..aaf5cd28 100644 --- a/extension/scripts/Selector.js +++ b/extension/scripts/Selector.js @@ -1,5 +1,5 @@ var Selector = function (selector) { - this.updateData(selector); + this.updateData(selector); this.initType(); }; diff --git a/extension/scripts/Selector/SelectorElement.js b/extension/scripts/Selector/SelectorElement.js index e1dc41f8..e3a66241 100644 --- a/extension/scripts/Selector/SelectorElement.js +++ b/extension/scripts/Selector/SelectorElement.js @@ -34,6 +34,6 @@ var SelectorElement = { }, getFeatures: function () { - return ['multiple', 'delay'] + return ['multiple', 'delay', 'datafilter', 'customColumns'] } }; diff --git a/extension/scripts/Selector/SelectorElementAttribute.js b/extension/scripts/Selector/SelectorElementAttribute.js index 3e0cdeab..91aab57b 100644 --- a/extension/scripts/Selector/SelectorElementAttribute.js +++ b/extension/scripts/Selector/SelectorElementAttribute.js @@ -46,6 +46,6 @@ var SelectorElementAttribute = { }, getFeatures: function () { - return ['multiple', 'extractAttribute', 'delay'] + return ['multiple', 'extractAttribute', 'datafilter', 'delay'] } }; \ No newline at end of file diff --git a/extension/scripts/Selector/SelectorElementClick.js b/extension/scripts/Selector/SelectorElementClick.js index 87592803..069a928c 100644 --- a/extension/scripts/Selector/SelectorElementClick.js +++ b/extension/scripts/Selector/SelectorElementClick.js @@ -169,6 +169,6 @@ var SelectorElementClick = { }, getFeatures: function () { - return ['multiple', 'delay', 'clickElementSelector', 'clickType', 'discardInitialElements', 'clickElementUniquenessType'] + return ['multiple', 'delay', 'clickElementSelector', 'clickType', 'datafilter', 'discardInitialElements', 'clickElementUniquenessType'] } }; diff --git a/extension/scripts/Selector/SelectorElementScroll.js b/extension/scripts/Selector/SelectorElementScroll.js index faef891d..5fac170f 100644 --- a/extension/scripts/Selector/SelectorElementScroll.js +++ b/extension/scripts/Selector/SelectorElementScroll.js @@ -63,6 +63,6 @@ var SelectorElementScroll = { }, getFeatures: function () { - return ['multiple', 'delay'] + return ['multiple', 'datafilter', 'delay'] } }; diff --git a/extension/scripts/Selector/SelectorGroup.js b/extension/scripts/Selector/SelectorGroup.js index ff3140d4..410da53b 100644 --- a/extension/scripts/Selector/SelectorGroup.js +++ b/extension/scripts/Selector/SelectorGroup.js @@ -50,6 +50,6 @@ var SelectorGroup = { }, getFeatures: function () { - return ['delay','extractAttribute'] + return ['delay', 'datafilter','extractAttribute'] } }; \ No newline at end of file diff --git a/extension/scripts/Selector/SelectorHTML.js b/extension/scripts/Selector/SelectorHTML.js index 48cbc1df..cf47c10a 100644 --- a/extension/scripts/Selector/SelectorHTML.js +++ b/extension/scripts/Selector/SelectorHTML.js @@ -58,6 +58,6 @@ var SelectorHTML = { }, getFeatures: function () { - return ['multiple', 'regex', 'delay'] + return ['multiple', 'datafilter', 'regex', 'delay'] } }; diff --git a/extension/scripts/Selector/SelectorImage.js b/extension/scripts/Selector/SelectorImage.js index 0339fd22..04edcf82 100644 --- a/extension/scripts/Selector/SelectorImage.js +++ b/extension/scripts/Selector/SelectorImage.js @@ -114,7 +114,7 @@ var SelectorImage = { }, getFeatures: function () { - return ['multiple', 'delay', 'downloadImage'] + return ['multiple', 'datafilter', 'delay', 'downloadImage'] }, getItemCSSSelector: function() { diff --git a/extension/scripts/Selector/SelectorLink.js b/extension/scripts/Selector/SelectorLink.js index b46b3dd7..8548fce5 100644 --- a/extension/scripts/Selector/SelectorLink.js +++ b/extension/scripts/Selector/SelectorLink.js @@ -33,7 +33,7 @@ var SelectorLink = { // extract links one by one var deferredDataExtractionCalls = []; $(elements).each(function (k, element) { - + if(!element.href.match(new RegExp(this.regex))) return; deferredDataExtractionCalls.push(function(element) { var deferredData = $.Deferred(); @@ -65,7 +65,7 @@ var SelectorLink = { }, getFeatures: function () { - return ['multiple', 'delay'] + return ['multiple', 'datafilter', 'delay', 'regex', 'customColumns'] }, getItemCSSSelector: function() { diff --git a/extension/scripts/Selector/SelectorPopupLink.js b/extension/scripts/Selector/SelectorPopupLink.js index f3c0cafb..532eb356 100644 --- a/extension/scripts/Selector/SelectorPopupLink.js +++ b/extension/scripts/Selector/SelectorPopupLink.js @@ -139,7 +139,7 @@ var SelectorPopupLink = { }, getFeatures: function () { - return ['multiple', 'delay'] + return ['multiple', 'datafilter', 'delay'] }, getItemCSSSelector: function() { diff --git a/extension/scripts/Selector/SelectorTable.js b/extension/scripts/Selector/SelectorTable.js index 713bcf24..3afd0907 100644 --- a/extension/scripts/Selector/SelectorTable.js +++ b/extension/scripts/Selector/SelectorTable.js @@ -77,7 +77,7 @@ var SelectorTable = { }, getFeatures: function () { - return ['multiple', 'columns', 'delay', 'tableDataRowSelector', 'tableHeaderRowSelector'] + return ['multiple', 'columns', 'datafilter', 'delay', 'tableDataRowSelector', 'tableHeaderRowSelector'] }, getItemCSSSelector: function () { diff --git a/extension/scripts/Selector/SelectorText.js b/extension/scripts/Selector/SelectorText.js index 51360163..d89f5686 100644 --- a/extension/scripts/Selector/SelectorText.js +++ b/extension/scripts/Selector/SelectorText.js @@ -64,6 +64,6 @@ var SelectorText = { }, getFeatures: function () { - return ['multiple', 'regex', 'delay'] + return ['multiple', 'datafilter', 'regex', 'delay'] } }; diff --git a/extension/scripts/Sitemap.js b/extension/scripts/Sitemap.js index 0cfaf47a..94263c41 100644 --- a/extension/scripts/Sitemap.js +++ b/extension/scripts/Sitemap.js @@ -10,6 +10,15 @@ Sitemap.prototype = { } var selectors = this.selectors; + + if(sitemapObj){ + var rootSelector = selectors.filter(s => s.parentSelectors[0] == "_root")[0]; + if(rootSelector && rootSelector.customColumns){ + var columnJSON = JSON.parse(rootSelector.customColumns); + this.customColumns = Array.isArray(columnJSON) ? columnJSON : null; + } + } + this.selectors = new SelectorList(this.selectors); }, @@ -61,6 +70,7 @@ Sitemap.prototype = { getStartUrls: function() { var startUrls = this.startUrl; + // single start url if(this.startUrl.push === undefined) { startUrls = [startUrls]; @@ -168,11 +178,9 @@ Sitemap.prototype = { getDataColumns: function () { var columns = []; this.selectors.forEach(function (selector) { - columns = columns.concat(selector.getDataColumns()); }); - - return columns; + return this.customColumns || columns; }, getDataExportCsvBlob: function (data) { diff --git a/extension/scripts/Store.js b/extension/scripts/Store.js index 09f2e219..14084a75 100644 --- a/extension/scripts/Store.js +++ b/extension/scripts/Store.js @@ -1,122 +1,327 @@ +var MySQLDB = function(restfulAddr, sitemapTable){ + this.url = restfulAddr; + this.sitemapTable = sitemapTable; +} + +MySQLDB.prototype = { + ADD: function(data) { + delete data.code; + delete data.id; + return new Promise((resolve, reject) => { + $.ajax({ + url: `${this.url}/${this.sitemapTable}`, + type: "POST", + data: data, + success: function(response){ + if(response.success){ + resolve(Object.assign(data, {code: "add"})); + }else{ + reject(Object.assign(response, {code: "add_error"})); + } + }, + error: r => console.log(JSON.stringify(r)) + }); + }); + }, + READ: function(field) { + return new Promise((resolve, reject) => { + $.ajax({ + url: `${this.url}/${this.sitemapTable}?field=${field}`, + type: "GET", + success: function(response){ + resolve(Object.assign(response, {code: "read"})); + }, + error: r => console.log(JSON.stringify(r)) + }); + }); + }, + GET: function(data) { + let sitemapid = data.sitemapid; + return new Promise((resolve, reject) => { + $.ajax({ + url: `${this.url}/${this.sitemapTable}/sitemapid/${sitemapid}`, + type: "GET", + success: function(response){ + if(response.error){ + reject(Object.assign(data, {code: "get_error"})); + }else{ + resolve({ + code: "get", + content: response[0].content, + data: response[0].data, + sitemapid: sitemapid, + id: response[0].id + }); + } + }, + error: r => console.log(JSON.stringify(r)) + }); + }); + }, + UPDATE: function(data) { + let param = ""; + let dataType = ""; + let id = ""; + + if(data.json){ + dataType = "json"; + id = `sitemapid/${data.sitemapid}`; + delete data.json; + delete data.sitemapid; + }else{ + id = data.id; + } + delete data.code; + + if(!data.data){ + data.data = "[]"; + } + + return new Promise((resolve, reject) => { + $.ajax({ + url: `${this.url}/${this.sitemapTable}/${id}`, + type: "PUT", + data: data, + dataType: dataType, + success: response => { + if(response.error){ + alert(JSON.stringify(response)) + reject(Object.assign(data, {code:"update_error", response})); + }else{ + if(this.browser){ + this.browser.sendNotification("Saved to DB"); + this.browser.close(); + } + resolve(Object.assign(data, {code:"update", response})); + } + }, + error: r => alert(JSON.stringify(r)) + }); + }); + }, + DELETE: function(id) { + return new Promise((resolve, reject) => { + $.ajax({ + url: `${this.url}/${this.sitemapTable}/${id}`, + type: "DELETE", + success: response => { + if(response.error){ + reject({code:"delete_error", response, id}); + }else{ + resolve({code:"delete", response, id}); + } + }, + error: r => console.log(JSON.stringify(r)) + }); + }); + }, + remove: function(sitemap) { + return this.GET(Object.assign(sitemap, {sitemapid: sitemap._id})).then(r => this.DELETE(r.id)); + }, + saveOrUpdate: function(data) { + return this.GET(data).then(r => this.UPDATE(Object.assign(r, {content: data.content || r.content, data: data.data || r.data}))).catch(r => this.ADD(r)); + } +} + var Store = function (config) { this.config = config; - - // configure couchdb - this.sitemapDb = new PouchDB(this.config.sitemapDb); + if(this.config.storageType == "mysql"){ + this.mysqlDB = new MySQLDB(this.config.mysqlDB, this.config.mysqlSitemap); + }else{ + // configure couchdb + this.sitemapDb = new PouchDB(this.config.sitemapDb); + } }; -var StoreScrapeResultWriter = function(db) { - this.db = db; +var StoreScrapeResultWriter = function(db, type, sitemapid) { + this.db = db; + this.type = type; + this.sitemapid = sitemapid; }; StoreScrapeResultWriter.prototype = { + tempDoc: null, writeDocs: function(docs, callback) { - if(docs.length === 0) { - callback(); - } - else { - this.db.bulkDocs({docs:docs}, function(err, response) { - if(err !== null) { - console.log("Error while persisting scraped data to db", err); - } - callback(); - }); - } + if(docs.length === 0) { + callback(this.tempDoc); + } + else if(this.type == "mysql") { + if(this.tempDoc){ + this.tempDoc = this.tempDoc.concat(docs); + }else{ + this.tempDoc = docs; + } + callback(this.tempDoc); + }else { + this.db.bulkDocs({docs:docs}, function(err, response) { + if(err !== null) { + console.log("Error while persisting scraped data to db", err); + } + callback(null); + }); + } } }; Store.prototype = { - - sanitizeSitemapDataDbName: function(dbName) { - return 'sitemap-data-'+dbName.replace(/[^a-z0-9_\$\(\)\+\-/]/gi, "_"); - }, - getSitemapDataDbLocation: function(sitemapId) { - var dbName = this.sanitizeSitemapDataDbName(sitemapId); - return this.config.dataDb+dbName; - }, - getSitemapDataDb: function(sitemapId) { - - var dbLocation = this.getSitemapDataDbLocation(sitemapId); - return new PouchDB(dbLocation); - }, - - /** - * creates or clears a sitemap db - * @param {type} sitemapId - * @returns {undefined} - */ + sanitizeSitemapDataDbName: function(dbName) { + return 'sitemap-data-' + dbName.replace(/[^a-z0-9_\$\(\)\+\-/]/gi, "_"); + }, + getSitemapDataDbLocation: function(sitemapId) { + var dbName = this.sanitizeSitemapDataDbName(sitemapId); + return this.config.dataDb + dbName; + }, + getSitemapDataDb: function(sitemapId) { + var dbLocation = this.getSitemapDataDbLocation(sitemapId); + if(this.config.storageType == "mysql") { + return new MySQLDB(this.config.mysqlDB, this.config.mysqlSitemap); + }else{ + return new PouchDB(dbLocation); + } + }, + + /** + * creates or clears a sitemap db + * @param {type} sitemapId + * @returns {undefined} + */ initSitemapDataDb: function(sitemapId, callback) { - var dbLocation = this.getSitemapDataDbLocation(sitemapId); - var store = this; - - PouchDB.destroy(dbLocation, function() { - var db = store.getSitemapDataDb(sitemapId); - var dbWriter = new StoreScrapeResultWriter(db); - callback(dbWriter); - }); - }, + var dbLocation = this.getSitemapDataDbLocation(sitemapId); + var store = this; + if(this.config.storageType == "mysql") { + var db = store.getSitemapDataDb(sitemapId); + var dbWriter = new StoreScrapeResultWriter(db, "mysql", sitemapId); + callback(dbWriter); + }else{ + PouchDB.destroy(dbLocation, function() { + var db = store.getSitemapDataDb(sitemapId); + var dbWriter = new StoreScrapeResultWriter(db); + callback(dbWriter); + }); + } + }, createSitemap: function (sitemap, callback) { - var sitemapJson = JSON.parse(JSON.stringify(sitemap)); - if(!sitemap._id) { console.log("cannot save sitemap without an id", sitemap); } + if(this.config.storageType == "mysql") { + const handler = async function(){ + let data = { + id: null, + sitemapid: sitemapJson._id, + content: JSON.stringify(sitemapJson) + }; - this.sitemapDb.put(sitemapJson, function(sitemap, err, response) { - // @TODO handle err - sitemap._rev = response.rev; - callback(sitemap); - }.bind(this, sitemap)); + let result = await this.mysqlDB.saveOrUpdate(data); + + let sitemap = JSON.parse(result.content); + switch (result.code) { + case "get_error": + sitemap._rev = `rev_${Date.now()}`; + break; + case "get": + break; + } + return Promise.resolve(sitemap); + }.bind(this); + handler().then(callback); + }else{ + this.sitemapDb.put(sitemapJson, function(sitemap, err, response) { + sitemap._rev = response.rev; + callback(sitemap); + }.bind(this, sitemap)); + } }, saveSitemap: function (sitemap, callback) { // @TODO remove this.createSitemap(sitemap, callback); }, deleteSitemap: function (sitemap, callback) { - sitemap = JSON.parse(JSON.stringify(sitemap)); - - this.sitemapDb.remove(sitemap, function(err, response){ - // @TODO handle err - - // delete sitemap data db - var dbLocation = this.getSitemapDataDbLocation(sitemap._id); - PouchDB.destroy(dbLocation, function() { - callback(); - }.bind(this)); - }.bind(this)); + var _this = this; + this.findSitemap(sitemap._id, function(_sitemap){ + if(!_sitemap) return; + if(_this.config.storageType == "mysql"){ + _this.mysqlDB.remove(sitemap).then(callback); + }else{ + _this.sitemapDb.remove(_sitemap, function(err, response){ + var dbLocation = _this.getSitemapDataDbLocation(_sitemap._id); + PouchDB.destroy(dbLocation, function() { + callback(); + }.bind(this)); + }.bind(this));} + }); }, getAllSitemaps: function (callback) { - - this.sitemapDb.allDocs({include_docs: true}, function(err, response) { - var sitemaps = []; - for (var i in response.rows) { - var sitemap = response.rows[i].doc; - if (!chrome.extension) { - sitemap = new Sitemap(sitemap); + if(this.config.storageType == "mysql"){ + const setupSitemap = response => { + let sitemaps = (response.error ? [] : response).map(r => JSON.parse(r.content)).map(s => chrome.extension ? s : new Sitemap(s)); + callback(sitemaps); + } + this.mysqlDB.READ("content").then(setupSitemap); + }else{ + this.sitemapDb.allDocs({include_docs: true}, function(err, response) { + var sitemaps = []; + for (var i in response.rows) { + var sitemap = response.rows[i].doc; + if (!chrome.extension) { + sitemap = new Sitemap(sitemap); + } + sitemaps.push(sitemap); + } + callback(sitemaps); + }); + } + }, + findSitemap: function (sitemapId, callback) { + this.getAllSitemaps(function (sitemaps) { + var sitemapFound = false; + for (var i in sitemaps) { + if (sitemaps[i]._id === sitemapId) { + callback(sitemaps[i]); + return; } - - sitemaps.push(sitemap); } - callback(sitemaps); + callback(sitemapFound); }); }, - - getSitemapData: function (sitemap, callback) { - - var db = this.getSitemapDataDb(sitemap._id); - db.allDocs({include_docs: true}, function(err, response) { - var responseData = []; - for (var i in response.rows) { - var doc = response.rows[i].doc; - responseData.push(doc); + findSimilar: function (sitemapId, callback) { + this.getAllSitemaps(function (sitemaps) { + var sitemapFound = false; + var count=0; + for (var i in sitemaps) { + var common=sharedStart([sitemaps[i]._id,sitemapId]); + if(count { + let responseData = (response.error ? [] : JSON.parse(response.data)); + callback(responseData); + } + db.GET({sitemapid: sitemap._id}).then(setupSitemapData); + }else{ + var db = this.getSitemapDataDb(sitemap._id); + db.allDocs({include_docs: true}, function(err, response) { + var responseData = []; + for (var i in response.rows) { + var doc = response.rows[i].doc; + responseData.push(doc); + } + callback(responseData); + }); + } + }, + // @TODO make this call lighter sitemapExists: function (sitemapId, callback) { this.getAllSitemaps(function (sitemaps) { var sitemapFound = false; @@ -127,5 +332,51 @@ Store.prototype = { } callback(sitemapFound); }); + }, + removeDuplicate: function(sitemapid, distinct, finalResult){ + if(distinct == "true"){ + distinct = "PATCH"; + }else{ + distinct = "PRESERVE"; + } + + const toSortedJSON = function(obj) { + return JSON.stringify(typeof obj == "object" ? Object.keys(obj).sort().reduce((o, key) => (o[key] = toSortedJSON(obj[key]), o), {}) : obj); + } + + const uniq = function(xs) { + let seen = {}; + return xs.filter(x => (k = (toSortedJSON || JSON.stringify)(x), !(k in seen) && (seen[k] = 1))); + } + + let content = null; + let db = this.getSitemapDataDb(sitemapid); + + if(finalResult){ + content = JSON.stringify(uniq(finalResult)); + }else{ + //let response = await db.GET({sitemapid}); + //content = JSON.stringify(uniq(JSON.parse(response.data))); + } + + if(this.browser){ + this.browser.sendNotification("Saving to DB"); + this.browser.sendNotification(content); + } + + + + let json = "json"; + let field = "data"; + let data = {content, field, distinct}; + + db.UPDATE({json, data, sitemapid}); } -}; \ No newline at end of file +}; + +function sharedStart(array){ + var A= array.concat().sort(), + a1= A[0], a2= A[A.length-1], L= a1.length, i= 0; + while(i> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } - }; - - self.reportSpecResults = function(spec) { - reporterView.specComplete(spec); - }; - - self.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } - }; - - self.specFilter = function(spec) { - if (!focusedSpecName()) { - return true; - } - - return spec.getFullName().indexOf(focusedSpecName()) === 0; - }; - - return self; - - function focusedSpecName() { - var specName; - - (function memoizeFocusedSpec() { - if (specName) { - return; - } - - var paramMap = []; - var params = jasmine.HtmlReporter.parameters(doc); - - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - specName = paramMap.spec; - })(); - - return specName; - } - - function createReporterDom(version) { - dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, - dom.banner = self.createDom('div', { className: 'banner' }, - self.createDom('span', { className: 'title' }, "Jasmine "), - self.createDom('span', { className: 'version' }, version)), - - dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), - dom.alert = self.createDom('div', {className: 'alert'}, - self.createDom('span', { className: 'exceptions' }, - self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), - self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), - dom.results = self.createDom('div', {className: 'results'}, - dom.summary = self.createDom('div', { className: 'summary' }), - dom.details = self.createDom('div', { id: 'details' })) - ); - } - - function noTryCatch() { - return window.location.search.match(/catch=false/); - } - - function searchWithCatch() { - var params = jasmine.HtmlReporter.parameters(window.document); - var removed = false; - var i = 0; - - while (!removed && i < params.length) { - if (params[i].match(/catch=/)) { - params.splice(i, 1); - removed = true; - } - i++; - } - if (jasmine.CATCH_EXCEPTIONS) { - params.push("catch=false"); - } - - return params.join("&"); - } - - function setExceptionHandling() { - var chxCatch = document.getElementById('no_try_catch'); - - if (noTryCatch()) { - chxCatch.setAttribute('checked', true); - jasmine.CATCH_EXCEPTIONS = false; - } - chxCatch.onclick = function() { - window.location.search = searchWithCatch(); - }; - } -}; -jasmine.HtmlReporter.parameters = function(doc) { - var paramStr = doc.location.search.substring(1); - var params = []; - - if (paramStr.length > 0) { - params = paramStr.split('&'); - } - return params; -} -jasmine.HtmlReporter.sectionLink = function(sectionName) { - var link = '?'; - var params = []; - - if (sectionName) { - params.push('spec=' + encodeURIComponent(sectionName)); - } - if (!jasmine.CATCH_EXCEPTIONS) { - params.push("catch=false"); - } - if (params.length > 0) { - link += params.join("&"); - } - - return link; -}; -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); -jasmine.HtmlReporter.ReporterView = function(dom) { - this.startedAt = new Date(); - this.runningSpecCount = 0; - this.completeSpecCount = 0; - this.passedCount = 0; - this.failedCount = 0; - this.skippedCount = 0; - - this.createResultsMenu = function() { - this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, - this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), - ' | ', - this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); - - this.summaryMenuItem.onclick = function() { - dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); - }; - - this.detailsMenuItem.onclick = function() { - showDetails(); - }; - }; - - this.addSpecs = function(specs, specFilter) { - this.totalSpecCount = specs.length; - - this.views = { - specs: {}, - suites: {} - }; - - for (var i = 0; i < specs.length; i++) { - var spec = specs[i]; - this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); - if (specFilter(spec)) { - this.runningSpecCount++; - } - } - }; - - this.specComplete = function(spec) { - this.completeSpecCount++; - - if (isUndefined(this.views.specs[spec.id])) { - this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); - } - - var specView = this.views.specs[spec.id]; - - switch (specView.status()) { - case 'passed': - this.passedCount++; - break; - - case 'failed': - this.failedCount++; - break; - - case 'skipped': - this.skippedCount++; - break; - } - - specView.refresh(); - this.refresh(); - }; - - this.suiteComplete = function(suite) { - var suiteView = this.views.suites[suite.id]; - if (isUndefined(suiteView)) { - return; - } - suiteView.refresh(); - }; - - this.refresh = function() { - - if (isUndefined(this.resultsMenu)) { - this.createResultsMenu(); - } - - // currently running UI - if (isUndefined(this.runningAlert)) { - this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); - dom.alert.appendChild(this.runningAlert); - } - this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); - - // skipped specs UI - if (isUndefined(this.skippedAlert)) { - this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); - } - - this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; - - if (this.skippedCount === 1 && isDefined(dom.alert)) { - dom.alert.appendChild(this.skippedAlert); - } - - // passing specs UI - if (isUndefined(this.passedAlert)) { - this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); - } - this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); - - // failing specs UI - if (isUndefined(this.failedAlert)) { - this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); - } - this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); - - if (this.failedCount === 1 && isDefined(dom.alert)) { - dom.alert.appendChild(this.failedAlert); - dom.alert.appendChild(this.resultsMenu); - } - - // summary info - this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); - this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; - }; - - this.complete = function() { - dom.alert.removeChild(this.runningAlert); - - this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; - - if (this.failedCount === 0) { - dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); - } else { - showDetails(); - } - - dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); - }; - - return this; - - function showDetails() { - if (dom.reporter.className.search(/showDetails/) === -1) { - dom.reporter.className += " showDetails"; - } - } - - function isUndefined(obj) { - return typeof obj === 'undefined'; - } - - function isDefined(obj) { - return !isUndefined(obj); - } - - function specPluralizedFor(count) { - var str = count + " spec"; - if (count > 1) { - str += "s" - } - return str; - } - -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); - - -jasmine.HtmlReporter.SpecView = function(spec, dom, views) { - this.spec = spec; - this.dom = dom; - this.views = views; - - this.symbol = this.createDom('li', { className: 'pending' }); - this.dom.symbolSummary.appendChild(this.symbol); - - this.summary = this.createDom('div', { className: 'specSummary' }, - this.createDom('a', { - className: 'description', - href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), - title: this.spec.getFullName() - }, this.spec.description) - ); - - this.detail = this.createDom('div', { className: 'specDetail' }, - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(this.spec.getFullName()), - title: this.spec.getFullName() - }, this.spec.getFullName()) - ); -}; - -jasmine.HtmlReporter.SpecView.prototype.status = function() { - return this.getSpecStatus(this.spec); -}; - -jasmine.HtmlReporter.SpecView.prototype.refresh = function() { - this.symbol.className = this.status(); - - switch (this.status()) { - case 'skipped': - break; - - case 'passed': - this.appendSummaryToSuiteDiv(); - break; - - case 'failed': - this.appendSummaryToSuiteDiv(); - this.appendFailureDetail(); - break; - } -}; - -jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { - this.summary.className += ' ' + this.status(); - this.appendToSummary(this.spec, this.summary); -}; - -jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { - this.detail.className += ' ' + this.status(); - - var resultItems = this.spec.results().getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - this.detail.appendChild(messagesDiv); - this.dom.details.appendChild(this.detail); - } -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { - this.suite = suite; - this.dom = dom; - this.views = views; - - this.element = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) - ); - - this.appendToSummary(this.suite, this.element); -}; - -jasmine.HtmlReporter.SuiteView.prototype.status = function() { - return this.getSpecStatus(this.suite); -}; - -jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { - this.element.className += " " + this.status(); -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); - -/* @deprecated Use jasmine.HtmlReporter instead - */ -jasmine.TrivialReporter = function(doc) { - this.document = doc || document; - this.suiteDivs = {}; - this.logRunningSpecs = false; -}; - -jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { el.appendChild(child); } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { - var showPassed, showSkipped; - - this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, - this.createDom('div', { className: 'banner' }, - this.createDom('div', { className: 'logo' }, - this.createDom('span', { className: 'title' }, "Jasmine"), - this.createDom('span', { className: 'version' }, runner.env.versionString())), - this.createDom('div', { className: 'options' }, - "Show ", - showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), - showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") - ) - ), - - this.runnerDiv = this.createDom('div', { className: 'runner running' }, - this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), - this.runnerMessageSpan = this.createDom('span', {}, "Running..."), - this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) - ); - - this.document.body.appendChild(this.outerDiv); - - var suites = runner.suites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - var suiteDiv = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); - this.suiteDivs[suite.id] = suiteDiv; - var parentDiv = this.outerDiv; - if (suite.parentSuite) { - parentDiv = this.suiteDivs[suite.parentSuite.id]; - } - parentDiv.appendChild(suiteDiv); - } - - this.startedAt = new Date(); - - var self = this; - showPassed.onclick = function(evt) { - if (showPassed.checked) { - self.outerDiv.className += ' show-passed'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); - } - }; - - showSkipped.onclick = function(evt) { - if (showSkipped.checked) { - self.outerDiv.className += ' show-skipped'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); - } - }; -}; - -jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { - var results = runner.results(); - var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; - this.runnerDiv.setAttribute("class", className); - //do it twice for IE - this.runnerDiv.setAttribute("className", className); - var specs = runner.specs(); - var specCount = 0; - for (var i = 0; i < specs.length; i++) { - if (this.specFilter(specs[i])) { - specCount++; - } - } - var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); - message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; - this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); - - this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); -}; - -jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { - var results = suite.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.totalCount === 0) { // todo: change this to check results.skipped - status = 'skipped'; - } - this.suiteDivs[suite.id].className += " " + status; -}; - -jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { - if (this.logRunningSpecs) { - this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } -}; - -jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { - var results = spec.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - var specDiv = this.createDom('div', { className: 'spec ' + status }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(spec.getFullName()), - title: spec.getFullName() - }, spec.description)); - - - var resultItems = results.getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - specDiv.appendChild(messagesDiv); - } - - this.suiteDivs[spec.suite.id].appendChild(specDiv); -}; - -jasmine.TrivialReporter.prototype.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } -}; - -jasmine.TrivialReporter.prototype.getLocation = function() { - return this.document.location; -}; - -jasmine.TrivialReporter.prototype.specFilter = function(spec) { - var paramMap = {}; - var params = this.getLocation().search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - if (!paramMap.spec) { - return true; - } - return spec.getFullName().indexOf(paramMap.spec) === 0; -}; diff --git a/jasmine-standalone/lib/jasmine-1.3.1/jasmine.css b/jasmine-standalone/lib/jasmine-1.3.1/jasmine.css deleted file mode 100644 index 8c008dc7..00000000 --- a/jasmine-standalone/lib/jasmine-1.3.1/jasmine.css +++ /dev/null @@ -1,82 +0,0 @@ -body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } - -#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } -#HTMLReporter a { text-decoration: none; } -#HTMLReporter a:hover { text-decoration: underline; } -#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } -#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } -#HTMLReporter #jasmine_content { position: fixed; right: 100%; } -#HTMLReporter .version { color: #aaaaaa; } -#HTMLReporter .banner { margin-top: 14px; } -#HTMLReporter .duration { color: #aaaaaa; float: right; } -#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } -#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } -#HTMLReporter .symbolSummary li.passed { font-size: 14px; } -#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } -#HTMLReporter .symbolSummary li.failed { line-height: 9px; } -#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } -#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } -#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } -#HTMLReporter .symbolSummary li.pending { line-height: 11px; } -#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } -#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } -#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } -#HTMLReporter .runningAlert { background-color: #666666; } -#HTMLReporter .skippedAlert { background-color: #aaaaaa; } -#HTMLReporter .skippedAlert:first-child { background-color: #333333; } -#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } -#HTMLReporter .passingAlert { background-color: #a6b779; } -#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } -#HTMLReporter .failingAlert { background-color: #cf867e; } -#HTMLReporter .failingAlert:first-child { background-color: #b03911; } -#HTMLReporter .results { margin-top: 14px; } -#HTMLReporter #details { display: none; } -#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } -#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } -#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } -#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } -#HTMLReporter.showDetails .summary { display: none; } -#HTMLReporter.showDetails #details { display: block; } -#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } -#HTMLReporter .summary { margin-top: 14px; } -#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } -#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } -#HTMLReporter .summary .specSummary.failed a { color: #b03911; } -#HTMLReporter .description + .suite { margin-top: 0; } -#HTMLReporter .suite { margin-top: 14px; } -#HTMLReporter .suite a { color: #333333; } -#HTMLReporter #details .specDetail { margin-bottom: 28px; } -#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } -#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } -#HTMLReporter .resultMessage span.result { display: block; } -#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } - -#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } -#TrivialReporter a:visited, #TrivialReporter a { color: #303; } -#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } -#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } -#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } -#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } -#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } -#TrivialReporter .runner.running { background-color: yellow; } -#TrivialReporter .options { text-align: right; font-size: .8em; } -#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } -#TrivialReporter .suite .suite { margin: 5px; } -#TrivialReporter .suite.passed { background-color: #dfd; } -#TrivialReporter .suite.failed { background-color: #fdd; } -#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } -#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } -#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } -#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } -#TrivialReporter .spec.skipped { background-color: #bbb; } -#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } -#TrivialReporter .passed { background-color: #cfc; display: none; } -#TrivialReporter .failed { background-color: #fbb; } -#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } -#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } -#TrivialReporter .resultMessage .mismatch { color: black; } -#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } -#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } -#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } -#TrivialReporter #jasmine_content { position: fixed; right: 100%; } -#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/jasmine-standalone/lib/jasmine-1.3.1/jasmine.js b/jasmine-standalone/lib/jasmine-1.3.1/jasmine.js deleted file mode 100644 index 6b3459b9..00000000 --- a/jasmine-standalone/lib/jasmine-1.3.1/jasmine.js +++ /dev/null @@ -1,2600 +0,0 @@ -var isCommonJS = typeof window == "undefined" && typeof exports == "object"; - -/** - * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. - * - * @namespace - */ -var jasmine = {}; -if (isCommonJS) exports.jasmine = jasmine; -/** - * @private - */ -jasmine.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); -}; - -/** - * Use jasmine.undefined instead of undefined, since undefined is just - * a plain old variable and may be redefined by somebody else. - * - * @private - */ -jasmine.undefined = jasmine.___undefined___; - -/** - * Show diagnostic messages in the console if set to true - * - */ -jasmine.VERBOSE = false; - -/** - * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. - * - */ -jasmine.DEFAULT_UPDATE_INTERVAL = 250; - -/** - * Maximum levels of nesting that will be included when an object is pretty-printed - */ -jasmine.MAX_PRETTY_PRINT_DEPTH = 40; - -/** - * Default timeout interval in milliseconds for waitsFor() blocks. - */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - -/** - * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. - * Set to false to let the exception bubble up in the browser. - * - */ -jasmine.CATCH_EXCEPTIONS = true; - -jasmine.getGlobal = function() { - function getGlobal() { - return this; - } - - return getGlobal(); -}; - -/** - * Allows for bound functions to be compared. Internal use only. - * - * @ignore - * @private - * @param base {Object} bound 'this' for the function - * @param name {Function} function to find - */ -jasmine.bindOriginal_ = function(base, name) { - var original = base[name]; - if (original.apply) { - return function() { - return original.apply(base, arguments); - }; - } else { - // IE support - return jasmine.getGlobal()[name]; - } -}; - -jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); -jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); -jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); -jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); - -jasmine.MessageResult = function(values) { - this.type = 'log'; - this.values = values; - this.trace = new Error(); // todo: test better -}; - -jasmine.MessageResult.prototype.toString = function() { - var text = ""; - for (var i = 0; i < this.values.length; i++) { - if (i > 0) text += " "; - if (jasmine.isString_(this.values[i])) { - text += this.values[i]; - } else { - text += jasmine.pp(this.values[i]); - } - } - return text; -}; - -jasmine.ExpectationResult = function(params) { - this.type = 'expect'; - this.matcherName = params.matcherName; - this.passed_ = params.passed; - this.expected = params.expected; - this.actual = params.actual; - this.message = this.passed_ ? 'Passed.' : params.message; - - var trace = (params.trace || new Error(this.message)); - this.trace = this.passed_ ? '' : trace; -}; - -jasmine.ExpectationResult.prototype.toString = function () { - return this.message; -}; - -jasmine.ExpectationResult.prototype.passed = function () { - return this.passed_; -}; - -/** - * Getter for the Jasmine environment. Ensures one gets created - */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); - return env; -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isArray_ = function(value) { - return jasmine.isA_("Array", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isString_ = function(value) { - return jasmine.isA_("String", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isNumber_ = function(value) { - return jasmine.isA_("Number", value); -}; - -/** - * @ignore - * @private - * @param {String} typeName - * @param value - * @returns {Boolean} - */ -jasmine.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; -}; - -/** - * Pretty printer for expecations. Takes any object and turns it into a human-readable string. - * - * @param value {Object} an object to be outputted - * @returns {String} - */ -jasmine.pp = function(value) { - var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; -}; - -/** - * Returns true if the object is a DOM Node. - * - * @param {Object} obj object to check - * @returns {Boolean} - */ -jasmine.isDomNode = function(obj) { - return obj.nodeType > 0; -}; - -/** - * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. - * - * @example - * // don't care about which function is passed in, as long as it's a function - * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); - * - * @param {Class} clazz - * @returns matchable object of the type clazz - */ -jasmine.any = function(clazz) { - return new jasmine.Matchers.Any(clazz); -}; - -/** - * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the - * attributes on the object. - * - * @example - * // don't care about any other attributes than foo. - * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); - * - * @param sample {Object} sample - * @returns matchable object for the sample - */ -jasmine.objectContaining = function (sample) { - return new jasmine.Matchers.ObjectContaining(sample); -}; - -/** - * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. - * - * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine - * expectation syntax. Spies can be checked if they were called or not and what the calling params were. - * - * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). - * - * Spies are torn down at the end of every spec. - * - * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. - * - * @example - * // a stub - * var myStub = jasmine.createSpy('myStub'); // can be used anywhere - * - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // actual foo.not will not be called, execution stops - * spyOn(foo, 'not'); - - // foo.not spied upon, execution will continue to implementation - * spyOn(foo, 'not').andCallThrough(); - * - * // fake example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // foo.not(val) will return val - * spyOn(foo, 'not').andCallFake(function(value) {return value;}); - * - * // mock example - * foo.not(7 == 7); - * expect(foo.not).toHaveBeenCalled(); - * expect(foo.not).toHaveBeenCalledWith(true); - * - * @constructor - * @see spyOn, jasmine.createSpy, jasmine.createSpyObj - * @param {String} name - */ -jasmine.Spy = function(name) { - /** - * The name of the spy, if provided. - */ - this.identity = name || 'unknown'; - /** - * Is this Object a spy? - */ - this.isSpy = true; - /** - * The actual function this spy stubs. - */ - this.plan = function() { - }; - /** - * Tracking of the most recent call to the spy. - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy.mostRecentCall.args = [1, 2]; - */ - this.mostRecentCall = {}; - - /** - * Holds arguments for each call to the spy, indexed by call count - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy(7, 8); - * mySpy.mostRecentCall.args = [7, 8]; - * mySpy.argsForCall[0] = [1, 2]; - * mySpy.argsForCall[1] = [7, 8]; - */ - this.argsForCall = []; - this.calls = []; -}; - -/** - * Tells a spy to call through to the actual implemenatation. - * - * @example - * var foo = { - * bar: function() { // do some stuff } - * } - * - * // defining a spy on an existing property: foo.bar - * spyOn(foo, 'bar').andCallThrough(); - */ -jasmine.Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; -}; - -/** - * For setting the return value of a spy. - * - * @example - * // defining a spy from scratch: foo() returns 'baz' - * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); - * - * // defining a spy on an existing property: foo.bar() returns 'baz' - * spyOn(foo, 'bar').andReturn('baz'); - * - * @param {Object} value - */ -jasmine.Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; -}; - -/** - * For throwing an exception when a spy is called. - * - * @example - * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' - * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); - * - * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' - * spyOn(foo, 'bar').andThrow('baz'); - * - * @param {String} exceptionMsg - */ -jasmine.Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; -}; - -/** - * Calls an alternate implementation when a spy is called. - * - * @example - * var baz = function() { - * // do some stuff, return something - * } - * // defining a spy from scratch: foo() calls the function baz - * var foo = jasmine.createSpy('spy on foo').andCall(baz); - * - * // defining a spy on an existing property: foo.bar() calls an anonymnous function - * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); - * - * @param {Function} fakeFunc - */ -jasmine.Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; -}; - -/** - * Resets all of a spy's the tracking variables so that it can be used again. - * - * @example - * spyOn(foo, 'bar'); - * - * foo.bar(); - * - * expect(foo.bar.callCount).toEqual(1); - * - * foo.bar.reset(); - * - * expect(foo.bar.callCount).toEqual(0); - */ -jasmine.Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; -}; - -jasmine.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = jasmine.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new jasmine.Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; -}; - -/** - * Determines whether an object is a spy. - * - * @param {jasmine.Spy|Object} putativeSpy - * @returns {Boolean} - */ -jasmine.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; -}; - -/** - * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something - * large in one call. - * - * @param {String} baseName name of spy class - * @param {Array} methodNames array of names of methods to make spies - */ -jasmine.createSpyObj = function(baseName, methodNames) { - if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { - throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); - } - return obj; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the current spec's output. - * - * Be careful not to leave calls to jasmine.log in production code. - */ -jasmine.log = function() { - var spec = jasmine.getEnv().currentSpec; - spec.log.apply(spec, arguments); -}; - -/** - * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. - * - * @example - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops - * - * @see jasmine.createSpy - * @param obj - * @param methodName - * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods - */ -var spyOn = function(obj, methodName) { - return jasmine.getEnv().currentSpec.spyOn(obj, methodName); -}; -if (isCommonJS) exports.spyOn = spyOn; - -/** - * Creates a Jasmine spec that will be added to the current suite. - * - * // TODO: pending tests - * - * @example - * it('should be true', function() { - * expect(true).toEqual(true); - * }); - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var it = function(desc, func) { - return jasmine.getEnv().it(desc, func); -}; -if (isCommonJS) exports.it = it; - -/** - * Creates a disabled Jasmine spec. - * - * A convenience method that allows existing specs to be disabled temporarily during development. - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var xit = function(desc, func) { - return jasmine.getEnv().xit(desc, func); -}; -if (isCommonJS) exports.xit = xit; - -/** - * Starts a chain for a Jasmine expectation. - * - * It is passed an Object that is the actual value and should chain to one of the many - * jasmine.Matchers functions. - * - * @param {Object} actual Actual value to test against and expected value - * @return {jasmine.Matchers} - */ -var expect = function(actual) { - return jasmine.getEnv().currentSpec.expect(actual); -}; -if (isCommonJS) exports.expect = expect; - -/** - * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. - * - * @param {Function} func Function that defines part of a jasmine spec. - */ -var runs = function(func) { - jasmine.getEnv().currentSpec.runs(func); -}; -if (isCommonJS) exports.runs = runs; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -var waits = function(timeout) { - jasmine.getEnv().currentSpec.waits(timeout); -}; -if (isCommonJS) exports.waits = waits; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); -}; -if (isCommonJS) exports.waitsFor = waitsFor; - -/** - * A function that is called before each spec in a suite. - * - * Used for spec setup, including validating assumptions. - * - * @param {Function} beforeEachFunction - */ -var beforeEach = function(beforeEachFunction) { - jasmine.getEnv().beforeEach(beforeEachFunction); -}; -if (isCommonJS) exports.beforeEach = beforeEach; - -/** - * A function that is called after each spec in a suite. - * - * Used for restoring any state that is hijacked during spec execution. - * - * @param {Function} afterEachFunction - */ -var afterEach = function(afterEachFunction) { - jasmine.getEnv().afterEach(afterEachFunction); -}; -if (isCommonJS) exports.afterEach = afterEach; - -/** - * Defines a suite of specifications. - * - * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared - * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization - * of setup in some tests. - * - * @example - * // TODO: a simple suite - * - * // TODO: a simple suite with a nested describe block - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var describe = function(description, specDefinitions) { - return jasmine.getEnv().describe(description, specDefinitions); -}; -if (isCommonJS) exports.describe = describe; - -/** - * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var xdescribe = function(description, specDefinitions) { - return jasmine.getEnv().xdescribe(description, specDefinitions); -}; -if (isCommonJS) exports.xdescribe = xdescribe; - - -// Provide the XMLHttpRequest class for IE 5.x-6.x: -jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { - function tryIt(f) { - try { - return f(); - } catch(e) { - } - return null; - } - - var xhr = tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.6.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.3.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP"); - }) || - tryIt(function() { - return new ActiveXObject("Microsoft.XMLHTTP"); - }); - - if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); - - return xhr; -} : XMLHttpRequest; -/** - * @namespace - */ -jasmine.util = {}; - -/** - * Declare that a child class inherit it's prototype from the parent class. - * - * @private - * @param {Function} childClass - * @param {Function} parentClass - */ -jasmine.util.inherit = function(childClass, parentClass) { - /** - * @private - */ - var subclass = function() { - }; - subclass.prototype = parentClass.prototype; - childClass.prototype = new subclass(); -}; - -jasmine.util.formatException = function(e) { - var lineNumber; - if (e.line) { - lineNumber = e.line; - } - else if (e.lineNumber) { - lineNumber = e.lineNumber; - } - - var file; - - if (e.sourceURL) { - file = e.sourceURL; - } - else if (e.fileName) { - file = e.fileName; - } - - var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); - - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; -}; - -jasmine.util.htmlEscape = function(str) { - if (!str) return str; - return str.replace(/&/g, '&') - .replace(//g, '>'); -}; - -jasmine.util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); - return arrayOfArgs; -}; - -jasmine.util.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; -}; - -/** - * Environment for Jasmine - * - * @constructor - */ -jasmine.Env = function() { - this.currentSpec = null; - this.currentSuite = null; - this.currentRunner_ = new jasmine.Runner(this); - - this.reporter = new jasmine.MultiReporter(); - - this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; - this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; - this.lastUpdate = 0; - this.specFilter = function() { - return true; - }; - - this.nextSpecId_ = 0; - this.nextSuiteId_ = 0; - this.equalityTesters_ = []; - - // wrap matchers - this.matchersClass = function() { - jasmine.Matchers.apply(this, arguments); - }; - jasmine.util.inherit(this.matchersClass, jasmine.Matchers); - - jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); -}; - - -jasmine.Env.prototype.setTimeout = jasmine.setTimeout; -jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; -jasmine.Env.prototype.setInterval = jasmine.setInterval; -jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - -/** - * @returns an object containing jasmine version build info, if set. - */ -jasmine.Env.prototype.version = function () { - if (jasmine.version_) { - return jasmine.version_; - } else { - throw new Error('Version not set'); - } -}; - -/** - * @returns string containing jasmine version build info, if set. - */ -jasmine.Env.prototype.versionString = function() { - if (!jasmine.version_) { - return "version unknown"; - } - - var version = this.version(); - var versionString = version.major + "." + version.minor + "." + version.build; - if (version.release_candidate) { - versionString += ".rc" + version.release_candidate; - } - versionString += " revision " + version.revision; - return versionString; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSpecId = function () { - return this.nextSpecId_++; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSuiteId = function () { - return this.nextSuiteId_++; -}; - -/** - * Register a reporter to receive status updates from Jasmine. - * @param {jasmine.Reporter} reporter An object which will receive status updates. - */ -jasmine.Env.prototype.addReporter = function(reporter) { - this.reporter.addReporter(reporter); -}; - -jasmine.Env.prototype.execute = function() { - this.currentRunner_.execute(); -}; - -jasmine.Env.prototype.describe = function(description, specDefinitions) { - var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); - - var parentSuite = this.currentSuite; - if (parentSuite) { - parentSuite.add(suite); - } else { - this.currentRunner_.add(suite); - } - - this.currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch(e) { - declarationError = e; - } - - if (declarationError) { - this.it("encountered a declaration exception", function() { - throw declarationError; - }); - } - - this.currentSuite = parentSuite; - - return suite; -}; - -jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { - if (this.currentSuite) { - this.currentSuite.beforeEach(beforeEachFunction); - } else { - this.currentRunner_.beforeEach(beforeEachFunction); - } -}; - -jasmine.Env.prototype.currentRunner = function () { - return this.currentRunner_; -}; - -jasmine.Env.prototype.afterEach = function(afterEachFunction) { - if (this.currentSuite) { - this.currentSuite.afterEach(afterEachFunction); - } else { - this.currentRunner_.afterEach(afterEachFunction); - } - -}; - -jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { - return { - execute: function() { - } - }; -}; - -jasmine.Env.prototype.it = function(description, func) { - var spec = new jasmine.Spec(this, this.currentSuite, description); - this.currentSuite.add(spec); - this.currentSpec = spec; - - if (func) { - spec.runs(func); - } - - return spec; -}; - -jasmine.Env.prototype.xit = function(desc, func) { - return { - id: this.nextSpecId(), - runs: function() { - } - }; -}; - -jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.source != b.source) - mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); - - if (a.ignoreCase != b.ignoreCase) - mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.global != b.global) - mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.multiline != b.multiline) - mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.sticky != b.sticky) - mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); - - return (mismatchValues.length === 0); -}; - -jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { - return true; - } - - a.__Jasmine_been_here_before__ = b; - b.__Jasmine_been_here_before__ = a; - - var hasKey = function(obj, keyName) { - return obj !== null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in b) { - if (!hasKey(a, property) && hasKey(b, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - } - for (property in a) { - if (!hasKey(b, property) && hasKey(a, property)) { - mismatchKeys.push("expected missing key '" + property + "', but present in actual."); - } - } - for (property in b) { - if (property == '__Jasmine_been_here_before__') continue; - if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); - } - } - - if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { - mismatchValues.push("arrays were not the same length"); - } - - delete a.__Jasmine_been_here_before__; - delete b.__Jasmine_been_here_before__; - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - for (var i = 0; i < this.equalityTesters_.length; i++) { - var equalityTester = this.equalityTesters_[i]; - var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); - if (result !== jasmine.undefined) return result; - } - - if (a === b) return true; - - if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { - return (a == jasmine.undefined && b == jasmine.undefined); - } - - if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { - return a === b; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() == b.getTime(); - } - - if (a.jasmineMatches) { - return a.jasmineMatches(b); - } - - if (b.jasmineMatches) { - return b.jasmineMatches(a); - } - - if (a instanceof jasmine.Matchers.ObjectContaining) { - return a.matches(b); - } - - if (b instanceof jasmine.Matchers.ObjectContaining) { - return b.matches(a); - } - - if (jasmine.isString_(a) && jasmine.isString_(b)) { - return (a == b); - } - - if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { - return (a == b); - } - - if (a instanceof RegExp && b instanceof RegExp) { - return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); - } - - if (typeof a === "object" && typeof b === "object") { - return this.compareObjects_(a, b, mismatchKeys, mismatchValues); - } - - //Straight check - return (a === b); -}; - -jasmine.Env.prototype.contains_ = function(haystack, needle) { - if (jasmine.isArray_(haystack)) { - for (var i = 0; i < haystack.length; i++) { - if (this.equals_(haystack[i], needle)) return true; - } - return false; - } - return haystack.indexOf(needle) >= 0; -}; - -jasmine.Env.prototype.addEqualityTester = function(equalityTester) { - this.equalityTesters_.push(equalityTester); -}; -/** No-op base class for Jasmine reporters. - * - * @constructor - */ -jasmine.Reporter = function() { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerResults = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecStarting = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecResults = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.log = function(str) { -}; - -/** - * Blocks are functions with executable code that make up a spec. - * - * @constructor - * @param {jasmine.Env} env - * @param {Function} func - * @param {jasmine.Spec} spec - */ -jasmine.Block = function(env, func, spec) { - this.env = env; - this.func = func; - this.spec = spec; -}; - -jasmine.Block.prototype.execute = function(onComplete) { - if (!jasmine.CATCH_EXCEPTIONS) { - this.func.apply(this.spec); - } - else { - try { - this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); - } - } - onComplete(); -}; -/** JavaScript API reporter. - * - * @constructor - */ -jasmine.JsApiReporter = function() { - this.started = false; - this.finished = false; - this.suites_ = []; - this.results_ = {}; -}; - -jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { - this.started = true; - var suites = runner.topLevelSuites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - this.suites_.push(this.summarize_(suite)); - } -}; - -jasmine.JsApiReporter.prototype.suites = function() { - return this.suites_; -}; - -jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { - var isSuite = suiteOrSpec instanceof jasmine.Suite; - var summary = { - id: suiteOrSpec.id, - name: suiteOrSpec.description, - type: isSuite ? 'suite' : 'spec', - children: [] - }; - - if (isSuite) { - var children = suiteOrSpec.children(); - for (var i = 0; i < children.length; i++) { - summary.children.push(this.summarize_(children[i])); - } - } - return summary; -}; - -jasmine.JsApiReporter.prototype.results = function() { - return this.results_; -}; - -jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { - return this.results_[specId]; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { - this.finished = true; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { - this.results_[spec.id] = { - messages: spec.results().getItems(), - result: spec.results().failedCount > 0 ? "failed" : "passed" - }; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.log = function(str) { -}; - -jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ - var results = {}; - for (var i = 0; i < specIds.length; i++) { - var specId = specIds[i]; - results[specId] = this.summarizeResult_(this.results_[specId]); - } - return results; -}; - -jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ - var summaryMessages = []; - var messagesLength = result.messages.length; - for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { - var resultMessage = result.messages[messageIndex]; - summaryMessages.push({ - text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, - passed: resultMessage.passed ? resultMessage.passed() : true, - type: resultMessage.type, - message: resultMessage.message, - trace: { - stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined - } - }); - } - - return { - result : result.result, - messages : summaryMessages - }; -}; - -/** - * @constructor - * @param {jasmine.Env} env - * @param actual - * @param {jasmine.Spec} spec - */ -jasmine.Matchers = function(env, actual, spec, opt_isNot) { - this.env = env; - this.actual = actual; - this.spec = spec; - this.isNot = opt_isNot || false; - this.reportWasCalled_ = false; -}; - -// todo: @deprecated as of Jasmine 0.11, remove soon [xw] -jasmine.Matchers.pp = function(str) { - throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); -}; - -// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] -jasmine.Matchers.prototype.report = function(result, failing_message, details) { - throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); -}; - -jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { - for (var methodName in prototype) { - if (methodName == 'report') continue; - var orig = prototype[methodName]; - matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); - } -}; - -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - return function() { - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); - - if (this.isNot) { - result = !result; - } - - if (this.reportWasCalled_) return result; - - var message; - if (!result) { - if (this.message) { - message = this.message.apply(this, arguments); - if (jasmine.isArray_(message)) { - message = message[this.isNot ? 1 : 0]; - } - } else { - var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; - if (matcherArgs.length > 0) { - for (var i = 0; i < matcherArgs.length; i++) { - if (i > 0) message += ","; - message += " " + jasmine.pp(matcherArgs[i]); - } - } - message += "."; - } - } - var expectationResult = new jasmine.ExpectationResult({ - matcherName: matcherName, - passed: result, - expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], - actual: this.actual, - message: message - }); - this.spec.addMatcherResult(expectationResult); - return jasmine.undefined; - }; -}; - - - - -/** - * toBe: compares the actual to the expected using === - * @param expected - */ -jasmine.Matchers.prototype.toBe = function(expected) { - return this.actual === expected; -}; - -/** - * toNotBe: compares the actual to the expected using !== - * @param expected - * @deprecated as of 1.0. Use not.toBe() instead. - */ -jasmine.Matchers.prototype.toNotBe = function(expected) { - return this.actual !== expected; -}; - -/** - * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. - * - * @param expected - */ -jasmine.Matchers.prototype.toEqual = function(expected) { - return this.env.equals_(this.actual, expected); -}; - -/** - * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual - * @param expected - * @deprecated as of 1.0. Use not.toEqual() instead. - */ -jasmine.Matchers.prototype.toNotEqual = function(expected) { - return !this.env.equals_(this.actual, expected); -}; - -/** - * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes - * a pattern or a String. - * - * @param expected - */ -jasmine.Matchers.prototype.toMatch = function(expected) { - return new RegExp(expected).test(this.actual); -}; - -/** - * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch - * @param expected - * @deprecated as of 1.0. Use not.toMatch() instead. - */ -jasmine.Matchers.prototype.toNotMatch = function(expected) { - return !(new RegExp(expected).test(this.actual)); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeDefined = function() { - return (this.actual !== jasmine.undefined); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeUndefined = function() { - return (this.actual === jasmine.undefined); -}; - -/** - * Matcher that compares the actual to null. - */ -jasmine.Matchers.prototype.toBeNull = function() { - return (this.actual === null); -}; - -/** - * Matcher that compares the actual to NaN. - */ -jasmine.Matchers.prototype.toBeNaN = function() { - this.message = function() { - return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; - }; - - return (this.actual !== this.actual); -}; - -/** - * Matcher that boolean not-nots the actual. - */ -jasmine.Matchers.prototype.toBeTruthy = function() { - return !!this.actual; -}; - - -/** - * Matcher that boolean nots the actual. - */ -jasmine.Matchers.prototype.toBeFalsy = function() { - return !this.actual; -}; - - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called. - */ -jasmine.Matchers.prototype.toHaveBeenCalled = function() { - if (arguments.length > 0) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to have been called.", - "Expected spy " + this.actual.identity + " not to have been called." - ]; - }; - - return this.actual.wasCalled; -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ -jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was not called. - * - * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead - */ -jasmine.Matchers.prototype.wasNotCalled = function() { - if (arguments.length > 0) { - throw new Error('wasNotCalled does not take arguments'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to not have been called.", - "Expected spy " + this.actual.identity + " to have been called." - ]; - }; - - return !this.actual.wasCalled; -}; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. - * - * @example - * - */ -jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - this.message = function() { - var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; - var positiveMessage = ""; - if (this.actual.callCount === 0) { - positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; - } else { - positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') - } - return [positiveMessage, invertedMessage]; - }; - - return this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; - -/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasNotCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" - ]; - }; - - return !this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** - * Matcher that checks that the expected item is an element in the actual Array. - * - * @param {Object} expected - */ -jasmine.Matchers.prototype.toContain = function(expected) { - return this.env.contains_(this.actual, expected); -}; - -/** - * Matcher that checks that the expected item is NOT an element in the actual Array. - * - * @param {Object} expected - * @deprecated as of 1.0. Use not.toContain() instead. - */ -jasmine.Matchers.prototype.toNotContain = function(expected) { - return !this.env.contains_(this.actual, expected); -}; - -jasmine.Matchers.prototype.toBeLessThan = function(expected) { - return this.actual < expected; -}; - -jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { - return this.actual > expected; -}; - -/** - * Matcher that checks that the expected item is equal to the actual item - * up to a given level of decimal precision (default 2). - * - * @param {Number} expected - * @param {Number} precision, as number of decimal places - */ -jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { - if (!(precision === 0)) { - precision = precision || 2; - } - return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); -}; - -/** - * Matcher that checks that the expected exception was thrown by the actual. - * - * @param {String} [expected] - */ -jasmine.Matchers.prototype.toThrow = function(expected) { - var result = false; - var exception; - if (typeof this.actual != 'function') { - throw new Error('Actual is not a function'); - } - try { - this.actual(); - } catch (e) { - exception = e; - } - if (exception) { - result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); - } - - var not = this.isNot ? "not " : ""; - - this.message = function() { - if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { - return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); - } else { - return "Expected function to throw an exception."; - } - }; - - return result; -}; - -jasmine.Matchers.Any = function(expectedClass) { - this.expectedClass = expectedClass; -}; - -jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { - if (this.expectedClass == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedClass == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedClass == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedClass == Object) { - return typeof other == 'object'; - } - - return other instanceof this.expectedClass; -}; - -jasmine.Matchers.Any.prototype.jasmineToString = function() { - return ''; -}; - -jasmine.Matchers.ObjectContaining = function (sample) { - this.sample = sample; -}; - -jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - var env = jasmine.getEnv(); - - var hasKey = function(obj, keyName) { - return obj != null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in this.sample) { - if (!hasKey(other, property) && hasKey(this.sample, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); - } - } - - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { - return ""; -}; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - -/** - * @constructor - */ -jasmine.MultiReporter = function() { - this.subReporters_ = []; -}; -jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); - -jasmine.MultiReporter.prototype.addReporter = function(reporter) { - this.subReporters_.push(reporter); -}; - -(function() { - var functionNames = [ - "reportRunnerStarting", - "reportRunnerResults", - "reportSuiteResults", - "reportSpecStarting", - "reportSpecResults", - "log" - ]; - for (var i = 0; i < functionNames.length; i++) { - var functionName = functionNames[i]; - jasmine.MultiReporter.prototype[functionName] = (function(functionName) { - return function() { - for (var j = 0; j < this.subReporters_.length; j++) { - var subReporter = this.subReporters_[j]; - if (subReporter[functionName]) { - subReporter[functionName].apply(subReporter, arguments); - } - } - }; - })(functionName); - } -})(); -/** - * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults - * - * @constructor - */ -jasmine.NestedResults = function() { - /** - * The total count of results - */ - this.totalCount = 0; - /** - * Number of passed results - */ - this.passedCount = 0; - /** - * Number of failed results - */ - this.failedCount = 0; - /** - * Was this suite/spec skipped? - */ - this.skipped = false; - /** - * @ignore - */ - this.items_ = []; -}; - -/** - * Roll up the result counts. - * - * @param result - */ -jasmine.NestedResults.prototype.rollupCounts = function(result) { - this.totalCount += result.totalCount; - this.passedCount += result.passedCount; - this.failedCount += result.failedCount; -}; - -/** - * Adds a log message. - * @param values Array of message parts which will be concatenated later. - */ -jasmine.NestedResults.prototype.log = function(values) { - this.items_.push(new jasmine.MessageResult(values)); -}; - -/** - * Getter for the results: message & results. - */ -jasmine.NestedResults.prototype.getItems = function() { - return this.items_; -}; - -/** - * Adds a result, tracking counts (total, passed, & failed) - * @param {jasmine.ExpectationResult|jasmine.NestedResults} result - */ -jasmine.NestedResults.prototype.addResult = function(result) { - if (result.type != 'log') { - if (result.items_) { - this.rollupCounts(result); - } else { - this.totalCount++; - if (result.passed()) { - this.passedCount++; - } else { - this.failedCount++; - } - } - } - this.items_.push(result); -}; - -/** - * @returns {Boolean} True if everything below passed - */ -jasmine.NestedResults.prototype.passed = function() { - return this.passedCount === this.totalCount; -}; -/** - * Base class for pretty printing for expectation results. - */ -jasmine.PrettyPrinter = function() { - this.ppNestLevel_ = 0; -}; - -/** - * Formats a value in a nice, human-readable string. - * - * @param value - */ -jasmine.PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (value === jasmine.undefined) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === jasmine.getGlobal()) { - this.emitScalar(''); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (jasmine.isSpy(value)) { - this.emitScalar("spy on " + value.identity); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_been_here_before__) { - this.emitScalar(''); - } else if (jasmine.isArray_(value) || typeof value == 'object') { - value.__Jasmine_been_here_before__ = true; - if (jasmine.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - delete value.__Jasmine_been_here_before__; - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } -}; - -jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!obj.hasOwnProperty(property)) continue; - if (property == '__Jasmine_been_here_before__') continue; - fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && - obj.__lookupGetter__(property) !== null) : false); - } -}; - -jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; - -jasmine.StringPrettyPrinter = function() { - jasmine.PrettyPrinter.call(this); - - this.string = ''; -}; -jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); - -jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); -}; - -jasmine.StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); -}; - -jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { - this.append("Array"); - return; - } - - this.append('[ '); - for (var i = 0; i < array.length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - this.append(' ]'); -}; - -jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { - if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { - this.append("Object"); - return; - } - - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(' : '); - if (isGetter) { - self.append(''); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); -}; - -jasmine.StringPrettyPrinter.prototype.append = function(value) { - this.string += value; -}; -jasmine.Queue = function(env) { - this.env = env; - - // parallel to blocks. each true value in this array means the block will - // get executed even if we abort - this.ensured = []; - this.blocks = []; - this.running = false; - this.index = 0; - this.offset = 0; - this.abort = false; -}; - -jasmine.Queue.prototype.addBefore = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.blocks.unshift(block); - this.ensured.unshift(ensure); -}; - -jasmine.Queue.prototype.add = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.blocks.push(block); - this.ensured.push(ensure); -}; - -jasmine.Queue.prototype.insertNext = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.ensured.splice((this.index + this.offset + 1), 0, ensure); - this.blocks.splice((this.index + this.offset + 1), 0, block); - this.offset++; -}; - -jasmine.Queue.prototype.start = function(onComplete) { - this.running = true; - this.onComplete = onComplete; - this.next_(); -}; - -jasmine.Queue.prototype.isRunning = function() { - return this.running; -}; - -jasmine.Queue.LOOP_DONT_RECURSE = true; - -jasmine.Queue.prototype.next_ = function() { - var self = this; - var goAgain = true; - - while (goAgain) { - goAgain = false; - - if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { - var calledSynchronously = true; - var completedSynchronously = false; - - var onComplete = function () { - if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { - completedSynchronously = true; - return; - } - - if (self.blocks[self.index].abort) { - self.abort = true; - } - - self.offset = 0; - self.index++; - - var now = new Date().getTime(); - if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { - self.env.lastUpdate = now; - self.env.setTimeout(function() { - self.next_(); - }, 0); - } else { - if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { - goAgain = true; - } else { - self.next_(); - } - } - }; - self.blocks[self.index].execute(onComplete); - - calledSynchronously = false; - if (completedSynchronously) { - onComplete(); - } - - } else { - self.running = false; - if (self.onComplete) { - self.onComplete(); - } - } - } -}; - -jasmine.Queue.prototype.results = function() { - var results = new jasmine.NestedResults(); - for (var i = 0; i < this.blocks.length; i++) { - if (this.blocks[i].results) { - results.addResult(this.blocks[i].results()); - } - } - return results; -}; - - -/** - * Runner - * - * @constructor - * @param {jasmine.Env} env - */ -jasmine.Runner = function(env) { - var self = this; - self.env = env; - self.queue = new jasmine.Queue(env); - self.before_ = []; - self.after_ = []; - self.suites_ = []; -}; - -jasmine.Runner.prototype.execute = function() { - var self = this; - if (self.env.reporter.reportRunnerStarting) { - self.env.reporter.reportRunnerStarting(this); - } - self.queue.start(function () { - self.finishCallback(); - }); -}; - -jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.splice(0,0,beforeEachFunction); -}; - -jasmine.Runner.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.splice(0,0,afterEachFunction); -}; - - -jasmine.Runner.prototype.finishCallback = function() { - this.env.reporter.reportRunnerResults(this); -}; - -jasmine.Runner.prototype.addSuite = function(suite) { - this.suites_.push(suite); -}; - -jasmine.Runner.prototype.add = function(block) { - if (block instanceof jasmine.Suite) { - this.addSuite(block); - } - this.queue.add(block); -}; - -jasmine.Runner.prototype.specs = function () { - var suites = this.suites(); - var specs = []; - for (var i = 0; i < suites.length; i++) { - specs = specs.concat(suites[i].specs()); - } - return specs; -}; - -jasmine.Runner.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Runner.prototype.topLevelSuites = function() { - var topLevelSuites = []; - for (var i = 0; i < this.suites_.length; i++) { - if (!this.suites_[i].parentSuite) { - topLevelSuites.push(this.suites_[i]); - } - } - return topLevelSuites; -}; - -jasmine.Runner.prototype.results = function() { - return this.queue.results(); -}; -/** - * Internal representation of a Jasmine specification, or test. - * - * @constructor - * @param {jasmine.Env} env - * @param {jasmine.Suite} suite - * @param {String} description - */ -jasmine.Spec = function(env, suite, description) { - if (!env) { - throw new Error('jasmine.Env() required'); - } - if (!suite) { - throw new Error('jasmine.Suite() required'); - } - var spec = this; - spec.id = env.nextSpecId ? env.nextSpecId() : null; - spec.env = env; - spec.suite = suite; - spec.description = description; - spec.queue = new jasmine.Queue(env); - - spec.afterCallbacks = []; - spec.spies_ = []; - - spec.results_ = new jasmine.NestedResults(); - spec.results_.description = description; - spec.matchersClass = null; -}; - -jasmine.Spec.prototype.getFullName = function() { - return this.suite.getFullName() + ' ' + this.description + '.'; -}; - - -jasmine.Spec.prototype.results = function() { - return this.results_; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the spec's output. - * - * Be careful not to leave calls to jasmine.log in production code. - */ -jasmine.Spec.prototype.log = function() { - return this.results_.log(arguments); -}; - -jasmine.Spec.prototype.runs = function (func) { - var block = new jasmine.Block(this.env, func, this); - this.addToQueue(block); - return this; -}; - -jasmine.Spec.prototype.addToQueue = function (block) { - if (this.queue.isRunning()) { - this.queue.insertNext(block); - } else { - this.queue.add(block); - } -}; - -/** - * @param {jasmine.ExpectationResult} result - */ -jasmine.Spec.prototype.addMatcherResult = function(result) { - this.results_.addResult(result); -}; - -jasmine.Spec.prototype.expect = function(actual) { - var positive = new (this.getMatchersClass_())(this.env, actual, this); - positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); - return positive; -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -jasmine.Spec.prototype.waits = function(timeout) { - var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); - this.addToQueue(waitsFunc); - return this; -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - var latchFunction_ = null; - var optional_timeoutMessage_ = null; - var optional_timeout_ = null; - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - switch (typeof arg) { - case 'function': - latchFunction_ = arg; - break; - case 'string': - optional_timeoutMessage_ = arg; - break; - case 'number': - optional_timeout_ = arg; - break; - } - } - - var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); - this.addToQueue(waitsForFunc); - return this; -}; - -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception', - trace: { stack: e.stack } - }); - this.results_.addResult(expectationResult); -}; - -jasmine.Spec.prototype.getMatchersClass_ = function() { - return this.matchersClass || this.env.matchersClass; -}; - -jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { - var parent = this.getMatchersClass_(); - var newMatchersClass = function() { - parent.apply(this, arguments); - }; - jasmine.util.inherit(newMatchersClass, parent); - jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); - this.matchersClass = newMatchersClass; -}; - -jasmine.Spec.prototype.finishCallback = function() { - this.env.reporter.reportSpecResults(this); -}; - -jasmine.Spec.prototype.finish = function(onComplete) { - this.removeAllSpies(); - this.finishCallback(); - if (onComplete) { - onComplete(); - } -}; - -jasmine.Spec.prototype.after = function(doAfter) { - if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this), true); - } else { - this.afterCallbacks.unshift(doAfter); - } -}; - -jasmine.Spec.prototype.execute = function(onComplete) { - var spec = this; - if (!spec.env.specFilter(spec)) { - spec.results_.skipped = true; - spec.finish(onComplete); - return; - } - - this.env.reporter.reportSpecStarting(this); - - spec.env.currentSpec = spec; - - spec.addBeforesAndAftersToQueue(); - - spec.queue.start(function () { - spec.finish(onComplete); - }); -}; - -jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { - var runner = this.env.currentRunner(); - var i; - - for (var suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); - } - } - for (i = 0; i < runner.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); - } - for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); - } - for (suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); - } - } - for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); - } -}; - -jasmine.Spec.prototype.explodes = function() { - throw 'explodes function should not have been called'; -}; - -jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { - if (obj == jasmine.undefined) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { - throw methodName + '() method does not exist'; - } - - if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = jasmine.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; -}; - -jasmine.Spec.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; -}; - -/** - * Internal representation of a Jasmine suite. - * - * @constructor - * @param {jasmine.Env} env - * @param {String} description - * @param {Function} specDefinitions - * @param {jasmine.Suite} parentSuite - */ -jasmine.Suite = function(env, description, specDefinitions, parentSuite) { - var self = this; - self.id = env.nextSuiteId ? env.nextSuiteId() : null; - self.description = description; - self.queue = new jasmine.Queue(env); - self.parentSuite = parentSuite; - self.env = env; - self.before_ = []; - self.after_ = []; - self.children_ = []; - self.suites_ = []; - self.specs_ = []; -}; - -jasmine.Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - return fullName; -}; - -jasmine.Suite.prototype.finish = function(onComplete) { - this.env.reporter.reportSuiteResults(this); - this.finished = true; - if (typeof(onComplete) == 'function') { - onComplete(); - } -}; - -jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.unshift(beforeEachFunction); -}; - -jasmine.Suite.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.unshift(afterEachFunction); -}; - -jasmine.Suite.prototype.results = function() { - return this.queue.results(); -}; - -jasmine.Suite.prototype.add = function(suiteOrSpec) { - this.children_.push(suiteOrSpec); - if (suiteOrSpec instanceof jasmine.Suite) { - this.suites_.push(suiteOrSpec); - this.env.currentRunner().addSuite(suiteOrSpec); - } else { - this.specs_.push(suiteOrSpec); - } - this.queue.add(suiteOrSpec); -}; - -jasmine.Suite.prototype.specs = function() { - return this.specs_; -}; - -jasmine.Suite.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Suite.prototype.children = function() { - return this.children_; -}; - -jasmine.Suite.prototype.execute = function(onComplete) { - var self = this; - this.queue.start(function () { - self.finish(onComplete); - }); -}; -jasmine.WaitsBlock = function(env, timeout, spec) { - this.timeout = timeout; - jasmine.Block.call(this, env, null, spec); -}; - -jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); - -jasmine.WaitsBlock.prototype.execute = function (onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); - } - this.env.setTimeout(function () { - onComplete(); - }, this.timeout); -}; -/** - * A block which waits for some condition to become true, with timeout. - * - * @constructor - * @extends jasmine.Block - * @param {jasmine.Env} env The Jasmine environment. - * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. - * @param {Function} latchFunction A function which returns true when the desired condition has been met. - * @param {String} message The message to display if the desired condition hasn't been met within the given time period. - * @param {jasmine.Spec} spec The Jasmine spec. - */ -jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { - this.timeout = timeout || env.defaultTimeoutInterval; - this.latchFunction = latchFunction; - this.message = message; - this.totalTimeSpentWaitingForLatch = 0; - jasmine.Block.call(this, env, null, spec); -}; -jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); - -jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; - -jasmine.WaitsForBlock.prototype.execute = function(onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); - } - var latchFunctionResult; - try { - latchFunctionResult = this.latchFunction.apply(this.spec); - } catch (e) { - this.spec.fail(e); - onComplete(); - return; - } - - if (latchFunctionResult) { - onComplete(); - } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { - var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); - this.spec.fail({ - name: 'timeout', - message: message - }); - - this.abort = true; - onComplete(); - } else { - this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; - var self = this; - this.env.setTimeout(function() { - self.execute(onComplete); - }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); - } -}; - -jasmine.version_= { - "major": 1, - "minor": 3, - "build": 1, - "revision": 1354556913 -};