diff options
296 files changed, 6355 insertions, 2465 deletions
diff --git a/.travis.yml b/.travis.yml index e89e27b2ec..551613da51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ language: php php: - 5.6 - - 5.3 + - 5.3.3 # - hhvm services: @@ -43,13 +43,13 @@ matrix: - php: hhvm exclude: # Run test suites separately only on PHP 5.6 with PDO - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=SystemTests MYSQL_ADAPTER=PDO_MYSQL - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=IntegrationTests MYSQL_ADAPTER=PDO_MYSQL - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=AllTests MYSQL_ADAPTER=PDO_MYSQL - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=UnitTests MYSQL_ADAPTER=PDO_MYSQL - php: hhvm env: TEST_SUITE=SystemTests MYSQL_ADAPTER=PDO_MYSQL @@ -57,23 +57,23 @@ matrix: env: TEST_SUITE=IntegrationTests MYSQL_ADAPTER=PDO_MYSQL - php: hhvm env: TEST_SUITE=UnitTests MYSQL_ADAPTER=PDO_MYSQL - # run UI tests on PHP 5.3 only + # run UI tests on PHP 5.3.3 only - php: 5.6 env: TEST_SUITE=UITests MYSQL_ADAPTER=PDO_MYSQL # run all tests not on PHP 5.6 and run MySQLI tests only on 5.6 - php: 5.6 env: TEST_SUITE=AllTests MYSQL_ADAPTER=PDO_MYSQL - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=AllTests MYSQL_ADAPTER=MYSQLI - php: hhvm env: TEST_SUITE=AllTests MYSQL_ADAPTER=MYSQLI # Javascript tests need to run only on one PHP version - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=JavascriptTests MYSQL_ADAPTER=PDO_MYSQL - php: hhvm env: TEST_SUITE=JavascriptTests MYSQL_ADAPTER=PDO_MYSQL # AngularJS tests need to run only on one PHP version - - php: 5.3 + - php: 5.3.3 env: TEST_SUITE=AngularJSTests MYSQL_ADAPTER=PDO_MYSQL - php: hhvm env: TEST_SUITE=AngularJSTests MYSQL_ADAPTER=PDO_MYSQL @@ -93,11 +93,7 @@ install: before_script: - ./tests/travis/install_mysql_5.6.sh - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then composer require satooshi/php-coveralls dev-master; - else - phpenv config-rm xdebug.ini; - fi + - phpenv config-rm xdebug.ini; # add always_populate_raw_post_data=-1 to php.ini - echo "always_populate_raw_post_data=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini @@ -152,10 +148,6 @@ after_script: # change directory back to root travis dir - cd $PIWIK_ROOT_DIR - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then php vendor/bin/coveralls -v; - fi - # output contents of files w/ debugging info to screen - cat /var/log/nginx/error.log - cat $PIWIK_ROOT_DIR/tmp/php-fpm.log diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a8eb4e96..82a9d15f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API's, Plugins, Themes, etc will be listed here. +## Piwik 2.12.0 + +### Breaking Changes +* The deprecated method `Period::factory()` has been removed. Use `Period\Factory` instead. +* The deprecated method `Config::getConfigSuperUserForBackwardCompatibility()` has been removed. +* The deprecated methods `MenuAdmin::addEntry()` and `MenuAdmin::removeEntry()` have been removed. Use `Piwik\Plugin\Menu` instead. +* The deprecated methods `MenuTop::addEntry()` and `MenuTop::removeEntry()` have been removed. Use `Piwik\Plugin\Menu` instead. +* The deprecated method `SettingsPiwik::rewriteTmpPathWithInstanceId()` has been removed. +* The following deprecated methods from the `Piwik\IP` class have been removed, use `Piwik\Network\IP` instead: + * `sanitizeIp()` + * `sanitizeIpRange()` + * `P2N()` + * `N2P()` + * `prettyPrint()` + * `isIPv4()` + * `long2ip()` + * `isIPv6()` + * `isMappedIPv4()` + * `getIPv4FromMappedIPv6()` + * `getIpsForRange()` + * `isIpInRange()` + * `getHostByAddr()` + ## Piwik 2.11.0 ### Breaking Changes diff --git a/composer.json b/composer.json index 76cadd2442..959c0a7a34 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "piwik/decompress": "~0.1.1", "piwik/network": "~0.1.0", "piwik/cache": "~0.2.5", - "piwik/ini": "~1.0,>=1.0.2", + "piwik/ini": "~1.0,>=1.0.3", "mnapoli/php-di": "5.0.x-dev", "psr/log": "~1.0", "monolog/monolog": "~1.11", diff --git a/composer.lock b/composer.lock index d0047d9b5f..2d48b31a06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "f9914d40211d9207a141d003cddb89ff", + "hash": "ad2f11fd442b91f4ac1e6a131d664f96", "packages": [ { "name": "container-interop/container-interop", @@ -655,16 +655,16 @@ }, { "name": "piwik/ini", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/piwik/component-ini.git", - "reference": "2a2179e56e6e906364e7e978305a2b75b7b34eb0" + "reference": "5863e8a006d490c2eb50e32ef08c1c684dff0ed0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/piwik/component-ini/zipball/2a2179e56e6e906364e7e978305a2b75b7b34eb0", - "reference": "2a2179e56e6e906364e7e978305a2b75b7b34eb0", + "url": "https://api.github.com/repos/piwik/component-ini/zipball/5863e8a006d490c2eb50e32ef08c1c684dff0ed0", + "reference": "5863e8a006d490c2eb50e32ef08c1c684dff0ed0", "shasum": "" }, "require": { @@ -683,7 +683,7 @@ "license": [ "LGPL-3.0" ], - "time": "2015-02-25 21:48:57" + "time": "2015-03-03 22:19:39" }, { "name": "piwik/network", diff --git a/config/global.ini.php b/config/global.ini.php index 2a2daf1367..de3e80ca72 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -463,10 +463,6 @@ enable_trusted_host_check = 1 ; independantly of the hostname Piwik is currently running under. ; instance_id = stats.example.com -; The release server is an essential part of the Piwik infrastructure/ecosystem -; to provide the latest software version. -latest_version_url = http://builds.piwik.org/piwik.zip - ; The API server is an essential part of the Piwik infrastructure/ecosystem to ; provide services to Piwik installations, e.g., getLatestVersion and ; subscribeNewsletter. @@ -646,7 +642,7 @@ bulk_requests_use_transaction = 1 ; Comma separated list of known Referrer Spammers, ie. bot visits that set a fake Referrer field. ; All Visits with a Referrer URL host set to one of these will be excluded. ; If you find new spam entries in Referrers>Websites, please report them here: https://github.com/piwik/piwik/issues/5099 -referrer_urls_spam = "semalt.com,buttons-for-website.com,7makemoneyonline.com,anticrawler.org,ranksonic.info,savetubevideo.com,kambasoft.com" +referrer_urls_spam = "semalt.com,buttons-for-website.com,7makemoneyonline.com,anticrawler.org,ranksonic.info,savetubevideo.com,kambasoft.com,ilovevitaly.com,priceg.com,blackhatworth.com,hulfingtonpost.com,darodar.com,econom.co,o-o-6-o-o.com,bestwebsitesawards.com,darodar.com,ranksonic.org,ranksonic.info" ; DO NOT USE THIS SETTING ON PUBLICLY AVAILABLE PIWIK SERVER ; !!! Security risk: if set to 0, it would allow anyone to push data to Piwik with custom dates in the past/future and even with fake IPs! diff --git a/core/API/DataTableManipulator/Flattener.php b/core/API/DataTableManipulator/Flattener.php index cfee16e6ba..a976b7a62e 100644 --- a/core/API/DataTableManipulator/Flattener.php +++ b/core/API/DataTableManipulator/Flattener.php @@ -38,17 +38,16 @@ class Flattener extends DataTableManipulator * Separator for building recursive labels (or paths) * @var string */ - public $recursiveLabelSeparator = ' - '; + public $recursiveLabelSeparator = ''; /** * @param DataTable $dataTable + * @param string $recursiveLabelSeparator * @return DataTable|DataTable\Map */ - public function flatten($dataTable) + public function flatten($dataTable, $recursiveLabelSeparator) { - if ($this->apiModule == 'Actions' || $this->apiMethod == 'getWebsites') { - $this->recursiveLabelSeparator = '/'; - } + $this->recursiveLabelSeparator = $recursiveLabelSeparator; return $this->manipulate($dataTable); } @@ -72,9 +71,10 @@ class Flattener extends DataTableManipulator } $newDataTable = $dataTable->getEmptyClone($keepFilters); - foreach ($dataTable->getRows() as $row) { - $this->flattenRow($row, $newDataTable); + foreach ($dataTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $newDataTable); } + return $newDataTable; } @@ -84,15 +84,21 @@ class Flattener extends DataTableManipulator * @param string $labelPrefix * @param bool $parentLogo */ - private function flattenRow(Row $row, DataTable $dataTable, + private function flattenRow(Row $row, $rowId, DataTable $dataTable, $labelPrefix = '', $parentLogo = false) { $label = $row->getColumn('label'); if ($label !== false) { $label = trim($label); - if (substr($label, 0, 1) == '/' && $this->recursiveLabelSeparator == '/') { - $label = substr($label, 1); + + if ($this->recursiveLabelSeparator == '/') { + if (substr($label, 0, 1) == '/') { + $label = substr($label, 1); + } elseif ($rowId === DataTable::ID_SUMMARY_ROW && $labelPrefix && $label != DataTable::LABEL_SUMMARY_ROW) { + $label = ' - ' . $label; + } } + $label = $labelPrefix . $label; $row->setColumn('label', $label); } @@ -103,7 +109,16 @@ class Flattener extends DataTableManipulator $row->setMetadata('logo', $logo); } - $subTable = $this->loadSubtable($dataTable, $row); + /** @var DataTable $subTable */ + $subTable = $row->getSubtable(); + + if ($subTable) { + $subTable->applyQueuedFilters(); + $row->deleteMetadata('idsubdatatable_in_db'); + } else { + $subTable = $this->loadSubtable($dataTable, $row); + } + $row->removeSubtable(); if ($subTable === null) { @@ -117,8 +132,8 @@ class Flattener extends DataTableManipulator $dataTable->addRow($row); } $prefix = $label . $this->recursiveLabelSeparator; - foreach ($subTable->getRows() as $row) { - $this->flattenRow($row, $dataTable, $prefix, $logo); + foreach ($subTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $dataTable, $prefix, $logo); } } } diff --git a/core/API/DataTableManipulator/LabelFilter.php b/core/API/DataTableManipulator/LabelFilter.php index cd8300991b..0d4e11772a 100644 --- a/core/API/DataTableManipulator/LabelFilter.php +++ b/core/API/DataTableManipulator/LabelFilter.php @@ -147,6 +147,8 @@ class LabelFilter extends DataTableManipulator } $variations[] = $label; + $variations = array_unique($variations); + return $variations; } diff --git a/core/API/DataTableManipulator/ReportTotalsCalculator.php b/core/API/DataTableManipulator/ReportTotalsCalculator.php index b6b82effad..4703c36c7d 100644 --- a/core/API/DataTableManipulator/ReportTotalsCalculator.php +++ b/core/API/DataTableManipulator/ReportTotalsCalculator.php @@ -66,14 +66,19 @@ class ReportTotalsCalculator extends DataTableManipulator $firstLevelTable = $this->makeSureToWorkOnFirstLevelDataTable($dataTable); $metricsToCalculate = Metrics::getMetricIdsToProcessReportTotal(); + $realMetricNames = array(); foreach ($metricsToCalculate as $metricId) { - $realMetricName = $this->hasDataTableMetric($firstLevelTable, $metricId); - if (empty($realMetricName)) { - continue; + $metricName = Metrics::getReadableColumnName($metricId); + $realMetricName = $this->hasDataTableMetric($firstLevelTable, $metricId, $metricName); + if (!empty($realMetricName)) { + $realMetricNames[$metricName] = $realMetricName; } + } - foreach ($firstLevelTable->getRows() as $row) { - $totalValues = $this->sumColumnValueToTotal($row, $metricId, $realMetricName, $totalValues); + foreach ($firstLevelTable->getRows() as $row) { + $columns = $row->getColumns(); + foreach ($realMetricNames as $metricName => $realMetricName) { + $totalValues = $this->sumColumnValueToTotal($columns, $metricName, $realMetricName, $totalValues); } } @@ -82,7 +87,7 @@ class ReportTotalsCalculator extends DataTableManipulator return $dataTable; } - private function hasDataTableMetric(DataTable $dataTable, $metricId) + private function hasDataTableMetric(DataTable $dataTable, $metricId, $readableColumnName) { $firstRow = $dataTable->getFirstRow(); @@ -90,7 +95,6 @@ class ReportTotalsCalculator extends DataTableManipulator return false; } - $readableColumnName = Metrics::getReadableColumnName($metricId); $columnAlternatives = array( $metricId, $readableColumnName, @@ -151,17 +155,18 @@ class ReportTotalsCalculator extends DataTableManipulator return $table; } - private function sumColumnValueToTotal(Row $row, $metricId, $realMetricId, $totalValues) + private function sumColumnValueToTotal($columns, $metricName, $realMetricId, $totalValues) { - $value = $row->getColumn($realMetricId); + $value = false; + if (array_key_exists($realMetricId, $columns)) { + $value = $columns[$realMetricId]; + } if (false === $value) { return $totalValues; } - $metricName = Metrics::getReadableColumnName($metricId); - if (array_key_exists($metricName, $totalValues)) { $totalValues[$metricName] += $value; } else { diff --git a/core/API/DataTablePostProcessor.php b/core/API/DataTablePostProcessor.php index 999b3fe339..dfa0434da6 100644 --- a/core/API/DataTablePostProcessor.php +++ b/core/API/DataTablePostProcessor.php @@ -59,6 +59,9 @@ class DataTablePostProcessor */ private $formatter; + private $callbackBeforeGenericFilters; + private $callbackAfterGenericFilters; + /** * Constructor. */ @@ -66,11 +69,31 @@ class DataTablePostProcessor { $this->apiModule = $apiModule; $this->apiMethod = $apiMethod; - $this->request = $request; + $this->setRequest($request); $this->report = Report::factory($apiModule, $apiMethod); $this->apiInconsistencies = new Inconsistencies(); - $this->formatter = new Formatter(); + $this->setFormatter(new Formatter()); + } + + public function setFormatter(Formatter $formatter) + { + $this->formatter = $formatter; + } + + public function setRequest($request) + { + $this->request = $request; + } + + public function setCallbackBeforeGenericFilters($callbackBeforeGenericFilters) + { + $this->callbackBeforeGenericFilters = $callbackBeforeGenericFilters; + } + + public function setCallbackAfterGenericFilters($callbackAfterGenericFilters) + { + $this->callbackAfterGenericFilters = $callbackAfterGenericFilters; } /** @@ -85,22 +108,27 @@ class DataTablePostProcessor // this is non-trivial since it will require, eg, to make sure processed metrics aren't added // after pivotBy is handled. $dataTable = $this->applyPivotByFilter($dataTable); - $dataTable = $this->applyFlattener($dataTable); $dataTable = $this->applyTotalsCalculator($dataTable); + $dataTable = $this->applyFlattener($dataTable); - $dataTable = $this->applyGenericFilters($dataTable); + if ($this->callbackBeforeGenericFilters) { + call_user_func($this->callbackBeforeGenericFilters, $dataTable); + } + $dataTable = $this->applyGenericFilters($dataTable); $this->applyComputeProcessedMetrics($dataTable); + if ($this->callbackAfterGenericFilters) { + call_user_func($this->callbackAfterGenericFilters, $dataTable); + } + // we automatically safe decode all datatable labels (against xss) $dataTable->queueFilter('SafeDecodeLabel'); - $dataTable = $this->convertSegmentValueToSegment($dataTable); $dataTable = $this->applyQueuedFilters($dataTable); $dataTable = $this->applyRequestedColumnDeletion($dataTable); $dataTable = $this->applyLabelFilter($dataTable); $dataTable = $this->applyMetricsFormatting($dataTable); - return $dataTable; } @@ -145,7 +173,13 @@ class DataTablePostProcessor if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') { $flattener->includeAggregateRows(); } - $dataTable = $flattener->flatten($dataTable); + + $recursiveLabelSeparator = ' - '; + if ($this->report) { + $recursiveLabelSeparator = $this->report->getRecursiveLabelSeparator(); + } + + $dataTable = $flattener->flatten($dataTable, $recursiveLabelSeparator); } return $dataTable; } diff --git a/core/API/Proxy.php b/core/API/Proxy.php index 0345693b62..0f8667d0d8 100644 --- a/core/API/Proxy.php +++ b/core/API/Proxy.php @@ -490,7 +490,7 @@ class Proxy extends Singleton $hideLine = trim($hideLine); $hideLine .= ' '; - $token = trim(strtok($hideLine, " "), "\n"); + $token = trim(strtok($hideLine, " "), "\n"); $hide = false; @@ -529,18 +529,6 @@ class Proxy extends Singleton } /** - * Returns the number of required parameters (parameters without default values). - * - * @param string $class The class name - * @param string $name The method name - * @return int The number of required parameters - */ - private function getNumberOfRequiredParameters($class, $name) - { - return $this->metadataArray[$class][$name]['numberOfRequiredParameters']; - } - - /** * Returns true if the method is found in the API of the given class name. * * @param string $className The class name diff --git a/core/API/Request.php b/core/API/Request.php index b135a5f20e..2ed5dfc567 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -18,6 +18,7 @@ use Piwik\SettingsServer; use Piwik\Url; use Piwik\UrlHelper; use Piwik\Log; +use Piwik\Plugin\Manager as PluginManager; /** * Dispatches API requests to the appropriate API method. @@ -218,12 +219,11 @@ class Request list($module, $method) = $this->extractModuleAndMethod($moduleMethod); - list($module, $method) = $this->getRenamedModuleAndAction($module, $method); + list($module, $method) = self::getRenamedModuleAndAction($module, $method); + + PluginManager::getInstance()->checkIsPluginActivated($module); - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) { - throw new PluginDeactivatedException($module); - } - $apiClassName = $this->getClassNameAPI($module); + $apiClassName = self::getClassNameAPI($module); self::reloadAuthUsingTokenAuth($this->request); diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index 4ebe50305d..68e448b0b4 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -25,6 +25,7 @@ class ResponseBuilder private $apiRenderer = null; private $request = null; private $sendHeader = true; + private $postProcessDataTable = true; private $apiModule = false; private $apiMethod = false; @@ -45,6 +46,11 @@ class ResponseBuilder $this->sendHeader = false; } + public function disableDataTablePostProcessor() + { + $this->postProcessDataTable = false; + } + /** * This method processes the data resulting from the API call. * @@ -164,8 +170,10 @@ class ResponseBuilder private function handleDataTable(DataTableInterface $datatable) { - $postProcessor = new DataTablePostProcessor($this->apiModule, $this->apiMethod, $this->request); - $datatable = $postProcessor->process($datatable); + if ($this->postProcessDataTable) { + $postProcessor = new DataTablePostProcessor($this->apiModule, $this->apiMethod, $this->request); + $datatable = $postProcessor->process($datatable); + } return $this->apiRenderer->renderDataTable($datatable); } diff --git a/core/Archive.php b/core/Archive.php index 1931b6a343..c7eb6e3106 100644 --- a/core/Archive.php +++ b/core/Archive.php @@ -452,6 +452,7 @@ class Archive * @return DataTable|DataTable\Map See {@link getDataTable()} and * {@link getDataTableExpanded()} for more * information + * @deprecated Since Piwik 2.12.0 Use Archive::createDataTableFromArchive() instead */ public static function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable = null, $depth = null) @@ -474,6 +475,38 @@ class Archive return $dataTable; } + /** + * Helper function that creates an Archive instance and queries for report data using + * query parameter data. API methods can use this method to reduce code redundancy. + * + * @param string $recordName The name of the report to return. + * @param int|string|array $idSite @see {@link build()} + * @param string $period @see {@link build()} + * @param string $date @see {@link build()} + * @param string $segment @see {@link build()} + * @param bool $expanded If true, loads all subtables. See {@link getDataTableExpanded()} + * @param bool $flat If true, loads all subtables and disabled all recursive filters. + * @param int|null $idSubtable See {@link getDataTableExpanded()} + * @param int|null $depth See {@link getDataTableExpanded()} + * @return DataTable|DataTable\Map + */ + public static function createDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null, $depth = null) + { + if ($flat && !$idSubtable) { + $expanded = true; + } + + $dataTable = self::getDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); + + $dataTable->filter('ReplaceColumnNames'); + + if ($flat) { + $dataTable->disableRecursiveFilters(); + } + + return $dataTable; + } + private function appendIdSubtable($recordName, $id) { return $recordName . "_" . $id; diff --git a/core/ArchiveProcessor/Rules.php b/core/ArchiveProcessor/Rules.php index a715623ca6..06b38aa27d 100644 --- a/core/ArchiveProcessor/Rules.php +++ b/core/ArchiveProcessor/Rules.php @@ -124,30 +124,15 @@ class Rules */ public static function shouldPurgeOutdatedArchives(Date $date) { - if (! self::isRequestAuthorizedToArchive()){ - Log::info("Purging temporary archives: skipped (no authorization)"); - return false; - } - - $key = self::FLAG_TABLE_PURGED . "blob_" . $date->toString('Y_m'); - $timestamp = Option::get($key); - - // we shall purge temporary archives after their timeout is finished, plus an extra 6 hours - // in case archiving is disabled or run once a day, we give it this extra time to run - // and re-process more recent records... - $temporaryArchivingTimeout = self::getTodayArchiveTimeToLive(); - $hoursBetweenPurge = 6; - $purgeEveryNSeconds = max($temporaryArchivingTimeout, $hoursBetweenPurge * 3600); - // we only delete archives if we are able to process them, otherwise, the browser might process reports // when &segment= is specified (or custom date range) and would below, delete temporary archives that the // browser is not able to process until next cron run (which could be more than 1 hour away) - if ($timestamp !== false && $timestamp >= time() - $purgeEveryNSeconds) { - Log::info("Purging temporary archives: skipped (purging every " . $hoursBetweenPurge . "hours)"); + if (! self::isRequestAuthorizedToArchive()){ + Log::info("Purging temporary archives: skipped (no authorization)"); return false; } - Option::set($key, time()); + $temporaryArchivingTimeout = self::getTodayArchiveTimeToLive(); if (self::isBrowserTriggerEnabled()) { // If Browser Archiving is enabled, it is likely there are many more temporary archives diff --git a/core/Common.php b/core/Common.php index 7e3bcbc7af..1ed8d48935 100644 --- a/core/Common.php +++ b/core/Common.php @@ -638,26 +638,6 @@ class Common } /** - * Convert IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). - * - * @see IP::N2P() - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @deprecated 1.4 - * - * @param string $ip IP address in network address format - * @return string - */ - public static function long2ip($ip) - { - return IP::long2ip($ip); - } - - /** * JSON encode wrapper * - missing or broken in some php 5.x versions * diff --git a/core/Config.php b/core/Config.php index 014424c119..e28f581ea8 100644 --- a/core/Config.php +++ b/core/Config.php @@ -455,10 +455,7 @@ class Config extends Singleton : $this->configLocal[$name]; } - if ($section === null && $name == 'superuser') { - $user = $this->getConfigSuperUserForBackwardCompatibility(); - return $user; - } else if ($section === null) { + if ($section === null) { $section = array(); } @@ -469,28 +466,6 @@ class Config extends Singleton return $tmp; } - /** - * @deprecated since version 2.0.4 - */ - public function getConfigSuperUserForBackwardCompatibility() - { - try { - $db = Db::get(); - $user = $db->fetchRow("SELECT login, email, password - FROM " . Common::prefixTable("user") . " - WHERE superuser_access = 1 - ORDER BY date_registered ASC LIMIT 1"); - - if (!empty($user)) { - $user['bridge'] = 1; - return $user; - } - - } catch (Exception $e) {} - - return array(); - } - public function getFromGlobalConfig($name) { if (isset($this->configGlobal[$name])) { diff --git a/core/Console.php b/core/Console.php index de51756f39..3eb7a5104b 100644 --- a/core/Console.php +++ b/core/Console.php @@ -163,6 +163,7 @@ class Console extends Application public static function initPlugins() { Plugin\Manager::getInstance()->loadActivatedPlugins(); + Plugin\Manager::getInstance()->loadPluginTranslations(); } private function getDefaultPiwikCommands() diff --git a/core/CronArchive.php b/core/CronArchive.php index 5c1b497649..29c897c8bf 100644 --- a/core/CronArchive.php +++ b/core/CronArchive.php @@ -825,11 +825,7 @@ class CronArchive public function log($m) { $this->output .= $m . "\n"; - try { - Log::info($m); - } catch(Exception $e) { - print($m . "\n"); - } + Log::info($m); } public function logError($m) diff --git a/core/DataFiles/SearchEngines.php b/core/DataFiles/SearchEngines.php index 9a40f96748..084c00e5e1 100644 --- a/core/DataFiles/SearchEngines.php +++ b/core/DataFiles/SearchEngines.php @@ -189,6 +189,8 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'searchqu.com' => array('Ask'), 'search.tb.ask.com' => array('Ask'), 'nortonsafe.search.ask.com' => array('Ask'), + 'safesearch.avira.com' => array('Ask'), + 'avira.search.ask.com' => array('Ask'), // Atlas 'searchatlas.centrum.cz' => array('Atlas', 'q', '?q={k}'), @@ -283,6 +285,7 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { // DasOertliche 'www.dasoertliche.de' => array('DasOertliche', 'kw'), + 'www2.dasoertliche.de' => array('DasOertliche', array('ph', 'kw')), // DasTelefonbuch 'www1.dastelefonbuch.de' => array('DasTelefonbuch', 'kw'), @@ -453,6 +456,7 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'suche.gmx.net' => array('Google', 'q', 'web?q={k}'), 'search.incredibar.com' => array('Google', 'q', 'search.php?q={k}'), 'www.delta-search.com' => array('Google', 'q'), + 'www1.delta-search.com' => array('Google', 'q'), 'search.1und1.de' => array('Google', 'q', 'web?q={k}'), 'search.zonealarm.com' => array('Google'), 'start.lenovo.com' => array('Google', 'q', 'search/index.php?q={k}'), @@ -464,11 +468,8 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'search.smt.docomo.ne.jp' => array('Google', 'MT'), 'image.search.smt.docomo.ne.jp' => array('Google', 'MT'), 'gfsoso.com' => array('Google', 'q'), - - // Google Earth - // - 2010-09-13: are these redirects now? - 'www.googleearth.de' => array('Google'), - 'www.googleearth.fr' => array('Google'), + 'searches.safehomepage.com' => array('Google', 'q'), + 'searches.f-secure.com' => array('Google', 'query', 'search?query={k}'), // Google Cache 'webcache.googleusercontent.com' => array('Google', '/\/search\?q=cache:[A-Za-z0-9]+:[^+]+([^&]+)/', 'search?q={k}'), @@ -619,6 +620,8 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'eu.ixquick.com' => array('Ixquick'), 's8-eu.ixquick.com' => array('Ixquick'), 's1-eu.ixquick.de' => array('Ixquick'), + 's2-eu4.ixquick.com' => array('Ixquick'), + 's5-eu4.ixquick.com' => array('Ixquick'), // Jyxo 'jyxo.1188.cz' => array('Jyxo', 'q', 's?q={k}'), @@ -680,6 +683,7 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { // Metager 'meta.rrzn.uni-hannover.de' => array('Metager', 'eingabe', 'meta/cgi-bin/meta.ger1?eingabe={k}'), 'www.metager.de' => array('Metager'), + 'metager.de' => array('Metager'), // Metager2 'metager2.de' => array('Metager2', 'q', 'search/index.php?q={k}'), @@ -756,6 +760,9 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { // PeoplePC 'search.peoplepc.com' => array('PeoplePC', 'q', 'search?q={k}'), + // PeopleCheck + 'extern.peoplecheck.de' => array('PeopleCheck', 'q', 'link.php?q={k}'), + // Picsearch 'www.picsearch.com' => array('Picsearch', 'q', 'index.cgi?q={k}'), @@ -777,6 +784,9 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'www.qualigo.de' => array('Qualigo'), 'www.qualigo.nl' => array('Qualigo'), + // Qwant + 'www.qwant.com' => array('Qwant', 'q'), + // Rakuten 'websearch.rakuten.co.jp' => array('Rakuten', 'qt', 'WebIS?qt={k}'), @@ -837,6 +847,9 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { 'm.sm.cn' => array('sm.cn', 'q', 's?q={k}'), 'm.sp.sm.cn' => array('sm.cn'), + // sm.de + 'www.sm.de' => array('sm.de', 'q', '?q={k}'), + // SmartAdressbar 'search.smartaddressbar.com' => array('SmartAddressbar', 's', '?s={k}'), @@ -1033,7 +1046,7 @@ if (!isset($GLOBALS['Piwik_SearchEngines'])) { // '{}.dir.yahoo.com' => array('Yahoo! Directory'), // Yahoo! Images - 'images.search.yahoo.com' => array('Yahoo! Images', 'p', 'search/images?p={k}'), + 'images.search.yahoo.com' => array('Yahoo! Images', array('p', 'va'), 'search/images?p={k}'), // '*.images.search.yahoo.com'=> array('Yahoo! Images'), // see built-in helper in Common.php '{}.images.yahoo.com' => array('Yahoo! Images'), 'cade.images.yahoo.com' => array('Yahoo! Images'), diff --git a/core/DataTable.php b/core/DataTable.php index b060a87245..0b0d9845f0 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -342,6 +342,17 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + * does not update the summary row! + */ + public function setRows($rows) + { + unset($this->rows); + $this->rows = $rows; + $this->indexNotUpToDate = true; + } + + /** * Sorts the DataTable rows using the supplied callback function. * * @param string $functionCallback A comparison callback compatible with {@link usort}. @@ -350,11 +361,11 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess */ public function sort($functionCallback, $columnSortedBy) { - $this->indexNotUpToDate = true; - $this->tableSortedBy = $columnSortedBy; + $this->setTableSortedBy($columnSortedBy); + usort($this->rows, $functionCallback); - if ($this->enableRecursiveSort === true) { + if ($this->isSortRecursiveEnabled()) { foreach ($this->getRows() as $row) { $subTable = $row->getSubtable(); @@ -388,6 +399,23 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function isSortRecursiveEnabled() + { + return $this->enableRecursiveSort === true; + } + + /** + * @ignore + */ + public function setTableSortedBy($column) + { + $this->indexNotUpToDate = true; + $this->tableSortedBy = $column; + } + + /** * Enables recursive filtering. If this method is called then the {@link filter()} method * will apply filters to every subtable in addition to this instance. */ @@ -397,6 +425,14 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function disableRecursiveFilters() + { + $this->enableRecursiveFilters = false; + } + + /** * Applies a filter to this datatable. * * If {@link enableRecursiveFilters()} was called, the filter will be applied @@ -434,6 +470,25 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * Applies a filter to all subtables but not to this datatable. + * + * @param string|Closure $className Class name, eg. `"Sort"` or "Piwik\DataTable\Filters\Sort"`. If no + * namespace is supplied, `Piwik\DataTable\BaseFilter` is assumed. This parameter + * can also be a closure that takes a DataTable as its first parameter. + * @param array $parameters Array of extra parameters to pass to the filter. + */ + public function filterSubtables($className, $parameters = array()) + { + foreach ($this->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $subtable->filter($className, $parameters); + $subtable->filterSubtables($className, $parameters); + } + } + } + + /** * Adds a filter and a list of parameters to the list of queued filters. These filters will be * executed when {@link applyQueuedFilters()} is called. * @@ -730,6 +785,14 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess } /** + * @ignore + */ + public function getRowsWithoutSummaryRow() + { + return $this->rows; + } + + /** * Returns an array containing all column values for the requested column. * * @param string $name The column name. diff --git a/core/DataTable/Filter/ColumnCallbackAddColumn.php b/core/DataTable/Filter/ColumnCallbackAddColumn.php index 27746c4f28..1d81f189be 100755 --- a/core/DataTable/Filter/ColumnCallbackAddColumn.php +++ b/core/DataTable/Filter/ColumnCallbackAddColumn.php @@ -10,6 +10,7 @@ namespace Piwik\DataTable\Filter; use Piwik\DataTable; use Piwik\DataTable\BaseFilter; +use Piwik\Plugins\CoreHome\Columns\Metrics\CallableProcessedMetric; /** * Adds a new column to every row of a {@link DataTable} based on the result of callback. @@ -83,20 +84,29 @@ class ColumnCallbackAddColumn extends BaseFilter $functionParams = $this->functionParameters; $functionToApply = $this->functionToApply; - foreach ($table->getRows() as $row) { + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); + + if (empty($extraProcessedMetrics)) { + $extraProcessedMetrics = array(); + } - $row->setColumn($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) { + $metric = new CallableProcessedMetric($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) { - $columnValues = array(); - foreach ($columns as $column) { - $columnValues[] = $row->getColumn($column); - } + $columnValues = array(); + foreach ($columns as $column) { + $columnValues[] = $row->getColumn($column); + } - $parameters = array_merge($columnValues, $functionParams); + $parameters = array_merge($columnValues, $functionParams); - return call_user_func_array($functionToApply, $parameters); - }); + return call_user_func_array($functionToApply, $parameters); + }, $columns); + $extraProcessedMetrics[] = $metric; + $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics); + + foreach ($table->getRows() as $row) { + $row->setColumn($this->columnToAdd, $metric->compute($row)); $this->filterSubTable($row); } } diff --git a/core/DataTable/Filter/PivotByDimension.php b/core/DataTable/Filter/PivotByDimension.php index 61e68423e8..3eeff39b44 100644 --- a/core/DataTable/Filter/PivotByDimension.php +++ b/core/DataTable/Filter/PivotByDimension.php @@ -286,13 +286,12 @@ class PivotByDimension extends BaseFilter return null; } - if ($row->isSubtableLoaded()) { - $subtable = $row->getSubtable(); - } else { + $subtable = $row->getSubtable(); + if (!$subtable) { $subtable = $this->thisReport->fetchSubtable($idSubtable, $this->getRequestParamOverride($table)); } - if ($subtable === null) { // sanity check + if (!$subtable) { // sanity check throw new Exception("Unexpected error: could not load subtable '$idSubtable'."); } diff --git a/core/DataTable/Filter/Sort.php b/core/DataTable/Filter/Sort.php index 42441c8e30..632da35dc8 100644 --- a/core/DataTable/Filter/Sort.php +++ b/core/DataTable/Filter/Sort.php @@ -21,7 +21,8 @@ use Piwik\Metrics; * * @api */ -class Sort extends BaseFilter +class +Sort extends BaseFilter { protected $columnToSort; protected $order; @@ -71,34 +72,21 @@ class Sort extends BaseFilter * @param Row $b * @return int */ - public function numberSort($a, $b) + public function numberSort($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + if (isset($rowA[0]) && isset($rowB[0])) { + if ($rowA[0] != $rowB[0] || !isset($rowA[1])) { + return $this->sign * ($rowA[0] < $rowB[0] ? -1 : 1); + } else { + return -1 * $this->sign * strnatcasecmp($rowA[1], $rowB[1]); + } + } elseif (!isset($rowB[0])) { + return -1; + } elseif (!isset($rowA[0])) { + return 1; + } - return !isset($valA) - && !isset($valB) - ? 0 - : ( - !isset($valA) - ? 1 - : ( - !isset($valB) - ? -1 - : (($valA != $valB - || !isset($a->c[Row::COLUMNS]['label'])) - ? ($this->sign * ( - $valA - < $valB - ? -1 - : 1) - ) - : -1 * $this->sign * strnatcasecmp( - $a->c[Row::COLUMNS]['label'], - $b->c[Row::COLUMNS]['label']) - ) - ) - ); + return 0; } /** @@ -108,10 +96,10 @@ class Sort extends BaseFilter * @param mixed $b * @return int */ - function naturalSort($a, $b) + function naturalSort($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + $valA = $rowA[0]; + $valB = $rowB[0]; return !isset($valA) && !isset($valB) @@ -135,10 +123,10 @@ class Sort extends BaseFilter * @param mixed $b * @return int */ - function sortString($a, $b) + function sortString($rowA, $rowB) { - $valA = $this->getColumnValue($a); - $valB = $this->getColumnValue($b); + $valA = $rowA[0]; + $valB = $rowB[0]; return !isset($valA) && !isset($valB) @@ -243,6 +231,51 @@ class Sort extends BaseFilter } } - $table->sort(array($this, $methodToUse), $this->columnToSort); + $this->sort($table, $methodToUse); } + + /** + * Sorts the DataTable rows using the supplied callback function. + * + * @param string $functionCallback A comparison callback compatible with {@link usort}. + * @param string $columnSortedBy The column name `$functionCallback` sorts by. This is stored + * so we can determine how the DataTable was sorted in the future. + */ + private function sort(DataTable $table, $functionCallback) + { + $table->setTableSortedBy($this->columnToSort); + + $rows = $table->getRowsWithoutSummaryRow(); + + // get column value and label only once for performance tweak + $values = array(); + foreach ($rows as $key => $row) { + $values[$key] = array($this->getColumnValue($row), $row->getColumn('label')); + } + + uasort($values, array($this, $functionCallback)); + + $sortedRows = array(); + foreach ($values as $key => $value) { + $sortedRows[$key] = $rows[$key]; + } + + $table->setRows(array_values($sortedRows)); + + unset($rows); + unset($sortedRows); + + if ($table->isSortRecursiveEnabled()) { + foreach ($table->getRows() as $row) { + + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->enableRecursiveSort(); + $this->sort($subTable, $functionCallback); + } + } + } + + } + } diff --git a/core/DataTable/Filter/Truncate.php b/core/DataTable/Filter/Truncate.php index 632eb2755e..a8fa0bd08f 100644 --- a/core/DataTable/Filter/Truncate.php +++ b/core/DataTable/Filter/Truncate.php @@ -96,7 +96,7 @@ class Truncate extends BaseFilter return; } - $rows = $table->getRows(); + $rows = array_values($table->getRows()); $count = $table->getRowsCount(); $newRow = new Row(array(Row::COLUMNS => array('label' => DataTable::LABEL_SUMMARY_ROW))); diff --git a/core/DataTable/Map.php b/core/DataTable/Map.php index 8795eac134..8787b06404 100644 --- a/core/DataTable/Map.php +++ b/core/DataTable/Map.php @@ -110,6 +110,19 @@ class Map implements DataTableInterface } /** + * Apply a filter to all subtables contained by this instance. + * + * @param string|Closure $className Name of filter class or a Closure. + * @param array $parameters Parameters to pass to the filter. + */ + public function filterSubtables($className, $parameters = array()) + { + foreach ($this->getDataTables() as $table) { + $table->filterSubtables($className, $parameters); + } + } + + /** * Returns the array of DataTables contained by this class. * * @return DataTable[]|Map[] @@ -185,6 +198,26 @@ class Map implements DataTableInterface } /** + * @ignore + */ + public function disableRecursiveFilters() + { + foreach ($this->getDataTables() as $table) { + $table->disableRecursiveFilters(); + } + } + + /** + * @ignore + */ + public function enableRecursiveFilters() + { + foreach ($this->getDataTables() as $table) { + $table->enableRecursiveFilters(); + } + } + + /** * Renames the given column in each contained {@link DataTable}. * * See {@link DataTable::renameColumn()}. diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php index 5f21f3fdec..71dde68031 100644 --- a/core/DataTable/Row.php +++ b/core/DataTable/Row.php @@ -209,37 +209,9 @@ class Row implements \ArrayAccess, \IteratorAggregate return false; } - if ($this->isColumnValueCallable($this->c[self::COLUMNS][$name])) { - $value = $this->resolveCallableColumn($name); - - if (!isset($value)) { - return false; - } - - return $value; - } - return $this->c[self::COLUMNS][$name]; } - private function isColumnValueCallable($name) - { - if (! is_callable($name)) { - return false; - } - - if (is_object($name) && ($name instanceof \Closure)) { - return true; - } - - return is_array($name) && isset($name[0]) && is_object($name[0]); - } - - private function resolveCallableColumn($columnName) - { - return call_user_func($this->c[self::COLUMNS][$columnName], $this); - } - /** * Returns the array of all metadata, or one requested metadata value. * @@ -287,16 +259,7 @@ class Row implements \ArrayAccess, \IteratorAggregate */ public function getColumns() { - $values = array(); - foreach ($this->c[self::COLUMNS] as $columnName => $val) { - if ($this->isColumnValueCallable($val)) { - $values[$columnName] = $this->resolveCallableColumn($columnName); - } else { - $values[$columnName] = $val; - } - } - - return $values; + return $this->c[self::COLUMNS]; } /** @@ -517,11 +480,6 @@ class Row implements \ArrayAccess, \IteratorAggregate continue; } - if ($this->isColumnValueCallable($columnToSumValue)) { - $this->setColumn($columnToSumName, $columnToSumValue); - continue; - } - $thisColumnValue = $this->getColumn($columnToSumName); $operation = 'sum'; diff --git a/core/FrontController.php b/core/FrontController.php index 8136b520bf..242ba03df6 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -14,10 +14,9 @@ use Piwik\API\Request; use Piwik\API\ResponseBuilder; use Piwik\Container\StaticContainer; use Piwik\Exception\AuthenticationFailedException; +use Piwik\Http\ControllerResolver; use Piwik\Http\Router; -use Piwik\Plugin\Controller; use Piwik\Plugin\Report; -use Piwik\Plugin\Widgets; use Piwik\Session; /** @@ -110,83 +109,6 @@ class FrontController extends Singleton } } - protected function makeController($module, $action, &$parameters) - { - $container = StaticContainer::getContainer(); - - $controllerClassName = $this->getClassNameController($module); - - // TRY TO FIND ACTION IN CONTROLLER - if (class_exists($controllerClassName)) { - - $class = $this->getClassNameController($module); - /** @var $controller Controller */ - $controller = $container->make($class); - - $controllerAction = $action; - if ($controllerAction === false) { - $controllerAction = $controller->getDefaultAction(); - } - - if (is_callable(array($controller, $controllerAction))) { - - return array($controller, $controllerAction); - } - - if ($action === false) { - $this->triggerControllerActionNotFoundError($module, $controllerAction); - } - - } - - // TRY TO FIND ACTION IN WIDGET - $widget = Widgets::factory($module, $action); - - if (!empty($widget)) { - - $parameters['widgetModule'] = $module; - $parameters['widgetMethod'] = $action; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderWidget'); - } - - // TRY TO FIND ACTION IN REPORT - $report = Report::factory($module, $action); - - if (!empty($report)) { - - $parameters['reportModule'] = $module; - $parameters['reportAction'] = $action; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderReportWidget'); - } - - if (!empty($action) && Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report - ::PREFIX_ACTION_IN_MENU))) { - $reportAction = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls - $report = Report::factory($module, $reportAction); - - if (!empty($report)) { - $parameters['reportModule'] = $module; - $parameters['reportAction'] = $reportAction; - - return array($container->make('Piwik\Plugins\CoreHome\Controller'), 'renderReportMenu'); - } - } - - $this->triggerControllerActionNotFoundError($module, $action); - } - - protected function triggerControllerActionNotFoundError($module, $action) - { - throw new Exception("Action '$action' not found in the module '$module'."); - } - - protected function getClassNameController($module) - { - return "\\Piwik\\Plugins\\$module\\Controller"; - } - /** * Executes the requested plugin controller method and returns the data, capturing anything the * method `echo`s. @@ -195,7 +117,7 @@ class FrontController extends Singleton * of whatever is in the output buffer._ * * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`. - * @param string $action The controller action name, eg, `'realtimeMap'`. + * @param string $actionName The controller action name, eg, `'realtimeMap'`. * @param array $parameters Array of parameters to pass to the controller action method. * @return string The `echo`'d data or the return value of the controller action. * @deprecated @@ -587,7 +509,10 @@ class FrontController extends Singleton */ Piwik::postEvent('Request.dispatch', array(&$module, &$action, &$parameters)); - list($controller, $actionToCall) = $this->makeController($module, $action, $parameters); + /** @var ControllerResolver $controllerResolver */ + $controllerResolver = StaticContainer::get('Piwik\Http\ControllerResolver'); + + $controller = $controllerResolver->getController($module, $action, $parameters); /** * Triggered directly before controller actions are dispatched. @@ -602,7 +527,7 @@ class FrontController extends Singleton */ Piwik::postEvent(sprintf('Controller.%s.%s', $module, $action), array(&$parameters)); - $result = call_user_func_array(array($controller, $actionToCall), $parameters); + $result = call_user_func_array($controller, $parameters); /** * Triggered after a controller action is successfully called. @@ -628,6 +553,7 @@ class FrontController extends Singleton * @param array $parameters The arguments passed to the controller action. */ Piwik::postEvent('Request.dispatch.end', array(&$result, $module, $action, $parameters)); + return $result; } diff --git a/core/Http.php b/core/Http.php index 25d6d59566..08bcbcef15 100644 --- a/core/Http.php +++ b/core/Http.php @@ -179,7 +179,7 @@ class Http throw new Exception('Malformed URL: ' . $aUrl); } - if ($url['scheme'] != 'http') { + if ($url['scheme'] != 'http' && $url['scheme'] != 'https') { throw new Exception('Invalid protocol/scheme: ' . $url['scheme']); } $host = $url['host']; @@ -405,7 +405,11 @@ class Http } fclose($handle); } else { - $response = file_get_contents($aUrl, 0, $ctx); + $response = @file_get_contents($aUrl, 0, $ctx); + if ($response === false) { + $error = error_get_last(); + throw new \Exception($error['message']); + } $fileLength = strlen($response); } diff --git a/core/Http/ControllerResolver.php b/core/Http/ControllerResolver.php new file mode 100644 index 0000000000..8d58f0ed73 --- /dev/null +++ b/core/Http/ControllerResolver.php @@ -0,0 +1,142 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Http; + +use DI\FactoryInterface; +use Exception; +use Piwik\Plugin\Controller; +use Piwik\Plugin\Report; +use Piwik\Plugin\Widgets; +use Piwik\Session; + +/** + * Resolves the controller that will handle the request. + * + * A controller is a PHP callable. + */ +class ControllerResolver +{ + /** + * @var FactoryInterface + */ + private $abstractFactory; + + public function __construct(FactoryInterface $abstractFactory) + { + $this->abstractFactory = $abstractFactory; + } + + /** + * @param string $module + * @param string|null $action + * @param array $parameters + * @throws Exception Controller not found. + * @return callable The controller is a PHP callable. + */ + public function getController($module, $action, array &$parameters) + { + $controller = $this->createPluginController($module, $action); + if ($controller) { + return $controller; + } + + $controller = $this->createWidgetController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportMenuController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + throw new Exception(sprintf("Action '%s' not found in the module '%s'", $action, $module)); + } + + private function createPluginController($module, $action) + { + $controllerClass = "Piwik\\Plugins\\$module\\Controller"; + if (!class_exists($controllerClass)) { + return null; + } + + /** @var $controller Controller */ + $controller = $this->abstractFactory->make($controllerClass); + + $action = $action ?: $controller->getDefaultAction(); + + if (!is_callable(array($controller, $action))) { + return null; + } + + return array($controller, $action); + } + + private function createWidgetController($module, $action, array &$parameters) + { + $widget = Widgets::factory($module, $action); + + if (!$widget) { + return null; + } + + $parameters['widget'] = $widget; + $parameters['method'] = $action; + + return array($this->createCoreHomeController(), 'renderWidget'); + } + + private function createReportController($module, $action, array &$parameters) + { + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportWidget'); + } + + private function createReportMenuController($module, $action, array &$parameters) + { + if (!$this->isReportMenuAction($action)) { + return null; + } + + $action = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportMenu'); + } + + private function isReportMenuAction($action) + { + $startsWithMenu = (Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report::PREFIX_ACTION_IN_MENU))); + + return !empty($action) && $startsWithMenu; + } + + private function createCoreHomeController() + { + return $this->abstractFactory->make('Piwik\Plugins\CoreHome\Controller'); + } +} diff --git a/core/IP.php b/core/IP.php index e952ddbe27..b6ec2c2d22 100644 --- a/core/IP.php +++ b/core/IP.php @@ -10,8 +10,6 @@ namespace Piwik; use Piwik\Network\IPUtils; -use Piwik\Network\IPv4; -use Piwik\Network\IPv6; /** * Contains IP address helper functions (for both IPv4 and IPv6). @@ -40,223 +38,6 @@ use Piwik\Network\IPv6; class IP { /** - * Removes the port and the last portion of a CIDR IP address. - * - * @param string $ipString The IP address to sanitize. - * @return string - * - * @deprecated Use IPUtils::sanitizeIp() instead - * @see \Piwik\Network\IPUtils - */ - public static function sanitizeIp($ipString) - { - return IPUtils::sanitizeIp($ipString); - } - - /** - * Sanitize human-readable (user-supplied) IP address range. - * - * Accepts the following formats for $ipRange: - * - single IPv4 address, e.g., 127.0.0.1 - * - single IPv6 address, e.g., ::1/128 - * - IPv4 block using CIDR notation, e.g., 192.168.0.0/22 represents the IPv4 addresses from 192.168.0.0 to 192.168.3.255 - * - IPv6 block using CIDR notation, e.g., 2001:DB8::/48 represents the IPv6 addresses from 2001:DB8:0:0:0:0:0:0 to 2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF - * - wildcards, e.g., 192.168.0.* - * - * @param string $ipRangeString IP address range - * @return string|bool IP address range in CIDR notation OR false - * - * @deprecated Use IPUtils::sanitizeIpRange() instead - * @see \Piwik\Network\IPUtils - */ - public static function sanitizeIpRange($ipRangeString) - { - $result = IPUtils::sanitizeIpRange($ipRangeString); - - return $result === null ? false : $result; - } - - /** - * Converts an IP address in presentation format to network address format. - * - * @param string $ipString IP address, either IPv4 or IPv6, e.g., `"127.0.0.1"`. - * @return string Binary-safe string, e.g., `"\x7F\x00\x00\x01"`. - * - * @deprecated Use IPUtils::stringToBinaryIP() instead - * @see \Piwik\Network\IPUtils - */ - public static function P2N($ipString) - { - return IPUtils::stringToBinaryIP($ipString); - } - - /** - * Convert network address format to presentation format. - * - * See also {@link prettyPrint()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - * - * @deprecated Use IPUtils::binaryToStringIP() instead - */ - public static function N2P($ip) - { - return IPUtils::binaryToStringIP($ip); - } - - /** - * Alias for {@link N2P()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - * - * @deprecated Will be removed - */ - public static function prettyPrint($ip) - { - return IPUtils::binaryToStringIP($ip); - } - - /** - * Returns true if `$ip` is an IPv4, IPv4-compat, or IPv4-mapped address, false - * if otherwise. - * - * @param string $ip IP address in network address format. - * @return bool True if IPv4, else false. - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isIPv4($ip) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip instanceof IPv4; - } - - /** - * Converts an IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @param string $ip IPv4 address in network address format. - * @return string IP address in presentation format. - * - * @deprecated This method was kept for backward compatibility and doesn't seem used - */ - public static function long2ip($ip) - { - // IPv4 - if (strlen($ip) == 4) { - return IPUtils::binaryToStringIP($ip); - } - - // IPv6 - transitional address? - if (strlen($ip) == 16) { - if (substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 0, 12) === 0 - || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0 - ) { - // remap 128-bit IPv4-mapped and IPv4-compat addresses - return IPUtils::binaryToStringIP(substr($ip, 12)); - } - } - - return '0.0.0.0'; - } - - /** - * Returns true if $ip is an IPv6 address, false if otherwise. This function does - * a naive check. It assumes that whatever format $ip is in, it is well-formed. - * - * @param string $ip - * @return bool - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isIPv6($ip) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip instanceof IPv6; - } - - /** - * Returns true if $ip is a IPv4 mapped address, false if otherwise. - * - * @param string $ip - * @return bool - * - * @deprecated Will be removed - * @see \Piwik\Network\IP - */ - public static function isMappedIPv4($ip) - { - $ip = Network\IP::fromStringIP($ip); - - if (! $ip instanceof IPv6) { - return false; - } - - return $ip->isMappedIPv4(); - } - - /** - * Returns an IPv4 address from a 'mapped' IPv6 address. - * - * @param string $ip eg, `'::ffff:192.0.2.128'` - * @return string eg, `'192.0.2.128'` - * - * @deprecated Use Piwik\Network\IP::toIPv4String() instead - * @see \Piwik\Network\IP - */ - public static function getIPv4FromMappedIPv6($ip) - { - $ip = Network\IP::fromStringIP($ip); - - return $ip->toIPv4String(); - } - - /** - * Get low and high IP addresses for a specified range. - * - * @param array $ipRange An IP address range in presentation format. - * @return array|bool Array `array($lowIp, $highIp)` in network address format, or false on failure. - * - * @deprecated Use Piwik\Network\IPUtils::getIPRangeBounds() instead - * @see \Piwik\Network\IPUtils - */ - public static function getIpsForRange($ipRange) - { - $result = IPUtils::getIPRangeBounds($ipRange); - - return $result === null ? false : $result; - } - - /** - * Determines if an IP address is in a specified IP address range. - * - * An IPv4-mapped address should be range checked with an IPv4-mapped address range. - * - * @param string $ip IP address in network address format - * @param array $ipRanges List of IP address ranges - * @return bool True if in any of the specified IP address ranges; else false. - * - * @deprecated Use Piwik\Network\IP::isInRanges() instead - * @see \Piwik\Network\IP - */ - public static function isIpInRange($ip, $ipRanges) - { - $ip = Network\IP::fromBinaryIP($ip); - - return $ip->isInRanges($ipRanges); - } - - /** * Returns the most accurate IP address availble for the current user, in * IPv4 format. This could be the proxy client's IP address. * @@ -333,22 +114,4 @@ class IP } return trim(Common::sanitizeInputValue($csv)); } - - /** - * Returns the hostname for a given IP address. - * - * @param string $ipStr Human-readable IP address. - * @return string The hostname or unmodified $ipStr on failure. - * - * @deprecated Use Piwik\Network\IP::getHostname() instead - * @see \Piwik\Network\IP - */ - public static function getHostByAddr($ipStr) - { - $ip = Network\IP::fromStringIP($ipStr); - - $host = $ip->getHostname(); - - return $host === null ? $ipStr : $host; - } } diff --git a/core/Menu/MenuAdmin.php b/core/Menu/MenuAdmin.php index 5335091285..9c05734e5f 100644 --- a/core/Menu/MenuAdmin.php +++ b/core/Menu/MenuAdmin.php @@ -33,24 +33,6 @@ use Piwik\Piwik; class MenuAdmin extends MenuAbstract { /** - * Adds a new AdminMenu entry under the 'Settings' category. - * - * @param string $adminMenuName The name of the admin menu entry. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function addEntry($adminMenuName, $url, $displayedForCurrentUser = true, $order = 20) - { - if ($displayedForCurrentUser) { - self::getInstance()->addItem('General_Settings', $adminMenuName, $url, $order); - } - } - - /** * See {@link add()}. Adds a new menu item to the development section of the admin menu. * @param string $menuName * @param array $url @@ -142,12 +124,4 @@ class MenuAdmin extends MenuAbstract return parent::getMenu(); } - - /** - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function removeEntry($menuName, $subMenuName = false) - { - MenuAdmin::getInstance()->remove($menuName, $subMenuName); - } } diff --git a/core/Menu/MenuTop.php b/core/Menu/MenuTop.php index 55c09c57af..87ccb3c5f8 100644 --- a/core/Menu/MenuTop.php +++ b/core/Menu/MenuTop.php @@ -32,37 +32,6 @@ use Piwik\Piwik; class MenuTop extends MenuAbstract { /** - * Adds a new entry to the TopMenu. - * - * @param string $topMenuName The menu item name. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. If `$isHTML` is true, this can be a string with - * HTML that is simply embedded. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. - * @param bool $isHTML Whether `$url` is an HTML string or a URL that will be rendered as a link. - * @param bool|string $tooltip Optional tooltip to display. - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function addEntry($topMenuName, $url, $displayedForCurrentUser = true, $order = 10, $isHTML = false, $tooltip = false) - { - if ($isHTML) { - MenuTop::getInstance()->addHtml($topMenuName, $url, $displayedForCurrentUser, $order, $tooltip); - } else { - MenuTop::getInstance()->add($topMenuName, null, $url, $displayedForCurrentUser, $order, $tooltip); - } - } - - /** - * @deprecated since version 2.4.0. See {@link Piwik\Plugin\Menu} for new implementation. - */ - public static function removeEntry($menuName, $subMenuName = false) - { - MenuTop::getInstance()->remove($menuName, $subMenuName); - } - - /** * Directly adds a menu entry containing html. * * @param string $menuName diff --git a/core/Period.php b/core/Period.php index d4044ed68e..b924e981c8 100644 --- a/core/Period.php +++ b/core/Period.php @@ -9,7 +9,6 @@ namespace Piwik; use Piwik\Container\StaticContainer; -use Piwik\Period\Factory as PeriodFactory; use Piwik\Period\Range; use Piwik\Translation\Translator; @@ -66,17 +65,6 @@ abstract class Period } /** - * @deprecated Use Factory::build instead - * @param $period - * @param $date - * @return Period - */ - public static function factory($period, $date) - { - return PeriodFactory::build($period, $date); - } - - /** * Returns true if `$dateString` and `$period` represent multiple periods. * * Will return true for date/period combinations where date references multiple diff --git a/core/Plugin/API.php b/core/Plugin/API.php index cc16cfb64c..4e5095a3aa 100644 --- a/core/Plugin/API.php +++ b/core/Plugin/API.php @@ -41,5 +41,5 @@ use Piwik\Singleton; */ abstract class API extends Singleton { - + } diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index 7b85c49a5a..22eb0296d1 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -21,6 +21,7 @@ use Piwik\Log; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugin; +use Piwik\PluginDeactivatedException; use Piwik\Singleton; use Piwik\Theme; use Piwik\Tracker; @@ -266,6 +267,19 @@ class Manager extends Singleton } /** + * Checks whether the given plugin is activated, if not triggers an exception. + * + * @param string $pluginName + * @throws PluginDeactivatedException + */ + public function checkIsPluginActivated($pluginName) + { + if (!$this->isPluginActivated($pluginName)) { + throw new PluginDeactivatedException($pluginName); + } + } + + /** * Returns `true` if plugin is loaded (in memory). * * @param string $name Name of plugin, eg, `'Acions'`. diff --git a/core/Plugin/Metric.php b/core/Plugin/Metric.php index e475eb837f..4122dfeedf 100644 --- a/core/Plugin/Metric.php +++ b/core/Plugin/Metric.php @@ -108,29 +108,44 @@ abstract class Metric */ public static function getMetric($row, $columnName, $mappingNameToId = null) { - if (empty($mappingNameToId)) { - $mappingNameToId = Metrics::getMappingFromNameToId(); - } - if ($row instanceof Row) { $value = $row->getColumn($columnName); - if ($value === false - && isset($mappingNameToId[$columnName]) - ) { - $value = $row->getColumn($mappingNameToId[$columnName]); - } - } else { - $value = @$row[$columnName]; - if ($value === null - && isset($mappingNameToId[$columnName]) - ) { - $columnName = $mappingNameToId[$columnName]; - $value = @$row[$columnName]; + + if ($value === false) { + + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + return $row->getColumn($mappingNameToId[$columnName]); + } } + return $value; + + } elseif (!empty($row)) { + + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + + } else { + + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + $columnName = $mappingNameToId[$columnName]; + + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + } + } + } } - return $value; + return null; } /** diff --git a/core/Plugin/Report.php b/core/Plugin/Report.php index 62ed594f8c..b9c407e10e 100644 --- a/core/Plugin/Report.php +++ b/core/Plugin/Report.php @@ -182,6 +182,13 @@ class Report protected $order = 1; /** + * Separator for building recursive labels (or paths) + * @var string + * @api + */ + protected $recursiveLabelSeparator = ' - '; + + /** * @var array * @ignore */ @@ -357,6 +364,15 @@ class Report } /** + * @ignore + * @see $recursiveLabelSeparator + */ + public function getRecursiveLabelSeparator() + { + return $this->recursiveLabelSeparator; + } + + /** * Returns an array of supported metrics and their corresponding translations. Eg `array('nb_visits' => 'Visits')`. * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically. * If a metric is not translated, you should add the default metric translation for this metric using diff --git a/core/Plugin/ViewDataTable.php b/core/Plugin/ViewDataTable.php index 4f3b854127..a49358e48b 100644 --- a/core/Plugin/ViewDataTable.php +++ b/core/Plugin/ViewDataTable.php @@ -316,7 +316,7 @@ abstract class ViewDataTable implements ViewInterface return new VizRequest(); } - protected function loadDataTableFromAPI($fixedRequestParams = array()) + protected function loadDataTableFromAPI() { if (!is_null($this->dataTable)) { // data table is already there @@ -324,7 +324,7 @@ abstract class ViewDataTable implements ViewInterface return $this->dataTable; } - $this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams); + $this->dataTable = $this->request->loadDataTableFromAPI(); return $this->dataTable; } diff --git a/core/Plugin/Visualization.php b/core/Plugin/Visualization.php index 15b468e362..3d48c24d15 100644 --- a/core/Plugin/Visualization.php +++ b/core/Plugin/Visualization.php @@ -10,6 +10,8 @@ namespace Piwik\Plugin; use Piwik\API\DataTablePostProcessor; +use Piwik\API\Proxy; +use Piwik\API\ResponseBuilder; use Piwik\Common; use Piwik\DataTable; use Piwik\Date; @@ -23,6 +25,8 @@ use Piwik\Plugins\API\API as ApiApi; use Piwik\Plugins\PrivacyManager\PrivacyManager; use Piwik\View; use Piwik\ViewDataTable\Manager as ViewDataTableManager; +use Piwik\Plugin\Manager as PluginManager; +use Piwik\API\Request as ApiRequest; /** * The base class for report visualizations that output HTML and use JavaScript. @@ -173,8 +177,7 @@ class Visualization extends ViewDataTable try { $this->beforeLoadDataTable(); - - $this->loadDataTableFromAPI(array('disable_generic_filters' => 1, 'format_metrics' => 0)); + $this->loadDataTableFromAPI(); $this->postDataTableLoadedFromAPI(); $requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties(); @@ -233,6 +236,35 @@ class Visualization extends ViewDataTable return $view; } + /** + * @internal + */ + protected function loadDataTableFromAPI() + { + if (!is_null($this->dataTable)) { + // data table is already there + // this happens when setDataTable has been used + return $this->dataTable; + } + + // we build the request (URL) to call the API + $request = $this->buildApiRequestArray(); + + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); + + PluginManager::getInstance()->checkIsPluginActivated($module); + + $class = ApiRequest::getClassNameAPI($module); + $dataTable = Proxy::getInstance()->call($class, $method, $request); + + $response = new ResponseBuilder($format = 'original', $request); + $response->disableSendHeader(); + $response->disableDataTablePostProcessor(); + + $this->dataTable = $response->getResponse($dataTable, $module, $method); + } + private function getReportMetadata() { $request = $this->request->getRequestArray() + $_GET + $_POST; @@ -255,11 +287,16 @@ class Visualization extends ViewDataTable $this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this); } - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals')) { + if (!$this->isPluginActivated('Goals')) { $this->config->show_goals = false; } } + private function isPluginActivated($pluginName) + { + return PluginManager::getInstance()->isPluginActivated($pluginName); + } + /** * Assigns a template variable making it available in the Twig template specified by * {@link TEMPLATE_FILE}. @@ -357,48 +394,39 @@ class Visualization extends ViewDataTable private function applyFilters() { - list($priorityFilters, $otherFilters) = $this->config->getFiltersToRun(); + $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final + $self = $this; - // First, filters that delete rows - foreach ($priorityFilters as $filter) { - $this->dataTable->filter($filter[0], $filter[1]); - } + $postProcessor->setCallbackBeforeGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self, $postProcessor) { - $this->beforeGenericFiltersAreAppliedToLoadedDataTable(); + // First, filters that delete rows + foreach ($self->config->getPriorityFilters() as $filter) { + $dataTable->filter($filter[0], $filter[1]); + } - if (!in_array($this->requestConfig->filter_sort_column, $this->config->columns_to_display)) { - $hasNbUniqVisitors = in_array('nb_uniq_visitors', $this->config->columns_to_display); - $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors, $this->dataTable->getColumns()); - } + $self->beforeGenericFiltersAreAppliedToLoadedDataTable(); - $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final + if (!in_array($self->requestConfig->filter_sort_column, $self->config->columns_to_display)) { + $hasNbUniqVisitors = in_array('nb_uniq_visitors', $self->config->columns_to_display); + $columns = $dataTable->getColumns(); + $self->requestConfig->setDefaultSort($self->config->columns_to_display, $hasNbUniqVisitors, $columns); + } - if (!$this->requestConfig->areGenericFiltersDisabled()) { - $this->dataTable = $postProcessor->applyGenericFilters($this->dataTable); - } + $postProcessor->setRequest($self->buildApiRequestArray()); + }); - $postProcessor->applyComputeProcessedMetrics($this->dataTable); + $postProcessor->setCallbackAfterGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self) { - $this->afterGenericFiltersAreAppliedToLoadedDataTable(); + $self->afterGenericFiltersAreAppliedToLoadedDataTable(); - // queue other filters so they can be applied later if queued filters are disabled - foreach ($otherFilters as $filter) { - $this->dataTable->queueFilter($filter[0], $filter[1]); - } + // queue other filters so they can be applied later if queued filters are disabled + foreach ($self->config->getPresentationFilters() as $filter) { + $dataTable->queueFilter($filter[0], $filter[1]); + } - // Finally, apply datatable filters that were queued (should be 'presentation' filters that - // do not affect the number of rows) - if (!$this->requestConfig->areQueuedFiltersDisabled()) { - $this->dataTable->applyQueuedFilters(); - } + }); - if ($this->requestConfig->shouldFormatMetrics()) { - $formatter = $this->metricsFormatter; - $report = $this->report; - $this->dataTable->filter(function (DataTable $table) use ($formatter, $report) { - $formatter->formatMetrics($table, $report); - }); - } + $this->dataTable = $postProcessor->process($this->dataTable); } private function removeEmptyColumnsFromDisplay() @@ -456,7 +484,7 @@ class Visualization extends ViewDataTable */ private function hasReportBeenPurged() { - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('PrivacyManager')) { + if (!$this->isPluginActivated('PrivacyManager')) { return false; } @@ -629,15 +657,14 @@ class Visualization extends ViewDataTable private function makeDataTablePostProcessor() { - $requestArray = $this->request->getRequestArray(); - $request = \Piwik\API\Request::getRequestArrayFromString($requestArray); + $request = $this->buildApiRequestArray(); + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); - if (false === $this->config->enable_sort) { - $request['filter_sort_column'] = ''; - $request['filter_sort_order'] = ''; - } + $processor = new DataTablePostProcessor($module, $method, $request); + $processor->setFormatter($this->metricsFormatter); - return new DataTablePostProcessor($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest(), $request); + return $processor; } private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore) @@ -685,4 +712,30 @@ class Visualization extends ViewDataTable return $result; } + + /** + * @internal + * + * @return array + */ + public function buildApiRequestArray() + { + $requestArray = $this->request->getRequestArray(); + $request = APIRequest::getRequestArrayFromString($requestArray); + + if (false === $this->config->enable_sort) { + $request['filter_sort_column'] = ''; + $request['filter_sort_order'] = ''; + } + + if (!array_key_exists('format_metrics', $request) || $request['format_metrics'] === 'bc') { + $request['format_metrics'] = '1'; + } + + if (!$this->requestConfig->disable_queued_filters && array_key_exists('disable_queued_filters', $request)) { + unset($request['disable_queued_filters']); + } + + return $request; + } } diff --git a/core/Plugin/Widgets.php b/core/Plugin/Widgets.php index 3199e7a80b..36081fe2d2 100644 --- a/core/Plugin/Widgets.php +++ b/core/Plugin/Widgets.php @@ -125,6 +125,7 @@ class Widgets /** * @ignore + * @return Widgets|null */ public static function factory($module, $action) { diff --git a/core/Session.php b/core/Session.php index 9e49987072..9733593205 100644 --- a/core/Session.php +++ b/core/Session.php @@ -124,8 +124,7 @@ class Session extends Zend_Session we recommend that you <a href='http://piwik.org/faq/how-to-install/#faq_133' rel='noreferrer' target='_blank'>enable database session storage</a>."; } - $pathToSessions = Filechecks::getErrorMessageMissingPermissions(Filesystem::getPathToPiwikRoot() . '/tmp/sessions/'); - $pathToSessions = SettingsPiwik::rewriteTmpPathWithInstanceId($pathToSessions); + $pathToSessions = Filechecks::getErrorMessageMissingPermissions(self::getSessionsDirectory()); $message = sprintf("Error: %s %s %s\n<pre>Debug: the original error was \n%s</pre>", Piwik::translate('General_ExceptionUnableToStartSession'), $pathToSessions, @@ -147,8 +146,7 @@ class Session extends Zend_Session */ public static function getSessionsDirectory() { - $path = PIWIK_USER_PATH . '/tmp/sessions'; - return SettingsPiwik::rewriteTmpPathWithInstanceId($path); + return StaticContainer::get('path.tmp') . '/sessions'; } public static function close() diff --git a/core/SettingsPiwik.php b/core/SettingsPiwik.php index 6a30ddfecb..7a731b9b98 100644 --- a/core/SettingsPiwik.php +++ b/core/SettingsPiwik.php @@ -267,21 +267,6 @@ class SettingsPiwik } /** - * If Piwik uses per-domain config file, also make tmp/ folder per-domain - * @param $path - * @return string - * @throws \Exception - * - * @deprecated Get the 'path.tmp' config from the container instead. - */ - public static function rewriteTmpPathWithInstanceId($path) - { - $tmp = '/tmp/'; - $path = self::rewritePathAppendPiwikInstanceId($path, $tmp); - return $path; - } - - /** * If Piwik uses per-domain config file, make sure CustomLogo is unique * @param $path * @return mixed diff --git a/core/Tracker/VisitExcluded.php b/core/Tracker/VisitExcluded.php index f3d8d0cab2..ae380cac42 100644 --- a/core/Tracker/VisitExcluded.php +++ b/core/Tracker/VisitExcluded.php @@ -11,7 +11,7 @@ namespace Piwik\Tracker; use Piwik\Common; use Piwik\Config; use Piwik\DeviceDetectorFactory; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Piwik; /** @@ -158,9 +158,10 @@ class VisitExcluded $deviceDetector = DeviceDetectorFactory::getInstance($this->userAgent); + $ip = IP::fromBinaryIP($this->ip); + return !$allowBots - && ($deviceDetector->isBot() - || IP::isIpInRange($this->ip, $this->getBotIpRanges())); + && ($deviceDetector->isBot() || $ip->isInRanges($this->getBotIpRanges())); } protected function getBotIpRanges() @@ -223,7 +224,7 @@ class VisitExcluded $websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite); if (!empty($websiteAttributes['excluded_ips'])) { - $ip = \Piwik\Network\IP::fromBinaryIP($this->ip); + $ip = IP::fromBinaryIP($this->ip); if ($ip->isInRanges($websiteAttributes['excluded_ips'])) { Common::printDebug('Visitor IP ' . $ip->toString() . ' is excluded from being tracked'); return true; diff --git a/core/UrlHelper.php b/core/UrlHelper.php index a0bc340bbd..78682acbfc 100644 --- a/core/UrlHelper.php +++ b/core/UrlHelper.php @@ -231,7 +231,10 @@ class UrlHelper $parsedUrl = parse_url($url); $result = ''; if (isset($parsedUrl['path'])) { - $result .= substr($parsedUrl['path'], 1); + if (substr($parsedUrl['path'], 0, 1) == '/') { + $parsedUrl['path'] = substr($parsedUrl['path'], 1); + } + $result .= $parsedUrl['path']; } if (isset($parsedUrl['query'])) { $result .= '?' . $parsedUrl['query']; diff --git a/core/Version.php b/core/Version.php index 08d4d4d809..0d5a9e14b9 100644 --- a/core/Version.php +++ b/core/Version.php @@ -20,7 +20,7 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '2.11.2-b2'; + const VERSION = '2.12.0-b1'; public function isStableVersion($version) { diff --git a/core/View.php b/core/View.php index 27fe7569a5..423aaeb278 100644 --- a/core/View.php +++ b/core/View.php @@ -221,6 +221,7 @@ class View implements ViewInterface $this->url = Common::sanitizeInputValue(Url::getCurrentUrl()); $this->token_auth = Piwik::getCurrentUserTokenAuth(); $this->userHasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess(); + $this->userIsAnonymous = Piwik::isUserIsAnonymous(); $this->userIsSuperUser = Piwik::hasUserSuperUserAccess(); $this->latest_version_available = UpdateCheck::isNewestVersionAvailable(); $this->disableLink = Common::getRequestVar('disableLink', 0, 'int'); @@ -368,7 +369,13 @@ class View implements ViewInterface $twig = new Twig(); $environment = $twig->getTwigEnvironment(); $environment->clearTemplateCache(); - $environment->clearCacheFiles(); + + $cacheDirectory = $environment->getCache(); + if (!empty($cacheDirectory) + && is_dir($cacheDirectory) + ) { + $environment->clearCacheFiles(); + } } /** diff --git a/core/ViewDataTable/Config.php b/core/ViewDataTable/Config.php index 220a253518..f4eef3c639 100644 --- a/core/ViewDataTable/Config.php +++ b/core/ViewDataTable/Config.php @@ -557,7 +557,7 @@ class Config /** * @ignore */ - public function getFiltersToRun() + private function getFiltersToRun() { $priorityFilters = array(); $presentationFilters = array(); @@ -581,6 +581,20 @@ class Config return array($priorityFilters, $presentationFilters); } + public function getPriorityFilters() + { + $filters = $this->getFiltersToRun(); + + return $filters[0]; + } + + public function getPresentationFilters() + { + $filters = $this->getFiltersToRun(); + + return $filters[1]; + } + /** * Adds a related report to the {@link $related_reports} property. If the report * references the one that is currently being displayed, it will not be added to the related diff --git a/core/ViewDataTable/Request.php b/core/ViewDataTable/Request.php index 352ce25899..259d4caaa1 100644 --- a/core/ViewDataTable/Request.php +++ b/core/ViewDataTable/Request.php @@ -32,15 +32,11 @@ class Request * It builds the API request string and uses Request to call the API. * The requested DataTable object is stored in $this->dataTable. */ - public function loadDataTableFromAPI($fixedRequestParams = array()) + public function loadDataTableFromAPI() { // we build the request (URL) to call the API $requestArray = $this->getRequestArray(); - foreach ($fixedRequestParams as $key => $value) { - $requestArray[$key] = $value; - } - // we make the request to the API $request = new ApiRequest($requestArray); @@ -104,6 +100,14 @@ class Request unset($requestArray['filter_limit']); } + if ($this->requestConfig->disable_generic_filters) { + $requestArray['disable_generic_filters'] = '0'; + } + + if ($this->requestConfig->disable_queued_filters) { + $requestArray['disable_queued_filters'] = 0; + } + return $requestArray; } diff --git a/core/ViewDataTable/RequestConfig.php b/core/ViewDataTable/RequestConfig.php index 753ee55b98..a6c9b7bed8 100644 --- a/core/ViewDataTable/RequestConfig.php +++ b/core/ViewDataTable/RequestConfig.php @@ -314,35 +314,6 @@ class RequestConfig $this->filter_sort_order = 'desc'; } - /** - * Returns `true` if queued filters have been disabled, `false` if otherwise. - * - * @return bool - */ - public function areQueuedFiltersDisabled() - { - return isset($this->disable_queued_filters) && $this->disable_queued_filters; - } - - /** - * Returns `true` if generic filters have been disabled, `false` if otherwise. - * - * @return bool - */ - public function areGenericFiltersDisabled() - { - // if disable_generic_filters query param is set to '1', generic filters are disabled - if (Common::getRequestVar('disable_generic_filters', '0', 'string') == 1) { - return true; - } - - if (isset($this->disable_generic_filters) && true === $this->disable_generic_filters) { - return true; - } - - return false; - } - public function getApiModuleToRequest() { list($module, $method) = explode('.', $this->apiMethodToRequestDataTable); @@ -356,9 +327,4 @@ class RequestConfig return $method; } - - public function shouldFormatMetrics() - { - return Common::getRequestVar('format_metrics', '1', 'string', $this->request_parameters_to_modify) != 0; - } } diff --git a/js/piwik.js b/js/piwik.js index 2d93b9365f..27413028ff 100644 --- a/js/piwik.js +++ b/js/piwik.js @@ -410,7 +410,7 @@ if (typeof JSON2 !== 'object') { exec, res, width, height, devicePixelRatio, pdf, qt, realp, wma, dir, fla, java, gears, ag, - hook, getHook, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, setTrackerUrl, appendToTrackingUrl, getRequest, addPlugin, + hook, getHook, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, getSiteId, setTrackerUrl, getTrackerUrl, appendToTrackingUrl, getRequest, addPlugin, getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword, getAttributionReferrerTimestamp, getAttributionReferrerUrl, setCustomData, getCustomData, @@ -426,7 +426,7 @@ if (typeof JSON2 !== 'object') { setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout, setConversionAttributionFirstReferrer, disablePerformanceTracking, setGenerationTimeMs, - doNotTrack, setDoNotTrack, msDoNotTrack, + doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie, addListener, enableLinkTracking, enableJSErrorTracking, setLinkTrackingTimer, setHeartBeatTimer, killFrame, redirectFile, setCountPreRendered, trackGoal, trackLink, trackPageView, trackSiteSearch, trackEvent, @@ -457,7 +457,9 @@ if (typeof JSON2 !== 'object') { trackVisibleContentImpressions, isTrackOnlyVisibleContentEnabled, port, isUrlToCurrentDomain, isNodeAuthorizedToTriggerInteraction, replaceHrefIfInternalLink, getConfigDownloadExtensions, disableLinkTracking, substr, setAnyAttribute, wasContentTargetAttrReplaced, max, abs, childNodes, compareDocumentPosition, body, - getConfigVisitorCookieTimeout, getRemainingVisitorCookieTimeout + getConfigVisitorCookieTimeout, getRemainingVisitorCookieTimeout, + newVisitor, uuid, createTs, visitCount, currentVisitTs, lastVisitTs, lastEcommerceOrderTs + */ /*global _paq:true */ /*members push */ @@ -2631,7 +2633,8 @@ if (typeof Piwik !== 'object') { nowTs = Math.round(now.getTime() / 1000), visitorIdCookieName = getCookieName('id'), id = getCookie(visitorIdCookieName), - cookieValue; + cookieValue, + uuid; if (id) { cookieValue = id.split('.'); @@ -2647,8 +2650,10 @@ if (typeof Piwik !== 'object') { // uuid - generate a pseudo-unique ID to fingerprint this user; // note: this isn't a RFC4122-compliant UUID - if (!visitorUUID.length) { - visitorUUID = hash( + if (visitorUUID.length) { + uuid = visitorUUID; + } else { + uuid = hash( (navigatorAlias.userAgent || '') + (navigatorAlias.platform || '') + JSON2.stringify(browserFeatures) + @@ -2662,7 +2667,7 @@ if (typeof Piwik !== 'object') { '1', // uuid - visitorUUID, + uuid, // creation timestamp - seconds since Unix epoch nowTs, @@ -2683,24 +2688,72 @@ if (typeof Piwik !== 'object') { return cookieValue; } + + /** + * Loads the Visitor ID cookie and returns a named array of values + */ + function getValuesFromVisitorIdCookie() { + var cookieVisitorIdValue = loadVisitorIdCookie(), + newVisitor = cookieVisitorIdValue[0], + uuid = cookieVisitorIdValue[1], + createTs = cookieVisitorIdValue[2], + visitCount = cookieVisitorIdValue[3], + currentVisitTs = cookieVisitorIdValue[4], + lastVisitTs = cookieVisitorIdValue[5]; + + // case migrating from pre-1.5 cookies + if (!isDefined(cookieVisitorIdValue[6])) { + cookieVisitorIdValue[6] = ""; + } + + var lastEcommerceOrderTs = cookieVisitorIdValue[6]; + + return { + newVisitor: newVisitor, + uuid: uuid, + createTs: createTs, + visitCount: visitCount, + currentVisitTs: currentVisitTs, + lastVisitTs: lastVisitTs, + lastEcommerceOrderTs: lastEcommerceOrderTs + }; + } + + function getRemainingVisitorCookieTimeout() { var now = new Date(), nowTs = now.getTime(), - visitorInfo = loadVisitorIdCookie(); + cookieCreatedTs = getValuesFromVisitorIdCookie().createTs; - var createTs = parseInt(visitorInfo[2], 10); + var createTs = parseInt(cookieCreatedTs, 10); var originalTimeout = (createTs * 1000) + configVisitorCookieTimeout - nowTs; return originalTimeout; } /* - * Sets the Visitor ID cookie: either the first time loadVisitorIdCookie is called - * or when there is a new visit or a new page view + * Sets the Visitor ID cookie */ - function setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, lastEcommerceOrderTs) { - var timeout = getRemainingVisitorCookieTimeout(); + function setVisitorIdCookie(visitorIdCookieValues) { + if(!configTrackerSiteId) { + // when called before Site ID was set + return; + } + + var now = new Date(), + nowTs = Math.round(now.getTime() / 1000); + + if(!isDefined(visitorIdCookieValues)) { + visitorIdCookieValues = getValuesFromVisitorIdCookie(); + } + + var cookieValue = visitorIdCookieValues.uuid + '.' + + visitorIdCookieValues.createTs + '.' + + visitorIdCookieValues.visitCount + '.' + + nowTs + '.' + + visitorIdCookieValues.lastVisitTs + '.' + + visitorIdCookieValues.lastEcommerceOrderTs; - setCookie(getCookieName('id'), uuid + '.' + createTs + '.' + visitCount + '.' + nowTs + '.' + lastVisitTs + '.' + lastEcommerceOrderTs, timeout, configCookiePath, configCookieDomain); + setCookie(getCookieName('id'), cookieValue, getRemainingVisitorCookieTimeout(), configCookiePath, configCookieDomain); } /* @@ -2750,6 +2803,11 @@ if (typeof Piwik !== 'object') { configCookiesDisabled = savedConfigCookiesDisabled; } + function setSiteId(siteId) { + configTrackerSiteId = siteId; + setVisitorIdCookie(); + } + function sortObjectByKeys(value) { if (!value || !isObject(value)) { return; @@ -2778,6 +2836,13 @@ if (typeof Piwik !== 'object') { } /** + * Creates the session cookie + */ + function setSessionCookie() { + setCookie(getCookieName('ses'), '*', configSessionCookieTimeout, configCookiePath, configCookieDomain); + } + + /** * Returns the URL to call piwik.php, * with the standard parameters (plugins, resolution, url, referrer, etc.). * Sends the pageview and browser settings with every request in case of race conditions. @@ -2786,24 +2851,16 @@ if (typeof Piwik !== 'object') { var i, now = new Date(), nowTs = Math.round(now.getTime() / 1000), - newVisitor, - uuid, - visitCount, - createTs, - currentVisitTs, - lastVisitTs, - lastEcommerceOrderTs, referralTs, referralUrl, referralUrlMaxLength = 1024, currentReferrerHostName, originalReferrerHostName, customVariablesCopy = customVariables, - sesname = getCookieName('ses'), - refname = getCookieName('ref'), - cvarname = getCookieName('cvar'), - id = loadVisitorIdCookie(), - ses = getCookie(sesname), + cookieSessionName = getCookieName('ses'), + cookieReferrerName = getCookieName('ref'), + cookieCustomVariablesName = getCookieName('cvar'), + cookieSessionValue = getCookie(cookieSessionName), attributionCookie = loadReferrerAttributionCookie(), currentUrl = configCustomUrl || locationHrefAlias, campaignNameDetected, @@ -2817,19 +2874,7 @@ if (typeof Piwik !== 'object') { return ''; } - newVisitor = id[0]; - uuid = id[1]; - createTs = id[2]; - visitCount = id[3]; - currentVisitTs = id[4]; - lastVisitTs = id[5]; - // case migrating from pre-1.5 cookies - if (!isDefined(id[6])) { - id[6] = ""; - } - - lastEcommerceOrderTs = id[6]; - + var cookieVisitorIdValues = getValuesFromVisitorIdCookie(); if (!isDefined(currentEcommerceOrderTs)) { currentEcommerceOrderTs = ""; } @@ -2848,17 +2893,17 @@ if (typeof Piwik !== 'object') { referralTs = attributionCookie[2]; referralUrl = attributionCookie[3]; - if (!ses) { + if (!cookieSessionValue) { // cookie 'ses' was not found: we consider this the start of a 'session' // here we make sure that if 'ses' cookie is deleted few times within the visit // and so this code path is triggered many times for one visit, // we only increase visitCount once per Visit window (default 30min) var visitDuration = configSessionCookieTimeout / 1000; - if (!lastVisitTs - || (nowTs - lastVisitTs) > visitDuration) { - visitCount++; - lastVisitTs = currentVisitTs; + if (!cookieVisitorIdValues.lastVisitTs + || (nowTs - cookieVisitorIdValues.lastVisitTs) > visitDuration) { + cookieVisitorIdValues.visitCount++; + cookieVisitorIdValues.lastVisitTs = cookieVisitorIdValues.currentVisitTs; } @@ -2867,7 +2912,7 @@ if (typeof Piwik !== 'object') { // Or if it was set but we must attribute to the most recent one // Note: we are working on the currentUrl before purify() since we can parse the campaign parameters in the hash tag if (!configConversionAttributionFirstReferrer - || !campaignNameDetected.length) { + || !campaignNameDetected.length) { for (i in configCampaignNameParameters) { if (Object.prototype.hasOwnProperty.call(configCampaignNameParameters, i)) { campaignNameDetected = getParameter(currentUrl, configCampaignNameParameters[i]); @@ -2895,16 +2940,16 @@ if (typeof Piwik !== 'object') { originalReferrerHostName = referralUrl.length ? getHostName(referralUrl) : ''; if (currentReferrerHostName.length && // there is a referrer - !isSiteHostName(currentReferrerHostName) && // domain is not the current domain - (!configConversionAttributionFirstReferrer || // attribute to last known referrer - !originalReferrerHostName.length || // previously empty - isSiteHostName(originalReferrerHostName))) { // previously set but in current domain + !isSiteHostName(currentReferrerHostName) && // domain is not the current domain + (!configConversionAttributionFirstReferrer || // attribute to last known referrer + !originalReferrerHostName.length || // previously empty + isSiteHostName(originalReferrerHostName))) { // previously set but in current domain referralUrl = configReferrerUrl; } // Set the referral cookie if we have either a Referrer URL, or detected a Campaign (or both) if (referralUrl.length - || campaignNameDetected.length) { + || campaignNameDetected.length) { referralTs = nowTs; attributionCookie = [ campaignNameDetected, @@ -2913,28 +2958,28 @@ if (typeof Piwik !== 'object') { purify(referralUrl.slice(0, referralUrlMaxLength)) ]; - setCookie(refname, JSON2.stringify(attributionCookie), configReferralCookieTimeout, configCookiePath, configCookieDomain); + setCookie(cookieReferrerName, JSON2.stringify(attributionCookie), configReferralCookieTimeout, configCookiePath, configCookieDomain); } } // build out the rest of the request request += '&idsite=' + configTrackerSiteId + - '&rec=1' + - '&r=' + String(Math.random()).slice(2, 8) + // keep the string to a minimum - '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() + - '&url=' + encodeWrapper(purify(currentUrl)) + - (configReferrerUrl.length ? '&urlref=' + encodeWrapper(purify(configReferrerUrl)) : '') + - ((configUserId && configUserId.length) ? '&uid=' + encodeWrapper(configUserId) : '') + - '&_id=' + uuid + '&_idts=' + createTs + '&_idvc=' + visitCount + - '&_idn=' + newVisitor + // currently unused - (campaignNameDetected.length ? '&_rcn=' + encodeWrapper(campaignNameDetected) : '') + - (campaignKeywordDetected.length ? '&_rck=' + encodeWrapper(campaignKeywordDetected) : '') + - '&_refts=' + referralTs + - '&_viewts=' + lastVisitTs + - (String(lastEcommerceOrderTs).length ? '&_ects=' + lastEcommerceOrderTs : '') + - (String(referralUrl).length ? '&_ref=' + encodeWrapper(purify(referralUrl.slice(0, referralUrlMaxLength))) : '') + - (charSet ? '&cs=' + encodeWrapper(charSet) : '') + - '&send_image=0'; + '&rec=1' + + '&r=' + String(Math.random()).slice(2, 8) + // keep the string to a minimum + '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() + + '&url=' + encodeWrapper(purify(currentUrl)) + + (configReferrerUrl.length ? '&urlref=' + encodeWrapper(purify(configReferrerUrl)) : '') + + ((configUserId && configUserId.length) ? '&uid=' + encodeWrapper(configUserId) : '') + + '&_id=' + cookieVisitorIdValues.uuid + '&_idts=' + cookieVisitorIdValues.createTs + '&_idvc=' + cookieVisitorIdValues.visitCount + + '&_idn=' + cookieVisitorIdValues.newVisitor + // currently unused + (campaignNameDetected.length ? '&_rcn=' + encodeWrapper(campaignNameDetected) : '') + + (campaignKeywordDetected.length ? '&_rck=' + encodeWrapper(campaignKeywordDetected) : '') + + '&_refts=' + referralTs + + '&_viewts=' + cookieVisitorIdValues.lastVisitTs + + (String(cookieVisitorIdValues.lastEcommerceOrderTs).length ? '&_ects=' + cookieVisitorIdValues.lastEcommerceOrderTs : '') + + (String(referralUrl).length ? '&_ref=' + encodeWrapper(purify(referralUrl.slice(0, referralUrlMaxLength))) : '') + + (charSet ? '&cs=' + encodeWrapper(charSet) : '') + + '&send_image=0'; // browser features for (i in browserFeatures) { @@ -2959,7 +3004,7 @@ if (typeof Piwik !== 'object') { return ''; } - var sortedCustomVarPage = sortObjectByKeys(customVariablesPage); + var sortedCustomVarPage = sortObjectByKeys(customVariablesPage); var sortedCustomVarEvent = sortObjectByKeys(customVariablesEvent); request += appendCustomVariablesToRequest(sortedCustomVarPage, 'cvar'); @@ -2979,7 +3024,7 @@ if (typeof Piwik !== 'object') { } if (configStoreCustomVariablesInCookie) { - setCookie(cvarname, JSON2.stringify(customVariables), configSessionCookieTimeout, configCookiePath, configCookieDomain); + setCookie(cookieCustomVariablesName, JSON2.stringify(customVariables), configSessionCookieTimeout, configCookiePath, configCookieDomain); } } @@ -2988,14 +3033,15 @@ if (typeof Piwik !== 'object') { if (configPerformanceGenerationTime) { request += '>_ms=' + configPerformanceGenerationTime; } else if (performanceAlias && performanceAlias.timing - && performanceAlias.timing.requestStart && performanceAlias.timing.responseEnd) { + && performanceAlias.timing.requestStart && performanceAlias.timing.responseEnd) { request += '>_ms=' + (performanceAlias.timing.responseEnd - performanceAlias.timing.requestStart); } } // update cookies - setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, isDefined(currentEcommerceOrderTs) && String(currentEcommerceOrderTs).length ? currentEcommerceOrderTs : lastEcommerceOrderTs); - setCookie(sesname, '*', configSessionCookieTimeout, configCookiePath, configCookieDomain); + cookieVisitorIdValues.lastEcommerceOrderTs = isDefined(currentEcommerceOrderTs) && String(currentEcommerceOrderTs).length ? currentEcommerceOrderTs : cookieVisitorIdValues.lastEcommerceOrderTs; + setVisitorIdCookie(cookieVisitorIdValues); + setSessionCookie(); // tracker plugin hook request += executePluginMethod(pluginMethod); @@ -3852,6 +3898,8 @@ if (typeof Piwik !== 'object') { } } } + + function enableTrackOnlyVisibleContent (checkOnSroll, timeIntervalInMs, tracker) { if (isTrackOnlyVisibleContentEnabled) { @@ -4012,6 +4060,7 @@ if (typeof Piwik !== 'object') { */ detectBrowserFeatures(); updateDomainHash(); + setVisitorIdCookie(); /*<DEBUG>*/ /* @@ -4068,9 +4117,6 @@ if (typeof Piwik !== 'object') { getTrackedContentImpressions: function () { return trackedContentImpressions; }, - getTrackerUrl: function () { - return configTrackerUrl; - }, clearEnableTrackOnlyVisibleContent: function () { isTrackOnlyVisibleContentEnabled = false; }, @@ -4090,7 +4136,7 @@ if (typeof Piwik !== 'object') { * @return string Visitor ID in hexits (or null, if not yet known) */ getVisitorId: function () { - return (loadVisitorIdCookie())[1]; + return getValuesFromVisitorIdCookie().uuid; }, /** @@ -4099,6 +4145,8 @@ if (typeof Piwik !== 'object') { * @return array */ getVisitorInfo: function () { + // Note: in a new method, we could return also return getValuesFromVisitorIdCookie() + // which returns named parameters rather than returning integer indexed array return loadVisitorIdCookie(); }, @@ -4163,13 +4211,32 @@ if (typeof Piwik !== 'object') { configTrackerUrl = trackerUrl; }, + + /** + * Returns the Piwik server URL + * @returns string + */ + getTrackerUrl: function () { + return configTrackerUrl; + }, + + + /** + * Returns the site ID + * + * @returns int + */ + getSiteId: function() { + return configTrackerSiteId; + }, + /** * Specify the site ID * * @param int|string siteId */ setSiteId: function (siteId) { - configTrackerSiteId = siteId; + setSiteId(siteId); }, /** @@ -5169,7 +5236,7 @@ if (typeof Piwik !== 'object') { if (applyFirst[methodName] > 1) { if (console !== undefined && console && console.error) { - console.error('The method ' + methodName + ' is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/api-reference/tracking-javascript#multiple-piwik-trackers'); + console.error('The method ' + methodName + ' is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers'); } } @@ -5210,6 +5277,12 @@ if (typeof Piwik !== 'object') { * @return Tracker */ getTracker: function (piwikUrl, siteId) { + if(!isDefined(siteId)) { + siteId = this.getAsyncTracker().getSiteId(); + } + if(!isDefined(piwikUrl)) { + piwikUrl = this.getAsyncTracker().getTrackerUrl(); + } return new Tracker(piwikUrl, siteId); }, diff --git a/misc/log-analytics/import_logs.py b/misc/log-analytics/import_logs.py index fda54ee34b..a65a0ffec4 100755 --- a/misc/log-analytics/import_logs.py +++ b/misc/log-analytics/import_logs.py @@ -592,11 +592,6 @@ class Configuration(object): help="Debug option that will force each recorder to record one hit every N secs." ) option_parser.add_option( - '--invalidate-dates', dest='invalidate_dates', default=None, - help="Invalidate reports for the specified dates (format: YYYY-MM-DD,YYYY-MM-DD,...). " - "By default, all dates found in the logs will be invalidated.", - ) - option_parser.add_option( '--force-lowercase-path', dest='force_lowercase_path', default=False, action='store_true', help="Make URL path lowercase so paths with the same letters but different cases are " "treated the same." diff --git a/misc/phpstorm-codestyles/Piwik_codestyle.xml b/misc/phpstorm-codestyles/Piwik_codestyle.xml index e04c442b53..e863de94cd 100644 --- a/misc/phpstorm-codestyles/Piwik_codestyle.xml +++ b/misc/phpstorm-codestyles/Piwik_codestyle.xml @@ -14,6 +14,11 @@ <option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" /> <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" /> </codeStyleSettings> + <codeStyleSettings language="LESS"> + <indentOptions> + <option name="INDENT_SIZE" value="4" /> + </indentOptions> + </codeStyleSettings> <codeStyleSettings language="PHP"> <option name="BLANK_LINES_AFTER_PACKAGE" value="1" /> <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" /> @@ -27,27 +27,28 @@ if(Q.hasNodeAttributeWithValue(W,"href")){X=Q.getAttributeValueFromNode(W,"href" }}}var ac=Q.findNodesByTagName(aa,"embed");if(ac&&ac.length){return this.findMediaUrlInNode(ac[0])}}},trim:function(W){if(W&&String(W)===W){return W.replace(/^\s+|\s+$/g,"")}return W},isOrWasNodeInViewport:function(ab){if(!ab||!ab.getBoundingClientRect||ab.nodeType!==1){return true}var aa=ab.getBoundingClientRect();var Z=u.documentElement||{};var Y=aa.top<0;if(Y&&ab.offsetTop){Y=(ab.offsetTop+aa.height)>0}var X=Z.clientWidth;if(G.innerWidth&&X>G.innerWidth){X=G.innerWidth}var W=Z.clientHeight;if(G.innerHeight&&W>G.innerHeight){W=G.innerHeight}return((aa.bottom>0||Y)&&aa.right>0&&aa.left<X&&((aa.top<W)||Y))},isNodeVisible:function(X){var W=g(X);var Y=this.isOrWasNodeInViewport(X);return W&&Y},buildInteractionRequestParams:function(W,X,Y,Z){var aa="";if(W){aa+="c_i="+l(W)}if(X){if(aa){aa+="&"}aa+="c_n="+l(X)}if(Y){if(aa){aa+="&"}aa+="c_p="+l(Y)}if(Z){if(aa){aa+="&"}aa+="c_t="+l(Z)}return aa},buildImpressionRequestParams:function(W,X,Y){var Z="c_n="+l(W)+"&c_p="+l(X);if(Y){Z+="&c_t="+l(Y)}return Z },buildContentBlock:function(Y){if(!Y){return}var W=this.findContentName(Y);var X=this.findContentPiece(Y);var Z=this.findContentTarget(Y);W=this.trim(W);X=this.trim(X);Z=this.trim(Z);return{name:W||"Unknown",piece:X||"Unknown",target:Z||""}},collectContent:function(Z){if(!Z||!Z.length){return[]}var Y=[];var W,X;for(W=0;W<Z.length;W++){X=this.buildContentBlock(Z[W]);if(w(X)){Y.push(X)}}return Y},setLocation:function(W){this.location=W},getLocation:function(){var W=this.location||G.location;if(!W.origin){W.origin=W.protocol+"//"+W.hostname+(W.port?":"+W.port:"")}return W},toAbsoluteUrl:function(X){if((!X||String(X)!==X)&&X!==""){return X}if(""===X){return this.getLocation().href}if(X.search(/^\/\//)!==-1){return this.getLocation().protocol+X}if(X.search(/:\/\//)!==-1){return X}if(0===X.indexOf("#")){return this.getLocation().origin+this.getLocation().pathname+X}if(0===X.indexOf("?")){return this.getLocation().origin+this.getLocation().pathname+X}if(0===X.search("^[a-zA-Z]{2,11}:")){return X }if(X.search(/^\//)!==-1){return this.getLocation().origin+X}var W="(.*/)";var Y=this.getLocation().origin+this.getLocation().pathname.match(new RegExp(W))[0];return Y+X},isUrlToCurrentDomain:function(X){var Y=this.toAbsoluteUrl(X);if(!Y){return false}var W=this.getLocation().origin;if(W===Y){return true}if(0===String(Y).indexOf(W)){if(":"===String(Y).substr(W.length,1)){return false}return true}return false},setHrefAttribute:function(X,W){if(!X||!W){return}Q.setAnyAttribute(X,"href",W)},shouldIgnoreInteraction:function(Y){var X=Q.hasNodeAttribute(Y,this.CONTENT_IGNOREINTERACTION_ATTR);var W=Q.hasNodeCssClass(Y,this.CONTENT_IGNOREINTERACTION_CLASS);return X||W}};function B(W,X){if(X){return X}if(W.slice(-9)==="piwik.php"){W=W.slice(0,W.length-9)}return W}function A(aa){var W="Piwik_Overlay";var ad=new RegExp("index\\.php\\?module=Overlay&action=startOverlaySession&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)$");var Y=ad.exec(u.referrer);if(Y){var Z=Y[1];if(Z!==String(aa)){return false}var ac=Y[2],X=Y[3]; -G.name=W+"###"+ac+"###"+X}var ab=G.name.split("###");return ab.length===3&&ab[0]===W}function M(X,ac,Z){var ab=G.name.split("###"),aa=ab[1],W=ab[2],Y=B(X,ac);i(Y+"plugins/Overlay/client/client.js?v=1",function(){Piwik_Overlay_Client.initialize(Y,Z,aa,W)})}function D(aE,bl){var ad=N(u.domain,G.location.href,x()),bK=y(ad[0]),b2=F(ad[1]),bt=F(ad[2]),b5=false,bp="GET",br=bp,bb="application/x-www-form-urlencoded; charset=UTF-8",aK=bb,aa=aE||"",ax="",bn="",bQ=bl||"",aJ="",a2="",a6,aS=u.title,aU="7z|aac|apk|ar[cj]|as[fx]|avi|azw3|bin|csv|deb|dmg|docx?|epub|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mobi|mp(2|3|4|e?g)|mov(ie)?|ms[ip]|od[bfgpst]|og[gv]|pdf|phps|png|pptx?|qtm?|ra[mr]?|rpm|sea|sit|tar|t?bz2?|tgz|torrent|txt|wav|wm[av]|wpd||xlsx?|xml|z|zip",bo=[bK],ai=[],be=[],aC=[],bm=500,aj,aG,ak,an,aY=["pk_campaign","piwik_campaign","utm_campaign","utm_source","utm_medium"],aP=["pk_kwd","piwik_kwd","utm_term"],b0="_pk_",aq,b1,ao=false,bU,a0,a4,aw=33955200000,az=1800000,a9=15768000000,a1=true,aI=0,a5=false,ag=false,au,bf={},ab={},bV=200,bD={},bR={},ah=[],ay=false,a8=false,by=false,bS=false,bv=false,bs,bi,at,aX=T,bx; -function bF(ce,cb,ca,cd,b9,cc){if(ao){return}var b8;if(ca){b8=new Date();b8.setTime(b8.getTime()+ca)}u.cookie=ce+"="+l(cb)+(ca?";expires="+b8.toGMTString():"")+";path="+(cd||"/")+(b9?";domain="+b9:"")+(cc?";secure":"")}function av(ca){if(ao){return 0}var b8=new RegExp("(^|;)[ ]*"+ca+"=([^;]*)"),b9=b8.exec(u.cookie);return b9?F(b9[2]):0}function bW(b8){var b9;if(ak){b9=new RegExp("#.*");return b8.replace(b9,"")}return b8}function bJ(ca,b8){var cb=k(b8),b9;if(cb){return b8}if(b8.slice(0,1)==="/"){return k(ca)+"://"+b(ca)+b8}ca=bW(ca);b9=ca.indexOf("?");if(b9>=0){ca=ca.slice(0,b9)}b9=ca.lastIndexOf("/");if(b9!==ca.length-1){ca=ca.slice(0,b9+1)}return ca+b8}function bq(cb){var b9,b8,ca;for(b9=0;b9<bo.length;b9++){b8=y(bo[b9].toLowerCase());if(cb===b8){return true}if(b8.slice(0,1)==="."){if(cb===b8.slice(1)){return true}ca=cb.length-b8.length;if((ca>0)&&(cb.slice(ca)===b8)){return true}}}return false}function b7(b8,ca){var b9=new Image(1,1);b9.onload=function(){t=0;if(typeof ca==="function"){ca() -}};b9.src=aa+(aa.indexOf("?")<0?"?":"&")+b8}function bG(b9,cc,b8){if(!w(b8)||null===b8){b8=true}try{var cb=G.XMLHttpRequest?new G.XMLHttpRequest():G.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):null;cb.open("POST",aa,true);cb.onreadystatechange=function(){if(this.readyState===4&&!(this.status>=200&&this.status<300)&&b8){b7(b9,cc)}else{if(typeof cc==="function"){cc()}}};cb.setRequestHeader("Content-Type",aK);cb.send(b9)}catch(ca){if(b8){b7(b9,cc)}}}function bX(b9){var b8=new Date();var ca=b8.getTime()+b9;if(!j||ca>j){j=ca}}function aD(cc){var b9=new Date();var b8=b9.getTime();if(a8&&b8<a8){var ca=a8-b8;setTimeout(cc,ca);bX(ca+50);a8+=50;return}if(a8===false){var cb=800;a8=b8+cb}cc()}function aZ(b9,b8,ca){if(!bU&&b9){aD(function(){if(br==="POST"){bG(b9,ca)}else{b7(b9,ca)}bX(b8)})}}function ba(b8){if(bU){return false}return(b8&&b8.length)}function ap(ca,b8){if(!ba(ca)){return}var b9='{"requests":["?'+ca.join('","?')+'"]}';aD(function(){bG(b9,null,false);bX(b8)})}function bE(b8){return b0+b8+"."+bQ+"."+bx -}function ae(){if(ao){return"0"}if(!w(e.cookieEnabled)){var b8=bE("testcookie");bF(b8,"1");return av(b8)==="1"?"1":"0"}return e.cookieEnabled?"1":"0"}function bj(){bx=aX((aq||bK)+(b1||"/")).slice(0,4)}function ar(){var b9=bE("cvar"),b8=av(b9);if(b8.length){b8=JSON2.parse(b8);if(J(b8)){return b8}}return{}}function Z(){if(ag===false){ag=ar()}}function bP(){var b8=new Date();bs=b8.getTime()}function Y(){var ca=new Date(),b8=Math.round(ca.getTime()/1000),b9=bE("id"),cc=av(b9),cb;if(cc){cb=cc.split(".");cb.unshift("0");if(a2.length){cb[1]=a2}return cb}if(!a2.length){a2=aX((e.userAgent||"")+(e.platform||"")+JSON2.stringify(bR)+ca.getTime()+Math.random()).slice(0,16)}cb=["1",a2,b8,0,b8,"",""];return cb}function aL(){var cc=new Date(),b9=cc.getTime(),cb=Y();var b8=parseInt(cb[2],10);var ca=(b8*1000)+aw-b9;return ca}function am(cc,b9,b8,cb,ca,ce){var cd=aL();bF(bE("id"),cc+"."+b9+"."+b8+"."+cb+"."+ca+"."+ce,cd,b1,aq)}function X(){var b8=av(bE("ref"));if(b8.length){try{b8=JSON2.parse(b8);if(J(b8)){return b8 -}}catch(b9){}}return["","",0,""]}function W(){var b8=ao;ao=false;bF(bE("id"),"",-86400,b1,aq);bF(bE("ses"),"",-86400,b1,aq);bF(bE("cvar"),"",-86400,b1,aq);bF(bE("ref"),"",-86400,b1,aq);ao=b8}function bO(cc){if(!cc||!J(cc)){return}var cb=[];var ca;for(ca in cc){if(Object.prototype.hasOwnProperty.call(cc,ca)){cb.push(ca)}}var cd={};cb.sort();var b8=cb.length;var b9;for(b9=0;b9<b8;b9++){cd[cb[b9]]=cc[cb[b9]]}return cd}function aT(ca,cy,cz,cb){var cw,b9=new Date(),ci=Math.round(b9.getTime()/1000),cC,cx,cd,co,ct,ch,cr,ce,cv,cc=1024,cE,cl,cs=ag,cj=bE("ses"),ck=bE("ref"),cF=bE("cvar"),cp=Y(),cn=av(cj),cu=X(),cB=a6||b2,cf,b8;if(ao){W()}if(bU){return""}cC=cp[0];cx=cp[1];co=cp[2];cd=cp[3];ct=cp[4];ch=cp[5];if(!w(cp[6])){cp[6]=""}cr=cp[6];if(!w(cb)){cb=""}var cm=u.characterSet||u.charset;if(!cm||cm.toLowerCase()==="utf-8"){cm=null}cf=cu[0];b8=cu[1];ce=cu[2];cv=cu[3];if(!cn){var cA=az/1000;if(!ch||(ci-ch)>cA){cd++;ch=ct}if(!a4||!cf.length){for(cw in aY){if(Object.prototype.hasOwnProperty.call(aY,cw)){cf=I(cB,aY[cw]); -if(cf.length){break}}}for(cw in aP){if(Object.prototype.hasOwnProperty.call(aP,cw)){b8=I(cB,aP[cw]);if(b8.length){break}}}}cE=b(bt);cl=cv.length?b(cv):"";if(cE.length&&!bq(cE)&&(!a4||!cl.length||bq(cl))){cv=bt}if(cv.length||cf.length){ce=ci;cu=[cf,b8,ce,bW(cv.slice(0,cc))];bF(ck,JSON2.stringify(cu),a9,b1,aq)}}ca+="&idsite="+bQ+"&rec=1&r="+String(Math.random()).slice(2,8)+"&h="+b9.getHours()+"&m="+b9.getMinutes()+"&s="+b9.getSeconds()+"&url="+l(bW(cB))+(bt.length?"&urlref="+l(bW(bt)):"")+((aJ&&aJ.length)?"&uid="+l(aJ):"")+"&_id="+cx+"&_idts="+co+"&_idvc="+cd+"&_idn="+cC+(cf.length?"&_rcn="+l(cf):"")+(b8.length?"&_rck="+l(b8):"")+"&_refts="+ce+"&_viewts="+ch+(String(cr).length?"&_ects="+cr:"")+(String(cv).length?"&_ref="+l(bW(cv.slice(0,cc))):"")+(cm?"&cs="+l(cm):"")+"&send_image=0";for(cw in bR){if(Object.prototype.hasOwnProperty.call(bR,cw)){ca+="&"+cw+"="+bR[cw]}}if(cy){ca+="&data="+l(JSON2.stringify(cy))}else{if(an){ca+="&data="+l(JSON2.stringify(an))}}function cg(cG,cH){var cI=JSON2.stringify(cG); -if(cI.length>2){return"&"+cH+"="+l(cI)}return""}var cD=bO(bf);var cq=bO(ab);ca+=cg(cD,"cvar");ca+=cg(cq,"e_cvar");if(ag){ca+=cg(ag,"_cvar");for(cw in cs){if(Object.prototype.hasOwnProperty.call(cs,cw)){if(ag[cw][0]===""||ag[cw][1]===""){delete ag[cw]}}}if(a5){bF(cF,JSON2.stringify(ag),az,b1,aq)}}if(a1){if(aI){ca+=">_ms="+aI}else{if(f&&f.timing&&f.timing.requestStart&&f.timing.responseEnd){ca+=">_ms="+(f.timing.responseEnd-f.timing.requestStart)}}}am(cx,co,cd,ci,ch,w(cb)&&String(cb).length?cb:cr);bF(cj,"*",az,b1,aq);ca+=O(cz);if(bn.length){ca+="&"+bn}if(q(au)){ca=au(ca)}return ca}function bI(cb,ca,cf,cc,b8,ci){var cd="idgoal=0",ce,b9=new Date(),cg=[],ch;if(String(cb).length){cd+="&ec_id="+l(cb);ce=Math.round(b9.getTime()/1000)}cd+="&revenue="+ca;if(String(cf).length){cd+="&ec_st="+cf}if(String(cc).length){cd+="&ec_tx="+cc}if(String(b8).length){cd+="&ec_sh="+b8}if(String(ci).length){cd+="&ec_dt="+ci}if(bD){for(ch in bD){if(Object.prototype.hasOwnProperty.call(bD,ch)){if(!w(bD[ch][1])){bD[ch][1]="" -}if(!w(bD[ch][2])){bD[ch][2]=""}if(!w(bD[ch][3])||String(bD[ch][3]).length===0){bD[ch][3]=0}if(!w(bD[ch][4])||String(bD[ch][4]).length===0){bD[ch][4]=1}cg.push(bD[ch])}}cd+="&ec_items="+l(JSON2.stringify(cg))}cd=aT(cd,an,"ecommerce",ce);aZ(cd,bm)}function bH(b8,cc,cb,ca,b9,cd){if(String(b8).length&&w(cc)){bI(b8,cc,cb,ca,b9,cd)}}function bZ(b8){if(w(b8)){bI("",b8,"","","","")}}function bd(cb,cc){var b8=new Date(),ca=aT("action_name="+l(V(cb||aS)),cc,"log");aZ(ca,bm);if(aj&&aG&&!bv){bv=true;U(u,"click",bP);U(u,"mouseup",bP);U(u,"mousedown",bP);U(u,"mousemove",bP);U(u,"mousewheel",bP);U(G,"DOMMouseScroll",bP);U(G,"scroll",bP);U(u,"keypress",bP);U(u,"keydown",bP);U(u,"keyup",bP);U(G,"resize",bP);U(G,"focus",bP);U(G,"blur",bP);bs=b8.getTime();setTimeout(function b9(){var cd;b8=new Date();if((bs+aG)>b8.getTime()){if(aj<b8.getTime()){cd=aT("ping=1",cc,"ping");aZ(cd,bm)}setTimeout(b9,aG)}},aG)}}function aH(ca,b9){var cb,b8="(^| )(piwik[_-]"+b9;if(ca){for(cb=0;cb<ca.length;cb++){b8+="|"+ca[cb]}}b8+=")( |$)"; -return new RegExp(b8)}function bB(b8){return(aa&&b8&&0===String(b8).indexOf(aa))}function bN(cb,b8,cc){if(bB(b8)){return 0}var ca=aH(be,"download"),b9=aH(aC,"link"),cd=new RegExp("\\.("+aU+")([?&#]|$)","i");if(b9.test(cb)){return"link"}if(ca.test(cb)||cd.test(b8)){return"download"}if(cc){return 0}return"link"}function a7(b9){var b8;b8=b9.parentNode;while(b8!==null&&w(b8)){if(Q.isLinkElement(b9)){break}b9=b8;b8=b9.parentNode}return b9}function bh(cc){cc=a7(cc);if(!Q.hasNodeAttribute(cc,"href")){return}if(!w(cc.href)){return}var cb=Q.getAttributeValueFromNode(cc,"href");if(bB(cb)){return}var cd=cc.hostname||b(cc.href);var ce=cd.toLowerCase();var b9=cc.href.replace(cd,ce);var ca=new RegExp("^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):","i");if(!ca.test(b9)){var b8=bN(cc.className,b9,bq(ce));if(b8){return{type:b8,href:b9}}}}function b4(b8,b9,ca,cb){var cc=m.buildInteractionRequestParams(b8,b9,ca,cb);if(!cc){return}return aT(cc,null,"contentInteraction")}function b3(ca,cb,cf,b8,b9){if(!w(ca)){return -}if(bB(ca)){return ca}var cd=m.toAbsoluteUrl(ca);var cc="redirecturl="+l(cd)+"&";cc+=b4(cb,cf,b8,(b9||ca));var ce="&";if(aa.indexOf("?")<0){ce="?"}return aa+ce+cc}function a3(b8,b9){if(!b8||!b9){return false}var ca=m.findTargetNode(b8);if(m.shouldIgnoreInteraction(ca)){return false}ca=m.findTargetNodeNoDefault(b8);if(ca&&!H(ca,b9)){return false}return true}function aR(ca,b9,cc){if(!ca){return}var b8=m.findParentContentNode(ca);if(!b8){return}if(!a3(b8,ca)){return}var cb=m.buildContentBlock(b8);if(!cb){return}if(!cb.target&&cc){cb.target=cc}return m.buildInteractionRequestParams(b9,cb.name,cb.piece,cb.target)}function aO(b9){if(!ah||!ah.length){return false}var b8,ca;for(b8=0;b8<ah.length;b8++){ca=ah[b8];if(ca&&ca.name===b9.name&&ca.piece===b9.piece&&ca.target===b9.target){return true}}return false}function ac(cb){if(!cb){return false}var ce=m.findTargetNode(cb);if(!ce||m.shouldIgnoreInteraction(ce)){return false}var cf=bh(ce);if(bS&&cf&&cf.type){return false}if(Q.isLinkElement(ce)&&Q.hasNodeAttributeWithValue(ce,"href")){var b8=String(Q.getAttributeValueFromNode(ce,"href")); -if(0===b8.indexOf("#")){return false}if(bB(b8)){return true}if(!m.isUrlToCurrentDomain(b8)){return false}var cc=m.buildContentBlock(cb);if(!cc){return}var ca=cc.name;var cg=cc.piece;var cd=cc.target;if(!Q.hasNodeAttributeWithValue(ce,m.CONTENT_TARGET_ATTR)||ce.wasContentTargetAttrReplaced){ce.wasContentTargetAttrReplaced=true;cd=m.toAbsoluteUrl(b8);Q.setAnyAttribute(ce,m.CONTENT_TARGET_ATTR,cd)}var b9=b3(b8,"click",ca,cg,cd);m.setHrefAttribute(ce,b9);return true}return false}function af(b9){if(!b9||!b9.length){return}var b8;for(b8=0;b8<b9.length;b8++){ac(b9[b8])}}function bg(b8){return function(b9){if(!b8){return}var cc=m.findParentContentNode(b8);var cd;if(b9){cd=b9.target||b9.srcElement}if(!cd){cd=b8}if(!a3(cc,cd)){return}bX(bm);if(Q.isLinkElement(b8)&&Q.hasNodeAttributeWithValue(b8,"href")&&Q.hasNodeAttributeWithValue(b8,m.CONTENT_TARGET_ATTR)){var ca=Q.getAttributeValueFromNode(b8,"href");if(!bB(ca)&&b8.wasContentTargetAttrReplaced){Q.setAnyAttribute(b8,m.CONTENT_TARGET_ATTR,"")}}var ch=bh(b8); -if(by&&ch&&ch.type){return ch.type}if(ac(cc)){return"href"}var ce=m.buildContentBlock(cc);if(!ce){return}var cb=ce.name;var ci=ce.piece;var cg=ce.target;var cf=b4("click",cb,ci,cg);aZ(cf,bm);return cf}}function aF(ca){if(!ca||!ca.length){return}var b8,b9;for(b8=0;b8<ca.length;b8++){b9=m.findTargetNode(ca[b8]);if(b9&&!b9.contentInteractionTrackingSetupDone){b9.contentInteractionTrackingSetupDone=true;U(b9,"click",bg(b9))}}}function aB(ca,cb){if(!ca||!ca.length){return[]}var b8,b9;for(b8=0;b8<ca.length;b8++){if(aO(ca[b8])){ca.splice(b8,1);b8--}else{ah.push(ca[b8])}}if(!ca||!ca.length){return[]}af(cb);aF(cb);var cc=[];for(b8=0;b8<ca.length;b8++){b9=aT(m.buildImpressionRequestParams(ca[b8].name,ca[b8].piece,ca[b8].target),undefined,"contentImpressions");cc.push(b9)}return cc}function aW(b9){var b8=m.collectContent(b9);return aB(b8,b9)}function bA(b9){if(!b9||!b9.length){return[]}var b8;for(b8=0;b8<b9.length;b8++){if(!m.isNodeVisible(b9[b8])){b9.splice(b8,1);b8--}}if(!b9||!b9.length){return[] -}return aW(b9)}function bL(ca,b8,b9){var cb=m.buildImpressionRequestParams(ca,b8,b9);return aT(cb,null,"contentImpression")}function aV(cb,b9){if(!cb){return}var b8=m.findParentContentNode(cb);var ca=m.buildContentBlock(b8);if(!ca){return}if(!b9){b9="Unknown"}return b4(b9,ca.name,ca.piece,ca.target)}function bw(b9,cb,b8,ca){return"e_c="+l(b9)+"&e_a="+l(cb)+(w(b8)?"&e_n="+l(b8):"")+(w(ca)?"&e_v="+l(ca):"")}function al(ca,cc,b8,cb,cd){if(String(ca).length===0||String(cc).length===0){return false}var b9=aT(bw(ca,cc,b8,cb),cd,"event");aZ(b9,bm)}function aN(b8,cb,b9,cc){var ca=aT("search="+l(b8)+(cb?"&search_cat="+l(cb):"")+(w(b9)?"&search_count="+b9:""),cc,"sitesearch");aZ(ca,bm)}function bk(b8,cb,ca){var b9=aT("idgoal="+b8+(cb?"&revenue="+cb:""),ca,"goal");aZ(b9,bm)}function bM(cb,b8,cf,ce,ca){var cd=b8+"="+l(bW(cb));var b9=aR(ca,"click",cb);if(b9){cd+="&"+b9}var cc=aT(cd,cf,"link");aZ(cc,(ce?0:bm),ce)}function bT(b9,b8){if(b9!==""){return b9+b8.charAt(0).toUpperCase()+b8.slice(1)}return b8 -}function aM(cd){var cc,b8,cb=["","webkit","ms","moz"],ca;if(!a0){for(b8=0;b8<cb.length;b8++){ca=cb[b8];if(Object.prototype.hasOwnProperty.call(u,bT(ca,"hidden"))){if(u[bT(ca,"visibilityState")]==="prerender"){cc=true}break}}}if(cc){U(u,ca+"visibilitychange",function b9(){u.removeEventListener(ca+"visibilitychange",b9,false);cd()});return}cd()}function aQ(b8){if(u.readyState==="complete"){b8()}else{if(G.addEventListener){G.addEventListener("load",b8)}else{if(G.attachEvent){G.attachEvent("onLoad",b8)}}}}function aA(b9){var b8=false;if(u.attachEvent){b8=u.readyState==="complete"}else{b8=u.readyState!=="loading"}if(b8){b9()}else{if(u.addEventListener){u.addEventListener("DOMContentLoaded",b9)}else{if(u.attachEvent){u.attachEvent("onreadystatechange",b9)}}}}function bC(b8){var b9=bh(b8);if(b9&&b9.type){b9.href=h(b9.href);bM(b9.href,b9.type,undefined,null,b8)}}function b6(b8){var b9,ca;b8=b8||G.event;b9=b8.which||b8.button;ca=b8.target||b8.srcElement;if(b8.type==="click"){if(ca){bC(ca)}}else{if(b8.type==="mousedown"){if((b9===1||b9===2)&&ca){bi=b9; -at=ca}else{bi=at=null}}else{if(b8.type==="mouseup"){if(b9===bi&&ca===at){bC(ca)}bi=at=null}}}}function bz(b9,b8){if(b8){U(b9,"mouseup",b6,false);U(b9,"mousedown",b6,false)}else{U(b9,"click",b6,false)}}function bc(b9){if(!by){by=true;var ca,b8=aH(ai,"ignore"),cb=u.links;if(cb){for(ca=0;ca<cb.length;ca++){if(!b8.test(cb[ca].className)){bz(cb[ca],b9)}}}}}function bu(ca,cc,cd){if(ay){return true}ay=true;var ce=false;var cb,b9;function b8(){ce=true}aQ(function(){function cf(ch){setTimeout(function(){if(!ay){return}ce=false;cd.trackVisibleContentImpressions();cf(ch)},ch)}function cg(ch){setTimeout(function(){if(!ay){return}if(ce){ce=false;cd.trackVisibleContentImpressions()}cg(ch)},ch)}if(ca){cb=["scroll","resize"];for(b9=0;b9<cb.length;b9++){if(u.addEventListener){u.addEventListener(cb[b9],b8)}else{G.attachEvent("on"+cb[b9],b8)}}cg(100)}if(cc&&cc>0){cc=parseInt(cc,10);cf(cc)}})}function bY(){var b9,ca,cb={pdf:"application/pdf",qt:"video/quicktime",realp:"audio/x-pn-realaudio-plugin",wma:"application/x-mplayer2",dir:"application/x-director",fla:"application/x-shockwave-flash",java:"application/x-java-vm",gears:"application/x-googlegears",ag:"application/x-silverlight"},b8=(new RegExp("Mac OS X.*Safari/")).test(e.userAgent)?G.devicePixelRatio||1:1; -if(!((new RegExp("MSIE")).test(e.userAgent))){if(e.mimeTypes&&e.mimeTypes.length){for(b9 in cb){if(Object.prototype.hasOwnProperty.call(cb,b9)){ca=e.mimeTypes[cb[b9]];bR[b9]=(ca&&ca.enabledPlugin)?"1":"0"}}}if(typeof navigator.javaEnabled!=="unknown"&&w(e.javaEnabled)&&e.javaEnabled()){bR.java="1"}if(q(G.GearsFactory)){bR.gears="1"}bR.cookie=ae()}bR.res=K.width*b8+"x"+K.height*b8}bY();bj();return{getVisitorId:function(){return(Y())[1]},getVisitorInfo:function(){return Y()},getAttributionInfo:function(){return X()},getAttributionCampaignName:function(){return X()[0]},getAttributionCampaignKeyword:function(){return X()[1]},getAttributionReferrerTimestamp:function(){return X()[2]},getAttributionReferrerUrl:function(){return X()[3]},setTrackerUrl:function(b8){aa=b8},setSiteId:function(b8){bQ=b8},setUserId:function(b8){aJ=b8;a2=aX(aJ).substr(0,16)},getUserId:function(){return aJ},setCustomData:function(b8,b9){if(J(b8)){an=b8}else{if(!an){an={}}an[b8]=b9}},getCustomData:function(){return an},setCustomRequestProcessing:function(b8){au=b8 -},appendToTrackingUrl:function(b8){bn=b8},getRequest:function(b8){return aT(b8)},addPlugin:function(b8,b9){a[b8]=b9},setCustomVariable:function(b9,b8,cc,ca){var cb;if(!w(ca)){ca="visit"}if(!w(b8)){return}if(!w(cc)){cc=""}if(b9>0){b8=!n(b8)?String(b8):b8;cc=!n(cc)?String(cc):cc;cb=[b8.slice(0,bV),cc.slice(0,bV)];if(ca==="visit"||ca===2){Z();ag[b9]=cb}else{if(ca==="page"||ca===3){bf[b9]=cb}else{if(ca==="event"){ab[b9]=cb}}}}},getCustomVariable:function(b9,ca){var b8;if(!w(ca)){ca="visit"}if(ca==="page"||ca===3){b8=bf[b9]}else{if(ca==="event"){b8=ab[b9]}else{if(ca==="visit"||ca===2){Z();b8=ag[b9]}}}if(!w(b8)||(b8&&b8[0]==="")){return false}return b8},deleteCustomVariable:function(b8,b9){if(this.getCustomVariable(b8,b9)){this.setCustomVariable(b8,"","",b9)}},storeCustomVariablesInCookie:function(){a5=true},setLinkTrackingTimer:function(b8){bm=b8},setDownloadExtensions:function(b8){aU=b8},addDownloadExtensions:function(b8){aU+="|"+b8},setDomains:function(b8){bo=n(b8)?[b8]:b8;bo.push(bK)},setIgnoreClasses:function(b8){ai=n(b8)?[b8]:b8 -},setRequestMethod:function(b8){br=b8||bp},setRequestContentType:function(b8){aK=b8||bb},setReferrerUrl:function(b8){bt=b8},setCustomUrl:function(b8){a6=bJ(b2,b8)},setDocumentTitle:function(b8){aS=b8},setAPIUrl:function(b8){ax=b8},setDownloadClasses:function(b8){be=n(b8)?[b8]:b8},setLinkClasses:function(b8){aC=n(b8)?[b8]:b8},setCampaignNameKey:function(b8){aY=n(b8)?[b8]:b8},setCampaignKeywordKey:function(b8){aP=n(b8)?[b8]:b8},discardHashTag:function(b8){ak=b8},setCookieNamePrefix:function(b8){b0=b8;ag=ar()},setCookieDomain:function(b8){aq=y(b8);bj()},setCookiePath:function(b8){b1=b8;bj()},setVisitorCookieTimeout:function(b8){aw=b8*1000},setSessionCookieTimeout:function(b8){az=b8*1000},setReferralCookieTimeout:function(b8){a9=b8*1000},setConversionAttributionFirstReferrer:function(b8){a4=b8},disableCookies:function(){ao=true;bR.cookie="0"},deleteCookies:function(){W()},setDoNotTrack:function(b9){var b8=e.doNotTrack||e.msDoNotTrack;bU=b9&&(b8==="yes"||b8==="1");if(bU){this.disableCookies() -}},addListener:function(b9,b8){bz(b9,b8)},enableLinkTracking:function(b8){bS=true;if(p){bc(b8)}else{E.push(function(){bc(b8)})}},enableJSErrorTracking:function(){if(b5){return}b5=true;var b8=G.onerror;G.onerror=function(cd,cb,ca,cc,b9){aM(function(){var ce="JavaScript Errors";var cf=cb+":"+ca;if(cc){cf+=":"+cc}al(ce,cf,cd)});if(b8){return b8(cd,cb,ca,cc,b9)}return false}},disablePerformanceTracking:function(){a1=false},setGenerationTimeMs:function(b8){aI=parseInt(b8,10)},setHeartBeatTimer:function(ca,b9){var b8=new Date();aj=b8.getTime()+ca*1000;aG=b9*1000},killFrame:function(){if(G.location!==G.top.location){G.top.location=G.location}},redirectFile:function(b8){if(G.location.protocol==="file:"){G.location=b8}},setCountPreRendered:function(b8){a0=b8},trackGoal:function(b8,ca,b9){aM(function(){bk(b8,ca,b9)})},trackLink:function(b9,b8,cb,ca){aM(function(){bM(b9,b8,cb,ca)})},trackPageView:function(b8,b9){ah=[];if(A(bQ)){aM(function(){M(aa,ax,bQ)})}else{aM(function(){bd(b8,b9)})}},trackAllContentImpressions:function(){if(A(bQ)){return -}aM(function(){aA(function(){var b8=m.findContentNodes();var b9=aW(b8);ap(b9,bm)})})},trackVisibleContentImpressions:function(b8,b9){if(A(bQ)){return}if(!w(b8)){b8=true}if(!w(b9)){b9=750}bu(b8,b9,this);aM(function(){aQ(function(){var ca=m.findContentNodes();var cb=bA(ca);ap(cb,bm)})})},trackContentImpression:function(ca,b8,b9){if(A(bQ)){return}if(!ca){return}b8=b8||"Unknown";aM(function(){var cb=bL(ca,b8,b9);aZ(cb,bm)})},trackContentImpressionsWithinNode:function(b8){if(A(bQ)||!b8){return}aM(function(){if(ay){aQ(function(){var b9=m.findContentNodesWithinNode(b8);var ca=bA(b9);ap(ca,bm)})}else{aA(function(){var b9=m.findContentNodesWithinNode(b8);var ca=aW(b9);ap(ca,bm)})}})},trackContentInteraction:function(ca,cb,b8,b9){if(A(bQ)){return}if(!ca||!cb){return}b8=b8||"Unknown";aM(function(){var cc=b4(ca,cb,b8,b9);aZ(cc,bm)})},trackContentInteractionNode:function(b9,b8){if(A(bQ)||!b9){return}aM(function(){var ca=aV(b9,b8);aZ(ca,bm)})},trackEvent:function(b9,cb,b8,ca){aM(function(){al(b9,cb,b8,ca) -})},trackSiteSearch:function(b8,ca,b9){aM(function(){aN(b8,ca,b9)})},setEcommerceView:function(cb,b8,ca,b9){if(!w(ca)||!ca.length){ca=""}else{if(ca instanceof Array){ca=JSON2.stringify(ca)}}bf[5]=["_pkc",ca];if(w(b9)&&String(b9).length){bf[2]=["_pkp",b9]}if((!w(cb)||!cb.length)&&(!w(b8)||!b8.length)){return}if(w(cb)&&cb.length){bf[3]=["_pks",cb]}if(!w(b8)||!b8.length){b8=""}bf[4]=["_pkn",b8]},addEcommerceItem:function(cc,b8,ca,b9,cb){if(cc.length){bD[cc]=[cc,b8,ca,b9,cb]}},trackEcommerceOrder:function(b8,cc,cb,ca,b9,cd){bH(b8,cc,cb,ca,b9,cd)},trackEcommerceCartUpdate:function(b8){bZ(b8)}}}function v(){return{push:R}}U(G,"beforeunload",S,false);o();Date.prototype.getTimeAlias=Date.prototype.getTime;L=new D();var r={setTrackerUrl:1,setAPIUrl:1,setUserId:1,setSiteId:1,disableCookies:1,enableLinkTracking:1};var d;for(t=0;t<_paq.length;t++){d=_paq[t][0];if(r[d]){R(_paq[t]);delete _paq[t];if(r[d]>1){if(console!==undefined&&console&&console.error){console.error("The method "+d+' is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/api-reference/tracking-javascript#multiple-piwik-trackers') -}}r[d]++}}for(t=0;t<_paq.length;t++){if(_paq[t]){R(_paq[t])}}_paq=new v();c={addPlugin:function(W,X){a[W]=X},getTracker:function(W,X){return new D(W,X)},getAsyncTracker:function(){return L}};if(typeof define==="function"&&define.amd){define("piwik",[],function(){return c})}return c}())}if(window&&window.piwikAsyncInit){window.piwikAsyncInit()}(function(){var a=(typeof AnalyticsTracker);if(a==="undefined"){AnalyticsTracker=Piwik}}());if(typeof piwik_log!=="function"){piwik_log=function(b,f,d,g){function a(h){try{return eval("piwik_"+h)}catch(i){}return}var c,e=Piwik.getTracker(d,f);e.setDocumentTitle(b);e.setCustomData(g);c=a("tracker_pause");if(c){e.setLinkTrackingTimer(c)}c=a("download_extensions");if(c){e.setDownloadExtensions(c)}c=a("hosts_alias");if(c){e.setDomains(c)}c=a("ignore_classes");if(c){e.setIgnoreClasses(c)}e.trackPageView();if(a("install_tracker")){piwik_track=function(i,k,j,h){e.setSiteId(k);e.setTrackerUrl(j);e.trackLink(i,h)};e.enableLinkTracking()}}; +G.name=W+"###"+ac+"###"+X}var ab=G.name.split("###");return ab.length===3&&ab[0]===W}function M(X,ac,Z){var ab=G.name.split("###"),aa=ab[1],W=ab[2],Y=B(X,ac);i(Y+"plugins/Overlay/client/client.js?v=1",function(){Piwik_Overlay_Client.initialize(Y,Z,aa,W)})}function D(aE,bm){var ad=N(u.domain,G.location.href,x()),bM=y(ad[0]),b4=F(ad[1]),bu=F(ad[2]),b8=false,bq="GET",bs=bq,bc="application/x-www-form-urlencoded; charset=UTF-8",aK=bc,aa=aE||"",ax="",bo="",bS=bm||"",aJ="",a2="",a7,aS=u.title,aU="7z|aac|apk|ar[cj]|as[fx]|avi|azw3|bin|csv|deb|dmg|docx?|epub|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mobi|mp(2|3|4|e?g)|mov(ie)?|ms[ip]|od[bfgpst]|og[gv]|pdf|phps|png|pptx?|qtm?|ra[mr]?|rpm|sea|sit|tar|t?bz2?|tgz|torrent|txt|wav|wm[av]|wpd||xlsx?|xml|z|zip",bp=[bM],ai=[],bf=[],aC=[],bn=500,aj,aG,ak,an,aY=["pk_campaign","piwik_campaign","utm_campaign","utm_source","utm_medium"],aP=["pk_kwd","piwik_kwd","utm_term"],b2="_pk_",aq,b3,ao=false,bW,a0,a5,aw=33955200000,az=1800000,ba=15768000000,a1=true,aI=0,a6=false,ag=false,au,bg={},ab={},bX=200,bF={},bT={},ah=[],ay=false,a9=false,bz=false,bU=false,bw=false,bt,bj,at,aX=T,by; +function bH(ch,ce,cd,cg,cc,cf){if(ao){return}var cb;if(cd){cb=new Date();cb.setTime(cb.getTime()+cd)}u.cookie=ch+"="+l(ce)+(cd?";expires="+cb.toGMTString():"")+";path="+(cg||"/")+(cc?";domain="+cc:"")+(cf?";secure":"")}function av(cd){if(ao){return 0}var cb=new RegExp("(^|;)[ ]*"+cd+"=([^;]*)"),cc=cb.exec(u.cookie);return cc?F(cc[2]):0}function bY(cb){var cc;if(ak){cc=new RegExp("#.*");return cb.replace(cc,"")}return cb}function bL(cd,cb){var ce=k(cb),cc;if(ce){return cb}if(cb.slice(0,1)==="/"){return k(cd)+"://"+b(cd)+cb}cd=bY(cd);cc=cd.indexOf("?");if(cc>=0){cd=cd.slice(0,cc)}cc=cd.lastIndexOf("/");if(cc!==cd.length-1){cd=cd.slice(0,cc+1)}return cd+cb}function br(ce){var cc,cb,cd;for(cc=0;cc<bp.length;cc++){cb=y(bp[cc].toLowerCase());if(ce===cb){return true}if(cb.slice(0,1)==="."){if(ce===cb.slice(1)){return true}cd=ce.length-cb.length;if((cd>0)&&(ce.slice(cd)===cb)){return true}}}return false}function ca(cb,cd){var cc=new Image(1,1);cc.onload=function(){t=0;if(typeof cd==="function"){cd() +}};cc.src=aa+(aa.indexOf("?")<0?"?":"&")+cb}function bI(cc,cf,cb){if(!w(cb)||null===cb){cb=true}try{var ce=G.XMLHttpRequest?new G.XMLHttpRequest():G.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):null;ce.open("POST",aa,true);ce.onreadystatechange=function(){if(this.readyState===4&&!(this.status>=200&&this.status<300)&&cb){ca(cc,cf)}else{if(typeof cf==="function"){cf()}}};ce.setRequestHeader("Content-Type",aK);ce.send(cc)}catch(cd){if(cb){ca(cc,cf)}}}function bZ(cc){var cb=new Date();var cd=cb.getTime()+cc;if(!j||cd>j){j=cd}}function aD(cf){var cc=new Date();var cb=cc.getTime();if(a9&&cb<a9){var cd=a9-cb;setTimeout(cf,cd);bZ(cd+50);a9+=50;return}if(a9===false){var ce=800;a9=cb+ce}cf()}function aZ(cc,cb,cd){if(!bW&&cc){aD(function(){if(bs==="POST"){bI(cc,cd)}else{ca(cc,cd)}bZ(cb)})}}function bb(cb){if(bW){return false}return(cb&&cb.length)}function ap(cd,cb){if(!bb(cd)){return}var cc='{"requests":["?'+cd.join('","?')+'"]}';aD(function(){bI(cc,null,false);bZ(cb)})}function bG(cb){return b2+cb+"."+bS+"."+by +}function ae(){if(ao){return"0"}if(!w(e.cookieEnabled)){var cb=bG("testcookie");bH(cb,"1");return av(cb)==="1"?"1":"0"}return e.cookieEnabled?"1":"0"}function bk(){by=aX((aq||bM)+(b3||"/")).slice(0,4)}function ar(){var cc=bG("cvar"),cb=av(cc);if(cb.length){cb=JSON2.parse(cb);if(J(cb)){return cb}}return{}}function Z(){if(ag===false){ag=ar()}}function bR(){var cb=new Date();bt=cb.getTime()}function Y(){var cd=new Date(),cb=Math.round(cd.getTime()/1000),cc=bG("id"),cg=av(cc),cf,ce;if(cg){cf=cg.split(".");cf.unshift("0");if(a2.length){cf[1]=a2}return cf}if(a2.length){ce=a2}else{ce=aX((e.userAgent||"")+(e.platform||"")+JSON2.stringify(bT)+cd.getTime()+Math.random()).slice(0,16)}cf=["1",ce,cb,0,cb,"",""];return cf}function bB(){var ci=Y(),ce=ci[0],cf=ci[1],cc=ci[2],cb=ci[3],cg=ci[4],cd=ci[5];if(!w(ci[6])){ci[6]=""}var ch=ci[6];return{newVisitor:ce,uuid:cf,createTs:cc,visitCount:cb,currentVisitTs:cg,lastVisitTs:cd,lastEcommerceOrderTs:ch}}function aL(){var ce=new Date(),cc=ce.getTime(),cf=bB().createTs; +var cb=parseInt(cf,10);var cd=(cb*1000)+aw-cc;return cd}function am(cb){if(!bS){return}var cd=new Date(),cc=Math.round(cd.getTime()/1000);if(!w(cb)){cb=bB()}var ce=cb.uuid+"."+cb.createTs+"."+cb.visitCount+"."+cc+"."+cb.lastVisitTs+"."+cb.lastEcommerceOrderTs;bH(bG("id"),ce,aL(),b3,aq)}function X(){var cb=av(bG("ref"));if(cb.length){try{cb=JSON2.parse(cb);if(J(cb)){return cb}}catch(cc){}}return["","",0,""]}function W(){var cb=ao;ao=false;bH(bG("id"),"",-86400,b3,aq);bH(bG("ses"),"",-86400,b3,aq);bH(bG("cvar"),"",-86400,b3,aq);bH(bG("ref"),"",-86400,b3,aq);ao=cb}function b7(cb){bS=cb;am()}function bQ(cf){if(!cf||!J(cf)){return}var ce=[];var cd;for(cd in cf){if(Object.prototype.hasOwnProperty.call(cf,cd)){ce.push(cd)}}var cg={};ce.sort();var cb=ce.length;var cc;for(cc=0;cc<cb;cc++){cg[ce[cc]]=cf[ce[cc]]}return cg}function a4(){bH(bG("ses"),"*",az,b3,aq)}function aT(cd,cw,cx,ce){var cv,cc=new Date(),ck=Math.round(cc.getTime()/1000),ch,cu,cf=1024,cB,cl,cs=ag,cg=bG("ses"),cq=bG("ref"),cn=bG("cvar"),co=av(cg),ct=X(),cz=a7||b4,ci,cb; +if(ao){W()}if(bW){return""}var cp=bB();if(!w(ce)){ce=""}var cm=u.characterSet||u.charset;if(!cm||cm.toLowerCase()==="utf-8"){cm=null}ci=ct[0];cb=ct[1];ch=ct[2];cu=ct[3];if(!co){var cy=az/1000;if(!cp.lastVisitTs||(ck-cp.lastVisitTs)>cy){cp.visitCount++;cp.lastVisitTs=cp.currentVisitTs}if(!a5||!ci.length){for(cv in aY){if(Object.prototype.hasOwnProperty.call(aY,cv)){ci=I(cz,aY[cv]);if(ci.length){break}}}for(cv in aP){if(Object.prototype.hasOwnProperty.call(aP,cv)){cb=I(cz,aP[cv]);if(cb.length){break}}}}cB=b(bu);cl=cu.length?b(cu):"";if(cB.length&&!br(cB)&&(!a5||!cl.length||br(cl))){cu=bu}if(cu.length||ci.length){ch=ck;ct=[ci,cb,ch,bY(cu.slice(0,cf))];bH(cq,JSON2.stringify(ct),ba,b3,aq)}}cd+="&idsite="+bS+"&rec=1&r="+String(Math.random()).slice(2,8)+"&h="+cc.getHours()+"&m="+cc.getMinutes()+"&s="+cc.getSeconds()+"&url="+l(bY(cz))+(bu.length?"&urlref="+l(bY(bu)):"")+((aJ&&aJ.length)?"&uid="+l(aJ):"")+"&_id="+cp.uuid+"&_idts="+cp.createTs+"&_idvc="+cp.visitCount+"&_idn="+cp.newVisitor+(ci.length?"&_rcn="+l(ci):"")+(cb.length?"&_rck="+l(cb):"")+"&_refts="+ch+"&_viewts="+cp.lastVisitTs+(String(cp.lastEcommerceOrderTs).length?"&_ects="+cp.lastEcommerceOrderTs:"")+(String(cu).length?"&_ref="+l(bY(cu.slice(0,cf))):"")+(cm?"&cs="+l(cm):"")+"&send_image=0"; +for(cv in bT){if(Object.prototype.hasOwnProperty.call(bT,cv)){cd+="&"+cv+"="+bT[cv]}}if(cw){cd+="&data="+l(JSON2.stringify(cw))}else{if(an){cd+="&data="+l(JSON2.stringify(an))}}function cj(cC,cD){var cE=JSON2.stringify(cC);if(cE.length>2){return"&"+cD+"="+l(cE)}return""}var cA=bQ(bg);var cr=bQ(ab);cd+=cj(cA,"cvar");cd+=cj(cr,"e_cvar");if(ag){cd+=cj(ag,"_cvar");for(cv in cs){if(Object.prototype.hasOwnProperty.call(cs,cv)){if(ag[cv][0]===""||ag[cv][1]===""){delete ag[cv]}}}if(a6){bH(cn,JSON2.stringify(ag),az,b3,aq)}}if(a1){if(aI){cd+=">_ms="+aI}else{if(f&&f.timing&&f.timing.requestStart&&f.timing.responseEnd){cd+=">_ms="+(f.timing.responseEnd-f.timing.requestStart)}}}cp.lastEcommerceOrderTs=w(ce)&&String(ce).length?ce:cp.lastEcommerceOrderTs;am(cp);a4();cd+=O(cx);if(bo.length){cd+="&"+bo}if(q(au)){cd=au(cd)}return cd}function bK(ce,cd,ci,cf,cb,cl){var cg="idgoal=0",ch,cc=new Date(),cj=[],ck;if(String(ce).length){cg+="&ec_id="+l(ce);ch=Math.round(cc.getTime()/1000)}cg+="&revenue="+cd;if(String(ci).length){cg+="&ec_st="+ci +}if(String(cf).length){cg+="&ec_tx="+cf}if(String(cb).length){cg+="&ec_sh="+cb}if(String(cl).length){cg+="&ec_dt="+cl}if(bF){for(ck in bF){if(Object.prototype.hasOwnProperty.call(bF,ck)){if(!w(bF[ck][1])){bF[ck][1]=""}if(!w(bF[ck][2])){bF[ck][2]=""}if(!w(bF[ck][3])||String(bF[ck][3]).length===0){bF[ck][3]=0}if(!w(bF[ck][4])||String(bF[ck][4]).length===0){bF[ck][4]=1}cj.push(bF[ck])}}cg+="&ec_items="+l(JSON2.stringify(cj))}cg=aT(cg,an,"ecommerce",ch);aZ(cg,bn)}function bJ(cb,cf,ce,cd,cc,cg){if(String(cb).length&&w(cf)){bK(cb,cf,ce,cd,cc,cg)}}function b1(cb){if(w(cb)){bK("",cb,"","","","")}}function be(ce,cf){var cb=new Date(),cd=aT("action_name="+l(V(ce||aS)),cf,"log");aZ(cd,bn);if(aj&&aG&&!bw){bw=true;U(u,"click",bR);U(u,"mouseup",bR);U(u,"mousedown",bR);U(u,"mousemove",bR);U(u,"mousewheel",bR);U(G,"DOMMouseScroll",bR);U(G,"scroll",bR);U(u,"keypress",bR);U(u,"keydown",bR);U(u,"keyup",bR);U(G,"resize",bR);U(G,"focus",bR);U(G,"blur",bR);bt=cb.getTime();setTimeout(function cc(){var cg;cb=new Date(); +if((bt+aG)>cb.getTime()){if(aj<cb.getTime()){cg=aT("ping=1",cf,"ping");aZ(cg,bn)}setTimeout(cc,aG)}},aG)}}function aH(cd,cc){var ce,cb="(^| )(piwik[_-]"+cc;if(cd){for(ce=0;ce<cd.length;ce++){cb+="|"+cd[ce]}}cb+=")( |$)";return new RegExp(cb)}function bD(cb){return(aa&&cb&&0===String(cb).indexOf(aa))}function bP(ce,cb,cf){if(bD(cb)){return 0}var cd=aH(bf,"download"),cc=aH(aC,"link"),cg=new RegExp("\\.("+aU+")([?&#]|$)","i");if(cc.test(ce)){return"link"}if(cd.test(ce)||cg.test(cb)){return"download"}if(cf){return 0}return"link"}function a8(cc){var cb;cb=cc.parentNode;while(cb!==null&&w(cb)){if(Q.isLinkElement(cc)){break}cc=cb;cb=cc.parentNode}return cc}function bi(cf){cf=a8(cf);if(!Q.hasNodeAttribute(cf,"href")){return}if(!w(cf.href)){return}var ce=Q.getAttributeValueFromNode(cf,"href");if(bD(ce)){return}var cg=cf.hostname||b(cf.href);var ch=cg.toLowerCase();var cc=cf.href.replace(cg,ch);var cd=new RegExp("^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):","i");if(!cd.test(cc)){var cb=bP(cf.className,cc,br(ch)); +if(cb){return{type:cb,href:cc}}}}function b6(cb,cc,cd,ce){var cf=m.buildInteractionRequestParams(cb,cc,cd,ce);if(!cf){return}return aT(cf,null,"contentInteraction")}function b5(cd,ce,ci,cb,cc){if(!w(cd)){return}if(bD(cd)){return cd}var cg=m.toAbsoluteUrl(cd);var cf="redirecturl="+l(cg)+"&";cf+=b6(ce,ci,cb,(cc||cd));var ch="&";if(aa.indexOf("?")<0){ch="?"}return aa+ch+cf}function a3(cb,cc){if(!cb||!cc){return false}var cd=m.findTargetNode(cb);if(m.shouldIgnoreInteraction(cd)){return false}cd=m.findTargetNodeNoDefault(cb);if(cd&&!H(cd,cc)){return false}return true}function aR(cd,cc,cf){if(!cd){return}var cb=m.findParentContentNode(cd);if(!cb){return}if(!a3(cb,cd)){return}var ce=m.buildContentBlock(cb);if(!ce){return}if(!ce.target&&cf){ce.target=cf}return m.buildInteractionRequestParams(cc,ce.name,ce.piece,ce.target)}function aO(cc){if(!ah||!ah.length){return false}var cb,cd;for(cb=0;cb<ah.length;cb++){cd=ah[cb];if(cd&&cd.name===cc.name&&cd.piece===cc.piece&&cd.target===cc.target){return true +}}return false}function ac(ce){if(!ce){return false}var ch=m.findTargetNode(ce);if(!ch||m.shouldIgnoreInteraction(ch)){return false}var ci=bi(ch);if(bU&&ci&&ci.type){return false}if(Q.isLinkElement(ch)&&Q.hasNodeAttributeWithValue(ch,"href")){var cb=String(Q.getAttributeValueFromNode(ch,"href"));if(0===cb.indexOf("#")){return false}if(bD(cb)){return true}if(!m.isUrlToCurrentDomain(cb)){return false}var cf=m.buildContentBlock(ce);if(!cf){return}var cd=cf.name;var cj=cf.piece;var cg=cf.target;if(!Q.hasNodeAttributeWithValue(ch,m.CONTENT_TARGET_ATTR)||ch.wasContentTargetAttrReplaced){ch.wasContentTargetAttrReplaced=true;cg=m.toAbsoluteUrl(cb);Q.setAnyAttribute(ch,m.CONTENT_TARGET_ATTR,cg)}var cc=b5(cb,"click",cd,cj,cg);m.setHrefAttribute(ch,cc);return true}return false}function af(cc){if(!cc||!cc.length){return}var cb;for(cb=0;cb<cc.length;cb++){ac(cc[cb])}}function bh(cb){return function(cc){if(!cb){return}var cf=m.findParentContentNode(cb);var cg;if(cc){cg=cc.target||cc.srcElement}if(!cg){cg=cb +}if(!a3(cf,cg)){return}bZ(bn);if(Q.isLinkElement(cb)&&Q.hasNodeAttributeWithValue(cb,"href")&&Q.hasNodeAttributeWithValue(cb,m.CONTENT_TARGET_ATTR)){var cd=Q.getAttributeValueFromNode(cb,"href");if(!bD(cd)&&cb.wasContentTargetAttrReplaced){Q.setAnyAttribute(cb,m.CONTENT_TARGET_ATTR,"")}}var ck=bi(cb);if(bz&&ck&&ck.type){return ck.type}if(ac(cf)){return"href"}var ch=m.buildContentBlock(cf);if(!ch){return}var ce=ch.name;var cl=ch.piece;var cj=ch.target;var ci=b6("click",ce,cl,cj);aZ(ci,bn);return ci}}function aF(cd){if(!cd||!cd.length){return}var cb,cc;for(cb=0;cb<cd.length;cb++){cc=m.findTargetNode(cd[cb]);if(cc&&!cc.contentInteractionTrackingSetupDone){cc.contentInteractionTrackingSetupDone=true;U(cc,"click",bh(cc))}}}function aB(cd,ce){if(!cd||!cd.length){return[]}var cb,cc;for(cb=0;cb<cd.length;cb++){if(aO(cd[cb])){cd.splice(cb,1);cb--}else{ah.push(cd[cb])}}if(!cd||!cd.length){return[]}af(ce);aF(ce);var cf=[];for(cb=0;cb<cd.length;cb++){cc=aT(m.buildImpressionRequestParams(cd[cb].name,cd[cb].piece,cd[cb].target),undefined,"contentImpressions"); +cf.push(cc)}return cf}function aW(cc){var cb=m.collectContent(cc);return aB(cb,cc)}function bC(cc){if(!cc||!cc.length){return[]}var cb;for(cb=0;cb<cc.length;cb++){if(!m.isNodeVisible(cc[cb])){cc.splice(cb,1);cb--}}if(!cc||!cc.length){return[]}return aW(cc)}function bN(cd,cb,cc){var ce=m.buildImpressionRequestParams(cd,cb,cc);return aT(ce,null,"contentImpression")}function aV(ce,cc){if(!ce){return}var cb=m.findParentContentNode(ce);var cd=m.buildContentBlock(cb);if(!cd){return}if(!cc){cc="Unknown"}return b6(cc,cd.name,cd.piece,cd.target)}function bx(cc,ce,cb,cd){return"e_c="+l(cc)+"&e_a="+l(ce)+(w(cb)?"&e_n="+l(cb):"")+(w(cd)?"&e_v="+l(cd):"")}function al(cd,cf,cb,ce,cg){if(String(cd).length===0||String(cf).length===0){return false}var cc=aT(bx(cd,cf,cb,ce),cg,"event");aZ(cc,bn)}function aN(cb,ce,cc,cf){var cd=aT("search="+l(cb)+(ce?"&search_cat="+l(ce):"")+(w(cc)?"&search_count="+cc:""),cf,"sitesearch");aZ(cd,bn)}function bl(cb,ce,cd){var cc=aT("idgoal="+cb+(ce?"&revenue="+ce:""),cd,"goal"); +aZ(cc,bn)}function bO(ce,cb,ci,ch,cd){var cg=cb+"="+l(bY(ce));var cc=aR(cd,"click",ce);if(cc){cg+="&"+cc}var cf=aT(cg,ci,"link");aZ(cf,(ch?0:bn),ch)}function bV(cc,cb){if(cc!==""){return cc+cb.charAt(0).toUpperCase()+cb.slice(1)}return cb}function aM(cg){var cf,cb,ce=["","webkit","ms","moz"],cd;if(!a0){for(cb=0;cb<ce.length;cb++){cd=ce[cb];if(Object.prototype.hasOwnProperty.call(u,bV(cd,"hidden"))){if(u[bV(cd,"visibilityState")]==="prerender"){cf=true}break}}}if(cf){U(u,cd+"visibilitychange",function cc(){u.removeEventListener(cd+"visibilitychange",cc,false);cg()});return}cg()}function aQ(cb){if(u.readyState==="complete"){cb()}else{if(G.addEventListener){G.addEventListener("load",cb)}else{if(G.attachEvent){G.attachEvent("onLoad",cb)}}}}function aA(cc){var cb=false;if(u.attachEvent){cb=u.readyState==="complete"}else{cb=u.readyState!=="loading"}if(cb){cc()}else{if(u.addEventListener){u.addEventListener("DOMContentLoaded",cc)}else{if(u.attachEvent){u.attachEvent("onreadystatechange",cc)}}}}function bE(cb){var cc=bi(cb); +if(cc&&cc.type){cc.href=h(cc.href);bO(cc.href,cc.type,undefined,null,cb)}}function b9(cb){var cc,cd;cb=cb||G.event;cc=cb.which||cb.button;cd=cb.target||cb.srcElement;if(cb.type==="click"){if(cd){bE(cd)}}else{if(cb.type==="mousedown"){if((cc===1||cc===2)&&cd){bj=cc;at=cd}else{bj=at=null}}else{if(cb.type==="mouseup"){if(cc===bj&&cd===at){bE(cd)}bj=at=null}}}}function bA(cc,cb){if(cb){U(cc,"mouseup",b9,false);U(cc,"mousedown",b9,false)}else{U(cc,"click",b9,false)}}function bd(cc){if(!bz){bz=true;var cd,cb=aH(ai,"ignore"),ce=u.links;if(ce){for(cd=0;cd<ce.length;cd++){if(!cb.test(ce[cd].className)){bA(ce[cd],cc)}}}}}function bv(cd,cf,cg){if(ay){return true}ay=true;var ch=false;var ce,cc;function cb(){ch=true}aQ(function(){function ci(ck){setTimeout(function(){if(!ay){return}ch=false;cg.trackVisibleContentImpressions();ci(ck)},ck)}function cj(ck){setTimeout(function(){if(!ay){return}if(ch){ch=false;cg.trackVisibleContentImpressions()}cj(ck)},ck)}if(cd){ce=["scroll","resize"];for(cc=0;cc<ce.length; +cc++){if(u.addEventListener){u.addEventListener(ce[cc],cb)}else{G.attachEvent("on"+ce[cc],cb)}}cj(100)}if(cf&&cf>0){cf=parseInt(cf,10);ci(cf)}})}function b0(){var cc,cd,ce={pdf:"application/pdf",qt:"video/quicktime",realp:"audio/x-pn-realaudio-plugin",wma:"application/x-mplayer2",dir:"application/x-director",fla:"application/x-shockwave-flash",java:"application/x-java-vm",gears:"application/x-googlegears",ag:"application/x-silverlight"},cb=(new RegExp("Mac OS X.*Safari/")).test(e.userAgent)?G.devicePixelRatio||1:1;if(!((new RegExp("MSIE")).test(e.userAgent))){if(e.mimeTypes&&e.mimeTypes.length){for(cc in ce){if(Object.prototype.hasOwnProperty.call(ce,cc)){cd=e.mimeTypes[ce[cc]];bT[cc]=(cd&&cd.enabledPlugin)?"1":"0"}}}if(typeof navigator.javaEnabled!=="unknown"&&w(e.javaEnabled)&&e.javaEnabled()){bT.java="1"}if(q(G.GearsFactory)){bT.gears="1"}bT.cookie=ae()}bT.res=K.width*cb+"x"+K.height*cb}b0();bk();am();return{getVisitorId:function(){return bB().uuid},getVisitorInfo:function(){return Y() +},getAttributionInfo:function(){return X()},getAttributionCampaignName:function(){return X()[0]},getAttributionCampaignKeyword:function(){return X()[1]},getAttributionReferrerTimestamp:function(){return X()[2]},getAttributionReferrerUrl:function(){return X()[3]},setTrackerUrl:function(cb){aa=cb},getTrackerUrl:function(){return aa},getSiteId:function(){return bS},setSiteId:function(cb){b7(cb)},setUserId:function(cb){aJ=cb;a2=aX(aJ).substr(0,16)},getUserId:function(){return aJ},setCustomData:function(cb,cc){if(J(cb)){an=cb}else{if(!an){an={}}an[cb]=cc}},getCustomData:function(){return an},setCustomRequestProcessing:function(cb){au=cb},appendToTrackingUrl:function(cb){bo=cb},getRequest:function(cb){return aT(cb)},addPlugin:function(cb,cc){a[cb]=cc},setCustomVariable:function(cc,cb,cf,cd){var ce;if(!w(cd)){cd="visit"}if(!w(cb)){return}if(!w(cf)){cf=""}if(cc>0){cb=!n(cb)?String(cb):cb;cf=!n(cf)?String(cf):cf;ce=[cb.slice(0,bX),cf.slice(0,bX)];if(cd==="visit"||cd===2){Z();ag[cc]=ce}else{if(cd==="page"||cd===3){bg[cc]=ce +}else{if(cd==="event"){ab[cc]=ce}}}}},getCustomVariable:function(cc,cd){var cb;if(!w(cd)){cd="visit"}if(cd==="page"||cd===3){cb=bg[cc]}else{if(cd==="event"){cb=ab[cc]}else{if(cd==="visit"||cd===2){Z();cb=ag[cc]}}}if(!w(cb)||(cb&&cb[0]==="")){return false}return cb},deleteCustomVariable:function(cb,cc){if(this.getCustomVariable(cb,cc)){this.setCustomVariable(cb,"","",cc)}},storeCustomVariablesInCookie:function(){a6=true},setLinkTrackingTimer:function(cb){bn=cb},setDownloadExtensions:function(cb){aU=cb},addDownloadExtensions:function(cb){aU+="|"+cb},setDomains:function(cb){bp=n(cb)?[cb]:cb;bp.push(bM)},setIgnoreClasses:function(cb){ai=n(cb)?[cb]:cb},setRequestMethod:function(cb){bs=cb||bq},setRequestContentType:function(cb){aK=cb||bc},setReferrerUrl:function(cb){bu=cb},setCustomUrl:function(cb){a7=bL(b4,cb)},setDocumentTitle:function(cb){aS=cb},setAPIUrl:function(cb){ax=cb},setDownloadClasses:function(cb){bf=n(cb)?[cb]:cb},setLinkClasses:function(cb){aC=n(cb)?[cb]:cb},setCampaignNameKey:function(cb){aY=n(cb)?[cb]:cb +},setCampaignKeywordKey:function(cb){aP=n(cb)?[cb]:cb},discardHashTag:function(cb){ak=cb},setCookieNamePrefix:function(cb){b2=cb;ag=ar()},setCookieDomain:function(cb){aq=y(cb);bk()},setCookiePath:function(cb){b3=cb;bk()},setVisitorCookieTimeout:function(cb){aw=cb*1000},setSessionCookieTimeout:function(cb){az=cb*1000},setReferralCookieTimeout:function(cb){ba=cb*1000},setConversionAttributionFirstReferrer:function(cb){a5=cb},disableCookies:function(){ao=true;bT.cookie="0"},deleteCookies:function(){W()},setDoNotTrack:function(cc){var cb=e.doNotTrack||e.msDoNotTrack;bW=cc&&(cb==="yes"||cb==="1");if(bW){this.disableCookies()}},addListener:function(cc,cb){bA(cc,cb)},enableLinkTracking:function(cb){bU=true;if(p){bd(cb)}else{E.push(function(){bd(cb)})}},enableJSErrorTracking:function(){if(b8){return}b8=true;var cb=G.onerror;G.onerror=function(cg,ce,cd,cf,cc){aM(function(){var ch="JavaScript Errors";var ci=ce+":"+cd;if(cf){ci+=":"+cf}al(ch,ci,cg)});if(cb){return cb(cg,ce,cd,cf,cc)}return false}},disablePerformanceTracking:function(){a1=false +},setGenerationTimeMs:function(cb){aI=parseInt(cb,10)},setHeartBeatTimer:function(cd,cc){var cb=new Date();aj=cb.getTime()+cd*1000;aG=cc*1000},killFrame:function(){if(G.location!==G.top.location){G.top.location=G.location}},redirectFile:function(cb){if(G.location.protocol==="file:"){G.location=cb}},setCountPreRendered:function(cb){a0=cb},trackGoal:function(cb,cd,cc){aM(function(){bl(cb,cd,cc)})},trackLink:function(cc,cb,ce,cd){aM(function(){bO(cc,cb,ce,cd)})},trackPageView:function(cb,cc){ah=[];if(A(bS)){aM(function(){M(aa,ax,bS)})}else{aM(function(){be(cb,cc)})}},trackAllContentImpressions:function(){if(A(bS)){return}aM(function(){aA(function(){var cb=m.findContentNodes();var cc=aW(cb);ap(cc,bn)})})},trackVisibleContentImpressions:function(cb,cc){if(A(bS)){return}if(!w(cb)){cb=true}if(!w(cc)){cc=750}bv(cb,cc,this);aM(function(){aQ(function(){var cd=m.findContentNodes();var ce=bC(cd);ap(ce,bn)})})},trackContentImpression:function(cd,cb,cc){if(A(bS)){return}if(!cd){return}cb=cb||"Unknown"; +aM(function(){var ce=bN(cd,cb,cc);aZ(ce,bn)})},trackContentImpressionsWithinNode:function(cb){if(A(bS)||!cb){return}aM(function(){if(ay){aQ(function(){var cc=m.findContentNodesWithinNode(cb);var cd=bC(cc);ap(cd,bn)})}else{aA(function(){var cc=m.findContentNodesWithinNode(cb);var cd=aW(cc);ap(cd,bn)})}})},trackContentInteraction:function(cd,ce,cb,cc){if(A(bS)){return}if(!cd||!ce){return}cb=cb||"Unknown";aM(function(){var cf=b6(cd,ce,cb,cc);aZ(cf,bn)})},trackContentInteractionNode:function(cc,cb){if(A(bS)||!cc){return}aM(function(){var cd=aV(cc,cb);aZ(cd,bn)})},trackEvent:function(cc,ce,cb,cd){aM(function(){al(cc,ce,cb,cd)})},trackSiteSearch:function(cb,cd,cc){aM(function(){aN(cb,cd,cc)})},setEcommerceView:function(ce,cb,cd,cc){if(!w(cd)||!cd.length){cd=""}else{if(cd instanceof Array){cd=JSON2.stringify(cd)}}bg[5]=["_pkc",cd];if(w(cc)&&String(cc).length){bg[2]=["_pkp",cc]}if((!w(ce)||!ce.length)&&(!w(cb)||!cb.length)){return}if(w(ce)&&ce.length){bg[3]=["_pks",ce]}if(!w(cb)||!cb.length){cb="" +}bg[4]=["_pkn",cb]},addEcommerceItem:function(cf,cb,cd,cc,ce){if(cf.length){bF[cf]=[cf,cb,cd,cc,ce]}},trackEcommerceOrder:function(cb,cf,ce,cd,cc,cg){bJ(cb,cf,ce,cd,cc,cg)},trackEcommerceCartUpdate:function(cb){b1(cb)}}}function v(){return{push:R}}U(G,"beforeunload",S,false);o();Date.prototype.getTimeAlias=Date.prototype.getTime;L=new D();var r={setTrackerUrl:1,setAPIUrl:1,setUserId:1,setSiteId:1,disableCookies:1,enableLinkTracking:1};var d;for(t=0;t<_paq.length;t++){d=_paq[t][0];if(r[d]){R(_paq[t]);delete _paq[t];if(r[d]>1){if(console!==undefined&&console&&console.error){console.error("The method "+d+' is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers')}}r[d]++}}for(t=0;t<_paq.length;t++){if(_paq[t]){R(_paq[t])}}_paq=new v();c={addPlugin:function(W,X){a[W]=X},getTracker:function(W,X){if(!w(X)){X=this.getAsyncTracker().getSiteId() +}if(!w(W)){W=this.getAsyncTracker().getTrackerUrl()}return new D(W,X)},getAsyncTracker:function(){return L}};if(typeof define==="function"&&define.amd){define("piwik",[],function(){return c})}return c}())}if(window&&window.piwikAsyncInit){window.piwikAsyncInit()}(function(){var a=(typeof AnalyticsTracker);if(a==="undefined"){AnalyticsTracker=Piwik}}());if(typeof piwik_log!=="function"){piwik_log=function(b,f,d,g){function a(h){try{return eval("piwik_"+h)}catch(i){}return}var c,e=Piwik.getTracker(d,f);e.setDocumentTitle(b);e.setCustomData(g);c=a("tracker_pause");if(c){e.setLinkTrackingTimer(c)}c=a("download_extensions");if(c){e.setDownloadExtensions(c)}c=a("hosts_alias");if(c){e.setDomains(c)}c=a("ignore_classes");if(c){e.setIgnoreClasses(c)}e.trackPageView();if(a("install_tracker")){piwik_track=function(i,k,j,h){e.setSiteId(k);e.setTrackerUrl(j);e.trackLink(i,h)};e.enableLinkTracking()}}; /*! @license-end */ };
\ No newline at end of file diff --git a/plugins/API/API.php b/plugins/API/API.php index 2f5f21f4e9..06f04447e2 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -196,7 +196,7 @@ class API extends \Piwik\Plugin\API 'segment' => 'visitIp', 'acceptedValues' => '13.54.122.1. </code>Select IP ranges with notation: <code>visitIp>13.54.122.0;visitIp<13.54.122.255', 'sqlSegment' => 'log_visit.location_ip', - 'sqlFilterValue' => array('Piwik\IP', 'P2N'), + 'sqlFilterValue' => array('Piwik\Network\IPUtils', 'stringToBinaryIP'), 'permission' => $isAuthenticatedWithViewAccess, ); diff --git a/plugins/Actions/API.php b/plugins/Actions/API.php index 91f1d70c45..7204ddfa50 100644 --- a/plugins/Actions/API.php +++ b/plugins/Actions/API.php @@ -9,7 +9,6 @@ namespace Piwik\Plugins\Actions; use Exception; -use Piwik\API\Request; use Piwik\Archive; use Piwik\Common; use Piwik\DataTable; @@ -84,14 +83,17 @@ class API extends \Piwik\Plugin\API * @param bool $expanded * @param bool|int $idSubtable * @param bool|int $depth + * @param bool|int $flat * * @return DataTable|DataTable\Map */ public function getPageUrls($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, - $depth = false) + $depth = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); - $this->filterActionsDataTable($dataTable, $expanded); + $dataTable = Archive::createDataTableFromArchive('Actions_actions_url', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable, $depth); + + $this->filterActionsDataTable($dataTable); + return $dataTable; } @@ -167,17 +169,19 @@ class API extends \Piwik\Plugin\API public function getPageUrl($pageUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_actions_url', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageUrl, Action::TYPE_PAGE_URL); $this->addPageProcessedMetrics($dataTable); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getPageTitles($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $idSubtable); - $this->filterActionsDataTable($dataTable, $expanded); + $dataTable = Archive::createDataTableFromArchive('Actions_actions', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); + + $this->filterActionsDataTable($dataTable); + return $dataTable; } @@ -207,38 +211,38 @@ class API extends \Piwik\Plugin\API public function getPageTitle($pageName, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_actions', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $pageName, Action::TYPE_PAGE_TITLE); $this->addPageProcessedMetrics($dataTable); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getDownloads($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $dataTable = Archive::createDataTableFromArchive('Actions_downloads', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); $this->filterActionsDataTable($dataTable, $expanded); return $dataTable; } public function getDownload($downloadUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_downloads', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $downloadUrl, Action::TYPE_DOWNLOAD); $this->filterActionsDataTable($dataTable); return $dataTable; } - public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false) + public function getOutlinks($idSite, $period, $date, $segment = false, $expanded = false, $idSubtable = false, $flat = false) { - $dataTable = $this->getDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $idSubtable); + $dataTable = Archive::createDataTableFromArchive('Actions_outlink', $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); $this->filterActionsDataTable($dataTable, $expanded); return $dataTable; } public function getOutlink($outlinkUrl, $idSite, $period, $date, $segment = false) { - $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $idSubtable = false); + $callBackParameters = array('Actions_outlink', $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null); $dataTable = $this->getFilterPageDatatableSearch($callBackParameters, $outlinkUrl, Action::TYPE_OUTLINK); $this->filterActionsDataTable($dataTable); return $dataTable; @@ -249,6 +253,7 @@ class API extends \Piwik\Plugin\API $dataTable = $this->getSiteSearchKeywordsRaw($idSite, $period, $date, $segment); $dataTable->deleteColumn(PiwikMetrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable); return $dataTable; } @@ -266,7 +271,7 @@ class API extends \Piwik\Plugin\API protected function getSiteSearchKeywordsRaw($idSite, $period, $date, $segment) { - $dataTable = $this->getDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); + $dataTable = Archive::createDataTableFromArchive('Actions_sitesearch', $idSite, $period, $date, $segment, $expanded = false); return $dataTable; } @@ -284,6 +289,7 @@ class API extends \Piwik\Plugin\API $dataTable->deleteRow(DataTable::ID_SUMMARY_ROW); $dataTable->deleteColumn(PiwikMetrics::INDEX_SITE_SEARCH_HAS_NO_RESULT); $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable); return $dataTable; } @@ -332,6 +338,7 @@ class API extends \Piwik\Plugin\API } } $this->filterActionsDataTable($dataTable); + $dataTable->filter('ReplaceColumnNames'); $this->addPagesPerSearchColumn($dataTable, $columnToRead = 'nb_actions'); return $dataTable; } @@ -361,7 +368,7 @@ class API extends \Piwik\Plugin\API if ($table === false) { // fetch the data table - $table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters); + $table = call_user_func_array('\Piwik\Archive::createDataTableFromArchive', $callBackParameters); if ($table instanceof DataTable\Map) { // search an array of tables, e.g. when using date=last30 @@ -424,7 +431,7 @@ class API extends \Piwik\Plugin\API // match found on this level and more levels remaining: go deeper $idSubTable = $row->getIdSubDataTable(); - $callBackParameters[6] = $idSubTable; + $callBackParameters[7] = $idSubTable; /** * @var \Piwik\Period $period @@ -434,7 +441,7 @@ class API extends \Piwik\Plugin\API $callBackParameters[3] = $period->getDateStart() . ',' . $period->getDateEnd(); } - $table = call_user_func_array(array($this, 'getDataTableFromArchive'), $callBackParameters); + $table = call_user_func_array('\Piwik\Archive::createDataTableFromArchive', $callBackParameters); return $this->doFilterPageDatatableSearch($callBackParameters, $table, $searchTree); } @@ -445,28 +452,15 @@ class API extends \Piwik\Plugin\API * Common filters for all Actions API * * @param DataTable|DataTable\Simple|DataTable\Map $dataTable - * @param bool $expanded */ - protected function filterActionsDataTable($dataTable, $expanded = false) + private function filterActionsDataTable($dataTable) { // Must be applied before Sort in this case, since the DataTable can contain both int and strings indexes // (in the transition period between pre 1.2 and post 1.2 datatable structure) - $dataTable->filter('ReplaceColumnNames'); - $dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false, $expanded)); - $dataTable->filter(function (DataTable $dataTable) { - foreach ($dataTable->getRows() as $row) { - $url = $row->getMetadata('url'); - if ($url) { - $row->setMetadata('segmentValue', urldecode($url)); - } - } - }); - $dataTable->filter('GroupBy', array('label', function ($label) { - return urldecode($label); - })); + $dataTable->filter('Piwik\Plugins\Actions\DataTable\Filter\Actions'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); + return $dataTable; } /** @@ -500,11 +494,6 @@ class API extends \Piwik\Plugin\API ); } - protected function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $depth = null) - { - return Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); - } - private function addPageProcessedMetrics(DataTable\DataTableInterface $dataTable) { $dataTable->filter(function (DataTable $table) { diff --git a/plugins/Actions/DataTable/Filter/Actions.php b/plugins/Actions/DataTable/Filter/Actions.php new file mode 100644 index 0000000000..71abe4190b --- /dev/null +++ b/plugins/Actions/DataTable/Filter/Actions.php @@ -0,0 +1,53 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\Actions\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; + +class Actions extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + $table->filter(function (DataTable $dataTable) { + foreach ($dataTable->getRows() as $row) { + $url = $row->getMetadata('url'); + if ($url) { + $row->setMetadata('segmentValue', urldecode($url)); + } + } + }); + + // TODO can we remove this one again? + $table->queueFilter('GroupBy', array('label', function ($label) { + return urldecode($label); + })); + + foreach ($table->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $this->filter($subtable); + } + } + } +}
\ No newline at end of file diff --git a/plugins/Actions/Reports/Base.php b/plugins/Actions/Reports/Base.php index 68bca741a3..6d7146ae73 100644 --- a/plugins/Actions/Reports/Base.php +++ b/plugins/Actions/Reports/Base.php @@ -22,6 +22,7 @@ abstract class Base extends \Piwik\Plugin\Report { $this->category = 'General_Actions'; $this->processedMetrics = false; + $this->recursiveLabelSeparator = '/'; } protected function addBaseDisplayProperties(ViewDataTable $view) diff --git a/plugins/CoreAdminHome/Menu.php b/plugins/CoreAdminHome/Menu.php index 311fa6dda1..7994979450 100644 --- a/plugins/CoreAdminHome/Menu.php +++ b/plugins/CoreAdminHome/Menu.php @@ -57,14 +57,16 @@ class Menu extends \Piwik\Plugin\Menu public function configureUserMenu(MenuUser $menu) { - $menu->addManageItem('CoreAdminHome_TrackingCode', - $this->urlForAction('trackingCodeGenerator'), - $order = 10); + if (!Piwik::isUserIsAnonymous()) { + $menu->addManageItem('CoreAdminHome_TrackingCode', + $this->urlForAction('trackingCodeGenerator'), + $order = 10); - if (SettingsManager::hasUserPluginsSettingsForCurrentUser()) { - $menu->addPersonalItem('CoreAdminHome_PluginSettings', - $this->urlForAction('userPluginSettings'), - $order = 15); + if (SettingsManager::hasUserPluginsSettingsForCurrentUser()) { + $menu->addPersonalItem('CoreAdminHome_PluginSettings', + $this->urlForAction('userPluginSettings'), + $order = 15); + } } } diff --git a/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php b/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php new file mode 100644 index 0000000000..646c48e862 --- /dev/null +++ b/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php @@ -0,0 +1,47 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +namespace Piwik\Plugins\CoreHome\Columns\Metrics; + +use Piwik\DataTable\Row; +use Piwik\Plugin\ProcessedMetric; + +class CallableProcessedMetric extends ProcessedMetric +{ + private $name; + private $callback; + private $dependentMetrics; + + public function __construct($name, $callback, $dependentMetrics = array()) + { + $this->name = $name; + $this->callback = $callback; + $this->dependentMetrics = $dependentMetrics; + } + + public function getName() + { + return $this->name; + } + + public function compute(Row $row) + { + if ($this->callback) { + return call_user_func($this->callback, $row); + } + } + + public function getTranslatedName() + { + return ''; + } + + public function getDependentMetrics() + { + return $this->dependentMetrics; + } +}
\ No newline at end of file diff --git a/plugins/CoreHome/Controller.php b/plugins/CoreHome/Controller.php index 7fdfe1d814..38474e5bb7 100644 --- a/plugins/CoreHome/Controller.php +++ b/plugins/CoreHome/Controller.php @@ -49,17 +49,11 @@ class Controller extends \Piwik\Plugin\Controller return 'redirectToCoreHomeIndex'; } - public function renderReportMenu($reportModule = null, $reportAction = null) + public function renderReportMenu(Report $report) { Piwik::checkUserHasSomeViewAccess(); $this->checkSitePermission(); - $report = Report::factory($reportModule, $reportAction); - - if (empty($report)) { - throw new Exception($this->translator->translate('General_ExceptionReportNotFound')); - } - $report->checkIsEnabled(); $menuTitle = $report->getMenuTitle(); @@ -69,38 +63,26 @@ class Controller extends \Piwik\Plugin\Controller } $menuTitle = $this->translator->translate($menuTitle); - $content = $this->renderReportWidget($reportModule, $reportAction); + $content = $this->renderReportWidget($report); return View::singleReport($menuTitle, $content); } - public function renderReportWidget($reportModule = null, $reportAction = null) + public function renderReportWidget(Report $report) { Piwik::checkUserHasSomeViewAccess(); $this->checkSitePermission(); - $report = Report::factory($reportModule, $reportAction); - - if (empty($report)) { - throw new Exception($this->translator->translate('General_ExceptionReportNotFound')); - } - $report->checkIsEnabled(); return $report->render(); } - public function renderWidget($widgetModule = null, $widgetAction = null) + public function renderWidget(PluginWidgets $widget, $method) { Piwik::checkUserHasSomeViewAccess(); - $widget = PluginWidgets::factory($widgetModule, $widgetAction); - - if (!empty($widget)) { - return $widget->$widgetAction(); - } - - throw new Exception($this->translator->translate('General_ExceptionWidgetNotFound')); + return $widget->$method(); } function redirectToCoreHomeIndex() diff --git a/plugins/CoreHome/DataTableRowAction/RowEvolution.php b/plugins/CoreHome/DataTableRowAction/RowEvolution.php index e1bc8d5d4f..85fa98bc3e 100644 --- a/plugins/CoreHome/DataTableRowAction/RowEvolution.php +++ b/plugins/CoreHome/DataTableRowAction/RowEvolution.php @@ -198,6 +198,7 @@ class RowEvolution $view->config->columns_to_display = array_keys($metrics ? : $this->graphMetrics); } + $view->requestConfig->request_parameters_to_modify['label'] = ''; $view->config->show_goals = false; $view->config->show_search = false; $view->config->show_all_views_icons = false; diff --git a/plugins/CoreHome/Menu.php b/plugins/CoreHome/Menu.php index 6881b689da..b2c239885e 100644 --- a/plugins/CoreHome/Menu.php +++ b/plugins/CoreHome/Menu.php @@ -12,6 +12,7 @@ use Piwik\Db; use Piwik\Menu\MenuTop; use Piwik\Menu\MenuUser; use Piwik\Piwik; +use Piwik\Plugin; use Piwik\Plugins\UsersManager\API as APIUsersManager; class Menu extends \Piwik\Plugin\Menu @@ -25,7 +26,15 @@ class Menu extends \Piwik\Plugin\Menu $login = $user['alias']; } - $menu->addItem($login, null, array('module' => 'UsersManager', 'action' => 'userSettings'), 998); + if (Piwik::isUserIsAnonymous()) { + if (Plugin\Manager::getInstance()->isPluginActivated('Feedback')) { + $menu->addItem($login, null, array('module' => 'Feedback', 'action' => 'index'), 998); + } else { + $menu->addItem($login, null, array('module' => 'API', 'action' => 'listAllAPI'), 998); + } + } else { + $menu->addItem($login, null, array('module' => 'UsersManager', 'action' => 'userSettings'), 998); + } $module = $this->getLoginModule(); if (Piwik::isUserIsAnonymous()) { diff --git a/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less index a2155872e4..da8b79a1fc 100644 --- a/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less +++ b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less @@ -15,7 +15,6 @@ right: -15px; color: @theme-color-link; display: inline; - content: " \25BC"; font-size: 1px; height: 0px; width: 0px; diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php index 1e2674c14e..e494923f34 100644 --- a/plugins/CorePluginsAdmin/Controller.php +++ b/plugins/CorePluginsAdmin/Controller.php @@ -418,7 +418,7 @@ class Controller extends Plugin\ControllerAdmin $message = $this->translator->translate('CorePluginsAdmin_SuccessfullyActicated', array($pluginName)); if (SettingsManager::hasSystemPluginSettingsForCurrentUser($pluginName)) { $target = sprintf('<a href="index.php%s#%s">', - Url::getCurrentQueryStringWithParametersModified(array('module' => 'CoreAdminHome', 'action' => 'pluginSettings')), + Url::getCurrentQueryStringWithParametersModified(array('module' => 'CoreAdminHome', 'action' => 'adminPluginSettings')), $pluginName); $message .= ' ' . $this->translator->translate('CorePluginsAdmin_ChangeSettingsPossible', array($target, '</a>')); } diff --git a/plugins/CorePluginsAdmin/MarketplaceApiClient.php b/plugins/CorePluginsAdmin/MarketplaceApiClient.php index 6472da3dea..37020ef8cd 100644 --- a/plugins/CorePluginsAdmin/MarketplaceApiClient.php +++ b/plugins/CorePluginsAdmin/MarketplaceApiClient.php @@ -18,7 +18,7 @@ use Piwik\Version; class MarketplaceApiClient { const CACHE_TIMEOUT_IN_SECONDS = 1200; - const HTTP_REQUEST_TIMEOUT = 3; + const HTTP_REQUEST_TIMEOUT = 10; private $domain = 'http://plugins.piwik.org'; diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php index 6d6cf69aaf..998ef3ece7 100644 --- a/plugins/CoreUpdater/Controller.php +++ b/plugins/CoreUpdater/Controller.php @@ -37,6 +37,8 @@ use Piwik\View; class Controller extends \Piwik\Plugin\Controller { const PATH_TO_EXTRACT_LATEST_VERSION = '/latest/'; + const LATEST_VERSION_URL = '://builds.piwik.org/piwik.zip'; + const LATEST_BETA_VERSION_URL = '://builds.piwik.org/piwik-%s.zip'; private $coreError = false; private $warningMessages = array(); @@ -48,9 +50,18 @@ class Controller extends \Piwik\Plugin\Controller protected static function getLatestZipUrl($newVersion) { if (@Config::getInstance()->Debug['allow_upgrades_to_beta']) { - return 'http://builds.piwik.org/piwik-' . $newVersion . '.zip'; + $url = sprintf(self::LATEST_BETA_VERSION_URL, $newVersion); + } else { + $url = self::LATEST_VERSION_URL; + } + + if (self::isUpdatingOverHttps()) { + $url = 'https' . $url; + } else { + $url = 'http' . $url; } - return Config::getInstance()->General['latest_version_url']; + + return $url; } public function newVersionAvailable() @@ -410,4 +421,11 @@ class Controller extends \Piwik\Plugin\Controller return PluginManager::getInstance()->getIncompatiblePlugins($piwikVersion); } + public static function isUpdatingOverHttps() + { + $openSslEnabled = extension_loaded('openssl'); + $usingMethodSupportingHttps = (Http::getTransportMethod() !== 'socket'); + + return $openSslEnabled && $usingMethodSupportingHttps; + } } diff --git a/plugins/CoreVisualizations/CoreVisualizations.php b/plugins/CoreVisualizations/CoreVisualizations.php index 54aa127c91..da8fbe9f94 100644 --- a/plugins/CoreVisualizations/CoreVisualizations.php +++ b/plugins/CoreVisualizations/CoreVisualizations.php @@ -9,6 +9,7 @@ namespace Piwik\Plugins\CoreVisualizations; +use Piwik\Common; use Piwik\ViewDataTable\Manager as ViewDataTableManager; require_once PIWIK_INCLUDE_PATH . '/plugins/CoreVisualizations/JqplotDataGenerator.php'; @@ -28,7 +29,8 @@ class CoreVisualizations extends \Piwik\Plugin 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles', 'AssetManager.getJavaScriptFiles' => 'getJsFiles', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', - 'UsersManager.deleteUser' => 'deleteUser' + 'UsersManager.deleteUser' => 'deleteUser', + 'ViewDataTable.addViewDataTable' => 'addViewDataTable' ); } @@ -37,6 +39,23 @@ class CoreVisualizations extends \Piwik\Plugin ViewDataTableManager::clearUserViewDataTableParameters($userLogin); } + public function addViewDataTable(&$viewDataTable) + { + // Both are the same HtmlTable, just the Pivot one has some extra logic in case Pivot is used. + // We don't want to use the same HtmlTable twice in the UI. Therefore we always need to remove one. + if (Common::getRequestVar('pivotBy', '')) { + $tableToRemove = 'Visualizations\HtmlTable'; + } else { + $tableToRemove = 'HtmlTable\PivotBy'; + } + + foreach ($viewDataTable as $index => $table) { + if (Common::stringEndsWith($table, $tableToRemove)) { + unset($viewDataTable[$index]); + } + } + } + public function getStylesheetFiles(&$stylesheets) { $stylesheets[] = "plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less"; diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable.php b/plugins/CoreVisualizations/Visualizations/HtmlTable.php index dcd2374df9..66cb4c26e9 100644 --- a/plugins/CoreVisualizations/Visualizations/HtmlTable.php +++ b/plugins/CoreVisualizations/Visualizations/HtmlTable.php @@ -70,10 +70,6 @@ class HtmlTable extends Visualization $dataTable = $request->process(); $this->assignTemplateVar('siteSummary', $dataTable); } - - if ($this->requestConfig->pivotBy) { - $this->config->columns_to_display = $this->dataTable->getColumns(); - } } } diff --git a/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php b/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php new file mode 100644 index 0000000000..1703988599 --- /dev/null +++ b/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php @@ -0,0 +1,36 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; + +use Piwik\DataTable; +use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable; +use Piwik\View; + +/** + * DataTable Visualization that derives from HtmlTable and sets show_extra_columns to true. + */ +class PivotBy extends HtmlTable +{ + public function beforeGenericFiltersAreAppliedToLoadedDataTable() + { + $this->config->columns_to_display = $this->dataTable->getColumns(); + + $this->dataTable->applyQueuedFilters(); + + parent::beforeGenericFiltersAreAppliedToLoadedDataTable(); + } + + public function beforeRender() + { + parent::beforeRender(); + + $this->config->columns_to_display = $this->dataTable->getColumns(); + } +} diff --git a/plugins/CustomAlerts b/plugins/CustomAlerts -Subproject 40db402aeacae2d911331ec94df5d86df6f8713 +Subproject 628d2086db2742a29f39173c8be2fd348a64c6a diff --git a/plugins/Events/API.php b/plugins/Events/API.php index bb6584b777..7950cd2637 100644 --- a/plugins/Events/API.php +++ b/plugins/Events/API.php @@ -148,14 +148,17 @@ class API extends \Piwik\Plugin\API } } - protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $secondaryDimension = false) + protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null, $secondaryDimension = false, $flat = false) { Piwik::checkUserHasViewAccess($idSite); $this->checkSecondaryDimension($name, $secondaryDimension); $recordName = $this->getRecordNameForAction($name, $secondaryDimension); - $dataTable = Archive::getDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $idSubtable); - if (empty($idSubtable)) { + $dataTable = Archive::createDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable); + + if ($flat) { + $dataTable->filterSubtables('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet'); + } else { $dataTable->filter('AddSegmentValue', array(function ($label) { if ($label === Archiver::EVENT_NAME_NOT_SET) { return false; @@ -165,23 +168,24 @@ class API extends \Piwik\Plugin\API })); } - $this->filterDataTable($dataTable); + $dataTable->filter('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet'); + return $dataTable; } - public function getCategory($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getCategory($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } - public function getAction($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getAction($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } - public function getName($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false) + public function getName($idSite, $period, $date, $segment = false, $expanded = false, $secondaryDimension = false, $flat = false) { - return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension); + return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, $idSubtable = false, $secondaryDimension, $flat); } public function getActionFromCategoryId($idSite, $period, $date, $idSubtable, $segment = false) @@ -213,20 +217,4 @@ class API extends \Piwik\Plugin\API { return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); } - - /** - * @param DataTable $dataTable - */ - protected function filterDataTable($dataTable) - { - $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS)); - $dataTable->queueFilter('ReplaceColumnNames'); - $dataTable->queueFilter('ReplaceSummaryRowLabel'); - $dataTable->filter(function (DataTable $table) { - $row = $table->getRowFromLabel(Archiver::EVENT_NAME_NOT_SET); - if ($row) { - $row->setColumn('label', Piwik::translate('General_NotDefined', Piwik::translate('Events_EventName'))); - } - }); - } }
\ No newline at end of file diff --git a/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php b/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php new file mode 100644 index 0000000000..a07695b5f8 --- /dev/null +++ b/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php @@ -0,0 +1,39 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\Events\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; +use Piwik\Piwik; +use Piwik\Plugins\Events\Archiver; + +class ReplaceEventNameNotSet extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + $row = $table->getRowFromLabel(Archiver::EVENT_NAME_NOT_SET); + if ($row) { + $row->setColumn('label', Piwik::translate('General_NotDefined', Piwik::translate('Events_EventName'))); + } + } +}
\ No newline at end of file diff --git a/plugins/ExampleUI/Controller.php b/plugins/ExampleUI/Controller.php index 9bfd517aa0..210093c8b5 100644 --- a/plugins/ExampleUI/Controller.php +++ b/plugins/ExampleUI/Controller.php @@ -90,6 +90,7 @@ class Controller extends \Piwik\Plugin\Controller $view = $this->getLastUnitGraphAcrossPlugins($this->pluginName, __FUNCTION__, $columns, $selectableColumns = array('server1', 'server2'), 'My documentation', 'ExampleUI.getTemperaturesEvolution'); $view->requestConfig->filter_sort_column = 'label'; + $view->requestConfig->filter_sort_order = 'asc'; if (empty($view->config->columns_to_display) && !empty($defaultColumns)) { $view->config->columns_to_display = $defaultColumns; diff --git a/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php b/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php index 1ae1c41149..d32290bfdc 100644 --- a/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php +++ b/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php @@ -9,9 +9,11 @@ namespace Piwik\Plugins\Goals\Columns\Metrics; use Piwik\Common; use Piwik\DataTable\Row; +use Piwik\Metrics; use Piwik\Piwik; use Piwik\Plugin\ProcessedMetric; use Piwik\Plugins\Goals\API as GoalsAPI; +use Piwik\Tracker\GoalManager; /** * Base class for processed metrics that are calculated using metrics that are @@ -60,6 +62,16 @@ abstract class GoalSpecificProcessedMetric extends ProcessedMetric $alternateKey = 'idgoal=' . $this->idGoal; if (isset($allGoalMetrics[$alternateKey])) { return $allGoalMetrics[$alternateKey]; + } elseif ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) { + $alternateKey = GoalManager::IDGOAL_ORDER; + if (isset($allGoalMetrics[$alternateKey])) { + return $allGoalMetrics[$alternateKey]; + } + } elseif ($this->idGoal === Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { + $alternateKey = GoalManager::IDGOAL_CART; + if (isset($allGoalMetrics[$alternateKey])) { + return $allGoalMetrics[$alternateKey]; + } } else { return array(); } diff --git a/plugins/Installation/SystemCheck.php b/plugins/Installation/SystemCheck.php index de55506884..93ed1ac75f 100644 --- a/plugins/Installation/SystemCheck.php +++ b/plugins/Installation/SystemCheck.php @@ -19,6 +19,7 @@ use Piwik\Filechecks; use Piwik\Filesystem; use Piwik\Http; use Piwik\Piwik; +use Piwik\Plugins\CoreUpdater; use Piwik\Plugins\UserCountry\LocationProvider; use Piwik\SettingsServer; use Piwik\Url; @@ -102,6 +103,9 @@ class SystemCheck $infos['tracker_status'] = Common::getRequestVar('trackerStatus', 0, 'int'); $infos['is_nfs'] = Filesystem::checkIfFileSystemIsNFS(); + + $infos['https_update'] = CoreUpdater\Controller::isUpdatingOverHttps(); + $infos = self::enrichSystemChecks($infos); return $infos; diff --git a/plugins/Installation/lang/en.json b/plugins/Installation/lang/en.json index 9711d997c3..2d3afbf8c9 100644 --- a/plugins/Installation/lang/en.json +++ b/plugins/Installation/lang/en.json @@ -115,6 +115,8 @@ "SystemCheckCronArchiveProcess": "Archive Cron", "SystemCheckCronArchiveProcessCLI": "Managing processes via CLI", "SystemCheckPhpSetting": "To prevent some critical issue, you must set the following in your php.ini file: %s", + "SystemCheckUpdateHttps": "Update over HTTPS", + "SystemCheckUpdateHttpsNotSupported": "Piwik cannot use HTTPS to update, it will fall back to the insecure HTTP update. Check that CURL or allow_url_fopen is supported and that the openssl PHP extension is installed: http://piwik.org/faq/troubleshooting/faq_177/.", "NotSupported": "not supported", "Tables": "Creating the Tables", "TablesCreatedSuccess": "Tables created with success!", diff --git a/plugins/Installation/templates/_systemCheckSection.twig b/plugins/Installation/templates/_systemCheckSection.twig index f797d18969..b56db9b672 100755 --- a/plugins/Installation/templates/_systemCheckSection.twig +++ b/plugins/Installation/templates/_systemCheckSection.twig @@ -356,6 +356,17 @@ </tr> {% endif %} + <tr> + <td class="label">{{ 'Installation_SystemCheckUpdateHttps'|translate }}</td> + <td> + {% if infos.https_update %} + {{ ok }} + {% else %} + {{ warning }} {{ 'Installation_SystemCheckUpdateHttpsNotSupported'|translate }} + {% endif %} + </td> + </tr> + </table> {% include "@Installation/_integrityDetails.twig" %} diff --git a/plugins/MobileMessaging/Menu.php b/plugins/MobileMessaging/Menu.php index 4612c79d21..8a17e42517 100644 --- a/plugins/MobileMessaging/Menu.php +++ b/plugins/MobileMessaging/Menu.php @@ -23,6 +23,8 @@ class Menu extends \Piwik\Plugin\Menu public function configureUserMenu(MenuUser $menu) { - $menu->addPersonalItem('MobileMessaging_SettingsMenu', $this->urlForAction('userSettings'), $order = 12); + if (!Piwik::isUserIsAnonymous()) { + $menu->addPersonalItem('MobileMessaging_SettingsMenu', $this->urlForAction('userSettings'), $order = 12); + } } } diff --git a/plugins/Morpheus/templates/user.twig b/plugins/Morpheus/templates/user.twig index 8a1bb23976..4234f07296 100644 --- a/plugins/Morpheus/templates/user.twig +++ b/plugins/Morpheus/templates/user.twig @@ -5,8 +5,13 @@ {% set bodyClass = postEvent('Template.bodyClass', 'admin') %} {% block body %} - {% set topMenuModule = 'UsersManager' %} - {% set topMenuAction = 'userSettings' %} + {% if userIsAnonymous %} + {% set topMenuModule = 'Feedback' %} + {% set topMenuAction = 'index' %} + {% else %} + {% set topMenuModule = 'UsersManager' %} + {% set topMenuAction = 'userSettings' %} + {% endif %} {{ parent() }} {% endblock %} diff --git a/plugins/Provider/Columns/Provider.php b/plugins/Provider/Columns/Provider.php index 24ab6cecd2..b7ca57e9c9 100644 --- a/plugins/Provider/Columns/Provider.php +++ b/plugins/Provider/Columns/Provider.php @@ -9,7 +9,7 @@ namespace Piwik\Plugins\Provider\Columns; use Piwik\Common; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Network\IPUtils; use Piwik\Piwik; use Piwik\Plugin\Dimension\VisitDimension; @@ -87,12 +87,17 @@ class Provider extends VisitDimension /** * Returns the hostname given the IP address string * - * @param string $ip IP Address + * @param string $ipStr IP Address * @return string hostname (or human-readable IP address) */ - private function getHost($ip) + private function getHost($ipStr) { - return trim(strtolower(@IP::getHostByAddr($ip))); + $ip = IP::fromStringIP($ipStr); + + $host = $ip->getHostname(); + $host = ($host === null ? $ipStr : $host); + + return trim(strtolower($host)); } public function getName() diff --git a/plugins/QueuedTracking b/plugins/QueuedTracking -Subproject ff1228265cc695fbec8b2997f442ed0bb90f289 +Subproject bc39d75e4c9df58c865ae44f6eae23676e7baf9 diff --git a/plugins/Referrers/API.php b/plugins/Referrers/API.php index 4ac28978e3..aa1a3e0419 100644 --- a/plugins/Referrers/API.php +++ b/plugins/Referrers/API.php @@ -43,7 +43,6 @@ class API extends \Piwik\Plugin\API protected function getDataTable($name, $idSite, $period, $date, $segment, $expanded = false, $idSubtable = null) { $dataTable = Archive::getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, $idSubtable); - $dataTable->filter('Sort', array(Metrics::INDEX_NB_VISITS, 'desc', $naturalSort = false, $expanded)); $dataTable->queueFilter('ReplaceColumnNames'); return $dataTable; } @@ -296,22 +295,24 @@ class API extends \Piwik\Plugin\API return $dataTable; } - public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false) + public function getWebsites($idSite, $period, $date, $segment = false, $expanded = false, $flat = false) { - $dataTable = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded); - $dataTable->filter('AddSegmentByLabel', array('referrerName')); + $dataTable = Archive::createDataTableFromArchive(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded, $flat, $idSubtable = null); + + if ($flat) { + $dataTable->filterSubtables('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId'); + } else { + $dataTable->filter('AddSegmentByLabel', array('referrerName')); + } + return $dataTable; } public function getUrlsFromWebsiteId($idSite, $period, $date, $idSubtable, $segment = false) { $dataTable = $this->getDataTable(Archiver::WEBSITES_RECORD_NAME, $idSite, $period, $date, $segment, $expanded = false, $idSubtable); - // the htmlspecialchars_decode call is for BC for before 1.1 - // as the Referrer URL was previously encoded in the log tables, but is now recorded raw - $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', function ($label) { - return htmlspecialchars_decode($label); - })); - $dataTable->queueFilter('ColumnCallbackReplace', array('label', __NAMESPACE__ . '\getPathFromUrl')); + $dataTable->filter('Piwik\Plugins\Referrers\DataTable\Filter\UrlsFromWebsiteId'); + return $dataTable; } diff --git a/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php b/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php new file mode 100644 index 0000000000..c35d60b463 --- /dev/null +++ b/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php @@ -0,0 +1,46 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\Referrers\DataTable\Filter; + +use Piwik\DataTable\BaseFilter; +use Piwik\DataTable\Row; +use Piwik\DataTable; + +class UrlsFromWebsiteId extends BaseFilter +{ + /** + * Constructor. + * + * @param DataTable $table The table to eventually filter. + */ + public function __construct($table) + { + parent::__construct($table); + } + + /** + * @param DataTable $table + */ + public function filter($table) + { + // the htmlspecialchars_decode call is for BC for before 1.1 + // as the Referrer URL was previously encoded in the log tables, but is now recorded raw + $table->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', function ($label) { + return htmlspecialchars_decode($label); + })); + $table->queueFilter('ColumnCallbackReplace', array('label', 'Piwik\Plugins\Referrers\getPathFromUrl')); + + foreach ($table->getRows() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $this->filter($subtable); + } + } + } +}
\ No newline at end of file diff --git a/plugins/Referrers/Reports/GetWebsites.php b/plugins/Referrers/Reports/GetWebsites.php index e394eb43d9..18f9336a63 100644 --- a/plugins/Referrers/Reports/GetWebsites.php +++ b/plugins/Referrers/Reports/GetWebsites.php @@ -21,6 +21,7 @@ class GetWebsites extends Base $this->dimension = new Website(); $this->name = Piwik::translate('CorePluginsAdmin_Websites'); $this->documentation = Piwik::translate('Referrers_WebsitesReportDocumentation', '<br />'); + $this->recursiveLabelSeparator = '/'; $this->actionToLoadSubTables = 'getUrlsFromWebsiteId'; $this->hasGoalMetrics = true; $this->order = 5; diff --git a/plugins/Referrers/images/searchEngines/extern.peoplecheck.de.png b/plugins/Referrers/images/searchEngines/extern.peoplecheck.de.png Binary files differnew file mode 100644 index 0000000000..6b4b314e3b --- /dev/null +++ b/plugins/Referrers/images/searchEngines/extern.peoplecheck.de.png diff --git a/plugins/Referrers/images/searchEngines/www.qwant.com.png b/plugins/Referrers/images/searchEngines/www.qwant.com.png Binary files differnew file mode 100644 index 0000000000..00946b0bcc --- /dev/null +++ b/plugins/Referrers/images/searchEngines/www.qwant.com.png diff --git a/plugins/Referrers/images/searchEngines/www.sm.de.png b/plugins/Referrers/images/searchEngines/www.sm.de.png Binary files differnew file mode 100644 index 0000000000..45a01277b1 --- /dev/null +++ b/plugins/Referrers/images/searchEngines/www.sm.de.png diff --git a/plugins/SecurityInfo b/plugins/SecurityInfo -Subproject a7e2d59ac96e39e1141808fd944e863dc9588e8 +Subproject f037ffc0d6402d084ff1b6525b4c010f56e4276 diff --git a/plugins/TasksTimetable b/plugins/TasksTimetable -Subproject 4af9daf3388f59159cf0ff9eb60b8bc38461149 +Subproject a0f38fec7cf905d34059ec1b6a207156adf7579 diff --git a/plugins/TestRunner/Aws/config.ini.php b/plugins/TestRunner/Aws/config.ini.php index a6111e5685..65c4498b89 100644 --- a/plugins/TestRunner/Aws/config.ini.php +++ b/plugins/TestRunner/Aws/config.ini.php @@ -12,6 +12,7 @@ charset = "utf8" request_uri = "/" [database_tests] +username = "root" password = "secure" tables_prefix = "" diff --git a/plugins/TestRunner/Commands/TestsRunOnAws.php b/plugins/TestRunner/Commands/TestsRunOnAws.php index 8ff81fa0a5..65bce267e0 100644 --- a/plugins/TestRunner/Commands/TestsRunOnAws.php +++ b/plugins/TestRunner/Commands/TestsRunOnAws.php @@ -35,9 +35,10 @@ class TestsRunOnAws extends ConsoleCommand { $this->setName('tests:run-aws'); $this->addArgument('testsuite', InputArgument::OPTIONAL, 'Allowed values: ' . implode(', ', $this->allowedTestSuites)); + $this->addArgument('arguments', InputArgument::IS_ARRAY, 'Any additional argument will be passed to the test command.'); $this->addOption('launch-only', null, InputOption::VALUE_NONE, 'Only launches an instance and outputs the connection parameters. Useful if you want to connect via SSH.'); $this->addOption('update-only', null, InputOption::VALUE_NONE, 'Launches an instance, outputs the connection parameters and prepares the instance for a test run but does not actually run the tests. It will also checkout the specified version.'); - $this->addOption('one-instance-per-testsuite', null, InputOption::VALUE_NONE, 'Launches an instance, outputs the connection parameters and prepares the instance for a test run but does not actually run the tests. It will also checkout the specified version.'); + $this->addOption('one-instance-per-testsuite', null, InputOption::VALUE_NONE, 'Launches one instance for system tests and one for ui tests.'); $this->addOption('checkout', null, InputOption::VALUE_REQUIRED, 'Git hash, tag or branch to checkout. Defaults to current hash', $this->getCurrentGitHash()); $this->addOption('patch-file', null, InputOption::VALUE_REQUIRED, 'Apply the given patch file after performing a checkout'); $this->setDescription('Run a specific testsuite on AWS'); @@ -57,15 +58,15 @@ If you want to apply a patch on top of the checked out version you can apply the <comment>./console tests:run-aws --patch-file=test.patch ui</comment> This will checkout the same revision as you are currently on and then apply the patch. To generate a diff use for instance the command <comment>git diff > test.patch</comment>. This feature is still beta and there might be problems with pictures and/or binaries etc. + +You can also pass any argument to the command and they will be forwarded to the test command, for example to run a specific UI test: <comment>./console tests:run-aws ui Dashboard</comment>. '); } - /** - * Execute command like: ./console core:clear-caches - */ protected function execute(InputInterface $input, OutputInterface $output) { $testSuite = $this->getTestSuite($input); + $arguments = $input->getArgument('arguments'); $patchFile = $this->getPatchFile($input); $launchOnly = $input->getOption('launch-only'); $updateOnly = $input->getOption('update-only'); @@ -102,7 +103,7 @@ This feature is still beta and there might be problems with pictures and/or bina return 0; } - $testRunner->runTests($host, $testSuite); + $testRunner->runTests($host, $testSuite, $arguments); $message = $this->buildFinishedMessage($testSuite, $host); $output->writeln("\n$message\n"); diff --git a/plugins/TestRunner/Runner/Remote.php b/plugins/TestRunner/Runner/Remote.php index c0a5c5d8e1..d85c4a268a 100644 --- a/plugins/TestRunner/Runner/Remote.php +++ b/plugins/TestRunner/Runner/Remote.php @@ -55,11 +55,11 @@ class Remote } } - public function runTests($host, $testSuite) + public function runTests($host, $testSuite, array $arguments) { $this->prepareTestRun($host); $this->printVersionInfo(); - $this->doRunTests($testSuite); + $this->doRunTests($testSuite, $arguments); } private function prepareTestRun($host) @@ -74,17 +74,19 @@ class Remote $this->ssh->exec('phantomjs --version'); } - private function doRunTests($testSuite) + private function doRunTests($testSuite, array $arguments) { + $arguments = implode(' ', $arguments); + $this->ssh->exec("ps -ef | grep \"php console tests:run\" | grep -v grep | awk '{print $2}' | xargs kill -9"); if ('all' === $testSuite) { - $this->ssh->exec('php console tests:run --options="--colors"'); + $this->ssh->exec('php console tests:run --options="--colors" ' . $arguments); } elseif ('ui' === $testSuite) { - $this->ssh->exec('php console tests:run-ui --persist-fixture-data --assume-artifacts'); + $this->ssh->exec('php console tests:run-ui --persist-fixture-data --assume-artifacts ' . $arguments); } else { - $this->ssh->exec('php console tests:run --options="--colors" --testsuite="unit"'); - $this->ssh->exec('php console tests:run --options="--colors" --testsuite="' . $testSuite . '"'); + $this->ssh->exec('php console tests:run --options="--colors" --testsuite="unit" ' . $arguments); + $this->ssh->exec('php console tests:run --options="--colors" --testsuite="' . $testSuite . '" ' . $arguments); } if ('system' === $testSuite) { diff --git a/plugins/TestRunner/templates/travis.yml.twig b/plugins/TestRunner/templates/travis.yml.twig index 1971674108..b70a9bf345 100644 --- a/plugins/TestRunner/templates/travis.yml.twig +++ b/plugins/TestRunner/templates/travis.yml.twig @@ -14,7 +14,7 @@ language: php {% if phpVersions|default is empty %} php: - 5.6 - - 5.3 + - 5.3.3 # - hhvm {% else %} php: @@ -102,10 +102,6 @@ install: - rm -rf plugins/$PLUGIN_NAME - mv ../$PLUGIN_NAME plugins - # copy .coveralls.yml if none exists - - if [ ! -f ../coveralls.yml ]; - then cp .coveralls.yml ../coveralls.yml || true; - fi {% endif %} # make sure travis test scripts are always latest (so in older releases/branches, the latest scripts will still be used) @@ -125,11 +121,7 @@ before_script: {% endif %} - ./tests/travis/install_mysql_5.6.sh - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then composer require satooshi/php-coveralls dev-master; - else - phpenv config-rm xdebug.ini; - fi + - phpenv config-rm xdebug.ini; # add always_populate_raw_post_data=-1 to php.ini - echo "always_populate_raw_post_data=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini @@ -191,10 +183,6 @@ after_script: # change directory back to root travis dir - cd $PIWIK_ROOT_DIR - - if ([ -z "$TEST_SUITE" ] || [ -n "$PLUGIN_NAME" ]); - then php vendor/bin/coveralls -v; - fi - # output contents of files w/ debugging info to screen - cat /var/log/nginx/error.log - cat $PIWIK_ROOT_DIR/tmp/php-fpm.log diff --git a/plugins/TreemapVisualization b/plugins/TreemapVisualization -Subproject 5dbb90f027b6d214aebfa4f222975d32e99d783 +Subproject bfd020cb3b25bbdb2bd44747f1c5a09396779d6 diff --git a/plugins/UserCountry/Columns/Country.php b/plugins/UserCountry/Columns/Country.php index db8026ba6b..458e92b068 100644 --- a/plugins/UserCountry/Columns/Country.php +++ b/plugins/UserCountry/Columns/Country.php @@ -12,7 +12,7 @@ use Piwik\Common; use Piwik\Config; use Piwik\Container\StaticContainer; use Piwik\Intl\Data\Provider\RegionDataProvider; -use Piwik\IP; +use Piwik\Network\IP; use Piwik\Piwik; use Piwik\Plugin\Manager; use Piwik\Plugins\Provider\Provider as ProviderProvider; @@ -107,12 +107,17 @@ class Country extends Base /** * Returns the hostname given the IP address string * - * @param string $ip IP Address + * @param string $ipStr IP Address * @return string hostname (or human-readable IP address) */ - private function getHost($ip) + private function getHost($ipStr) { - return trim(strtolower(@IP::getHostByAddr($ip))); + $ip = IP::fromStringIP($ipStr); + + $host = $ip->getHostname(); + $host = ($host === null ? $ipStr : $host); + + return trim(strtolower($host)); } /** diff --git a/plugins/VisitorGenerator b/plugins/VisitorGenerator -Subproject 6a9cf2957920aa19b84ab10b348edbd80c58835 +Subproject 34d796330e256e947ff976f57e350c775dc061d diff --git a/tests/PHPUnit/Integration/HttpTest.php b/tests/PHPUnit/Integration/HttpTest.php index 3e7f38577c..fdb0917765 100644 --- a/tests/PHPUnit/Integration/HttpTest.php +++ b/tests/PHPUnit/Integration/HttpTest.php @@ -115,4 +115,41 @@ class HttpTest extends \PHPUnit_Framework_TestCase $this->assertTrue(is_numeric($result['headers']['Content-Length']), "Content-Length header not numeric!"); $this->assertTrue(in_array($result['headers']['Content-Type'], array('application/zip', 'application/x-zip-compressed'))); } + + /** + * @dataProvider getMethodsToTest + */ + public function testHttpsWorksWithValidCertificate($method) + { + $result = Http::sendHttpRequestBy($method, 'https://builds.piwik.org/LATEST', 10); + + $this->assertStringMatchesFormat('%d.%d.%d', $result); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage curl_exec: SSL + */ + public function testCurlHttpsFailsWithInvalidCertificate() + { + Http::sendHttpRequestBy('curl', 'https://divezone.net', 10); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage failed to open stream: operation failed + */ + public function testFopenHttpsFailsWithInvalidCertificate() + { + Http::sendHttpRequestBy('fopen', 'https://divezone.net', 10); + } + + /** + * We check that HTTPS is not supported with the "socket" method + */ + public function testSocketHttpsWorksEvenWithInvalidCertificate() + { + $result = Http::sendHttpRequestBy('socket', 'https://divezone.net', 10); + $this->assertNotEmpty($result); + } } diff --git a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php index f22408348d..42d247f924 100644 --- a/tests/PHPUnit/System/BackwardsCompatibility1XTest.php +++ b/tests/PHPUnit/System/BackwardsCompatibility1XTest.php @@ -146,6 +146,21 @@ class BackwardsCompatibility1XTest extends SystemTestCase array('VisitFrequency.get', array('idSite' => $idSite, 'date' => '2012-03-06,2012-12-31', 'periods' => array('range'), 'disableArchiving' => true)), + array('Actions.getPageUrls', array('idSite' => $idSite, 'date' => '2012-03-06,2012-12-31', + 'otherRequestParameters' => array('expanded' => '1'), + 'testSuffix' => '_expanded', + 'periods' => array('range'), 'disableArchiving' => true)), + + array('Actions.getPageUrls', array('idSite' => $idSite, 'date' => '2012-03-06,2012-12-31', + 'otherRequestParameters' => array('flat' => '1'), + 'testSuffix' => '_flat', + 'periods' => array('range'), 'disableArchiving' => true)), + + array('Actions.getPageUrls', array('idSite' => $idSite, 'date' => '2012-03-06', + 'otherRequestParameters' => array('idSubtable' => '30'), + 'testSuffix' => '_subtable', + 'periods' => array('day'), 'disableArchiving' => true)), + array('VisitFrequency.get', array('idSite' => $idSite, 'date' => '2012-03-03,2012-12-12', 'periods' => array('month'), 'testSuffix' => '_multipleOldNew', 'disableArchiving' => true)), array($reportsToCompareSeparately, $defaultOptions), diff --git a/tests/PHPUnit/System/BlobReportLimitingTest.php b/tests/PHPUnit/System/BlobReportLimitingTest.php index f40305d1ad..84734b7b80 100755 --- a/tests/PHPUnit/System/BlobReportLimitingTest.php +++ b/tests/PHPUnit/System/BlobReportLimitingTest.php @@ -94,6 +94,23 @@ class BlobReportLimitingTest extends SystemTestCase $this->runApiTests($api, $params); } + /** + * @dataProvider getApiForTesting + */ + public function testApiWithFlattening($apiToCall, $params) + { + if (empty($params['testSuffix'])) { + $params['testSuffix'] = ''; + } + $params['testSuffix'] .= '_flattened'; + if (empty($params['otherRequestParameters'])) { + $params['otherRequestParameters'] = array(); + } + $params['otherRequestParameters']['flat'] = '1'; + + $this->runApiTests($apiToCall, $params); + } + public function testApiWithRankingQuery() { // custom setup diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_expanded__Actions.getPageUrls_range.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_expanded__Actions.getPageUrls_range.xml new file mode 100644 index 0000000000..9e5d3bbf74 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_expanded__Actions.getPageUrls_range.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>/index</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.224</min_time_generation> + <max_time_generation>0.224</max_time_generation> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.224</avg_time_generation> + <url>http://example.org/</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2F</segment> + </row> + <row> + <label>/index.htm</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.023</min_time_generation> + <max_time_generation>0.023</max_time_generation> + <exit_nb_visits>1</exit_nb_visits> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_exit_nb_uniq_visitors>1</sum_daily_exit_nb_uniq_visitors> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.023</avg_time_generation> + <url>http://example.org/index.htm</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm</segment> + </row> + <row> + <label>/index.htm?parameter=Should display</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.234</min_time_generation> + <max_time_generation>0.234</max_time_generation> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>7</entry_nb_actions> + <entry_sum_visit_length>1621</entry_sum_visit_length> + <entry_bounce_count>0</entry_bounce_count> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_entry_nb_uniq_visitors>1</sum_daily_entry_nb_uniq_visitors> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.234</avg_time_generation> + <url>http://example.org/index.htm?parameter=Should display</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm%3Fparameter%3DShould+display</segment> + </row> + <row> + <label>store</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.134</min_time_generation> + <max_time_generation>0.134</max_time_generation> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.134</avg_time_generation> + <subtable> + <row> + <label>/purchase.htm</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.134</min_time_generation> + <max_time_generation>0.134</max_time_generation> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_visits>1</exit_nb_visits> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_entry_nb_uniq_visitors>1</sum_daily_entry_nb_uniq_visitors> + <sum_daily_exit_nb_uniq_visitors>1</sum_daily_exit_nb_uniq_visitors> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.134</avg_time_generation> + <url>http://example.org/store/purchase.htm</url> + </row> + </subtable> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_day.xml new file mode 100644 index 0000000000..ece48d484c --- /dev/null +++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_day.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>index</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.224</min_time_generation> + <max_time_generation>0.224</max_time_generation> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.224</avg_time_generation> + <url>http://example.org/</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2F</segment> + </row> + <row> + <label>index.htm</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.023</min_time_generation> + <max_time_generation>0.023</max_time_generation> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.023</avg_time_generation> + <url>http://example.org/index.htm</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm</segment> + </row> + <row> + <label>index.htm?parameter=Should display</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.234</min_time_generation> + <max_time_generation>0.234</max_time_generation> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>7</entry_nb_actions> + <entry_sum_visit_length>1621</entry_sum_visit_length> + <entry_bounce_count>0</entry_bounce_count> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.234</avg_time_generation> + <url>http://example.org/index.htm?parameter=Should display</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm%3Fparameter%3DShould+display</segment> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_range.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_range.xml new file mode 100644 index 0000000000..47bcc76ce1 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_flat__Actions.getPageUrls_range.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>index</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.224</min_time_generation> + <max_time_generation>0.224</max_time_generation> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.224</avg_time_generation> + <url>http://example.org/</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2F</segment> + </row> + <row> + <label>index.htm</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.023</min_time_generation> + <max_time_generation>0.023</max_time_generation> + <exit_nb_visits>1</exit_nb_visits> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_exit_nb_uniq_visitors>1</sum_daily_exit_nb_uniq_visitors> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.023</avg_time_generation> + <url>http://example.org/index.htm</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm</segment> + </row> + <row> + <label>index.htm?parameter=Should display</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>180</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.234</min_time_generation> + <max_time_generation>0.234</max_time_generation> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>7</entry_nb_actions> + <entry_sum_visit_length>1621</entry_sum_visit_length> + <entry_bounce_count>0</entry_bounce_count> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_entry_nb_uniq_visitors>1</sum_daily_entry_nb_uniq_visitors> + <avg_time_on_page>180</avg_time_on_page> + <bounce_rate>0%</bounce_rate> + <exit_rate>0%</exit_rate> + <avg_time_generation>0.234</avg_time_generation> + <url>http://example.org/index.htm?parameter=Should display</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Findex.htm%3Fparameter%3DShould+display</segment> + </row> + <row> + <label>store/purchase.htm</label> + <nb_visits>1</nb_visits> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.134</min_time_generation> + <max_time_generation>0.134</max_time_generation> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_visits>1</exit_nb_visits> + <sum_daily_nb_uniq_visitors>1</sum_daily_nb_uniq_visitors> + <sum_daily_entry_nb_uniq_visitors>1</sum_daily_entry_nb_uniq_visitors> + <sum_daily_exit_nb_uniq_visitors>1</sum_daily_exit_nb_uniq_visitors> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.134</avg_time_generation> + <url>http://example.org/store/purchase.htm</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fstore%2Fpurchase.htm</segment> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_subtable__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_subtable__Actions.getPageUrls_day.xml new file mode 100644 index 0000000000..22804beb71 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest_subtable__Actions.getPageUrls_day.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>/purchase.htm</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>1</nb_hits_with_time_generation> + <min_time_generation>0.134</min_time_generation> + <max_time_generation>0.134</max_time_generation> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <avg_time_generation>0.134</avg_time_generation> + <url>http://example.org/store/purchase.htm</url> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fstore%2Fpurchase.htm</segment> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_CustomEvents_Events.getAction_flat__API.getProcessedReport_day.xml b/tests/PHPUnit/System/expected/test_CustomEvents_Events.getAction_flat__API.getProcessedReport_day.xml index ee9ccea1e0..acf2eb6cf2 100644 --- a/tests/PHPUnit/System/expected/test_CustomEvents_Events.getAction_flat__API.getProcessedReport_day.xml +++ b/tests/PHPUnit/System/expected/test_CustomEvents_Events.getAction_flat__API.getProcessedReport_day.xml @@ -222,11 +222,7 @@ <result prettyDate="Saturday 9 January 2010" /> </reportData> <reportMetadata> - <result prettyDate="Sunday 3 January 2010"> - <row> - <segment>eventAction==Purchase</segment> - </row> - </result> + <result prettyDate="Sunday 3 January 2010" /> <result prettyDate="Monday 4 January 2010" /> <result prettyDate="Tuesday 5 January 2010" /> <result prettyDate="Wednesday 6 January 2010" /> @@ -235,7 +231,7 @@ <result prettyDate="Saturday 9 January 2010" /> </reportMetadata> <reportTotal> - <nb_visits>38</nb_visits> - <nb_uniq_visitors>38</nb_uniq_visitors> + <nb_visits>42</nb_visits> + <nb_uniq_visitors>40</nb_uniq_visitors> </reportTotal> </result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_FlattenReports__Actions.getPageUrls_week.xml b/tests/PHPUnit/System/expected/test_FlattenReports__Actions.getPageUrls_week.xml index 59b45f61fe..1f0e32ad7d 100644 --- a/tests/PHPUnit/System/expected/test_FlattenReports__Actions.getPageUrls_week.xml +++ b/tests/PHPUnit/System/expected/test_FlattenReports__Actions.getPageUrls_week.xml @@ -179,6 +179,7 @@ <avg_time_on_page>0</avg_time_on_page> <bounce_rate>0%</bounce_rate> <exit_rate>100%</exit_rate> + <avg_time_generation>0</avg_time_generation> <url>http://example.org/page1.html</url> <segment>pageUrl==http%3A%2F%2Fexample.org%2Fpage1.html</segment> </row> @@ -187,6 +188,9 @@ <nb_visits>1</nb_visits> <nb_hits>1</nb_hits> <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>0</nb_hits_with_time_generation> + <min_time_generation /> + <max_time_generation /> <entry_nb_visits>1</entry_nb_visits> <entry_nb_actions>2</entry_nb_actions> <entry_sum_visit_length>1</entry_sum_visit_length> @@ -196,6 +200,7 @@ <avg_time_on_page>0</avg_time_on_page> <bounce_rate>0%</bounce_rate> <exit_rate>0%</exit_rate> + <avg_time_generation>0</avg_time_generation> <url>http://example.org/sub/dir/dir1/page1.html</url> <segment>pageUrl==http%3A%2F%2Fexample.org%2Fsub%2Fdir%2Fdir1%2Fpage1.html</segment> </row> diff --git a/tests/PHPUnit/System/expected/test_FlattenReports_withAggregate__Actions.getPageUrls_week.xml b/tests/PHPUnit/System/expected/test_FlattenReports_withAggregate__Actions.getPageUrls_week.xml index d02a61ce87..f5a617dfd0 100644 --- a/tests/PHPUnit/System/expected/test_FlattenReports_withAggregate__Actions.getPageUrls_week.xml +++ b/tests/PHPUnit/System/expected/test_FlattenReports_withAggregate__Actions.getPageUrls_week.xml @@ -76,8 +76,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.15</avg_time_generation> <url>http://example.org/dir1/sub/dir/page0.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage0.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage0.html</segment> </row> <row> <label>dir1/sub/dir/page1.html</label> @@ -93,8 +93,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.3</avg_time_generation> <url>http://example.org/dir1/sub/dir/page1.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage1.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage1.html</segment> </row> <row> <label>dir1/sub/dir/page2.html</label> @@ -112,8 +112,8 @@ <exit_rate>100%</exit_rate> <avg_time_generation>0.45</avg_time_generation> <url>http://example.org/dir1/sub/dir/page2.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage2.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir1%2Fsub%2Fdir%2Fpage2.html</segment> </row> <row> <label>dir2</label> @@ -191,8 +191,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.3</avg_time_generation> <url>http://example.org/dir2/sub/dir/page0.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage0.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage0.html</segment> </row> <row> <label>dir2/sub/dir/page1.html</label> @@ -208,8 +208,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.6</avg_time_generation> <url>http://example.org/dir2/sub/dir/page1.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage1.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage1.html</segment> </row> <row> <label>dir2/sub/dir/page2.html</label> @@ -227,8 +227,8 @@ <exit_rate>100%</exit_rate> <avg_time_generation>0.9</avg_time_generation> <url>http://example.org/dir2/sub/dir/page2.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage2.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir2%2Fsub%2Fdir%2Fpage2.html</segment> </row> <row> <label>dir3</label> @@ -306,8 +306,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.45</avg_time_generation> <url>http://example.org/dir3/sub/dir/page0.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage0.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage0.html</segment> </row> <row> <label>dir3/sub/dir/page1.html</label> @@ -323,8 +323,8 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0.9</avg_time_generation> <url>http://example.org/dir3/sub/dir/page1.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage1.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage1.html</segment> </row> <row> <label>dir3/sub/dir/page2.html</label> @@ -342,8 +342,8 @@ <exit_rate>100%</exit_rate> <avg_time_generation>1.35</avg_time_generation> <url>http://example.org/dir3/sub/dir/page2.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage2.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fdir3%2Fsub%2Fdir%2Fpage2.html</segment> </row> <row> <label>page1.html</label> @@ -387,6 +387,9 @@ <nb_visits>1</nb_visits> <nb_hits>1</nb_hits> <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>0</nb_hits_with_time_generation> + <min_time_generation /> + <max_time_generation>0</max_time_generation> <entry_nb_visits>1</entry_nb_visits> <entry_nb_actions>2</entry_nb_actions> <entry_sum_visit_length>1</entry_sum_visit_length> @@ -402,6 +405,9 @@ <nb_visits>1</nb_visits> <nb_hits>1</nb_hits> <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>0</nb_hits_with_time_generation> + <min_time_generation /> + <max_time_generation>0</max_time_generation> <entry_nb_visits>1</entry_nb_visits> <entry_nb_actions>2</entry_nb_actions> <entry_sum_visit_length>1</entry_sum_visit_length> @@ -417,6 +423,9 @@ <nb_visits>1</nb_visits> <nb_hits>1</nb_hits> <sum_time_spent>0</sum_time_spent> + <nb_hits_with_time_generation>0</nb_hits_with_time_generation> + <min_time_generation /> + <max_time_generation /> <entry_nb_visits>1</entry_nb_visits> <entry_nb_actions>2</entry_nb_actions> <entry_sum_visit_length>1</entry_sum_visit_length> @@ -428,7 +437,7 @@ <exit_rate>0%</exit_rate> <avg_time_generation>0</avg_time_generation> <url>http://example.org/sub/dir/dir1/page1.html</url> - <segment>pageUrl==http%3A%2F%2Fexample.org%2Fsub%2Fdir%2Fdir1%2Fpage1.html</segment> <is_aggregate>0</is_aggregate> + <segment>pageUrl==http%3A%2F%2Fexample.org%2Fsub%2Fdir%2Fdir1%2Fpage1.html</segment> </row> </result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getDownloads_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getDownloads_day.xml new file mode 100644 index 0000000000..72511ca5a3 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getDownloads_day.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>cloudsite5.com/20/download</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://cloudsite5.com/20/download</url> + </row> + <row> + <label>cloudsite5.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>cloudsite6.com/24/download</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://cloudsite6.com/24/download</url> + </row> + <row> + <label>cloudsite6.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>cloudsite7.com/28/download</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://cloudsite7.com/28/download</url> + </row> + <row> + <label>cloudsite7.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>Others</label> + <nb_visits>10</nb_visits> + <nb_hits>10</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>10</entry_nb_visits> + <entry_nb_actions>10</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>10</entry_bounce_count> + <exit_nb_visits>10</exit_nb_visits> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getOutlinks_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getOutlinks_day.xml new file mode 100644 index 0000000000..b64d792953 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getOutlinks_day.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>othersite10.com/</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://othersite10.com/</url> + </row> + <row> + <label>othersite10.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>othersite11.com/</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://othersite11.com/</url> + </row> + <row> + <label>othersite11.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>othersite12.com/</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <url>http://othersite12.com/</url> + </row> + <row> + <label>othersite12.com/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + </row> + <row> + <label>Others</label> + <nb_visits>10</nb_visits> + <nb_hits>10</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>10</entry_nb_visits> + <entry_nb_actions>10</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>10</entry_bounce_count> + <exit_nb_visits>10</exit_nb_visits> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageTitles_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageTitles_day.xml new file mode 100644 index 0000000000..cad09e34a6 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageTitles_day.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>title_0/title_0</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>title_0/ - Others</label> + <nb_visits>3</nb_visits> + <nb_hits>3</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>3</entry_nb_visits> + <entry_nb_actions>3</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>3</entry_bounce_count> + <exit_nb_visits>3</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>title_0</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>title_1</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>Others</label> + <nb_visits>19</nb_visits> + <nb_hits>19</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>19</entry_nb_visits> + <entry_nb_actions>19</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>19</entry_bounce_count> + <exit_nb_visits>19</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageUrls_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageUrls_day.xml new file mode 100644 index 0000000000..d040324503 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Actions.getPageUrls_day.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>0/index</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <url>http://piwik.net/0/</url> + <segment>pageUrl==http%3A%2F%2Fpiwik.net%2F0%2F</segment> + </row> + <row> + <label>0/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>1/index</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <url>http://piwik.net/1/</url> + <segment>pageUrl==http%3A%2F%2Fpiwik.net%2F1%2F</segment> + </row> + <row> + <label>1/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>2/index</label> + <nb_visits>1</nb_visits> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_hits>1</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_uniq_visitors>1</entry_nb_uniq_visitors> + <entry_nb_visits>1</entry_nb_visits> + <entry_nb_actions>1</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>1</entry_bounce_count> + <exit_nb_uniq_visitors>1</exit_nb_uniq_visitors> + <exit_nb_visits>1</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + <url>http://piwik.net/2/</url> + <segment>pageUrl==http%3A%2F%2Fpiwik.net%2F2%2F</segment> + </row> + <row> + <label>2/ - Others</label> + <nb_visits>4</nb_visits> + <nb_hits>4</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>4</entry_nb_visits> + <entry_nb_actions>4</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>4</entry_bounce_count> + <exit_nb_visits>4</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> + <row> + <label>Others</label> + <nb_visits>10</nb_visits> + <nb_hits>10</nb_hits> + <sum_time_spent>0</sum_time_spent> + <entry_nb_visits>10</entry_nb_visits> + <entry_nb_actions>10</entry_nb_actions> + <entry_sum_visit_length>0</entry_sum_visit_length> + <entry_bounce_count>10</entry_bounce_count> + <exit_nb_visits>10</exit_nb_visits> + <avg_time_on_page>0</avg_time_on_page> + <bounce_rate>100%</bounce_rate> + <exit_rate>100%</exit_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__CustomVariables.getCustomVariables_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__CustomVariables.getCustomVariables_day.xml new file mode 100644 index 0000000000..174bde9a67 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__CustomVariables.getCustomVariables_day.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>liked - y</label> + <nb_visits>16</nb_visits> + <nb_actions>16</nb_actions> + </row> + <row> + <label>liked - Others</label> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> + </row> + <row> + <label>tweeted - n</label> + <nb_visits>10</nb_visits> + <nb_actions>10</nb_actions> + </row> + <row> + <label>tweeted - Others</label> + <nb_visits>10</nb_visits> + <nb_actions>10</nb_actions> + </row> + <row> + <label>Others</label> + <nb_actions>60</nb_actions> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getBrowserVersions_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getBrowserVersions_day.xml new file mode 100644 index 0000000000..23bcf1329b --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getBrowserVersions_day.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Firefox 6.0</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>15</nb_visits> + <nb_actions>15</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>15</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>browserCode==FF;browserVersion==6.0</segment> + <logo>plugins/DevicesDetection/images/browsers/FF.gif</logo> + </row> + <row> + <label>Internet Explorer 9.0</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>15</nb_visits> + <nb_actions>15</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>15</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>browserCode==IE;browserVersion==9.0</segment> + <logo>plugins/DevicesDetection/images/browsers/IE.gif</logo> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>9</nb_uniq_visitors> + <nb_visits>45</nb_visits> + <nb_actions>45</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>45</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/DevicesDetection/images/browsers/UNK.gif</logo> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getOsVersions_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getOsVersions_day.xml new file mode 100644 index 0000000000..99b2ce1993 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__DevicesDetection.getOsVersions_day.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>GNU/Linux</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>operatingSystemCode==LIN;operatingSystemVersion==</segment> + <logo>plugins/DevicesDetection/images/os/LIN.gif</logo> + </row> + <row> + <label>Android 4.0</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>15</nb_visits> + <nb_actions>15</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>15</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>operatingSystemCode==AND;operatingSystemVersion==4.0</segment> + <logo>plugins/DevicesDetection/images/os/AND.gif</logo> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>8</nb_uniq_visitors> + <nb_visits>40</nb_visits> + <nb_actions>40</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>40</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/DevicesDetection/images/os/UNK.gif</logo> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsCategory_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsCategory_day.xml new file mode 100644 index 0000000000..a8d3e17e53 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsCategory_day.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Books Cat #0</label> + <nb_uniq_visitors>5</nb_uniq_visitors> + <nb_visits>5</nb_visits> + <nb_actions>5</nb_actions> + <avg_price>7.5</avg_price> + <avg_quantity>0</avg_quantity> + <conversion_rate>0%</conversion_rate> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>20</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <avg_price>30</avg_price> + <avg_quantity>0</avg_quantity> + <conversion_rate>0%</conversion_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsName_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsName_day.xml new file mode 100644 index 0000000000..95cb364b56 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsName_day.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Book0</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <avg_price>7.5</avg_price> + <avg_quantity>0</avg_quantity> + <conversion_rate>0%</conversion_rate> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>24</nb_uniq_visitors> + <nb_visits>24</nb_visits> + <nb_actions>24</nb_actions> + <avg_price>180</avg_price> + <avg_quantity>0</avg_quantity> + <conversion_rate>0%</conversion_rate> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsSku_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsSku_day.xml new file mode 100644 index 0000000000..c234bed59e --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Goals.getItemsSku_day.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result />
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getAll_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getAll_day.xml new file mode 100644 index 0000000000..8202c35460 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getAll_day.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>whatever1.com</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <referer_type>3</referer_type> + </row> + <row> + <label>whatever0.com</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>4</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <referer_type>3</referer_type> + </row> + <row> + <label>search term 2</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <referer_type>2</referer_type> + </row> + <row> + <label>this search term</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>3</nb_visits> + <nb_actions>3</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>3</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <referer_type>2</referer_type> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>9</nb_uniq_visitors> + <nb_visits>9</nb_visits> + <nb_actions>9</nb_actions> + <nb_users>0</nb_users> + <max_actions>2</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>9</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getKeywords_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getKeywords_day.xml new file mode 100644 index 0000000000..d093798ebf --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getKeywords_day.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>this search term - Ask</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>this search term - Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>search term 2 - Alexa</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>search term 2 - Babylon</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>7</nb_uniq_visitors> + <nb_visits>7</nb_visits> + <nb_actions>7</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>7</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getReferrerType_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getReferrerType_day.xml new file mode 100644 index 0000000000..f07a335d7a --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getReferrerType_day.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Direct Entry</label> + <nb_uniq_visitors>15</nb_uniq_visitors> + <nb_visits>55</nb_visits> + <nb_actions>55</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>55</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Search Engines - this search term</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>3</nb_visits> + <nb_actions>3</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>3</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Search Engines - search term 2</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Search Engines - Others</label> + <nb_uniq_visitors>7</nb_uniq_visitors> + <nb_visits>7</nb_visits> + <nb_actions>7</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>7</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Websites - whatever0.com</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>4</nb_visits> + <nb_actions>4</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>4</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Websites - whatever1.com</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Websites - Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getSearchEngines_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getSearchEngines_day.xml new file mode 100644 index 0000000000..cb1d70e35b --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getSearchEngines_day.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Google - search term 3</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/Referrers/images/searchEngines/google.com.png</logo> + </row> + <row> + <label>Google - Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/Referrers/images/searchEngines/google.com.png</logo> + </row> + <row> + <label>Yahoo! - search term 4</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/Referrers/images/searchEngines/search.yahoo.com.png</logo> + </row> + <row> + <label>Yahoo! - Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/Referrers/images/searchEngines/search.yahoo.com.png</logo> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>6</nb_uniq_visitors> + <nb_visits>6</nb_visits> + <nb_actions>6</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>6</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <url>URL unknown!</url> + <logo>plugins/Referrers/images/searchEngines/xx.png</logo> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getWebsites_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getWebsites_day.xml new file mode 100644 index 0000000000..3691c0259b --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Referrers.getWebsites_day.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>whatever0.com/index</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <url>http://whatever0.com/0</url> + </row> + <row> + <label>whatever0.com/ - Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <url>Others</url> + </row> + <row> + <label>whatever1.com/index</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <url>http://whatever1.com/0</url> + </row> + <row> + <label>whatever1.com/2</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>1</nb_visits> + <nb_actions>1</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>1</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <url>http://whatever1.com/2</url> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>2</nb_visits> + <nb_actions>2</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>2</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getConfiguration_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getConfiguration_day.xml new file mode 100644 index 0000000000..b90214c2e9 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getConfiguration_day.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>GNU/Linux / Firefox / 1920x1080</label> + <nb_uniq_visitors>2</nb_uniq_visitors> + <nb_visits>10</nb_visits> + <nb_actions>10</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>10</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Android / Chrome / 1280x1024</label> + <nb_uniq_visitors>1</nb_uniq_visitors> + <nb_visits>5</nb_visits> + <nb_actions>5</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>5</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>12</nb_uniq_visitors> + <nb_visits>60</nb_visits> + <nb_actions>60</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>60</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getResolution_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getResolution_day.xml new file mode 100644 index 0000000000..70e4bab0ff --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__Resolution.getResolution_day.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>1920x1080</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>resolution==1920x1080</segment> + </row> + <row> + <label>1280x1024</label> + <nb_uniq_visitors>3</nb_uniq_visitors> + <nb_visits>15</nb_visits> + <nb_actions>15</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>15</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>resolution==1280x1024</segment> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>8</nb_uniq_visitors> + <nb_visits>40</nb_visits> + <nb_actions>40</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>40</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getCity_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getCity_day.xml new file mode 100644 index 0000000000..b9c8e33524 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getCity_day.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Melbourne, Victoria, Australia</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>city==Melbourne;regionCode==07;countryCode==au</segment> + <city_name>Melbourne</city_name> + <region>07</region> + <country>au</country> + <country_name>Australia</country_name> + <region_name>Victoria</region_name> + <logo>plugins/UserCountry/images/flags/au.png</logo> + </row> + <row> + <label>Nice, Provence-Alpes-Cote d&#039;Azur, France</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>city==Nice;regionCode==B8;countryCode==fr</segment> + <city_name>Nice</city_name> + <region>B8</region> + <country>fr</country> + <country_name>France</country_name> + <region_name>Provence-Alpes-Cote d'Azur</region_name> + <logo>plugins/UserCountry/images/flags/fr.png</logo> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>7</nb_uniq_visitors> + <nb_visits>35</nb_visits> + <nb_actions>35</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>35</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/UserCountry/images/flags/xx.png</logo> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getRegion_day.xml b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getRegion_day.xml new file mode 100644 index 0000000000..e1782dfd61 --- /dev/null +++ b/tests/PHPUnit/System/expected/test_reportLimiting_flattened__UserCountry.getRegion_day.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <row> + <label>Victoria, Australia</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>regionCode==07;countryCode==au</segment> + <region>07</region> + <country>au</country> + <country_name>Australia</country_name> + <region_name>Victoria</region_name> + <logo>plugins/UserCountry/images/flags/au.png</logo> + </row> + <row> + <label>Provence-Alpes-Cote d&#039;Azur, France</label> + <nb_uniq_visitors>4</nb_uniq_visitors> + <nb_visits>20</nb_visits> + <nb_actions>20</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>20</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <segment>regionCode==B8;countryCode==fr</segment> + <region>B8</region> + <country>fr</country> + <country_name>France</country_name> + <region_name>Provence-Alpes-Cote d'Azur</region_name> + <logo>plugins/UserCountry/images/flags/fr.png</logo> + </row> + <row> + <label>Others</label> + <nb_uniq_visitors>7</nb_uniq_visitors> + <nb_visits>35</nb_visits> + <nb_actions>35</nb_actions> + <nb_users>0</nb_users> + <max_actions>1</max_actions> + <sum_visit_length>0</sum_visit_length> + <bounce_count>35</bounce_count> + <nb_visits_converted>0</nb_visits_converted> + <logo>plugins/UserCountry/images/flags/xx.png</logo> + </row> +</result>
\ No newline at end of file diff --git a/tests/PHPUnit/Unit/DataTable/RowTest.php b/tests/PHPUnit/Unit/DataTable/RowTest.php index 7033299df5..ae8bf699ef 100644 --- a/tests/PHPUnit/Unit/DataTable/RowTest.php +++ b/tests/PHPUnit/Unit/DataTable/RowTest.php @@ -89,35 +89,9 @@ class RowTest extends \PHPUnit_Framework_TestCase $this->assertColumnSavesValue(array(array(1 => '5')), 'testArray', array(array(1 => '5'))); } - public function test_getColumn_shouldResolveACallable() - { - $this->assertColumnSavesValue(6, 'testClosure', function () { - return 6; - }); - - $this->assertColumnSavesValue(7, 'testCallable', array($this, 'callbackReturnScalar')); - } - - public function test_getColumn_shouldPassRowToCallable() - { - $callbackRow = null; - - $this->row->addColumn('testClosure', function (Row $row) use (&$callbackRow) { - $callbackRow = $row; - return $row; - }); - - $returnedRow = $this->row->getColumn('testClosure'); - $this->assertNotEmpty($callbackRow); - $this->assertSame($returnedRow, $callbackRow); - } - public function test_getColumn_shouldReturnFalseIfValueIsNull() { $this->assertColumnSavesValue(false, 'testScalar', null); - $this->assertColumnSavesValue(false, 'testClosure', function () { - return null; - }); } public function test_getColumns_shouldNotCallAnyCallableForSecurity() @@ -136,64 +110,18 @@ class RowTest extends \PHPUnit_Framework_TestCase $this->row->setColumns(array( 'nb_visits' => 4, 'label' => 'Test', - 'closure' => function () { return 5; }, - 'callable' => array($this, 'callbackReturnScalar'), 'goals' => array(1 => array()) )); $expected = array( 'nb_visits' => 4, 'label' => 'Test', - 'closure' => 5, - 'callable' => 7, 'goals' => array(1 => array()) ); $this->assertEquals($expected, $this->row->getColumns()); } - public function test_getColumns_shouldNotConvertNullValuesToFalse() - { - $this->row->setColumns(array( - 'nb_visits' => null, - 'label' => 'Test', - 'closure' => function () { return null; }, - 'boolean' => false - )); - - $expected = array( - 'nb_visits' => null, - 'label' => 'Test', - 'closure' => null, - 'boolean' => false - ); - - $this->assertSame($expected, $this->row->getColumns()); - } - - public function callbackReturnScalar(Row $row) - { - return 7; - } - - public function test_SumRow_shouldIgnoreCallableValues_AndNotRaiseAnyException() - { - $columns = array( - 'nb_visits' => 5, - 'label' => 'Test', - 'closure' => function () { return 7; }, - ); - - $this->row->setColumns($columns); - - $secondRow = new Row(array(Row::COLUMNS => $columns)); - - $this->row->sumRow($secondRow); - - $this->assertEquals(10, $this->row->getColumn('nb_visits')); - $this->assertEquals(7, $this->row->getColumn('closure')); - } - public function test_sumSubTable_whenSubTableAlreadyExists_overwriteExistingSubtable() { $testRow = $this->getTestRowWithSubDataTableNotLoaded(); diff --git a/tests/PHPUnit/Unit/DeprecatedMethodsTest.php b/tests/PHPUnit/Unit/DeprecatedMethodsTest.php index 8336c73c4e..f1a03890c8 100644 --- a/tests/PHPUnit/Unit/DeprecatedMethodsTest.php +++ b/tests/PHPUnit/Unit/DeprecatedMethodsTest.php @@ -61,6 +61,7 @@ class DeprecatedMethodsTest extends \PHPUnit_Framework_TestCase $this->assertDeprecatedMethodIsRemoved('Piwik\Plugins\UserSettings\UserSettings', 'renameDeprecatedModuleAndAction', $validTill); $this->assertDeprecatedMethodIsRemovedInPiwik3('\Piwik\Menu\MenuAbstract', 'add'); + $this->assertDeprecatedMethodIsRemovedInPiwik3('\Piwik\Archive', 'getDataTableFromArchive'); } private function assertDeprecatedMethodIsRemoved($className, $method, $removalDate) diff --git a/tests/PHPUnit/Unit/IPTest.php b/tests/PHPUnit/Unit/IPTest.php index a78d7ddeca..1fa7ea4a8b 100644 --- a/tests/PHPUnit/Unit/IPTest.php +++ b/tests/PHPUnit/Unit/IPTest.php @@ -64,17 +64,6 @@ class IPTest extends \PHPUnit_Framework_TestCase } /** - * @dataProvider getLong2IPTestData - * @group Core - */ - public function testLong2ip($N, $P) - { - $this->assertEquals($P, IP::long2ip($N), bin2hex($N)); - // this is our compatibility function - $this->assertEquals($P, Common::long2ip($N), bin2hex($N)); - } - - /** * Dataprovider for ip from header tests */ public function getIpFromHeaderTestData() diff --git a/tests/PHPUnit/Unit/UrlHelperTest.php b/tests/PHPUnit/Unit/UrlHelperTest.php index 189b026828..8ae396d7df 100644 --- a/tests/PHPUnit/Unit/UrlHelperTest.php +++ b/tests/PHPUnit/Unit/UrlHelperTest.php @@ -122,6 +122,14 @@ class UrlHelperTest extends \PHPUnit_Framework_TestCase /**
* @group Core
*/
+ public function testGetPathAndQueryFromNonUrl()
+ {
+ $this->assertEquals('Others', UrlHelper::getPathAndQueryFromUrl('Others'));
+ }
+
+ /**
+ * @group Core
+ */
public function testGetArrayFromQueryString()
{
$expected = array(
diff --git a/tests/README.md b/tests/README.md index 8849370096..555f07c5de 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,7 +7,6 @@ We use Travis CI for our continuous integration server. It automatically runs ou after each commit to our GIT repo. More information at the links: * Piwik on Travis CI: https://travis-ci.org/piwik/piwik - * Piwik on Coveralls.io: https://coveralls.io/r/piwik * QA in Piwik: http://piwik.org/qa/ Each core Piwik developer is responsible to keep the build green. If a developer breaks the build, he will receive an email from Travis CI. diff --git a/tests/UI/config.dist.js b/tests/UI/config.dist.js index 93aa763cf1..687cebc0de 100644 --- a/tests/UI/config.dist.js +++ b/tests/UI/config.dist.js @@ -30,7 +30,7 @@ exports.php = 'php'; /** * The folder in tests/lib that holds mocha. */ -exports.mocha = 'mocha-1.17.1'; +exports.mocha = 'mocha-2.1.0'; /** * The folder in tests/lib that holds chai. diff --git a/tests/UI/expected-ui-screenshots b/tests/UI/expected-ui-screenshots -Subproject a19b0967a0b59bdb51c42d3517015789d2aef40 +Subproject 75ef889b855f7b410e6778702ee0a4dc10cabf6 diff --git a/tests/javascript/index.php b/tests/javascript/index.php index 6e1c2cbb5c..20fade48ea 100644 --- a/tests/javascript/index.php +++ b/tests/javascript/index.php @@ -407,6 +407,10 @@ function PiwikTest() { module('externals'); + + // Delete cookies to prevent cookie store from impacting tests + deleteCookies(); + test("JSLint", function() { expect(1); var src = '<?php @@ -1555,7 +1559,9 @@ function PiwikTest() { } strictEqual(actual.indexOf(expectedStartsWith), 0, message + actual + ' should start with ' + expectedStartsWith); - strictEqual(actual.indexOf('&idsite=&rec=1'), expectedStartsWith.length); + + var expectedString = '&idsite=1&rec=1'; + strictEqual(actual.indexOf(expectedString), expectedStartsWith.length, 'did not find ' + expectedString + ' in ' + actual); // make sure it contains all those other tracking stuff directly afterwards so we can assume it did append // the other request stuff and we also make sure to compare the whole custom string as we check from // expectedStartsWith.length @@ -1611,13 +1617,15 @@ function PiwikTest() { strictEqual(actual, undefined, 'nothing set'); actual = tracker.buildContentInteractionTrackingRedirectUrl('/path?a=b'); - assertTrackingRequest(actual, '?redirecturl=' + encodeWrapper(origin + '/path?a=b') + '&c_t=%2Fpath%3Fa%3Db', 'should build redirect url including domain when absolute path. Target should also fallback to passed url if not set'); + assertTrackingRequest(actual, 'piwik.php?redirecturl=' + encodeWrapper(origin + '/path?a=b') + '&c_t=%2Fpath%3Fa%3Db', + 'should build redirect url including domain when absolute path. Target should also fallback to passed url if not set'); actual = tracker.buildContentInteractionTrackingRedirectUrl('path?a=b'); - assertTrackingRequest(actual, '?redirecturl=' + toEncodedAbsoluteUrl('path?a=b') + '&c_t=path%3Fa%3Db', 'should build redirect url including domain when relative path. Target should also fallback to passed url if not set'); + assertTrackingRequest(actual, 'piwik.php?redirecturl=' + toEncodedAbsoluteUrl('path?a=b') + '&c_t=path%3Fa%3Db', + 'should build redirect url including domain when relative path. Target should also fallback to passed url if not set'); actual = tracker.buildContentInteractionTrackingRedirectUrl('#test', 'click', 'name', 'piece', 'target'); - assertTrackingRequest(actual, '?redirecturl=' + toEncodedAbsoluteUrl('#test') + '&c_i=click&c_n=name&c_p=piece&c_t=target', 'all params set'); + assertTrackingRequest(actual, 'piwik.php?redirecturl=' + toEncodedAbsoluteUrl('#test') + '&c_i=click&c_n=name&c_p=piece&c_t=target', 'all params set'); trackerUrl = tracker.getTrackerUrl(); tracker.setTrackerUrl('piwik.php?test=1'); @@ -1725,7 +1733,7 @@ function PiwikTest() { strictEqual($(_s('#ex111')).attr('href'), 'piwik.php?xyz=makesnosense', 'trackContentImpressionClickInteraction, a tracking link should not be changed'); actual = (tracker.trackContentImpressionClickInteraction(_s('#ex112')))({target: _s('#ex112')}); - assertTrackingRequest(actual, 'c_i=click&c_n=img.jpg&c_p=img.jpg&c_t=' + originEncoded + '%2Ftests%2Fjavascript%2F%23example', 'trackContentImpressionClickInteraction, a link that is an anchor should be tracked as XHR and no redirect'); + assertTrackingRequest(actual, 'c_i=click&c_n=img.jpg&c_p=img.jpg&c_t=' + toEncodedAbsoluteUrl('#example'), 'trackContentImpressionClickInteraction, a link that is an anchor should be tracked as XHR and no redirect'); actual = (tracker.trackContentImpressionClickInteraction(_s('#ex113_target')))({target: _s('#ex113_target')}); assertTrackingRequest(actual, 'c_i=click&c_n=img.jpg&c_p=img.jpg', 'trackContentImpressionClickInteraction, if element is not A or AREA it should always use xhr'); @@ -2007,29 +2015,31 @@ function PiwikTest() { }); test("Default visitorId should be equal across Trackers", function() { - expect(4); + expect(5); deleteCookies(); var asyncTracker = Piwik.getAsyncTracker(); - var asyncVistorId = asyncTracker.getVisitorId(); - equal(Piwik.getAsyncTracker().getVisitorId(), asyncVistorId, 'asyncVistorId'); - - wait(2000); + var asyncVisitorId = asyncTracker.getVisitorId(); + equal(Piwik.getAsyncTracker().getSiteId(), asyncTracker.getSiteId(), 'async same site id'); + equal(Piwik.getAsyncTracker().getTrackerUrl(), asyncTracker.getTrackerUrl(), 'async same getTrackerUrl()'); + wait(2000); var delayedTracker = Piwik.getTracker(); var delayedVisitorId = delayedTracker.getVisitorId(); - equal(Piwik.getAsyncTracker().getVisitorId(), delayedVisitorId, 'delayedVisitorId'); + equal(Piwik.getAsyncTracker().getVisitorId(), delayedVisitorId, 'delayedVisitorId ' + delayedVisitorId + ' should be the same as ' + Piwik.getAsyncTracker().getVisitorId()); var prefixTracker = Piwik.getTracker(); prefixTracker.setCookieNamePrefix('_test_cookie_prefix'); var prefixVisitorId = prefixTracker.getVisitorId(); - equal(Piwik.getAsyncTracker().getVisitorId(), prefixVisitorId, 'prefixVisitorId'); + notEqual(Piwik.getAsyncTracker().getVisitorId(), prefixVisitorId, 'Visitor ID are different when using a different cookie prefix'); var customTracker = Piwik.getTracker('customTrackerUrl', '71'); var customVisitorId = customTracker.getVisitorId(); - equal(Piwik.getAsyncTracker().getVisitorId(), customVisitorId, 'customVisitorId'); + notEqual(Piwik.getAsyncTracker().getVisitorId(), customVisitorId, 'Visitor ID are different on different websites'); + + }); test("AnalyticsTracker alias", function() { @@ -2433,10 +2443,11 @@ function PiwikTest() { test("getRequest()", function() { expect(2); - var tracker = Piwik.getTracker(); + var tracker = Piwik.getTracker('hostname', 4); tracker.setCustomData("key is X", "value is Y"); - equal( tracker.getRequest('hello=world').indexOf('hello=world&idsite=&rec=1&r='), 0); + var requestString = tracker.getRequest('hello=world'); + equal( requestString.indexOf('hello=world&idsite=4&rec=1&r='), 0, "Request string " + requestString); ok( -1 !== tracker.getRequest('hello=world').indexOf('send_image=0'), 'should disable sending image response'); }); @@ -2774,7 +2785,7 @@ if ($sqlite) { visitorId1 = Piwik.getAsyncTracker().getVisitorId(); }]); visitorId2 = tracker.getVisitorId(); - ok( visitorId1 && visitorId1 != "" && visitorId2 && visitorId2 != "" && (visitorId1 == visitorId2), "getVisitorId()" ); + ok( visitorId1 && visitorId1 != "" && visitorId2 && visitorId2 != "" && (visitorId1 == visitorId2), "getVisitorId()" + visitorId1 + " VS " + visitorId2 ); var visitorInfo1, visitorInfo2; @@ -2786,7 +2797,7 @@ if ($sqlite) { referrer1 = Piwik.getAsyncTracker().getAttributionReferrerUrl(); }]); visitorInfo2 = tracker.getVisitorInfo(); - ok( visitorInfo1 && visitorInfo2 && visitorInfo1.length == visitorInfo2.length, "getVisitorInfo()" ); + ok( visitorInfo1 && visitorInfo2 && visitorInfo1.length == visitorInfo2.length, "getVisitorInfo() " ); for (var i = 0; i < 6; i++) { ok( visitorInfo1[i] == visitorInfo2[i], "(loadVisitorId())["+i+"]" ); } diff --git a/tests/lib/mocha-1.17.1/.travis.yml b/tests/lib/mocha-1.17.1/.travis.yml deleted file mode 100644 index 09d3ef3784..0000000000 --- a/tests/lib/mocha-1.17.1/.travis.yml +++ /dev/null @@ -1,4 +0,0 @@ -language: node_js -node_js: - - 0.8 - - 0.10 diff --git a/tests/lib/mocha-1.17.1/Readme.md b/tests/lib/mocha-1.17.1/Readme.md deleted file mode 100644 index 9340cacfda..0000000000 --- a/tests/lib/mocha-1.17.1/Readme.md +++ /dev/null @@ -1,172 +0,0 @@ - [![Build Status](https://secure.travis-ci.org/visionmedia/mocha.png)](http://travis-ci.org/visionmedia/mocha) - - [![Mocha test framework](http://f.cl.ly/items/3l1k0n2A1U3M1I1L210p/Screen%20Shot%202012-02-24%20at%202.21.43%20PM.png)](http://visionmedia.github.io/mocha) - - Mocha is a simple, flexible, fun JavaScript test framework for node.js and the browser. For more information view the [documentation](http://visionmedia.github.io/mocha). - -## Contributors - -``` - - project : mocha - repo age : 2 years, 4 months ago - commits : 1314 - active : 372 days - files : 141 - authors : - 582 TJ Holowaychuk 44.3% - 389 Tj Holowaychuk 29.6% - 46 Travis Jeffery 3.5% - 31 Guillermo Rauch 2.4% - 13 Attila Domokos 1.0% - 10 John Firebaugh 0.8% - 8 Jo Liss 0.6% - 7 Nathan Rajlich 0.5% - 6 Mike Pennisi 0.5% - 6 James Carr 0.5% - 6 Brendan Nee 0.5% - 5 Aaron Heckmann 0.4% - 5 Ryunosuke SATO 0.4% - 4 hokaccha 0.3% - 4 Joshua Krall 0.3% - 4 Xavier Antoviaque 0.3% - 3 Jesse Dailey 0.2% - 3 Forbes Lindesay 0.2% - 3 Sindre Sorhus 0.2% - 3 Cory Thomas 0.2% - 3 Fredrik Enestad 0.2% - 3 Ben Lindsey 0.2% - 3 Tyson Tate 0.2% - 3 Mathieu Desvé 0.2% - 3 Valentin Agachi 0.2% - 3 Wil Moore III 0.2% - 3 Merrick Christensen 0.2% - 3 eiji.ienaga 0.2% - 3 fool2fish 0.2% - 3 Nathan Bowser 0.2% - 3 Paul Miller 0.2% - 2 Juzer Ali 0.2% - 2 Pete Hawkins 0.2% - 2 Jonas Westerlund 0.2% - 2 Arian Stolwijk 0.2% - 2 Quang Van 0.2% - 2 Glen Mailer 0.2% - 2 Justin DuJardin 0.2% - 2 FARKAS Máté 0.2% - 2 Raynos 0.2% - 2 Michael Riley 0.2% - 2 Michael Schoonmaker 0.2% - 2 Domenic Denicola 0.2% - 2 Simon Gaeremynck 0.2% - 2 Konstantin Käfer 0.2% - 2 domenic 0.2% - 2 Paul Armstrong 0.2% - 2 fcrisci 0.2% - 2 Alexander Early 0.2% - 2 Shawn Krisman 0.2% - 2 Brian Beck 0.2% - 2 Nathan Alderson 0.2% - 2 David Henderson 0.2% - 2 Timo Tijhof 0.2% - 2 Ian Storm Taylor 0.2% - 2 travis jeffery 0.2% - 1 Matt Smith 0.1% - 1 Matthew Shanley 0.1% - 1 Nathan Black 0.1% - 1 Phil Sung 0.1% - 1 R56 0.1% - 1 Refael Ackermann 0.1% - 1 Richard Dingwall 0.1% - 1 Romain Prieto 0.1% - 1 Roman Neuhauser 0.1% - 1 Roman Shtylman 0.1% - 1 Russ Bradberry 0.1% - 1 Russell Munson 0.1% - 1 Rustem Mustafin 0.1% - 1 Salehen Shovon Rahman 0.1% - 1 Sasha Koss 0.1% - 1 Seiya Konno 0.1% - 1 Simon Goumaz 0.1% - 1 Standa Opichal 0.1% - 1 Stephen Mathieson 0.1% - 1 Steve Mason 0.1% - 1 Tapiwa Kelvin 0.1% - 1 Teddy Zeenny 0.1% - 1 Tim Ehat 0.1% - 1 Vadim Nikitin 0.1% - 1 Victor Costan 0.1% - 1 Will Langstroth 0.1% - 1 Yanis Wang 0.1% - 1 Yuest Wang 0.1% - 1 abrkn 0.1% - 1 airportyh 0.1% - 1 badunk 0.1% - 1 fengmk2 0.1% - 1 grasGendarme 0.1% - 1 lodr 0.1% - 1 tgautier@yahoo.com 0.1% - 1 traleig1 0.1% - 1 vlad 0.1% - 1 yuitest 0.1% - 1 Adam Crabtree 0.1% - 1 Andreas Brekken 0.1% - 1 Andreas Lind Petersen 0.1% - 1 Andrew Nesbitt 0.1% - 1 Andrey Popp 0.1% - 1 Arnaud Brousseau 0.1% - 1 Atsuya Takagi 0.1% - 1 Austin Birch 0.1% - 1 Bjørge Næss 0.1% - 1 Brian Lalor 0.1% - 1 Brian M. Carlson 0.1% - 1 Brian Moore 0.1% - 1 Bryan Donovan 0.1% - 1 Casey Foster 0.1% - 1 ChrisWren 0.1% - 1 Corey Butler 0.1% - 1 Daniel Stockman 0.1% - 1 Dave McKenna 0.1% - 1 Di Wu 0.1% - 1 Dmitry Shirokov 0.1% - 1 Fedor Indutny 0.1% - 1 Florian Margaine 0.1% - 1 Frederico Silva 0.1% - 1 Fredrik Lindin 0.1% - 1 Gareth Murphy 0.1% - 1 Gavin Mogan 0.1% - 1 Glen Huang 0.1% - 1 Greg Perkins 0.1% - 1 Harry Brundage 0.1% - 1 Herman Junge 0.1% - 1 Ian Young 0.1% - 1 Ivan 0.1% - 1 JP Bochi 0.1% - 1 Jaakko Salonen 0.1% - 1 Jakub Nešetřil 0.1% - 1 James Bowes 0.1% - 1 James Lal 0.1% - 1 Jason Barry 0.1% - 1 Javier Aranda 0.1% - 1 Jeff Kunkle 0.1% - 1 Jeremy Martin 0.1% - 1 Jimmy Cuadra 0.1% - 1 Jonathan Creamer 0.1% - 1 Jussi Virtanen 0.1% - 1 Katie Gengler 0.1% - 1 Kazuhito Hokamura 0.1% - 1 Kirill Korolyov 0.1% - 1 Koen Punt 0.1% - 1 Laszlo Bacsi 0.1% - 1 Liam Newman 0.1% - 1 László Bácsi 0.1% - 1 Maciej Małecki 0.1% - 1 Mal Graty 0.1% - 1 Marc Kuo 0.1% - 1 Matt Robenolt 0.1% -``` - -## Links - - - [Google Group](http://groups.google.com/group/mochajs) - - [Wiki](https://github.com/visionmedia/mocha/wiki) - - Mocha [Extensions and reporters](https://github.com/visionmedia/mocha/wiki) diff --git a/tests/lib/mocha-1.17.1/bower.json b/tests/lib/mocha-1.17.1/bower.json deleted file mode 100644 index 84becdabb9..0000000000 --- a/tests/lib/mocha-1.17.1/bower.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "mocha", - "version": "1.17.1", - "main": "mocha.js", - "ignore": [ - "bin", - "editors", - "images", - "lib", - "support", - "test", - ".gitignore", - ".npmignore", - ".travis.yml", - "component.json", - "index.js", - "Makefile", - "package.json" - ] -} diff --git a/tests/lib/mocha-1.17.1/lib/utils.js b/tests/lib/mocha-1.17.1/lib/utils.js deleted file mode 100644 index 37fd5d7e1b..0000000000 --- a/tests/lib/mocha-1.17.1/lib/utils.js +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Module dependencies. - */ - -var fs = require('fs') - , path = require('path') - , join = path.join - , debug = require('debug')('mocha:watch'); - -/** - * Ignored directories. - */ - -var ignore = ['node_modules', '.git']; - -/** - * Escape special characters in the given string of html. - * - * @param {String} html - * @return {String} - * @api private - */ - -exports.escape = function(html){ - return String(html) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/</g, '<') - .replace(/>/g, '>'); -}; - -/** - * Array#forEach (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.forEach = function(arr, fn, scope){ - for (var i = 0, l = arr.length; i < l; i++) - fn.call(scope, arr[i], i); -}; - -/** - * Array#map (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.map = function(arr, fn, scope){ - var result = []; - for (var i = 0, l = arr.length; i < l; i++) - result.push(fn.call(scope, arr[i], i)); - return result; -}; - -/** - * Array#indexOf (<=IE8) - * - * @parma {Array} arr - * @param {Object} obj to find index of - * @param {Number} start - * @api private - */ - -exports.indexOf = function(arr, obj, start){ - for (var i = start || 0, l = arr.length; i < l; i++) { - if (arr[i] === obj) - return i; - } - return -1; -}; - -/** - * Array#reduce (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} initial value - * @api private - */ - -exports.reduce = function(arr, fn, val){ - var rval = val; - - for (var i = 0, l = arr.length; i < l; i++) { - rval = fn(rval, arr[i], i, arr); - } - - return rval; -}; - -/** - * Array#filter (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @api private - */ - -exports.filter = function(arr, fn){ - var ret = []; - - for (var i = 0, l = arr.length; i < l; i++) { - var val = arr[i]; - if (fn(val, i, arr)) ret.push(val); - } - - return ret; -}; - -/** - * Object.keys (<=IE8) - * - * @param {Object} obj - * @return {Array} keys - * @api private - */ - -exports.keys = Object.keys || function(obj) { - var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 - - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - - return keys; -}; - -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @param {Array} files - * @param {Function} fn - * @api private - */ - -exports.watch = function(files, fn){ - var options = { interval: 100 }; - files.forEach(function(file){ - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev){ - if (prev.mtime < curr.mtime) fn(file); - }); - }); -}; - -/** - * Ignored files. - */ - -function ignored(path){ - return !~ignore.indexOf(path); -} - -/** - * Lookup files in the given `dir`. - * - * @return {Array} - * @api private - */ - -exports.files = function(dir, ret){ - ret = ret || []; - - fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) { - ret.push(path); - } - }); - - return ret; -}; - -/** - * Compute a slug from the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.slug = function(str){ - return str - .toLowerCase() - .replace(/ +/g, '-') - .replace(/[^-\w]/g, ''); -}; - -/** - * Strip the function definition from `str`, - * and re-indent for pre whitespace. - */ - -exports.clean = function(str) { - str = str - .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') - .replace(/^function *\(.*\) *{/, '') - .replace(/\s+\}$/, ''); - - var spaces = str.match(/^\n?( *)/)[1].length - , tabs = str.match(/^\n?(\t*)/)[1].length - , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); - - str = str.replace(re, ''); - - return exports.trim(str); -}; - -/** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; - -/** - * Trim the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.trim = function(str){ - return str.replace(/^\s+|\s+$/g, ''); -}; - -/** - * Parse the given `qs`. - * - * @param {String} qs - * @return {Object} - * @api private - */ - -exports.parseQuery = function(qs){ - return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ - var i = pair.indexOf('=') - , key = pair.slice(0, i) - , val = pair.slice(++i); - - obj[key] = decodeURIComponent(val); - return obj; - }, {}); -}; - -/** - * Highlight the given string of `js`. - * - * @param {String} js - * @return {String} - * @api private - */ - -function highlight(js) { - return js - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') - .replace(/('.*?')/gm, '<span class="string">$1</span>') - .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') - .replace(/(\d+)/gm, '<span class="number">$1</span>') - .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') - .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>') -} - -/** - * Highlight the contents of tag `name`. - * - * @param {String} name - * @api private - */ - -exports.highlightTags = function(name) { - var code = document.getElementsByTagName(name); - for (var i = 0, len = code.length; i < len; ++i) { - code[i].innerHTML = highlight(code[i].innerHTML); - } -}; diff --git a/tests/lib/mocha-1.17.1/package.json b/tests/lib/mocha-1.17.1/package.json deleted file mode 100644 index 2f5f07a8dc..0000000000 --- a/tests/lib/mocha-1.17.1/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "mocha", - "version": "1.17.1", - "description": "simple, flexible, fun test framework", - "keywords": [ - "mocha", - "test", - "bdd", - "tdd", - "tap" - ], - "author": "TJ Holowaychuk <tj@vision-media.ca>", - "repository": { - "type": "git", - "url": "git://github.com/visionmedia/mocha.git" - }, - "main": "./index", - "bin": { - "mocha": "./bin/mocha", - "_mocha": "./bin/_mocha" - }, - "engines": { - "node": ">= 0.4.x" - }, - "scripts": { - "test": "make test-all" - }, - "dependencies": { - "commander": "2.0.0", - "growl": "1.7.x", - "jade": "0.26.3", - "diff": "1.0.7", - "debug": "*", - "mkdirp": "0.3.5", - "glob": "3.2.3" - }, - "devDependencies": { - "should": ">= 2.0.x", - "coffee-script": "1.2" - }, - "files": [ - "bin", - "images", - "lib", - "index.js", - "mocha.css", - "mocha.js" - ] -} diff --git a/tests/lib/mocha-1.17.1/support/foot.js b/tests/lib/mocha-1.17.1/support/foot.js deleted file mode 100644 index 158693a025..0000000000 --- a/tests/lib/mocha-1.17.1/support/foot.js +++ /dev/null @@ -1 +0,0 @@ -})();
\ No newline at end of file diff --git a/tests/lib/mocha-1.17.1/test/acceptance/context.js b/tests/lib/mocha-1.17.1/test/acceptance/context.js deleted file mode 100644 index e2af9d5eae..0000000000 --- a/tests/lib/mocha-1.17.1/test/acceptance/context.js +++ /dev/null @@ -1,26 +0,0 @@ - -describe('Context', function(){ - beforeEach(function(){ - this.calls = ['before']; - }) - - describe('nested', function(){ - beforeEach(function(){ - this.calls.push('before two'); - }) - - it('should work', function(){ - this.calls.should.eql(['before', 'before two']); - this.calls.push('test'); - }) - - after(function(){ - this.calls.should.eql(['before', 'before two', 'test']); - this.calls.push('after two'); - }) - }) - - after(function(){ - this.calls.should.eql(['before', 'before two', 'test', 'after two']); - }) -})
\ No newline at end of file diff --git a/tests/lib/mocha-1.17.1/test/acceptance/timeout.js b/tests/lib/mocha-1.17.1/test/acceptance/timeout.js deleted file mode 100644 index 109808bf5b..0000000000 --- a/tests/lib/mocha-1.17.1/test/acceptance/timeout.js +++ /dev/null @@ -1,21 +0,0 @@ - -describe('timeouts', function(){ - beforeEach(function(done){ - // uncomment - // setTimeout(done, 3000); - done(); - }) - - it('should error on timeout', function(done){ - // uncomment - // setTimeout(done, 3000); - done(); - }) - - it('should allow overriding per-test', function(done){ - this.timeout(1000); - setTimeout(function(){ - done(); - }, 300); - }) -}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/utils.js b/tests/lib/mocha-1.17.1/test/acceptance/utils.js deleted file mode 100644 index 97373687a6..0000000000 --- a/tests/lib/mocha-1.17.1/test/acceptance/utils.js +++ /dev/null @@ -1,59 +0,0 @@ -var utils = require('../../lib/utils'); - -describe('lib/utils', function () { - describe('clean', function () { - it("should format a single line test function", function () { - var fn = [ - "function () {" - , " var a = 1;" - , "}" - ].join("\n"); - utils.clean(fn).should.equal("var a = 1;"); - }); - - it("should format a multi line test indented with spaces", function () { - // and no new lines after curly braces, shouldn't matter - var fn = [ - "function(){ var a = 1;" - , " var b = 2;" // this one has more spaces - , " var c = 3; }" - ].join("\n"); - utils.clean(fn).should.equal("var a = 1;\n var b = 2;\nvar c = 3;"); - }); - - it("should format a multi line test indented with tabs", function () { - var fn = [ - "function (arg1, arg2) {" - , "\tif (true) {" - , "\t\tvar a = 1;" - , "\t}" - , "}" - ].join("\n"); - utils.clean(fn).should.equal("if (true) {\n\tvar a = 1;\n}"); - }); - - it("should format functions saved in windows style - spaces", function () { - var fn = [ - "function (one) {" - , " do {", - , ' "nothing";', - , " } while (false);" - , ' }' - ].join("\r\n"); - utils.clean(fn).should.equal('do {\n "nothing";\n} while (false);'); - }); - - it("should format functions saved in windows style - tabs", function () { - var fn = [ - "function ( ) {" - , "\tif (false) {" - , "\t\tvar json = {" - , '\t\t\tone : 1' - , '\t\t};' - , "\t}" - , "}" - ].join("\r\n"); - utils.clean(fn).should.equal("if (false) {\n\tvar json = {\n\t\tone : 1\n\t};\n}"); - }); - }); -});
\ No newline at end of file diff --git a/tests/lib/mocha-2.1.0/.editorconfig b/tests/lib/mocha-2.1.0/.editorconfig new file mode 100644 index 0000000000..e3a4859eeb --- /dev/null +++ b/tests/lib/mocha-2.1.0/.editorconfig @@ -0,0 +1,18 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/tests/lib/mocha-1.17.1/.gitignore b/tests/lib/mocha-2.1.0/.gitignore index 8df0ed783e..aba55e682e 100644 --- a/tests/lib/mocha-1.17.1/.gitignore +++ b/tests/lib/mocha-2.1.0/.gitignore @@ -2,6 +2,7 @@ coverage.html lib-cov .DS_Store node_modules +test-outputs *.sock testing _mocha.js @@ -10,3 +11,5 @@ my-reporter.js lib/browser/diff.js .idea *.iml +*.patch +*.diff diff --git a/tests/lib/mocha-2.1.0/.mailmap b/tests/lib/mocha-2.1.0/.mailmap new file mode 100644 index 0000000000..704013125b --- /dev/null +++ b/tests/lib/mocha-2.1.0/.mailmap @@ -0,0 +1 @@ +TJ Holowaychuk <tj@vision-media.ca>
\ No newline at end of file diff --git a/tests/lib/mocha-2.1.0/.travis.yml b/tests/lib/mocha-2.1.0/.travis.yml new file mode 100644 index 0000000000..c33b3fa3a1 --- /dev/null +++ b/tests/lib/mocha-2.1.0/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +script: travis_retry npm test +node_js: +- '0.11' +- '0.10' +- '0.8' +notifications: + slack: + secure: Xov4Q/jpmTr6p5E/kYeC0HF1tWm0YqBWvCUbNuJiti3CtFpVmOwXEvxGFcvZNAXApEVrs1uhLZLifuL3oJ/TNdqzM5nT5jDGawPYgf8ly+fTaEiCpsRCdQhFLVqYoN671eyx7QILtre9b51SeaFVVksMrfTmvh4aKJCN01iuJm4= diff --git a/tests/lib/mocha-2.1.0/CONTRIBUTING.md b/tests/lib/mocha-2.1.0/CONTRIBUTING.md new file mode 100644 index 0000000000..6d172f0800 --- /dev/null +++ b/tests/lib/mocha-2.1.0/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to Mocha + +Hi! We could use your help. Let us help you help us. Or something. + +## General + +1. If you are looking for a place to begin, **please send PRs for bugfixes instead of new features**, and/or **look for issues labeled `PR PLEASE`.** + +2. **Help with documentation and the wiki is always appreciated**. + +3. Please **be courteous and constructive** when commenting on issues, commits, and pull requests. + +## Bug Reports & Issues + +1. When reporting a bug, please **provide steps to reproduce**. If possible, show code. + +2. Please **show all code in JavaScript**. We don't all read `<insert-language-that-compiles-to-JavaScript-here>`. If you do not, you will be asked to. + +3. Because Mocha works with many third-party libraries and tools, **ensure the bug you are reporting is actually within Mocha**. + +4. If you report a bug, and it is inactive for a significant amount of time, it may be closed. **Please respond promptly to requests for more information**. + +## Pull Requests + +1. Before sending a large PR, it's recommended to **create an issue to propose the change**. Nobody wants to write a book of code and throw it away. + +2. Because Mocha should be kept as maintainable as possible, its codebase must be kept slim. Historically, *most PRs for new features are not merged*. New features inevitably increase the size of the codebase, and thus reduce maintainability. Only features *deemed essential* are likely to be merged--this is at the discretion of the maintainer(s). If your PR for a feature is not merged, this doesn't necessarily mean your PR was a bad idea, wouldn't be used, or otherwise sucks. It just means **only essential PRs for new features are likely to be merged**. + +3. Due to the above, before creating a PR for a new feature, **create an issue to propose the feature.** + +4. Please **respect existing coding conventions**, whatever those may be. + +5. If your PR has been waiting in limbo for some time, it's very helpful to **rebase against master**, which will make it easier to merge. + +6. Please **add tests for new code**. + +7. **Always run `npm test` before sending a PR.** If you break the tests, your PR will not be accepted until they are fixed. + +## Source Control + +1. Please **squash your commits** when sending a pull request. If you are unfamiliar with this process, see [this guide](https://help.github.com/articles/about-git-rebase/). If you have already pushed your changesets and are squashing thereafter, this may necessitate the use of a "force push". Please [read the docs](http://git-scm.com/docs/git-push) before you attempt this. + +2. Please **follow the commit message conventions [outlined here](https://medium.com/code-adventures/git-conventions-a940ee20862d).** + +## TL;DR + +**Be kind, be diligent, look before you leap into a PR, and follow common community conventions**. + +*- The Mocha Team* diff --git a/tests/lib/mocha-1.17.1/History.md b/tests/lib/mocha-2.1.0/HISTORY.md index db7c60f508..95f0f0f9db 100644 --- a/tests/lib/mocha-1.17.1/History.md +++ b/tests/lib/mocha-2.1.0/HISTORY.md @@ -1,3 +1,150 @@ + +2.1.0 / 2014-12-23 +================== + + * showDiff: don’t stringify strings + * Clean up unused module dependencies. + * Filter zero-length strings from mocha.opts + * only write to stdout in reporters + * Revert "only write to stdout in reporters" + * Print colored output only to a tty + * update summary in README.md + * rename Readme.md/History.md to README.md/HISTORY.md because neurotic + * add .mailmap to fix "git shortlog" or "git summary" output + * fixes #1461: nyan-reporter now respects Base.useColors, fixed bug where Base.color would not return a string when str wasn't a string. + * Use existing test URL builder in failed replay links + * modify .travis.yml: use travis_retry; closes #1449 + * fix -t 0 behavior; closes #1446 + * fix tests (whoops) + * improve diff behavior + * Preserve pathname when linking to individual tests + * Fix test + * Tiny typo in comments fixed + * after hooks now being called on failed tests when using bail, fixes #1093 + * fix throwing undefined/null now makes tests fail, fixes #1395 + * compiler extensions are added as watched extensions, removed non-standard extensions from watch regex, resolves #1221 + * prefix/namespace for suite titles in markdown reporter, fixes #554 + * fix more bad markdown in CONTRIBUTING.md + * fix bad markdown in CONTRIBUTING.md + * add setImmediate/clearImmediate to globals; closes #1435 + * Fix buffer diffs (closes #1132, closes #1433) + * add a CONTRIBUTING.md. closes #882 + * fix intermittent build failures (maybe). closes #1407 + * add Slack notification to .travis.yml + * Fix slack link + * Add slack room to readme + * Update maintainers + * update maintainers and contributors + * resolves #1393: kill children with more effort on SIGINT + * xunit reporter support for optionally writing to a file + * if a reporter has a .done method, call it before exiting + * add support for reporter options + * only write to stdout in reporters + +2.0.0 / 2014-10-21 +================== + + * remove: support for node 0.6.x, 0.4.x + * fix: landing reporter with non ansi characters (#211) + * fix: html reporter - preserve query params when navigating to suites/tests (#1358) + * fix: json stream reporter add error message to failed test + * fix: fixes for visionmedia -> mochajs + * fix: use stdio, fixes node deprecation warnings (#1391) + +1.21.5 / 2014-10-11 +================== + + * fix: build for NodeJS v0.6.x + * fix: do not attempt to highlight syntax when non-HTML reporter is used + * update: escape-string-regexp to 1.0.2. + * fix: botched indentation in canonicalize() + * fix: .gitignore: ignore .patch and .diff files + * fix: changed 'Catched' to 'Caught' in uncaught exception error handler messages + * add: `pending` field for json reporter + * fix: Runner.prototype.uncaught: don't double-end runnables that already have a state. + * fix: --recursive, broken by f0facd2e + * update: replaces escapeRegexp with the escape-string-regexp package. + * update: commander to 2.3.0. + * update: diff to 1.0.8. + * fix: ability to disable syntax highlighting (#1329) + * fix: added empty object to errorJSON() call to catch when no error is present + * fix: never time out after calling enableTimeouts(false) + * fix: timeout(0) will work at suite level (#1300) + * Fix for --watch+only() issue (#888 ) + * fix: respect err.showDiff, add Base reporter test (#810) + +1.22.1-3 / 2014-07-27 +================== + + * fix: disabling timeouts with this.timeout(0) (#1301) + +1.22.1-3 / 2014-07-27 +================== + + * fix: local uis and reporters (#1288) + * fix: building 1.21.0's changes in the browser (#1284) + +1.21.0 / 2014-07-23 +================== + + * add: --no-timeouts option (#1262, #1268) + * add: --*- deprecation node flags (#1217) + * add: --watch-extensions argument (#1247) + * change: spec reporter is default (#1228) + * fix: diff output showing incorrect +/- (#1182) + * fix: diffs of circular structures (#1179) + * fix: re-render the progress bar when progress has changed only (#1151) + * fix support for environments with global and window (#1159) + * fix: reverting to previously defined onerror handler (#1178) + * fix: stringify non error objects passed to done() (#1270) + * fix: using local ui, reporters (#1267) + * fix: cleaning es6 arrows (#1176) + * fix: don't include attrs in failure tag for xunit (#1244) + * fix: fail tests that return a promise if promise is rejected w/o a reason (#1224) + * fix: showing failed tests in doc reporter (#1117) + * fix: dot reporter dots being off (#1204) + * fix: catch empty throws (#1219) + * fix: honoring timeout for sync operations (#1242) + * update: growl to 1.8.0 + +1.20.1 / 2014-06-03 +================== + + * update: should dev dependency to ~4.0.0 (#1231) + +1.20.0 / 2014-05-28 +================== + + * add: filenames to suite objects (#1222) + +1.19.0 / 2014-05-17 +================== + + * add: browser script option to package.json + * add: export file in Mocha.Test objects (#1174) + * add: add docs for wrapped node flags + * fix: mocha.run() to return error status in browser (#1216) + * fix: clean() to show failure details (#1205) + * fix: regex that generates html for new keyword (#1201) + * fix: sibling suites have inherited but separate contexts (#1164) + + +1.18.2 / 2014-03-18 +================== + + * fix: html runner was prevented from using #mocha as the default root el (#1162) + +1.18.1 / 2014-03-18 +================== + + * fix: named before/after hooks in bdd, tdd, qunit interfaces (#1161) + +1.18.0 / 2014-03-13 +================== + + * add: promise support (#329) + * add: named before/after hooks (#966) + 1.17.1 / 2014-01-22 ================== @@ -15,6 +162,7 @@ * fix: canonicalize objects before stringifying and diffing them (#1079) * fix: make CR call behave like carriage return for non tty (#1087) + 1.16.2 / 2013-12-23 ================== @@ -22,11 +170,13 @@ * fix: issue running the xunit reporter in browsers (#1068) * fix: issue with firefox < 3.5 (#725) + 1.16.1 / 2013-12-19 ================== * fix: recompiled for missed changes from the last release + 1.16.0 / 2013-12-19 ================== diff --git a/tests/lib/mocha-1.17.1/LICENSE b/tests/lib/mocha-2.1.0/LICENSE index 00e9a0c3cc..1c5d7fa873 100644 --- a/tests/lib/mocha-1.17.1/LICENSE +++ b/tests/lib/mocha-2.1.0/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2011-2013 TJ Holowaychuk <tj@vision-media.ca> +Copyright (c) 2011-2014 TJ Holowaychuk <tj@vision-media.ca> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/tests/lib/mocha-1.17.1/Makefile b/tests/lib/mocha-2.1.0/Makefile index 6c555012f3..72c4814c10 100644 --- a/tests/lib/mocha-1.17.1/Makefile +++ b/tests/lib/mocha-2.1.0/Makefile @@ -1,5 +1,5 @@ -REPORTER ?= dot +REPORTER ?= spec TM_BUNDLE = JavaScript\ mocha.tmbundle SRC = $(shell find lib -name "*.js" -type f | sort) SUPPORT = $(wildcard support/*.js) @@ -9,7 +9,10 @@ all: mocha.js lib/browser/diff.js: node_modules/diff/diff.js cp node_modules/diff/diff.js lib/browser/diff.js -mocha.js: $(SRC) $(SUPPORT) lib/browser/diff.js +lib/browser/escape-string-regexp.js: node_modules/escape-string-regexp/index.js + cp node_modules/escape-string-regexp/index.js lib/browser/escape-string-regexp.js + +mocha.js: $(SRC) $(SUPPORT) lib/browser/diff.js lib/browser/escape-string-regexp.js @node support/compile $(SRC) @cat \ support/head.js \ @@ -20,6 +23,7 @@ mocha.js: $(SRC) $(SUPPORT) lib/browser/diff.js clean: rm -f mocha.js + rm -rf test-outputs rm -fr lib-cov rm -f coverage.html @@ -32,7 +36,7 @@ lib-cov: test: test-unit -test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-only +test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-only test-failing test-regression test-jsapi: @node test/jsapi @@ -44,17 +48,37 @@ test-unit: --growl \ test/*.js +test-regression: test-outputs/issue1327/case-out.json + @./bin/mocha \ + --reporter $(REPORTER) \ + test/regression/issue*/control.js + +test-outputs/issue1327/case-out.json: test/regression/issue1327/case.js + @mkdir -p $(dir $@) || true + @./bin/mocha --reporter json $< > $@ || true + +test-failing: + ./bin/mocha \ + --reporter $(REPORTER) \ + test/acceptance/failing/timeout.js > /dev/null 2>&1 ; \ + failures="$$?" ; \ + if [ "$$failures" != '2' ] ; then \ + echo 'test-failing:' ; \ + echo " expected 2 failing tests but saw $$failures" ; \ + exit 1 ; \ + fi + test-compilers: @./bin/mocha \ --reporter $(REPORTER) \ - --compilers coffee:coffee-script,foo:./test/compiler/foo \ + --compilers coffee:coffee-script/register,foo:./test/compiler/foo \ test/acceptance/test.coffee \ test/acceptance/test.foo test-requires: @./bin/mocha \ --reporter $(REPORTER) \ - --compilers coffee:coffee-script \ + --compilers coffee:coffee-script/register \ --require test/acceptance/require/a.js \ --require test/acceptance/require/b.coffee \ --require test/acceptance/require/c.js \ @@ -165,4 +189,4 @@ non-tty: tm: @open editors/$(TM_BUNDLE) -.PHONY: test-cov test-jsapi test-compilers watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean +.PHONY: test-cov test-jsapi test-compilers watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep test-failing tm clean diff --git a/tests/lib/mocha-2.1.0/README.md b/tests/lib/mocha-2.1.0/README.md new file mode 100644 index 0000000000..1a3a948936 --- /dev/null +++ b/tests/lib/mocha-2.1.0/README.md @@ -0,0 +1,226 @@ + [![Build Status](https://secure.travis-ci.org/mochajs/mocha.png)](http://travis-ci.org/mochajs/mocha) + + [![Mocha test framework](http://f.cl.ly/items/3l1k0n2A1U3M1I1L210p/Screen%20Shot%202012-02-24%20at%202.21.43%20PM.png)](http://mochajs.org) + + Mocha is a simple, flexible, fun JavaScript test framework for node.js and the browser. For more information view the [documentation](http://mochajs.org). + +## Contributors + +``` + project : mocha + repo age : 3 years, 4 months + active : 509 days + commits : 1575 + files : 153 + authors : + 977 TJ Holowaychuk 62.0% + 132 Travis Jeffery 8.4% + 63 Christopher Hiller 4.0% + 31 Guillermo Rauch 2.0% + 27 Joshua Appelman 1.7% + 13 Attila Domokos 0.8% + 10 John Firebaugh 0.6% + 8 Nathan Rajlich 0.5% + 8 Jo Liss 0.5% + 6 Mike Pennisi 0.4% + 6 Brendan Nee 0.4% + 6 James Carr 0.4% + 5 Aaron Heckmann 0.3% + 5 Raynos 0.3% + 5 Ryunosuke SATO 0.3% + 4 hokaccha 0.3% + 4 Jonathan Ong 0.3% + 4 Joshua Krall 0.3% + 4 Domenic Denicola 0.3% + 4 Forbes Lindesay 0.3% + 4 Xavier Antoviaque 0.3% + 4 David da Silva 0.3% + 3 Ariel Mashraki 0.2% + 3 Ben Bradley 0.2% + 3 Merrick Christensen 0.2% + 3 Andreas Lind Petersen 0.2% + 3 Nathan Bowser 0.2% + 3 Cory Thomas 0.2% + 3 Benjie Gillam 0.2% + 3 Wil Moore III 0.2% + 3 Ben Lindsey 0.2% + 3 Tyson Tate 0.2% + 3 Paul Miller 0.2% + 3 eiji.ienaga 0.2% + 3 Mathieu Desvé 0.2% + 3 Jesse Dailey 0.2% + 3 fool2fish 0.2% + 3 Fredrik Enestad 0.2% + 3 Sindre Sorhus 0.2% + 3 Valentin Agachi 0.2% + 2 jsdevel 0.1% + 2 Arian Stolwijk 0.1% + 2 Juzer Ali 0.1% + 2 David Henderson 0.1% + 2 Justin DuJardin 0.1% + 2 Paul Armstrong 0.1% + 2 Pete Hawkins 0.1% + 2 Jonas Westerlund 0.1% + 2 Quang Van 0.1% + 2 Simon Gaeremynck 0.1% + 2 travis jeffery 0.1% + 2 Dominique Quatravaux 0.1% + 2 Jacob Wejendorp 0.1% + 2 Shawn Krisman 0.1% + 2 FARKAS Máté 0.1% + 2 Konstantin Käfer 0.1% + 2 Timo Tijhof 0.1% + 2 Sean Lang 0.1% + 2 Quanlong He 0.1% + 2 Glen Mailer 0.1% + 2 Alexander Early 0.1% + 2 Ian Storm Taylor 0.1% + 2 Brian Beck 0.1% + 2 Michael Riley 0.1% + 2 Michael Schoonmaker 0.1% + 2 domenic 0.1% + 2 fcrisci 0.1% + 2 Buck Doyle 0.1% + 2 Nathan Alderson 0.1% + 1 Mal Graty 0.1% + 1 Marc Kuo 0.1% + 1 Matija Marohnić 0.1% + 1 Matt Robenolt 0.1% + 1 Matt Smith 0.1% + 1 Matthew Shanley 0.1% + 1 Mattias Tidlund 0.1% + 1 Michael Jackson 0.1% + 1 Michael Olson 0.1% + 1 Michal Charemza 0.1% + 1 Nathan Black 0.1% + 1 Nick Fitzgerald 0.1% + 1 Noshir Patel 0.1% + 1 Panu Horsmalahti 0.1% + 1 Phil Sung 0.1% + 1 R56 0.1% + 1 Refael Ackermann 0.1% + 1 Richard Dingwall 0.1% + 1 Richard Knop 0.1% + 1 Rob Wu 0.1% + 1 Romain Prieto 0.1% + 1 Roman Neuhauser 0.1% + 1 Roman Shtylman 0.1% + 1 Russ Bradberry 0.1% + 1 Russell Munson 0.1% + 1 Rustem Mustafin 0.1% + 1 Salehen Shovon Rahman 0.1% + 1 Sasha Koss 0.1% + 1 Seiya Konno 0.1% + 1 Shaine Hatch 0.1% + 1 Simon Goumaz 0.1% + 1 Standa Opichal 0.1% + 1 Stephen Mathieson 0.1% + 1 Steve Mason 0.1% + 1 Tapiwa Kelvin 0.1% + 1 Teddy Zeenny 0.1% + 1 Tim Ehat 0.1% + 1 Vadim Nikitin 0.1% + 1 Victor Costan 0.1% + 1 Will Langstroth 0.1% + 1 Yanis Wang 0.1% + 1 Yuest Wang 0.1% + 1 Zsolt Takács 0.1% + 1 abrkn 0.1% + 1 airportyh 0.1% + 1 badunk 0.1% + 1 claudyus 0.1% + 1 dasilvacontin 0.1% + 1 fengmk2 0.1% + 1 gaye 0.1% + 1 grasGendarme 0.1% + 1 lakmeer 0.1% + 1 lodr 0.1% + 1 mrShturman 0.1% + 1 nishigori 0.1% + 1 omardelarosa 0.1% + 1 qiuzuhui 0.1% + 1 samuel goldszmidt 0.1% + 1 sebv 0.1% + 1 startswithaj 0.1% + 1 tgautier@yahoo.com 0.1% + 1 traleig1 0.1% + 1 vlad 0.1% + 1 yuitest 0.1% + 1 zhiyelee 0.1% + 1 Adam Crabtree 0.1% + 1 Andreas Brekken 0.1% + 1 Andrew Nesbitt 0.1% + 1 Andrey Popp 0.1% + 1 Arnaud Brousseau 0.1% + 1 Atsuya Takagi 0.1% + 1 Austin Birch 0.1% + 1 Ben Noordhuis 0.1% + 1 Bjørge Næss 0.1% + 1 Brian Lalor 0.1% + 1 Brian M. Carlson 0.1% + 1 Brian Moore 0.1% + 1 Bryan Donovan 0.1% + 1 C. Scott Ananian 0.1% + 1 Casey Foster 0.1% + 1 ChrisWren 0.1% + 1 Connor Dunn 0.1% + 1 Corey Butler 0.1% + 1 Daniel Stockman 0.1% + 1 Dave McKenna 0.1% + 1 Denis Bardadym 0.1% + 1 Devin Weaver 0.1% + 1 Di Wu 0.1% + 1 Diogo Monteiro 0.1% + 1 Dmitry Shirokov 0.1% + 1 Dr. Travis Jeffery 0.1% + 1 Fedor Indutny 0.1% + 1 Florian Margaine 0.1% + 1 Frederico Silva 0.1% + 1 Fredrik Lindin 0.1% + 1 Gareth Aye 0.1% + 1 Gareth Murphy 0.1% + 1 Gavin Mogan 0.1% + 1 Giovanni Bassi 0.1% + 1 Glen Huang 0.1% + 1 Greg Perkins 0.1% + 1 Harish 0.1% + 1 Harry Brundage 0.1% + 1 Herman Junge 0.1% + 1 Ian Young 0.1% + 1 Ivan 0.1% + 1 JP Bochi 0.1% + 1 Jaakko Salonen 0.1% + 1 Jakub Nešetřil 0.1% + 1 James Bowes 0.1% + 1 James Lal 0.1% + 1 Jan Kopriva 0.1% + 1 Jason Barry 0.1% + 1 Javier Aranda 0.1% + 1 Jean Ponchon 0.1% + 1 Jeff Kunkle 0.1% + 1 Jeremy Martin 0.1% + 1 Jimmy Cuadra 0.1% + 1 John Doty 0.1% + 1 Jonathan Creamer 0.1% + 1 Jonathan Park 0.1% + 1 Jussi Virtanen 0.1% + 1 Katie Gengler 0.1% + 1 Kazuhito Hokamura 0.1% + 1 Kent C. Dodds 0.1% + 1 Kevin Conway 0.1% + 1 Kirill Korolyov 0.1% + 1 Koen Punt 0.1% + 1 Laszlo Bacsi 0.1% + 1 Liam Newman 0.1% + 1 Linus Unnebäck 0.1% + 1 László Bácsi 0.1% + 1 Maciej Małecki 0.1% +``` + +## Links + + - [Google Group](http://groups.google.com/group/mochajs) + - [Wiki](https://github.com/mochajs/mocha/wiki) + - Mocha [Extensions and reporters](https://github.com/mochajs/mocha/wiki) + +Mocha also has a chat room on [Slack](https://slack.com). If you'd like to join, [shoot us an email](mailto:tj@travisjeffery.com?subject=mocha%20slack%20room%20invite) from the address you want us to invite you under and we'll happily send you an invite! diff --git a/tests/lib/mocha-1.17.1/bin/_mocha b/tests/lib/mocha-2.1.0/bin/_mocha index bea6df85f7..4948197394 100644 --- a/tests/lib/mocha-1.17.1/bin/_mocha +++ b/tests/lib/mocha-2.1.0/bin/_mocha @@ -5,17 +5,13 @@ */ var program = require('commander') - , sprintf = require('util').format , path = require('path') , fs = require('fs') - , glob = require('glob') , resolve = path.resolve , exists = fs.existsSync || path.existsSync , Mocha = require('../') , utils = Mocha.utils - , interfaces = Mocha.interfaces , join = path.join - , basename = path.basename , cwd = process.cwd() , mocha = new Mocha; @@ -61,30 +57,44 @@ var images = { program .version(JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')).version) .usage('[debug] [options] [files]') - .option('-r, --require <name>', 'require the given module') - .option('-R, --reporter <name>', 'specify the reporter to use', 'dot') - .option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd') - .option('-g, --grep <pattern>', 'only run tests matching <pattern>') - .option('-i, --invert', 'inverts --grep matches') - .option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]') - .option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]') - .option('-w, --watch', 'watch files for changes') + .option('-A, --async-only', "force all tests to take a callback (async)") .option('-c, --colors', 'force enabling of colors') .option('-C, --no-colors', 'force disabling of colors') .option('-G, --growl', 'enable growl notification support') - .option('-d, --debug', "enable node's debugger, synonym for node --debug") - .option('-b, --bail', "bail after first test failure") - .option('-A, --async-only', "force all tests to take a callback (async)") + .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options') + .option('-R, --reporter <name>', 'specify the reporter to use', 'spec') .option('-S, --sort', "sort test files") - .option('--recursive', 'include sub directories') - .option('--debug-brk', "enable node's debugger breaking on the first line") - .option('--globals <names>', 'allow the given comma-delimited global [names]', list, []) + .option('-b, --bail', "bail after first test failure") + .option('-d, --debug', "enable node's debugger, synonym for node --debug") + .option('-g, --grep <pattern>', 'only run tests matching <pattern>') + .option('-gc', '--expose-gc', 'expose gc extension') + .option('-i, --invert', 'inverts --grep matches') + .option('-r, --require <name>', 'require the given module') + .option('-s, --slow <ms>', '"slow" test threshold in milliseconds [75]') + .option('-t, --timeout <ms>', 'set test-case timeout in milliseconds [2000]') + .option('-u, --ui <name>', 'specify user-interface (bdd|tdd|exports)', 'bdd') + .option('-w, --watch', 'watch files for changes') .option('--check-leaks', 'check for global variable leaks') - .option('--interfaces', 'display available interfaces') - .option('--reporters', 'display available reporters') .option('--compilers <ext>:<module>,...', 'use the given module(s) to compile files', list, []) + .option('--debug-brk', "enable node's debugger breaking on the first line") + .option('--globals <names>', 'allow the given comma-delimited global [names]', list, []) + .option('--harmony', 'enable all harmony features (except typeof)') + .option('--harmony-collections', 'enable harmony collections (sets, maps, and weak maps)') + .option('--harmony-generators', 'enable harmony generators') + .option('--harmony-proxies', 'enable harmony proxies') .option('--inline-diffs', 'display actual/expected differences inline within each string') + .option('--interfaces', 'display available interfaces') + .option('--no-deprecation', 'silence deprecation warnings') .option('--no-exit', 'require a clean shutdown of the event loop: mocha will not call process.exit') + .option('--no-timeouts', 'disables timeouts, given implicitly with --debug') + .option('--opts <path>', 'specify opts path', 'test/mocha.opts') + .option('--prof', 'log statistical profiling information') + .option('--recursive', 'include sub directories') + .option('--reporters', 'display available reporters') + .option('--throw-deprecation', 'throw an exception anytime a deprecated function is used') + .option('--trace', 'trace function calls') + .option('--trace-deprecation', 'show stack traces on deprecations') + .option('--watch-extensions <ext>,...', 'additional extensions to monitor with --watch', list, []) program.name = 'mocha'; @@ -157,12 +167,19 @@ program.on('require', function(mod){ requires.push(mod); }); -// mocha.opts support +// --opts + +var optsPath = process.argv.indexOf('--opts') !== -1 + ? process.argv[process.argv.indexOf('--opts') + 1] + : 'test/mocha.opts'; try { - var opts = fs.readFileSync('test/mocha.opts', 'utf8') + var opts = fs.readFileSync(optsPath, 'utf8') .trim() - .split(/\s+/); + .split(/\s+/) + .filter(function(value) { + return value ? true : false; + }); process.argv = process.argv .slice(0, 2) @@ -179,9 +196,22 @@ program.parse(process.argv); Error.stackTraceLimit = Infinity; // TODO: config +// reporter options + +var reporterOptions = {}; +if (program.reporterOptions !== undefined) { + program.reporterOptions.split(",").forEach(function(opt) { + var L = opt.split("="); + if (L.length != 2) { + throw new Error("invalid reporter option '" + opt + "'"); + } + reporterOptions[L[0]] = L[1]; + }); +} + // reporter -mocha.reporter(program.reporter); +mocha.reporter(program.reporter, reporterOptions); // interface @@ -218,6 +248,10 @@ if (program.inlineDiffs) mocha.useInlineDiffs(true); if (program.slow) mocha.suite.slow(program.slow); +// --no-timeouts + +if (!program.timeouts) mocha.enableTimeouts(false); + // --timeout if (program.timeout) mocha.suite.timeout(program.timeout); @@ -261,27 +295,25 @@ program.compilers.forEach(function(c) { if (mod[0] == '.') mod = join(process.cwd(), mod); require(mod); extensions.push(ext); + program.watchExtensions.push(ext); }); -var re = new RegExp('\\.(' + extensions.join('|') + ')$'); - // requires requires.forEach(function(mod) { require(mod); }); -// files +//args -var files = [] - , args = program.args; +var args = program.args; // default files to test/*.{js,coffee} if (!args.length) args.push('test'); args.forEach(function(arg){ - files = files.concat(lookupFiles(arg, program.recursive)); + files = files.concat(utils.lookupFiles(arg, extensions, program.recursive)); }); // resolve @@ -306,7 +338,8 @@ if (program.watch) { process.exit(); }); - var watchFiles = utils.files(cwd); + + var watchFiles = utils.files(cwd, [ 'js' ].concat(program.watchExtensions)); var runAgain = false; function loadAndRun() { @@ -335,6 +368,8 @@ if (program.watch) { function rerun() { purge(); stop() + if (!program.grep) + mocha.grep(null); mocha.suite = mocha.suite.clone(); mocha.suite.ctx = new Mocha.Context; mocha.ui(program.ui); @@ -418,40 +453,6 @@ function stop() { } /** - * Lookup file names at the given `path`. - */ - -function lookupFiles(path, recursive) { - var files = []; - - if (!exists(path)) { - if (exists(path + '.js')) { - path += '.js' - } else { - files = glob.sync(path); - if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'"); - return files; - } - } - - var stat = fs.statSync(path); - if (stat.isFile()) return path; - - fs.readdirSync(path).forEach(function(file){ - file = join(path, file); - var stat = fs.statSync(file); - if (stat.isDirectory()) { - if (recursive) files = files.concat(lookupFiles(file, recursive)); - return - } - if (!stat.isFile() || !re.test(file) || basename(file)[0] == '.') return; - files.push(file); - }); - - return files; -} - -/** * Play the given array of strings. */ diff --git a/tests/lib/mocha-1.17.1/bin/mocha b/tests/lib/mocha-2.1.0/bin/mocha index 742d60779e..4ab296473c 100644 --- a/tests/lib/mocha-1.17.1/bin/mocha +++ b/tests/lib/mocha-2.1.0/bin/mocha @@ -14,11 +14,13 @@ process.argv.slice(2).forEach(function(arg){ switch (flag) { case '-d': args.unshift('--debug'); + args.push('--no-timeouts'); break; case 'debug': case '--debug': case '--debug-brk': args.unshift(arg); + args.push('--no-timeouts'); break; case '-gc': case '--expose-gc': @@ -29,7 +31,10 @@ process.argv.slice(2).forEach(function(arg){ case '--harmony-proxies': case '--harmony-collections': case '--harmony-generators': + case '--no-deprecation': case '--prof': + case '--throw-deprecation': + case '--trace-deprecation': args.unshift(arg); break; default: @@ -39,7 +44,7 @@ process.argv.slice(2).forEach(function(arg){ } }); -var proc = spawn(process.argv[0], args, { customFds: [0,1,2] }); +var proc = spawn(process.argv[0], args, { stdio: 'inherit' }); proc.on('exit', function (code, signal) { process.on('exit', function(){ if (signal) { @@ -49,3 +54,10 @@ proc.on('exit', function (code, signal) { } }); }); + +// terminate children. +process.on('SIGINT', function () { + proc.kill('SIGINT'); // calls runner.abort() + proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die. + process.kill(process.pid, 'SIGINT'); +}); diff --git a/tests/lib/mocha-2.1.0/bower.json b/tests/lib/mocha-2.1.0/bower.json new file mode 100644 index 0000000000..3ec9d01958 --- /dev/null +++ b/tests/lib/mocha-2.1.0/bower.json @@ -0,0 +1,47 @@ +{ + "name": "mocha", + "version": "2.1.0", + "homepage": "http://mocha.github.io/mocha", + "description": "simple, flexible, fun test framework", + "authors": [ + "TJ Holowaychuk <tj@vision-media.ca>", + "Joshua Appelman <joshua@jbna.nl>", + "Oleg Gaidarenko <markelog@gmail.com>", + "Christoffer Hallas <christoffer.hallas@gmail.com>", + "Christopher Hiller <chiller@badwing.com>", + "Travis Jeffery <tj@travisjeffery.com>", + "Johnathan Ong <me@jongleberry.com>", + "Guillermo Rauch <rauchg@gmail.com>" + ], + "repository": { + "type": "git", + "url": "git://github.com/mochajs/mocha.git" + }, + "main": [ + "mocha.js", + "mocha.css" + ], + "ignore": [ + "bin", + "editors", + "images", + "lib", + "support", + "test", + ".gitignore", + ".npmignore", + ".travis.yml", + "component.json", + "index.js", + "Makefile", + "package.json" + ], + "keywords": [ + "mocha", + "test", + "bdd", + "tdd", + "tap" + ], + "license": "MIT" +}
\ No newline at end of file diff --git a/tests/lib/mocha-1.17.1/component.json b/tests/lib/mocha-2.1.0/component.json index 1f1cc968c3..e0c07a2664 100644 --- a/tests/lib/mocha-1.17.1/component.json +++ b/tests/lib/mocha-2.1.0/component.json @@ -1,7 +1,7 @@ { "name": "mocha", - "version": "1.17.1", - "repo": "visionmedia/mocha", + "version": "2.1.0", + "repo": "mochajs/mocha", "description": "simple, flexible, fun test framework", "keywords": [ "mocha", @@ -11,6 +11,10 @@ "tap" ], "main": "mocha.js", - "scripts": ["mocha.js"], - "styles": ["mocha.css"] -} + "scripts": [ + "mocha.js" + ], + "styles": [ + "mocha.css" + ] +}
\ No newline at end of file diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet index c722cfe8ae..c722cfe8ae 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - after each.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet index c0b1f6bd82..c0b1f6bd82 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - after.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet index 14fa93b1b0..14fa93b1b0 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - before each.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet index 18b48c0eeb..18b48c0eeb 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - before.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - describe.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - describe.tmSnippet index 041726c067..041726c067 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - describe.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - describe.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet index 72a3b0ee3a..72a3b0ee3a 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/bdd - it.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert.tmSnippet index 38d78ce2ec..38d78ce2ec 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_deepEqual.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_deepEqual.tmSnippet index 4204e4c4f2..4204e4c4f2 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_deepEqual.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_deepEqual.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_equal.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_equal.tmSnippet index 8f09805968..8f09805968 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_equal.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_equal.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_fail.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_fail.tmSnippet index 3e8dbe3b9e..3e8dbe3b9e 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_fail.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_fail.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_isFunction.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_isFunction.tmSnippet index 903d7f6fdf..903d7f6fdf 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_isFunction.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - assert_isFunction.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - setup.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - setup.tmSnippet index a30e650187..a30e650187 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - setup.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - setup.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - suite.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - suite.tmSnippet index 37cfd8a566..37cfd8a566 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - suite.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - suite.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - teardown.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - teardown.tmSnippet index 68fd441016..68fd441016 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - teardown.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - teardown.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - test.tmSnippet b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - test.tmSnippet index 279da8900d..279da8900d 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/Snippets/tdd - test.tmSnippet +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/Snippets/tdd - test.tmSnippet diff --git a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/info.plist b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/info.plist index 16f6587a6c..16f6587a6c 100644 --- a/tests/lib/mocha-1.17.1/editors/JavaScript mocha.tmbundle/info.plist +++ b/tests/lib/mocha-2.1.0/editors/JavaScript mocha.tmbundle/info.plist diff --git a/tests/lib/mocha-1.17.1/images/error.png b/tests/lib/mocha-2.1.0/images/error.png Binary files differindex a07a1ba5ef..a07a1ba5ef 100644 --- a/tests/lib/mocha-1.17.1/images/error.png +++ b/tests/lib/mocha-2.1.0/images/error.png diff --git a/tests/lib/mocha-1.17.1/images/ok.png b/tests/lib/mocha-2.1.0/images/ok.png Binary files differindex b3623a5994..b3623a5994 100644 --- a/tests/lib/mocha-1.17.1/images/ok.png +++ b/tests/lib/mocha-2.1.0/images/ok.png diff --git a/tests/lib/mocha-1.17.1/index.js b/tests/lib/mocha-2.1.0/index.js index 507566fac2..169b271770 100644 --- a/tests/lib/mocha-1.17.1/index.js +++ b/tests/lib/mocha-2.1.0/index.js @@ -1,4 +1,3 @@ - module.exports = process.env.COV ? require('./lib-cov/mocha') - : require('./lib/mocha');
\ No newline at end of file + : require('./lib/mocha'); diff --git a/tests/lib/mocha-1.17.1/lib/browser/debug.js b/tests/lib/mocha-2.1.0/lib/browser/debug.js index 03cf59234c..0d939e5c07 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/debug.js +++ b/tests/lib/mocha-2.1.0/lib/browser/debug.js @@ -1,4 +1,3 @@ - module.exports = function(type){ return function(){ } diff --git a/tests/lib/mocha-2.1.0/lib/browser/escape-string-regexp.js b/tests/lib/mocha-2.1.0/lib/browser/escape-string-regexp.js new file mode 100644 index 0000000000..21a95663ed --- /dev/null +++ b/tests/lib/mocha-2.1.0/lib/browser/escape-string-regexp.js @@ -0,0 +1,11 @@ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; diff --git a/tests/lib/mocha-1.17.1/lib/browser/events.js b/tests/lib/mocha-2.1.0/lib/browser/events.js index cfbd072028..f708260896 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/events.js +++ b/tests/lib/mocha-2.1.0/lib/browser/events.js @@ -1,4 +1,3 @@ - /** * Module exports. */ @@ -175,4 +174,4 @@ EventEmitter.prototype.emit = function (name) { } return true; -};
\ No newline at end of file +}; diff --git a/tests/lib/mocha-1.17.1/lib/browser/fs.js b/tests/lib/mocha-2.1.0/lib/browser/fs.js index e69de29bb2..e69de29bb2 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/fs.js +++ b/tests/lib/mocha-2.1.0/lib/browser/fs.js diff --git a/tests/lib/mocha-1.17.1/lib/browser/path.js b/tests/lib/mocha-2.1.0/lib/browser/glob.js index e69de29bb2..e69de29bb2 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/path.js +++ b/tests/lib/mocha-2.1.0/lib/browser/glob.js diff --git a/tests/lib/mocha-2.1.0/lib/browser/path.js b/tests/lib/mocha-2.1.0/lib/browser/path.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/lib/mocha-2.1.0/lib/browser/path.js diff --git a/tests/lib/mocha-1.17.1/lib/browser/progress.js b/tests/lib/mocha-2.1.0/lib/browser/progress.js index b30e5179ea..b30e5179ea 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/progress.js +++ b/tests/lib/mocha-2.1.0/lib/browser/progress.js diff --git a/tests/lib/mocha-1.17.1/lib/browser/tty.js b/tests/lib/mocha-2.1.0/lib/browser/tty.js index 6f5f079a15..eab6388270 100644 --- a/tests/lib/mocha-1.17.1/lib/browser/tty.js +++ b/tests/lib/mocha-2.1.0/lib/browser/tty.js @@ -1,4 +1,3 @@ - exports.isatty = function(){ return true; }; diff --git a/tests/lib/mocha-1.17.1/lib/context.js b/tests/lib/mocha-2.1.0/lib/context.js index 6d6422a1ff..c983b6eb6a 100644 --- a/tests/lib/mocha-1.17.1/lib/context.js +++ b/tests/lib/mocha-2.1.0/lib/context.js @@ -1,4 +1,3 @@ - /** * Expose `Context`. */ @@ -36,11 +35,26 @@ Context.prototype.runnable = function(runnable){ */ Context.prototype.timeout = function(ms){ + if (arguments.length === 0) return this.runnable().timeout(); this.runnable().timeout(ms); return this; }; /** + * Set test timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Context} self + * @api private + */ + +Context.prototype.enableTimeouts = function (enabled) { + this.runnable().enableTimeouts(enabled); + return this; +}; + + +/** * Set test slowness threshold `ms`. * * @param {Number} ms diff --git a/tests/lib/mocha-1.17.1/lib/hook.js b/tests/lib/mocha-2.1.0/lib/hook.js index 814e7b6242..c2dc346b44 100644 --- a/tests/lib/mocha-1.17.1/lib/hook.js +++ b/tests/lib/mocha-2.1.0/lib/hook.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-1.17.1/lib/interfaces/bdd.js b/tests/lib/mocha-2.1.0/lib/interfaces/bdd.js index 3a5b1ed6fc..d212da29fa 100644 --- a/tests/lib/mocha-1.17.1/lib/interfaces/bdd.js +++ b/tests/lib/mocha-2.1.0/lib/interfaces/bdd.js @@ -1,11 +1,11 @@ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') - , utils = require('../utils'); + , utils = require('../utils') + , escapeRe = require('escape-string-regexp'); /** * BDD-style interface: @@ -33,32 +33,32 @@ module.exports = function(suite){ * Execute before running tests. */ - context.before = function(fn){ - suites[0].beforeAll(fn); + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after running tests. */ - context.after = function(fn){ - suites[0].afterAll(fn); + context.after = function(name, fn){ + suites[0].afterAll(name, fn); }; /** * Execute before each test case. */ - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.afterEach = function(fn){ - suites[0].afterEach(fn); + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); }; /** @@ -69,6 +69,7 @@ module.exports = function(suite){ context.describe = context.context = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); @@ -107,8 +108,9 @@ module.exports = function(suite){ context.it = context.specify = function(title, fn){ var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.pending) fn = null; var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; @@ -119,7 +121,7 @@ module.exports = function(suite){ context.it.only = function(title, fn){ var test = context.it(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); return test; }; diff --git a/tests/lib/mocha-1.17.1/lib/interfaces/exports.js b/tests/lib/mocha-2.1.0/lib/interfaces/exports.js index 6b229c0ff5..95e8a07012 100644 --- a/tests/lib/mocha-1.17.1/lib/interfaces/exports.js +++ b/tests/lib/mocha-2.1.0/lib/interfaces/exports.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -28,7 +27,7 @@ module.exports = function(suite){ suite.on('require', visit); - function visit(obj) { + function visit(obj, file) { var suite; for (var key in obj) { if ('function' == typeof obj[key]) { @@ -47,10 +46,12 @@ module.exports = function(suite){ suites[0].afterEach(fn); break; default: - suites[0].addTest(new Test(key, fn)); + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); } } else { - var suite = Suite.create(suites[0], key); + suite = Suite.create(suites[0], key); suites.unshift(suite); visit(obj[key]); suites.shift(); diff --git a/tests/lib/mocha-1.17.1/lib/interfaces/index.js b/tests/lib/mocha-2.1.0/lib/interfaces/index.js index f7b2655e37..4f825d15b2 100644 --- a/tests/lib/mocha-1.17.1/lib/interfaces/index.js +++ b/tests/lib/mocha-2.1.0/lib/interfaces/index.js @@ -1,4 +1,3 @@ - exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); diff --git a/tests/lib/mocha-1.17.1/lib/interfaces/qunit.js b/tests/lib/mocha-2.1.0/lib/interfaces/qunit.js index 30f6748fbc..fee8ac4499 100644 --- a/tests/lib/mocha-1.17.1/lib/interfaces/qunit.js +++ b/tests/lib/mocha-2.1.0/lib/interfaces/qunit.js @@ -1,10 +1,10 @@ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') + , escapeRe = require('escape-string-regexp') , utils = require('../utils'); /** @@ -41,32 +41,32 @@ module.exports = function(suite){ * Execute before running tests. */ - context.before = function(fn){ - suites[0].beforeAll(fn); + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after running tests. */ - context.after = function(fn){ - suites[0].afterAll(fn); + context.after = function(name, fn){ + suites[0].afterAll(name, fn); }; /** * Execute before each test case. */ - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.afterEach = function(fn){ - suites[0].afterEach(fn); + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); }; /** @@ -76,6 +76,7 @@ module.exports = function(suite){ context.suite = function(title){ if (suites.length > 1) suites.shift(); var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); return suite; }; @@ -97,6 +98,7 @@ module.exports = function(suite){ context.test = function(title, fn){ var test = new Test(title, fn); + test.file = file; suites[0].addTest(test); return test; }; @@ -107,7 +109,7 @@ module.exports = function(suite){ context.test.only = function(title, fn){ var test = context.test(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; diff --git a/tests/lib/mocha-1.17.1/lib/interfaces/tdd.js b/tests/lib/mocha-2.1.0/lib/interfaces/tdd.js index da5ca2c554..951fc9e1ac 100644 --- a/tests/lib/mocha-1.17.1/lib/interfaces/tdd.js +++ b/tests/lib/mocha-2.1.0/lib/interfaces/tdd.js @@ -1,11 +1,11 @@ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') - , utils = require('../utils');; + , escapeRe = require('escape-string-regexp') + , utils = require('../utils'); /** * TDD-style interface: @@ -41,32 +41,32 @@ module.exports = function(suite){ * Execute before each test case. */ - context.setup = function(fn){ - suites[0].beforeEach(fn); + context.setup = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.teardown = function(fn){ - suites[0].afterEach(fn); + context.teardown = function(name, fn){ + suites[0].afterEach(name, fn); }; /** * Execute before the suite. */ - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); + context.suiteSetup = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after the suite. */ - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); + context.suiteTeardown = function(name, fn){ + suites[0].afterAll(name, fn); }; /** @@ -77,6 +77,7 @@ module.exports = function(suite){ context.suite = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); @@ -111,8 +112,9 @@ module.exports = function(suite){ context.test = function(title, fn){ var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.pending) fn = null; var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; @@ -123,7 +125,7 @@ module.exports = function(suite){ context.test.only = function(title, fn){ var test = context.test(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; diff --git a/tests/lib/mocha-1.17.1/lib/mocha.js b/tests/lib/mocha-2.1.0/lib/mocha.js index 3bc9bd5450..27b45021df 100644 --- a/tests/lib/mocha-1.17.1/lib/mocha.js +++ b/tests/lib/mocha-2.1.0/lib/mocha.js @@ -9,6 +9,7 @@ */ var path = require('path') + , escapeRe = require('escape-string-regexp') , utils = require('./utils'); /** @@ -18,6 +19,16 @@ var path = require('path') exports = module.exports = Mocha; /** + * To require local UIs and reporters when running in node. + */ + +if (typeof process !== 'undefined' && typeof process.cwd === 'function') { + var join = path.join + , cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); +} + +/** * Expose internals. */ @@ -49,7 +60,7 @@ function image(name) { * Options: * * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `reporter` reporter instance, defaults to `mocha.reporters.spec` * - `globals` array of accepted globals * - `timeout` timeout in milliseconds * - `bail` bail on the first test failure @@ -69,9 +80,10 @@ function Mocha(options) { this.suite = new exports.Suite('', new exports.Context); this.ui(options.ui); this.bail(options.bail); - this.reporter(options.reporter); + this.reporter(options.reporter, options.reporterOptions); if (null != options.timeout) this.timeout(options.timeout); this.useColors(options.useColors) + if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts); if (options.slow) this.slow(options.slow); this.suite.on('pre-require', function (context) { @@ -116,17 +128,17 @@ Mocha.prototype.addFile = function(file){ }; /** - * Set reporter to `reporter`, defaults to "dot". + * Set reporter to `reporter`, defaults to "spec". * * @param {String|Function} reporter name or constructor + * @param {Object} reporterOptions optional options * @api public */ - -Mocha.prototype.reporter = function(reporter){ +Mocha.prototype.reporter = function(reporter, reporterOptions){ if ('function' == typeof reporter) { this._reporter = reporter; } else { - reporter = reporter || 'dot'; + reporter = reporter || 'spec'; var _reporter; try { _reporter = require('./reporters/' + reporter); } catch (err) {}; if (!_reporter) try { _reporter = require(reporter); } catch (err) {}; @@ -137,6 +149,7 @@ Mocha.prototype.reporter = function(reporter){ if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); this._reporter = _reporter; } + this.options.reporterOptions = reporterOptions; return this; }; @@ -209,7 +222,7 @@ Mocha.prototype._growl = function(runner, reporter) { Mocha.prototype.grep = function(re){ this.options.grep = 'string' == typeof re - ? new RegExp(utils.escapeRegexp(re)) + ? new RegExp(escapeRe(re)) : re; return this; }; @@ -285,9 +298,9 @@ Mocha.prototype.globals = function(globals){ */ Mocha.prototype.useColors = function(colors){ - this.options.useColors = arguments.length && colors != undefined - ? colors - : true; + if (colors !== undefined) { + this.options.useColors = colors; + } return this; }; @@ -333,6 +346,21 @@ Mocha.prototype.slow = function(slow){ }; /** + * Enable timeouts. + * + * @param {Boolean} enabled + * @return {Mocha} + * @api public + */ + +Mocha.prototype.enableTimeouts = function(enabled) { + this.suite.enableTimeouts(arguments.length && enabled !== undefined + ? enabled + : true); + return this +}; + +/** * Makes all tests async (accepting a callback) * * @return {Mocha} @@ -345,6 +373,16 @@ Mocha.prototype.asyncOnly = function(){ }; /** + * Disable syntax highlighting (in browser). + * @returns {Mocha} + * @api public + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** * Run tests and invoke `fn()` when complete. * * @param {Function} fn @@ -356,14 +394,26 @@ Mocha.prototype.run = function(fn){ if (this.files.length) this.loadFiles(); var suite = this.suite; var options = this.options; + options.files = this.files; var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); + var reporter = new this._reporter(runner, options); runner.ignoreLeaks = false !== options.ignoreLeaks; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); if (options.growl) this._growl(runner, reporter); - exports.reporters.Base.useColors = options.useColors; + if (options.useColors !== undefined) { + exports.reporters.Base.useColors = options.useColors; + } exports.reporters.Base.inlineDiffs = options.useInlineDiffs; - return runner.run(fn); + + function done(failures) { + if (reporter.done) { + reporter.done(failures, fn); + } else { + fn(failures); + } + } + + return runner.run(done); }; diff --git a/tests/lib/mocha-1.17.1/lib/ms.js b/tests/lib/mocha-2.1.0/lib/ms.js index 4096637431..ba451fab6c 100644 --- a/tests/lib/mocha-1.17.1/lib/ms.js +++ b/tests/lib/mocha-2.1.0/lib/ms.js @@ -24,7 +24,7 @@ var y = d * 365.25; module.exports = function(val, options){ options = options || {}; if ('string' == typeof val) return parse(val); - return options.long ? longFormat(val) : shortFormat(val); + return options['long'] ? longFormat(val) : shortFormat(val); }; /** diff --git a/tests/lib/mocha-1.17.1/lib/reporters/base.js b/tests/lib/mocha-2.1.0/lib/reporters/base.js index a0b83a648e..97b12ba5c5 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/base.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/base.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -98,7 +97,7 @@ if ('win32' == process.platform) { */ var color = exports.color = function(type, str) { - if (!exports.useColors) return str; + if (!exports.useColors) return String(str); return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; }; @@ -155,7 +154,7 @@ exports.cursor = { */ exports.list = function(failures){ - console.error(); + console.log(); failures.forEach(function(test, i){ // format var fmt = color('error title', ' %s) %s:\n') @@ -179,13 +178,13 @@ exports.list = function(failures){ // explicitly show diff if (err.showDiff && sameType(actual, expected)) { - escape = false; - err.actual = actual = stringify(canonicalize(actual)); - err.expected = expected = stringify(canonicalize(expected)); - } - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { + if ('string' !== typeof actual) { + escape = false; + err.actual = actual = utils.stringify(actual); + err.expected = expected = utils.stringify(expected); + } + fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); var match = message.match(/^([^:]+): expected/); msg = '\n ' + color('error message', match ? match[1] : msg); @@ -201,7 +200,7 @@ exports.list = function(failures){ stack = stack.slice(index ? index + 1 : index) .replace(/^/gm, ' '); - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + console.log(fmt, (i + 1), test.fullTitle(), msg, stack); }); }; @@ -306,11 +305,10 @@ Base.prototype.epilogue = function(){ if (stats.failures) { fmt = color('fail', ' %d failing'); - console.error(fmt, - stats.failures); + console.log(fmt, stats.failures); Base.list(this.failures); - console.error(); + console.log(); } console.log(); @@ -330,6 +328,7 @@ function pad(str, len) { return Array(len - str.length + 1).join(' ') + str; } + /** * Returns an inline diff between 2 strings with coloured ANSI output * @@ -443,53 +442,6 @@ function colorLines(name, str) { } /** - * Stringify `obj`. - * - * @param {Object} obj - * @return {String} - * @api private - */ - -function stringify(obj) { - if (obj instanceof RegExp) return obj.toString(); - return JSON.stringify(obj, null, 2); -} - -/** - * Return a new object that has the keys in sorted order. - * @param {Object} obj - * @return {Object} - * @api private - */ - - function canonicalize(obj, stack) { - stack = stack || []; - - if (utils.indexOf(stack, obj) !== -1) return obj; - - var canonicalizedObj; - - if ('[object Array]' == {}.toString.call(obj)) { - stack.push(obj); - canonicalizedObj = utils.map(obj, function(item) { - return canonicalize(item, stack); - }); - stack.pop(); - } else if (typeof obj === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - utils.forEach(utils.keys(obj).sort(), function(key) { - canonicalizedObj[key] = canonicalize(obj[key], stack); - }); - stack.pop(); - } else { - canonicalizedObj = obj; - } - - return canonicalizedObj; - } - -/** * Check that a / b have the same type. * * @param {Object} a @@ -503,4 +455,3 @@ function sameType(a, b) { b = Object.prototype.toString.call(b); return a == b; } - diff --git a/tests/lib/mocha-1.17.1/lib/reporters/doc.js b/tests/lib/mocha-2.1.0/lib/reporters/doc.js index 2e5bf57fc4..d194eb0e69 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/doc.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/doc.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -53,4 +52,11 @@ function Doc(runner) { var code = utils.escape(utils.clean(test.fn.toString())); console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code); }); + + runner.on('fail', function(test, err){ + console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code); + console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err)); + }); } diff --git a/tests/lib/mocha-1.17.1/lib/reporters/dot.js b/tests/lib/mocha-2.1.0/lib/reporters/dot.js index 0c298ba71d..3c7445760d 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/dot.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/dot.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -25,13 +24,14 @@ function Dot(runner) { var self = this , stats = this.stats , width = Base.window.width * .75 | 0 - , n = 0; + , n = -1; runner.on('start', function(){ process.stdout.write('\n '); }); runner.on('pending', function(test){ + if (++n % width == 0) process.stdout.write('\n '); process.stdout.write(color('pending', Base.symbols.dot)); }); @@ -59,4 +59,4 @@ function Dot(runner) { * Inherit from `Base.prototype`. */ -Dot.prototype.__proto__ = Base.prototype;
\ No newline at end of file +Dot.prototype.__proto__ = Base.prototype; diff --git a/tests/lib/mocha-1.17.1/lib/reporters/html-cov.js b/tests/lib/mocha-2.1.0/lib/reporters/html-cov.js index bfb27ffdf6..74b46adcd9 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/html-cov.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/html-cov.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -48,4 +47,4 @@ function coverageClass(n) { if (n >= 50) return 'medium'; if (n >= 25) return 'low'; return 'terrible'; -}
\ No newline at end of file +} diff --git a/tests/lib/mocha-1.17.1/lib/reporters/html.js b/tests/lib/mocha-2.1.0/lib/reporters/html.js index 873f024d7c..31ec106106 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/html.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/html.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -42,7 +41,7 @@ var statsTemplate = '<ul id="mocha-stats">' * @api public */ -function HTML(runner, root) { +function HTML(runner) { Base.call(this, runner); var self = this @@ -60,8 +59,7 @@ function HTML(runner, root) { , stack = [report] , progress , ctx - - root = root || document.getElementById('mocha'); + , root = document.getElementById('mocha'); if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; @@ -137,7 +135,7 @@ function HTML(runner, root) { } else if (test.pending) { var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title); } else { - var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle())); + var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test)); var str = test.err.stack || test.err.toString(); // FF / Opera do not add the message @@ -179,13 +177,22 @@ function HTML(runner, root) { } /** + * Makes a URL, preserving querystring ("search") parameters. + * @param {string} s + * @returns {string} your new URL + */ +var makeUrl = function makeUrl(s) { + var search = window.location.search; + return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s); +}; + +/** * Provide suite URL * * @param {Object} [suite] */ - HTML.prototype.suiteURL = function(suite){ - return '?grep=' + encodeURIComponent(suite.fullTitle()); + return makeUrl(suite.fullTitle()); }; /** @@ -195,7 +202,7 @@ HTML.prototype.suiteURL = function(suite){ */ HTML.prototype.testURL = function(test){ - return '?grep=' + encodeURIComponent(test.fullTitle()); + return makeUrl(test.fullTitle()); }; /** diff --git a/tests/lib/mocha-1.17.1/lib/reporters/index.js b/tests/lib/mocha-2.1.0/lib/reporters/index.js index 1c4fccf060..87b76d904f 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/index.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/index.js @@ -1,4 +1,3 @@ - exports.Base = require('./base'); exports.Dot = require('./dot'); exports.Doc = require('./doc'); diff --git a/tests/lib/mocha-1.17.1/lib/reporters/json-cov.js b/tests/lib/mocha-2.1.0/lib/reporters/json-cov.js index 73d0009377..309c0ef54a 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/json-cov.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/json-cov.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -89,7 +88,7 @@ function map(cov) { } return ret; -}; +} /** * Map jscoverage data for a single source file diff --git a/tests/lib/mocha-1.17.1/lib/reporters/json-stream.js b/tests/lib/mocha-2.1.0/lib/reporters/json-stream.js index 7cb8fbede7..f7c05a89c2 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/json-stream.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/json-stream.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -35,7 +34,9 @@ function List(runner) { }); runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); + test = clean(test); + test.err = err.message; + console.log(JSON.stringify(['fail', test])); }); runner.on('end', function(){ @@ -58,4 +59,4 @@ function clean(test) { , fullTitle: test.fullTitle() , duration: test.duration } -}
\ No newline at end of file +} diff --git a/tests/lib/mocha-1.17.1/lib/reporters/json.js b/tests/lib/mocha-2.1.0/lib/reporters/json.js index a699f5014a..f565506c6e 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/json.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/json.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -25,6 +24,7 @@ function JSONReporter(runner) { Base.call(this, runner); var tests = [] + , pending = [] , failures = [] , passes = []; @@ -40,14 +40,21 @@ function JSONReporter(runner) { failures.push(test); }); + runner.on('pending', function(test){ + pending.push(test); + }); + runner.on('end', function(){ var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) }; + runner.testResults = obj; + process.stdout.write(JSON.stringify(obj, null, 2)); }); } @@ -63,8 +70,23 @@ function JSONReporter(runner) { function clean(test) { return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + err: errorJSON(test.err || {}) } -}
\ No newline at end of file +} + +/** + * Transform `error` into a JSON object. + * @param {Error} err + * @return {Object} + */ + +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} diff --git a/tests/lib/mocha-1.17.1/lib/reporters/landing.js b/tests/lib/mocha-2.1.0/lib/reporters/landing.js index bf064f64b2..ee004a20aa 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/landing.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/landing.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -56,7 +55,7 @@ function Landing(runner) { } runner.on('start', function(){ - stream.write('\n '); + stream.write('\n\n\n '); cursor.hide(); }); @@ -73,7 +72,7 @@ function Landing(runner) { } // render landing strip - stream.write('\u001b[4F\n\n'); + stream.write('\u001b['+(width+1)+'D\u001b[2A'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('â‹…'))); @@ -94,4 +93,4 @@ function Landing(runner) { * Inherit from `Base.prototype`. */ -Landing.prototype.__proto__ = Base.prototype;
\ No newline at end of file +Landing.prototype.__proto__ = Base.prototype; diff --git a/tests/lib/mocha-1.17.1/lib/reporters/list.js b/tests/lib/mocha-2.1.0/lib/reporters/list.js index 3328e157a8..f64367a410 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/list.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/list.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-1.17.1/lib/reporters/markdown.js b/tests/lib/mocha-2.1.0/lib/reporters/markdown.js index 6383a64248..e14174c30e 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/markdown.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/markdown.js @@ -6,6 +6,12 @@ var Base = require('./base') , utils = require('../utils'); /** + * Constants + */ + +var SUITE_PREFIX = '$'; + +/** * Expose `Markdown`. */ @@ -35,8 +41,9 @@ function Markdown(runner) { } function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + var ret = obj, + key = SUITE_PREFIX + suite.title; + obj = obj[key] = obj[key] || { suite: suite }; suite.suites.forEach(function(suite){ mapTOC(suite, obj); }); @@ -49,11 +56,13 @@ function Markdown(runner) { var link; for (var key in obj) { if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } buf += stringifyTOC(obj[key], level); } - --level; return buf; } @@ -88,4 +97,4 @@ function Markdown(runner) { process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); }); -}
\ No newline at end of file +} diff --git a/tests/lib/mocha-1.17.1/lib/reporters/min.js b/tests/lib/mocha-2.1.0/lib/reporters/min.js index 1b6117d065..ce1a3fef12 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/min.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/min.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-1.17.1/lib/reporters/nyan.js b/tests/lib/mocha-2.1.0/lib/reporters/nyan.js index 4501f6b264..63056b1777 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/nyan.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/nyan.js @@ -2,8 +2,7 @@ * Module dependencies. */ -var Base = require('./base') - , color = Base.color; +var Base = require('./base'); /** * Expose `Dot`. @@ -80,17 +79,16 @@ NyanCat.prototype.draw = function(){ NyanCat.prototype.drawScoreboard = function(){ var stats = this.stats; - var colors = Base.colors; - function draw(color, n) { + function draw(type, n) { write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write(Base.color(type, n)); write('\n'); } - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); write('\n'); this.cursorUp(this.numberOfLines); @@ -140,26 +138,26 @@ NyanCat.prototype.drawRainbow = function(){ NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; - var color = '\u001b[' + startWidth + 'C'; + var dist = '\u001b[' + startWidth + 'C'; var padding = ''; - write(color); + write(dist); write('_,------,'); write('\n'); - write(color); + write(dist); padding = self.tick ? ' ' : ' '; write('_|' + padding + '/\\_/\\ '); write('\n'); - write(color); + write(dist); padding = self.tick ? '_' : '__'; var tail = self.tick ? '~' : '^'; var face; write(tail + '|' + padding + this.face() + ' '); write('\n'); - write(color); + write(dist); padding = self.tick ? ' ' : ' '; write(padding + '"" "" '); write('\n'); @@ -185,7 +183,7 @@ NyanCat.prototype.face = function() { } else { return '( - .-)'; } -} +}; /** * Move cursor up `n`. @@ -240,6 +238,8 @@ NyanCat.prototype.generateColors = function(){ */ NyanCat.prototype.rainbowify = function(str){ + if (!Base.useColors) + return str; var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; diff --git a/tests/lib/mocha-1.17.1/lib/reporters/progress.js b/tests/lib/mocha-2.1.0/lib/reporters/progress.js index 59536386cb..2debb94541 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/progress.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/progress.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -36,7 +35,8 @@ function Progress(runner, options) { , width = Base.window.width * .50 | 0 , total = runner.total , complete = 0 - , max = Math.max; + , max = Math.max + , lastN = -1; // default chars options.open = options.open || '['; @@ -59,6 +59,12 @@ function Progress(runner, options) { , n = width * percent | 0 , i = width - n; + if (lastN === n && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + cursor.CR(); process.stdout.write('\u001b[J'); process.stdout.write(color('progress', ' ' + options.open)); diff --git a/tests/lib/mocha-1.17.1/lib/reporters/spec.js b/tests/lib/mocha-2.1.0/lib/reporters/spec.js index ada25c3eae..e74f4ace7c 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/spec.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/spec.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-1.17.1/lib/reporters/tap.js b/tests/lib/mocha-2.1.0/lib/reporters/tap.js index 2bcd995baa..01a92eb076 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/tap.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/tap.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-1.17.1/lib/reporters/templates/coverage.jade b/tests/lib/mocha-2.1.0/lib/reporters/templates/coverage.jade index b78119fbc1..edd59d8864 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/templates/coverage.jade +++ b/tests/lib/mocha-2.1.0/lib/reporters/templates/coverage.jade @@ -1,7 +1,8 @@ -!!! 5 +doctype html html head title Coverage + meta(charset='utf-8') include script.html include style.html body diff --git a/tests/lib/mocha-1.17.1/lib/reporters/templates/menu.jade b/tests/lib/mocha-2.1.0/lib/reporters/templates/menu.jade index e9ba4648a6..e9ba4648a6 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/templates/menu.jade +++ b/tests/lib/mocha-2.1.0/lib/reporters/templates/menu.jade diff --git a/tests/lib/mocha-1.17.1/lib/reporters/templates/script.html b/tests/lib/mocha-2.1.0/lib/reporters/templates/script.html index 073cf7939c..073cf7939c 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/templates/script.html +++ b/tests/lib/mocha-2.1.0/lib/reporters/templates/script.html diff --git a/tests/lib/mocha-1.17.1/lib/reporters/templates/style.html b/tests/lib/mocha-2.1.0/lib/reporters/templates/style.html index 643c0ab7b8..4b1f6826c6 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/templates/style.html +++ b/tests/lib/mocha-2.1.0/lib/reporters/templates/style.html @@ -317,4 +317,4 @@ code .init { color: #2F6FAD } code .string { color: #5890AD } code .keyword { color: #8A6343 } code .number { color: #2F6FAD } -</style>
\ No newline at end of file +</style> diff --git a/tests/lib/mocha-1.17.1/lib/reporters/xunit.js b/tests/lib/mocha-2.1.0/lib/reporters/xunit.js index e956380d8e..77cd347094 100644 --- a/tests/lib/mocha-1.17.1/lib/reporters/xunit.js +++ b/tests/lib/mocha-2.1.0/lib/reporters/xunit.js @@ -1,10 +1,10 @@ - /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils') + , fs = require('fs') , escape = utils.escape; /** @@ -30,12 +30,19 @@ exports = module.exports = XUnit; * @api public */ -function XUnit(runner) { +function XUnit(runner, options) { Base.call(this, runner); var stats = this.stats , tests = [] , self = this; + if (options.reporterOptions && options.reporterOptions.output) { + if (! fs.createWriteStream) { + throw new Error('file output not supported in browser'); + } + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + runner.on('pending', function(test){ tests.push(test); }); @@ -49,7 +56,7 @@ function XUnit(runner) { }); runner.on('end', function(){ - console.log(tag('testsuite', { + self.write(tag('testsuite', { name: 'Mocha Tests' , tests: stats.tests , failures: stats.failures @@ -59,22 +66,46 @@ function XUnit(runner) { , time: (stats.duration / 1000) || 0 }, false)); - tests.forEach(test); - console.log('</testsuite>'); + tests.forEach(function(t) { self.test(t); }); + self.write('</testsuite>'); }); } /** + * Override done to close the stream (if it's a file). + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + +/** * Inherit from `Base.prototype`. */ XUnit.prototype.__proto__ = Base.prototype; /** + * Write out the given line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else { + console.log(line); + } +}; + +/** * Output tag for the given `test.` */ -function test(test) { +XUnit.prototype.test = function(test, ostream) { var attrs = { classname: test.parent.fullTitle() , name: test.title @@ -83,14 +114,13 @@ function test(test) { if ('failed' == test.state) { var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { - console.log(tag('testcase', attrs, true) ); + this.write(tag('testcase', attrs, true) ); } -} +}; /** * HTML tag helper. diff --git a/tests/lib/mocha-1.17.1/lib/runnable.js b/tests/lib/mocha-2.1.0/lib/runnable.js index 03b8792b10..cad9dc78b2 100644 --- a/tests/lib/mocha-1.17.1/lib/runnable.js +++ b/tests/lib/mocha-2.1.0/lib/runnable.js @@ -1,11 +1,11 @@ - /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter , debug = require('debug')('mocha:runnable') - , milliseconds = require('./ms'); + , milliseconds = require('./ms') + , utils = require('./utils'); /** * Save timer references to avoid Sinon interfering (see GH-237). @@ -44,7 +44,9 @@ function Runnable(title, fn) { this.sync = ! this.async; this._timeout = 2000; this._slow = 75; + this._enableTimeouts = true; this.timedOut = false; + this._trace = new Error('done() called multiple times') } /** @@ -63,6 +65,7 @@ Runnable.prototype.__proto__ = EventEmitter.prototype; Runnable.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms === 0) this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = ms; @@ -87,6 +90,21 @@ Runnable.prototype.slow = function(ms){ }; /** + * Set and & get timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Runnable|Boolean} enabled or self + * @api private + */ + +Runnable.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** * Return the full title generated by recursively * concatenating the parent's full title. * @@ -134,8 +152,10 @@ Runnable.prototype.resetTimeout = function(){ var self = this; var ms = this.timeout() || 1e9; + if (!this._enableTimeouts) return; this.clearTimeout(); this.timer = setTimeout(function(){ + if (!self._enableTimeouts) return; self.callback(new Error('timeout of ' + ms + 'ms exceeded')); self.timedOut = true; }, ms); @@ -160,54 +180,54 @@ Runnable.prototype.globals = function(arr){ Runnable.prototype.run = function(fn){ var self = this - , ms = this.timeout() , start = new Date , ctx = this.ctx , finished , emitted; - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } + // Some times the ctx exists but it is not runnable + if (ctx && ctx.runnable) ctx.runnable(this); // called multiple times function multiple(err) { if (emitted) return; emitted = true; - self.emit('error', err || new Error('done() called multiple times')); + self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate')); } // finished function done(err) { + var ms = self.timeout(); if (self.timedOut) return; - if (finished) return multiple(err); + if (finished) return multiple(err || self._trace); self.clearTimeout(); self.duration = new Date - start; finished = true; + if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded'); fn(err); } // for .resetTimeout() this.callback = done; - // async + // explicit async with `done` argument if (this.async) { + this.resetTimeout(); + try { this.fn.call(ctx, function(err){ if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + if (null != err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); + } else { + return done(new Error('done() invoked with non-Error: ' + err)); + } + } done(); }); } catch (err) { - done(err); + done(utils.getError(err)); } return; } @@ -216,12 +236,30 @@ Runnable.prototype.run = function(fn){ return done(new Error('--async-only option in use without declaring `done()`')); } - // sync + // sync or promise-returning try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); + if (this.pending) { + done(); + } else { + callFn(this.fn); + } } catch (err) { - fn(err); + done(utils.getError(err)); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result + .then(function() { + done() + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')) + }); + } else { + done(); + } } }; diff --git a/tests/lib/mocha-1.17.1/lib/runner.js b/tests/lib/mocha-2.1.0/lib/runner.js index a604aeb582..ba02d9a53e 100644 --- a/tests/lib/mocha-1.17.1/lib/runner.js +++ b/tests/lib/mocha-2.1.0/lib/runner.js @@ -19,7 +19,9 @@ var globals = [ 'setInterval', 'clearInterval', 'XMLHttpRequest', - 'Date' + 'Date', + 'setImmediate', + 'clearImmediate' ]; /** @@ -161,7 +163,6 @@ Runner.prototype.checkGlobals = function(test){ var ok = this._globals; var globals = this.globalProps(); - var isNode = process.kill; var leaks; if (test) { @@ -245,7 +246,6 @@ Runner.prototype.hook = function(name, fn){ function next(i) { var hook = hooks[i]; if (!hook) return fn(); - if (self.failures && suite.bail()) return fn(); self.currentRunnable = hook; hook.ctx.currentTest = self.test; @@ -391,6 +391,7 @@ Runner.prototype.runTests = function(suite, fn){ , tests = suite.tests.slice() , test; + function hookErr(err, errSuite, after) { // before/after Each hook for errSuite failed: var orig = self.suite; @@ -532,13 +533,26 @@ Runner.prototype.runSuite = function(suite, fn){ */ Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); + if (err) { + debug('uncaught exception %s', err !== function () { + return this; + }.call(err) ? err : ( err.message || err )); + } else { + debug('uncaught undefined exception'); + err = utils.undefinedError(); + } err.uncaught = true; + + var runnable = this.currentRunnable; + if (!runnable) return; + + var wasAlreadyDone = runnable.state; this.fail(runnable, err); + runnable.clearTimeout(); + + if (wasAlreadyDone) return; + // recover from test if ('test' == runnable.type) { this.emit('test end', runnable); @@ -598,7 +612,7 @@ Runner.prototype.run = function(fn){ Runner.prototype.abort = function(){ debug('aborting'); this._abort = true; -} +}; /** * Filter leaks with the given globals flagged as `ok`. diff --git a/tests/lib/mocha-1.17.1/lib/suite.js b/tests/lib/mocha-2.1.0/lib/suite.js index 869bb88d52..65cdbffc0e 100644 --- a/tests/lib/mocha-1.17.1/lib/suite.js +++ b/tests/lib/mocha-2.1.0/lib/suite.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -46,9 +45,11 @@ exports.create = function(parent, title){ * @api private */ -function Suite(title, ctx) { +function Suite(title, parentContext) { this.title = title; - this.ctx = ctx; + var context = function() {}; + context.prototype = parentContext; + this.ctx = new context(); this.suites = []; this.tests = []; this.pending = false; @@ -58,6 +59,7 @@ function Suite(title, ctx) { this._afterAll = []; this.root = !title; this._timeout = 2000; + this._enableTimeouts = true; this._slow = 75; this._bail = false; } @@ -80,6 +82,7 @@ Suite.prototype.clone = function(){ debug('clone'); suite.ctx = this.ctx; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; @@ -95,6 +98,7 @@ Suite.prototype.clone = function(){ Suite.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms.toString() === '0') this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = parseInt(ms, 10); @@ -102,6 +106,21 @@ Suite.prototype.timeout = function(ms){ }; /** + * Set timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Suite|Boolean} self or enabled + * @api private + */ + +Suite.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** * Set slow `ms` or short-hand such as "2s". * * @param {Number|String} ms @@ -120,7 +139,7 @@ Suite.prototype.slow = function(ms){ /** * Sets whether to bail after first error. * - * @parma {Boolean} bail + * @param {Boolean} bail * @return {Suite|Number} for chaining * @api private */ @@ -140,11 +159,18 @@ Suite.prototype.bail = function(bail){ * @api private */ -Suite.prototype.beforeAll = function(fn){ +Suite.prototype.beforeAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeAll.push(hook); @@ -160,11 +186,18 @@ Suite.prototype.beforeAll = function(fn){ * @api private */ -Suite.prototype.afterAll = function(fn){ +Suite.prototype.afterAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterAll.push(hook); @@ -180,11 +213,18 @@ Suite.prototype.afterAll = function(fn){ * @api private */ -Suite.prototype.beforeEach = function(fn){ +Suite.prototype.beforeEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeEach.push(hook); @@ -200,11 +240,18 @@ Suite.prototype.beforeEach = function(fn){ * @api private */ -Suite.prototype.afterEach = function(fn){ +Suite.prototype.afterEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterEach.push(hook); @@ -223,6 +270,7 @@ Suite.prototype.afterEach = function(fn){ Suite.prototype.addSuite = function(suite){ suite.parent = this; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); @@ -241,6 +289,7 @@ Suite.prototype.addSuite = function(suite){ Suite.prototype.addTest = function(test){ test.parent = this; test.timeout(this.timeout()); + test.enableTimeouts(this.enableTimeouts()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); diff --git a/tests/lib/mocha-1.17.1/lib/template.html b/tests/lib/mocha-2.1.0/lib/template.html index 0590d4aac2..0590d4aac2 100644 --- a/tests/lib/mocha-1.17.1/lib/template.html +++ b/tests/lib/mocha-2.1.0/lib/template.html diff --git a/tests/lib/mocha-1.17.1/lib/test.js b/tests/lib/mocha-2.1.0/lib/test.js index 11773e0cc9..4a4cf63cce 100644 --- a/tests/lib/mocha-1.17.1/lib/test.js +++ b/tests/lib/mocha-2.1.0/lib/test.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ diff --git a/tests/lib/mocha-2.1.0/lib/utils.js b/tests/lib/mocha-2.1.0/lib/utils.js new file mode 100644 index 0000000000..c624375025 --- /dev/null +++ b/tests/lib/mocha-2.1.0/lib/utils.js @@ -0,0 +1,548 @@ +/** + * Module dependencies. + */ + +var fs = require('fs') + , path = require('path') + , basename = path.basename + , exists = fs.existsSync || path.existsSync + , glob = require('glob') + , join = path.join + , debug = require('debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#map (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.map = function(arr, fn, scope){ + var result = []; + for (var i = 0, l = arr.length; i < l; i++) + result.push(fn.call(scope, arr[i], i)); + return result; +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ext, ret){ + ret = ret || []; + ext = ext || ['js']; + + var re = new RegExp('\\.(' + ext.join('|') + ')$'); + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ext, ret); + } else if (path.match(re)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') + .replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '') + .replace(/\s+\}$/, ''); + + var spaces = str.match(/^\n?( *)/)[1].length + , tabs = str.match(/^\n?(\t*)/)[1].length + , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') + .replace(/('.*?')/gm, '<span class="string">$1</span>') + .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') + .replace(/(\d+)/gm, '<span class="number">$1</span>') + .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementById('mocha').getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +/** + * If a value could have properties, and has none, this function is called, which returns + * a string representation of the empty value. + * + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @param {*} value Value to inspect + * @param {string} [type] The type of the value, if known. + * @returns {string} + */ +var emptyRepresentation = function emptyRepresentation(value, type) { + type = type || exports.type(value); + + switch(type) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +}; + +/** + * Takes some variable and asks `{}.toString()` what it thinks it is. + * @param {*} value Anything + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + * @api private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @returns {string} + */ +exports.type = function type(value) { + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return 'buffer'; + } + return Object.prototype.toString.call(value) + .replace(/^\[.+\s(.+?)\]$/, '$1') + .toLowerCase(); +}; + +/** + * @summary Stringify `value`. + * @description Different behavior depending on type of value. + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @see exports.type + * @param {*} value + * @return {string} + * @api private + */ + +exports.stringify = function(value) { + var prop, + type = exports.type(value); + + if (type === 'null' || type === 'undefined') { + return '[' + type + ']'; + } + + if (type === 'date') { + return '[Date: ' + value.toISOString() + ']'; + } + + if (!~exports.indexOf(['object', 'array', 'function'], type)) { + return value.toString(); + } + + for (prop in value) { + if (value.hasOwnProperty(prop)) { + return JSON.stringify(exports.canonicalize(value), null, 2).replace(/,(\n|$)/g, '$1'); + } + } + + return emptyRepresentation(value, type); +}; + +/** + * Return if obj is a Buffer + * @param {Object} arg + * @return {Boolean} + * @api private + */ +exports.isBuffer = function (arg) { + return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg); +}; + +/** + * @summary Return a new Thing that has the keys in sorted order. Recursive. + * @description If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @return {(Object|Array|Function|string|undefined)} + * @see {@link exports.stringify} + * @api private + */ + +exports.canonicalize = function(value, stack) { + var canonicalizedObj, + type = exports.type(value), + prop, + withStack = function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + }; + + stack = stack || []; + + if (exports.indexOf(stack, value) !== -1) { + return '[Circular]'; + } + + switch(type) { + case 'undefined': + canonicalizedObj = '[undefined]'; + break; + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function () { + canonicalizedObj = exports.map(value, function (item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'date': + canonicalizedObj = '[Date: ' + value.toISOString() + ']'; + break; + case 'function': + for (prop in value) { + canonicalizedObj = {}; + break; + } + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, type); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function () { + exports.forEach(exports.keys(value).sort(), function (key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'number': + case 'boolean': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value.toString(); + } + + return canonicalizedObj; +}; + +/** + * Lookup file names at the given `path`. + */ +exports.lookupFiles = function lookupFiles(path, extensions, recursive) { + var files = []; + var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + + if (!exists(path)) { + if (exists(path + '.js')) { + path += '.js'; + } else { + files = glob.sync(path); + if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'"); + return files; + } + } + + try { + var stat = fs.statSync(path); + if (stat.isFile()) return path; + } + catch (ignored) { + return; + } + + fs.readdirSync(path).forEach(function(file){ + file = join(path, file); + try { + var stat = fs.statSync(file); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(file, extensions, recursive)); + } + return; + } + } + catch (ignored) { + return; + } + if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return; + files.push(file); + }); + + return files; +}; + +/** + * Generate an undefined error with a message warning the user. + * + * @return {Error} + */ + +exports.undefinedError = function(){ + return new Error('Caught undefined error, did you throw without specifying what?'); +}; + +/** + * Generate an undefined error if `err` is not defined. + * + * @param {Error} err + * @return {Error} + */ + +exports.getError = function(err){ + return err || exports.undefinedError(); +}; + diff --git a/tests/lib/mocha-1.17.1/media/logo.svg b/tests/lib/mocha-2.1.0/media/logo.svg index 88d3713080..bc3cb4b5df 100644 --- a/tests/lib/mocha-1.17.1/media/logo.svg +++ b/tests/lib/mocha-2.1.0/media/logo.svg @@ -1,8 +1,7 @@ -<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
- width="612px" height="792px" viewBox="0 0 612 792" enable-background="new 0 0 612 792" xml:space="preserve">
-<circle fill="#8A6343" cx="306" cy="396" r="306"/>
-<text transform="matrix(1 0 0 1 72.1431 424.7633)" fill="#FFFFFF" font-family="'HelveticaNeue'" font-size="153">mocha</text>
-</svg>
+<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="612px" height="792px" viewBox="0 0 612 792" enable-background="new 0 0 612 792" xml:space="preserve"> +<circle fill="#8A6343" cx="306" cy="396" r="306"/> +<text transform="matrix(1 0 0 1 72.1431 424.7633)" fill="#FFFFFF" font-family="'HelveticaNeue'" font-size="153">mocha</text> +</svg> diff --git a/tests/lib/mocha-1.17.1/mocha.css b/tests/lib/mocha-2.1.0/mocha.css index 42b9798fa4..42b9798fa4 100644 --- a/tests/lib/mocha-1.17.1/mocha.css +++ b/tests/lib/mocha-2.1.0/mocha.css diff --git a/tests/lib/mocha-1.17.1/mocha.js b/tests/lib/mocha-2.1.0/mocha.js index 5fafaa8f5a..22d1a9f324 100644 --- a/tests/lib/mocha-1.17.1/mocha.js +++ b/tests/lib/mocha-2.1.0/mocha.js @@ -46,8 +46,8 @@ require.relative = function (parent) { }; }; -require.register("browser/debug.js", function(module, exports, require){ +require.register("browser/debug.js", function(module, exports, require){ module.exports = function(type){ return function(){ } @@ -223,7 +223,22 @@ var JsDiff = (function() { var LineDiff = new Diff(); LineDiff.tokenize = function(value) { - return value.split(/^/m); + var retLines = [], + lines = value.split(/^/m); + + for(var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1]; + + // Merge lines that may contain windows new lines + if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') { + retLines[retLines.length - 1] += '\n'; + } else if (line) { + retLines.push(line); + } + } + + return retLines; }; return { @@ -413,8 +428,22 @@ if (typeof module !== 'undefined') { }); // module: browser/diff.js -require.register("browser/events.js", function(module, exports, require){ +require.register("browser/escape-string-regexp.js", function(module, exports, require){ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; +}); // module: browser/escape-string-regexp.js + +require.register("browser/events.js", function(module, exports, require){ /** * Module exports. */ @@ -592,12 +621,17 @@ EventEmitter.prototype.emit = function (name) { return true; }; + }); // module: browser/events.js require.register("browser/fs.js", function(module, exports, require){ }); // module: browser/fs.js +require.register("browser/glob.js", function(module, exports, require){ + +}); // module: browser/glob.js + require.register("browser/path.js", function(module, exports, require){ }); // module: browser/path.js @@ -732,7 +766,6 @@ Progress.prototype.draw = function(ctx){ }); // module: browser/progress.js require.register("browser/tty.js", function(module, exports, require){ - exports.isatty = function(){ return true; }; @@ -749,7 +782,6 @@ exports.getWindowSize = function(){ }); // module: browser/tty.js require.register("context.js", function(module, exports, require){ - /** * Expose `Context`. */ @@ -787,11 +819,26 @@ Context.prototype.runnable = function(runnable){ */ Context.prototype.timeout = function(ms){ + if (arguments.length === 0) return this.runnable().timeout(); this.runnable().timeout(ms); return this; }; /** + * Set test timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Context} self + * @api private + */ + +Context.prototype.enableTimeouts = function (enabled) { + this.runnable().enableTimeouts(enabled); + return this; +}; + + +/** * Set test slowness threshold `ms`. * * @param {Number} ms @@ -822,7 +869,6 @@ Context.prototype.inspect = function(){ }); // module: context.js require.register("hook.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -857,6 +903,7 @@ F.prototype = Runnable.prototype; Hook.prototype = new F; Hook.prototype.constructor = Hook; + /** * Get or set the test `err`. * @@ -878,14 +925,14 @@ Hook.prototype.error = function(err){ }); // module: hook.js require.register("interfaces/bdd.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') - , utils = require('../utils'); + , utils = require('../utils') + , escapeRe = require('browser/escape-string-regexp'); /** * BDD-style interface: @@ -913,32 +960,32 @@ module.exports = function(suite){ * Execute before running tests. */ - context.before = function(fn){ - suites[0].beforeAll(fn); + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after running tests. */ - context.after = function(fn){ - suites[0].afterAll(fn); + context.after = function(name, fn){ + suites[0].afterAll(name, fn); }; /** * Execute before each test case. */ - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.afterEach = function(fn){ - suites[0].afterEach(fn); + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); }; /** @@ -949,6 +996,7 @@ module.exports = function(suite){ context.describe = context.context = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); @@ -987,8 +1035,9 @@ module.exports = function(suite){ context.it = context.specify = function(title, fn){ var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.pending) fn = null; var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; @@ -999,7 +1048,7 @@ module.exports = function(suite){ context.it.only = function(title, fn){ var test = context.it(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); return test; }; @@ -1019,7 +1068,6 @@ module.exports = function(suite){ }); // module: interfaces/bdd.js require.register("interfaces/exports.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -1049,7 +1097,7 @@ module.exports = function(suite){ suite.on('require', visit); - function visit(obj) { + function visit(obj, file) { var suite; for (var key in obj) { if ('function' == typeof obj[key]) { @@ -1068,10 +1116,12 @@ module.exports = function(suite){ suites[0].afterEach(fn); break; default: - suites[0].addTest(new Test(key, fn)); + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); } } else { - var suite = Suite.create(suites[0], key); + suite = Suite.create(suites[0], key); suites.unshift(suite); visit(obj[key]); suites.shift(); @@ -1083,7 +1133,6 @@ module.exports = function(suite){ }); // module: interfaces/exports.js require.register("interfaces/index.js", function(module, exports, require){ - exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); @@ -1092,13 +1141,13 @@ exports.exports = require('./exports'); }); // module: interfaces/index.js require.register("interfaces/qunit.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') + , escapeRe = require('browser/escape-string-regexp') , utils = require('../utils'); /** @@ -1135,32 +1184,32 @@ module.exports = function(suite){ * Execute before running tests. */ - context.before = function(fn){ - suites[0].beforeAll(fn); + context.before = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after running tests. */ - context.after = function(fn){ - suites[0].afterAll(fn); + context.after = function(name, fn){ + suites[0].afterAll(name, fn); }; /** * Execute before each test case. */ - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); + context.beforeEach = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.afterEach = function(fn){ - suites[0].afterEach(fn); + context.afterEach = function(name, fn){ + suites[0].afterEach(name, fn); }; /** @@ -1170,6 +1219,7 @@ module.exports = function(suite){ context.suite = function(title){ if (suites.length > 1) suites.shift(); var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); return suite; }; @@ -1191,6 +1241,7 @@ module.exports = function(suite){ context.test = function(title, fn){ var test = new Test(title, fn); + test.file = file; suites[0].addTest(test); return test; }; @@ -1201,7 +1252,7 @@ module.exports = function(suite){ context.test.only = function(title, fn){ var test = context.test(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; @@ -1218,14 +1269,14 @@ module.exports = function(suite){ }); // module: interfaces/qunit.js require.register("interfaces/tdd.js", function(module, exports, require){ - /** * Module dependencies. */ var Suite = require('../suite') , Test = require('../test') - , utils = require('../utils');; + , escapeRe = require('browser/escape-string-regexp') + , utils = require('../utils'); /** * TDD-style interface: @@ -1261,32 +1312,32 @@ module.exports = function(suite){ * Execute before each test case. */ - context.setup = function(fn){ - suites[0].beforeEach(fn); + context.setup = function(name, fn){ + suites[0].beforeEach(name, fn); }; /** * Execute after each test case. */ - context.teardown = function(fn){ - suites[0].afterEach(fn); + context.teardown = function(name, fn){ + suites[0].afterEach(name, fn); }; /** * Execute before the suite. */ - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); + context.suiteSetup = function(name, fn){ + suites[0].beforeAll(name, fn); }; /** * Execute after the suite. */ - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); + context.suiteTeardown = function(name, fn){ + suites[0].afterAll(name, fn); }; /** @@ -1297,6 +1348,7 @@ module.exports = function(suite){ context.suite = function(title, fn){ var suite = Suite.create(suites[0], title); + suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); @@ -1331,8 +1383,9 @@ module.exports = function(suite){ context.test = function(title, fn){ var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.pending) fn = null; var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; @@ -1343,7 +1396,7 @@ module.exports = function(suite){ context.test.only = function(title, fn){ var test = context.test(title, fn); - var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; + var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; @@ -1371,6 +1424,7 @@ require.register("mocha.js", function(module, exports, require){ */ var path = require('browser/path') + , escapeRe = require('browser/escape-string-regexp') , utils = require('./utils'); /** @@ -1380,6 +1434,16 @@ var path = require('browser/path') exports = module.exports = Mocha; /** + * To require local UIs and reporters when running in node. + */ + +if (typeof process !== 'undefined' && typeof process.cwd === 'function') { + var join = path.join + , cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); +} + +/** * Expose internals. */ @@ -1411,7 +1475,7 @@ function image(name) { * Options: * * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `reporter` reporter instance, defaults to `mocha.reporters.spec` * - `globals` array of accepted globals * - `timeout` timeout in milliseconds * - `bail` bail on the first test failure @@ -1431,9 +1495,10 @@ function Mocha(options) { this.suite = new exports.Suite('', new exports.Context); this.ui(options.ui); this.bail(options.bail); - this.reporter(options.reporter); + this.reporter(options.reporter, options.reporterOptions); if (null != options.timeout) this.timeout(options.timeout); this.useColors(options.useColors) + if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts); if (options.slow) this.slow(options.slow); this.suite.on('pre-require', function (context) { @@ -1478,17 +1543,17 @@ Mocha.prototype.addFile = function(file){ }; /** - * Set reporter to `reporter`, defaults to "dot". + * Set reporter to `reporter`, defaults to "spec". * * @param {String|Function} reporter name or constructor + * @param {Object} reporterOptions optional options * @api public */ - -Mocha.prototype.reporter = function(reporter){ +Mocha.prototype.reporter = function(reporter, reporterOptions){ if ('function' == typeof reporter) { this._reporter = reporter; } else { - reporter = reporter || 'dot'; + reporter = reporter || 'spec'; var _reporter; try { _reporter = require('./reporters/' + reporter); } catch (err) {}; if (!_reporter) try { _reporter = require(reporter); } catch (err) {}; @@ -1499,6 +1564,7 @@ Mocha.prototype.reporter = function(reporter){ if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); this._reporter = _reporter; } + this.options.reporterOptions = reporterOptions; return this; }; @@ -1571,7 +1637,7 @@ Mocha.prototype._growl = function(runner, reporter) { Mocha.prototype.grep = function(re){ this.options.grep = 'string' == typeof re - ? new RegExp(utils.escapeRegexp(re)) + ? new RegExp(escapeRe(re)) : re; return this; }; @@ -1647,9 +1713,9 @@ Mocha.prototype.globals = function(globals){ */ Mocha.prototype.useColors = function(colors){ - this.options.useColors = arguments.length && colors != undefined - ? colors - : true; + if (colors !== undefined) { + this.options.useColors = colors; + } return this; }; @@ -1695,6 +1761,21 @@ Mocha.prototype.slow = function(slow){ }; /** + * Enable timeouts. + * + * @param {Boolean} enabled + * @return {Mocha} + * @api public + */ + +Mocha.prototype.enableTimeouts = function(enabled) { + this.suite.enableTimeouts(arguments.length && enabled !== undefined + ? enabled + : true); + return this +}; + +/** * Makes all tests async (accepting a callback) * * @return {Mocha} @@ -1707,6 +1788,16 @@ Mocha.prototype.asyncOnly = function(){ }; /** + * Disable syntax highlighting (in browser). + * @returns {Mocha} + * @api public + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** * Run tests and invoke `fn()` when complete. * * @param {Function} fn @@ -1718,16 +1809,28 @@ Mocha.prototype.run = function(fn){ if (this.files.length) this.loadFiles(); var suite = this.suite; var options = this.options; + options.files = this.files; var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); + var reporter = new this._reporter(runner, options); runner.ignoreLeaks = false !== options.ignoreLeaks; runner.asyncOnly = options.asyncOnly; if (options.grep) runner.grep(options.grep, options.invert); if (options.globals) runner.globals(options.globals); if (options.growl) this._growl(runner, reporter); - exports.reporters.Base.useColors = options.useColors; + if (options.useColors !== undefined) { + exports.reporters.Base.useColors = options.useColors; + } exports.reporters.Base.inlineDiffs = options.useInlineDiffs; - return runner.run(fn); + + function done(failures) { + if (reporter.done) { + reporter.done(failures, fn); + } else { + fn(failures); + } + } + + return runner.run(done); }; }); // module: mocha.js @@ -1759,7 +1862,7 @@ var y = d * 365.25; module.exports = function(val, options){ options = options || {}; if ('string' == typeof val) return parse(val); - return options.long ? longFormat(val) : shortFormat(val); + return options['long'] ? longFormat(val) : shortFormat(val); }; /** @@ -1846,7 +1949,6 @@ function plural(ms, n, name) { }); // module: ms.js require.register("reporters/base.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -1946,7 +2048,7 @@ if ('win32' == process.platform) { */ var color = exports.color = function(type, str) { - if (!exports.useColors) return str; + if (!exports.useColors) return String(str); return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; }; @@ -2003,7 +2105,7 @@ exports.cursor = { */ exports.list = function(failures){ - console.error(); + console.log(); failures.forEach(function(test, i){ // format var fmt = color('error title', ' %s) %s:\n') @@ -2027,13 +2129,13 @@ exports.list = function(failures){ // explicitly show diff if (err.showDiff && sameType(actual, expected)) { - escape = false; - err.actual = actual = stringify(canonicalize(actual)); - err.expected = expected = stringify(canonicalize(expected)); - } - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { + if ('string' !== typeof actual) { + escape = false; + err.actual = actual = utils.stringify(actual); + err.expected = expected = utils.stringify(expected); + } + fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); var match = message.match(/^([^:]+): expected/); msg = '\n ' + color('error message', match ? match[1] : msg); @@ -2049,7 +2151,7 @@ exports.list = function(failures){ stack = stack.slice(index ? index + 1 : index) .replace(/^/gm, ' '); - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + console.log(fmt, (i + 1), test.fullTitle(), msg, stack); }); }; @@ -2154,11 +2256,10 @@ Base.prototype.epilogue = function(){ if (stats.failures) { fmt = color('fail', ' %d failing'); - console.error(fmt, - stats.failures); + console.log(fmt, stats.failures); Base.list(this.failures); - console.error(); + console.log(); } console.log(); @@ -2178,6 +2279,7 @@ function pad(str, len) { return Array(len - str.length + 1).join(' ') + str; } + /** * Returns an inline diff between 2 strings with coloured ANSI output * @@ -2291,53 +2393,6 @@ function colorLines(name, str) { } /** - * Stringify `obj`. - * - * @param {Object} obj - * @return {String} - * @api private - */ - -function stringify(obj) { - if (obj instanceof RegExp) return obj.toString(); - return JSON.stringify(obj, null, 2); -} - -/** - * Return a new object that has the keys in sorted order. - * @param {Object} obj - * @return {Object} - * @api private - */ - - function canonicalize(obj, stack) { - stack = stack || []; - - if (utils.indexOf(stack, obj) !== -1) return obj; - - var canonicalizedObj; - - if ('[object Array]' == {}.toString.call(obj)) { - stack.push(obj); - canonicalizedObj = utils.map(obj, function(item) { - return canonicalize(item, stack); - }); - stack.pop(); - } else if (typeof obj === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - utils.forEach(utils.keys(obj).sort(), function(key) { - canonicalizedObj[key] = canonicalize(obj[key], stack); - }); - stack.pop(); - } else { - canonicalizedObj = obj; - } - - return canonicalizedObj; - } - -/** * Check that a / b have the same type. * * @param {Object} a @@ -2355,7 +2410,6 @@ function sameType(a, b) { }); // module: reporters/base.js require.register("reporters/doc.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -2410,12 +2464,18 @@ function Doc(runner) { var code = utils.escape(utils.clean(test.fn.toString())); console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code); }); + + runner.on('fail', function(test, err){ + console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code); + console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err)); + }); } }); // module: reporters/doc.js require.register("reporters/dot.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -2442,13 +2502,14 @@ function Dot(runner) { var self = this , stats = this.stats , width = Base.window.width * .75 | 0 - , n = 0; + , n = -1; runner.on('start', function(){ process.stdout.write('\n '); }); runner.on('pending', function(test){ + if (++n % width == 0) process.stdout.write('\n '); process.stdout.write(color('pending', Base.symbols.dot)); }); @@ -2481,10 +2542,10 @@ F.prototype = Base.prototype; Dot.prototype = new F; Dot.prototype.constructor = Dot; + }); // module: reporters/dot.js require.register("reporters/html-cov.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -2535,10 +2596,10 @@ function coverageClass(n) { if (n >= 25) return 'low'; return 'terrible'; } + }); // module: reporters/html-cov.js require.register("reporters/html.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -2582,7 +2643,7 @@ var statsTemplate = '<ul id="mocha-stats">' * @api public */ -function HTML(runner, root) { +function HTML(runner) { Base.call(this, runner); var self = this @@ -2600,8 +2661,7 @@ function HTML(runner, root) { , stack = [report] , progress , ctx - - root = root || document.getElementById('mocha'); + , root = document.getElementById('mocha'); if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; @@ -2677,7 +2737,7 @@ function HTML(runner, root) { } else if (test.pending) { var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title); } else { - var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle())); + var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test)); var str = test.err.stack || test.err.toString(); // FF / Opera do not add the message @@ -2719,13 +2779,22 @@ function HTML(runner, root) { } /** + * Makes a URL, preserving querystring ("search") parameters. + * @param {string} s + * @returns {string} your new URL + */ +var makeUrl = function makeUrl(s) { + var search = window.location.search; + return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s); +}; + +/** * Provide suite URL * * @param {Object} [suite] */ - HTML.prototype.suiteURL = function(suite){ - return '?grep=' + encodeURIComponent(suite.fullTitle()); + return makeUrl(suite.fullTitle()); }; /** @@ -2735,7 +2804,7 @@ HTML.prototype.suiteURL = function(suite){ */ HTML.prototype.testURL = function(test){ - return '?grep=' + encodeURIComponent(test.fullTitle()); + return makeUrl(test.fullTitle()); }; /** @@ -2816,7 +2885,6 @@ function on(el, event, fn) { }); // module: reporters/html.js require.register("reporters/index.js", function(module, exports, require){ - exports.Base = require('./base'); exports.Dot = require('./dot'); exports.Doc = require('./doc'); @@ -2838,7 +2906,6 @@ exports.JSONStream = require('./json-stream'); }); // module: reporters/index.js require.register("reporters/json-cov.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -2929,7 +2996,7 @@ function map(cov) { } return ret; -}; +} /** * Map jscoverage data for a single source file @@ -2995,7 +3062,6 @@ function clean(test) { }); // module: reporters/json-cov.js require.register("reporters/json-stream.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3032,7 +3098,9 @@ function List(runner) { }); runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); + test = clean(test); + test.err = err.message; + console.log(JSON.stringify(['fail', test])); }); runner.on('end', function(){ @@ -3056,10 +3124,10 @@ function clean(test) { , duration: test.duration } } + }); // module: reporters/json-stream.js require.register("reporters/json.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3086,6 +3154,7 @@ function JSONReporter(runner) { Base.call(this, runner); var tests = [] + , pending = [] , failures = [] , passes = []; @@ -3101,14 +3170,21 @@ function JSONReporter(runner) { failures.push(test); }); + runner.on('pending', function(test){ + pending.push(test); + }); + runner.on('end', function(){ var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) }; + runner.testResults = obj; + process.stdout.write(JSON.stringify(obj, null, 2)); }); } @@ -3124,15 +3200,30 @@ function JSONReporter(runner) { function clean(test) { return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + err: errorJSON(test.err || {}) } } + +/** + * Transform `error` into a JSON object. + * @param {Error} err + * @return {Object} + */ + +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + }); // module: reporters/json.js require.register("reporters/landing.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3190,7 +3281,7 @@ function Landing(runner) { } runner.on('start', function(){ - stream.write('\n '); + stream.write('\n\n\n '); cursor.hide(); }); @@ -3207,7 +3298,7 @@ function Landing(runner) { } // render landing strip - stream.write('\u001b[4F\n\n'); + stream.write('\u001b['+(width+1)+'D\u001b[2A'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('⋅'))); @@ -3233,10 +3324,10 @@ F.prototype = Base.prototype; Landing.prototype = new F; Landing.prototype.constructor = Landing; + }); // module: reporters/landing.js require.register("reporters/list.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3304,6 +3395,7 @@ F.prototype = Base.prototype; List.prototype = new F; List.prototype.constructor = List; + }); // module: reporters/list.js require.register("reporters/markdown.js", function(module, exports, require){ @@ -3315,6 +3407,12 @@ var Base = require('./base') , utils = require('../utils'); /** + * Constants + */ + +var SUITE_PREFIX = '$'; + +/** * Expose `Markdown`. */ @@ -3344,8 +3442,9 @@ function Markdown(runner) { } function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + var ret = obj, + key = SUITE_PREFIX + suite.title; + obj = obj[key] = obj[key] || { suite: suite }; suite.suites.forEach(function(suite){ mapTOC(suite, obj); }); @@ -3358,11 +3457,13 @@ function Markdown(runner) { var link; for (var key in obj) { if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } buf += stringifyTOC(obj[key], level); } - --level; return buf; } @@ -3398,10 +3499,10 @@ function Markdown(runner) { process.stdout.write(buf); }); } + }); // module: reporters/markdown.js require.register("reporters/min.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3443,6 +3544,7 @@ F.prototype = Base.prototype; Min.prototype = new F; Min.prototype.constructor = Min; + }); // module: reporters/min.js require.register("reporters/nyan.js", function(module, exports, require){ @@ -3450,8 +3552,7 @@ require.register("reporters/nyan.js", function(module, exports, require){ * Module dependencies. */ -var Base = require('./base') - , color = Base.color; +var Base = require('./base'); /** * Expose `Dot`. @@ -3528,17 +3629,16 @@ NyanCat.prototype.draw = function(){ NyanCat.prototype.drawScoreboard = function(){ var stats = this.stats; - var colors = Base.colors; - function draw(color, n) { + function draw(type, n) { write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write(Base.color(type, n)); write('\n'); } - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); write('\n'); this.cursorUp(this.numberOfLines); @@ -3588,26 +3688,26 @@ NyanCat.prototype.drawRainbow = function(){ NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; - var color = '\u001b[' + startWidth + 'C'; + var dist = '\u001b[' + startWidth + 'C'; var padding = ''; - write(color); + write(dist); write('_,------,'); write('\n'); - write(color); + write(dist); padding = self.tick ? ' ' : ' '; write('_|' + padding + '/\\_/\\ '); write('\n'); - write(color); + write(dist); padding = self.tick ? '_' : '__'; var tail = self.tick ? '~' : '^'; var face; write(tail + '|' + padding + this.face() + ' '); write('\n'); - write(color); + write(dist); padding = self.tick ? ' ' : ' '; write(padding + '"" "" '); write('\n'); @@ -3633,7 +3733,7 @@ NyanCat.prototype.face = function() { } else { return '( - .-)'; } -} +}; /** * Move cursor up `n`. @@ -3688,6 +3788,8 @@ NyanCat.prototype.generateColors = function(){ */ NyanCat.prototype.rainbowify = function(str){ + if (!Base.useColors) + return str; var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; @@ -3710,10 +3812,10 @@ F.prototype = Base.prototype; NyanCat.prototype = new F; NyanCat.prototype.constructor = NyanCat; + }); // module: reporters/nyan.js require.register("reporters/progress.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3751,7 +3853,8 @@ function Progress(runner, options) { , width = Base.window.width * .50 | 0 , total = runner.total , complete = 0 - , max = Math.max; + , max = Math.max + , lastN = -1; // default chars options.open = options.open || '['; @@ -3774,6 +3877,12 @@ function Progress(runner, options) { , n = width * percent | 0 , i = width - n; + if (lastN === n && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; + cursor.CR(); process.stdout.write('\u001b[J'); process.stdout.write(color('progress', ' ' + options.open)); @@ -3803,10 +3912,10 @@ F.prototype = Base.prototype; Progress.prototype = new F; Progress.prototype.constructor = Progress; + }); // module: reporters/progress.js require.register("reporters/spec.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3893,10 +4002,10 @@ F.prototype = Base.prototype; Spec.prototype = new F; Spec.prototype.constructor = Spec; + }); // module: reporters/spec.js require.register("reporters/tap.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -3973,13 +4082,13 @@ function title(test) { }); // module: reporters/tap.js require.register("reporters/xunit.js", function(module, exports, require){ - /** * Module dependencies. */ var Base = require('./base') , utils = require('../utils') + , fs = require('browser/fs') , escape = utils.escape; /** @@ -4005,12 +4114,19 @@ exports = module.exports = XUnit; * @api public */ -function XUnit(runner) { +function XUnit(runner, options) { Base.call(this, runner); var stats = this.stats , tests = [] , self = this; + if (options.reporterOptions && options.reporterOptions.output) { + if (! fs.createWriteStream) { + throw new Error('file output not supported in browser'); + } + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + runner.on('pending', function(test){ tests.push(test); }); @@ -4024,7 +4140,7 @@ function XUnit(runner) { }); runner.on('end', function(){ - console.log(tag('testsuite', { + self.write(tag('testsuite', { name: 'Mocha Tests' , tests: stats.tests , failures: stats.failures @@ -4034,12 +4150,25 @@ function XUnit(runner) { , time: (stats.duration / 1000) || 0 }, false)); - tests.forEach(test); - console.log('</testsuite>'); + tests.forEach(function(t) { self.test(t); }); + self.write('</testsuite>'); }); } /** + * Override done to close the stream (if it's a file). + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; + +/** * Inherit from `Base.prototype`. */ @@ -4048,11 +4177,23 @@ F.prototype = Base.prototype; XUnit.prototype = new F; XUnit.prototype.constructor = XUnit; + +/** + * Write out the given line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else { + console.log(line); + } +}; + /** * Output tag for the given `test.` */ -function test(test) { +XUnit.prototype.test = function(test, ostream) { var attrs = { classname: test.parent.fullTitle() , name: test.title @@ -4061,14 +4202,13 @@ function test(test) { if ('failed' == test.state) { var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack)))); } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { - console.log(tag('testcase', attrs, true) ); + this.write(tag('testcase', attrs, true) ); } -} +}; /** * HTML tag helper. @@ -4099,14 +4239,14 @@ function cdata(str) { }); // module: reporters/xunit.js require.register("runnable.js", function(module, exports, require){ - /** * Module dependencies. */ var EventEmitter = require('browser/events').EventEmitter , debug = require('browser/debug')('mocha:runnable') - , milliseconds = require('./ms'); + , milliseconds = require('./ms') + , utils = require('./utils'); /** * Save timer references to avoid Sinon interfering (see GH-237). @@ -4145,7 +4285,9 @@ function Runnable(title, fn) { this.sync = ! this.async; this._timeout = 2000; this._slow = 75; + this._enableTimeouts = true; this.timedOut = false; + this._trace = new Error('done() called multiple times') } /** @@ -4157,6 +4299,7 @@ F.prototype = EventEmitter.prototype; Runnable.prototype = new F; Runnable.prototype.constructor = Runnable; + /** * Set & get timeout `ms`. * @@ -4167,6 +4310,7 @@ Runnable.prototype.constructor = Runnable; Runnable.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms === 0) this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = ms; @@ -4191,6 +4335,21 @@ Runnable.prototype.slow = function(ms){ }; /** + * Set and & get timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Runnable|Boolean} enabled or self + * @api private + */ + +Runnable.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** * Return the full title generated by recursively * concatenating the parent's full title. * @@ -4238,8 +4397,10 @@ Runnable.prototype.resetTimeout = function(){ var self = this; var ms = this.timeout() || 1e9; + if (!this._enableTimeouts) return; this.clearTimeout(); this.timer = setTimeout(function(){ + if (!self._enableTimeouts) return; self.callback(new Error('timeout of ' + ms + 'ms exceeded')); self.timedOut = true; }, ms); @@ -4264,54 +4425,54 @@ Runnable.prototype.globals = function(arr){ Runnable.prototype.run = function(fn){ var self = this - , ms = this.timeout() , start = new Date , ctx = this.ctx , finished , emitted; - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } + // Some times the ctx exists but it is not runnable + if (ctx && ctx.runnable) ctx.runnable(this); // called multiple times function multiple(err) { if (emitted) return; emitted = true; - self.emit('error', err || new Error('done() called multiple times')); + self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate')); } // finished function done(err) { + var ms = self.timeout(); if (self.timedOut) return; - if (finished) return multiple(err); + if (finished) return multiple(err || self._trace); self.clearTimeout(); self.duration = new Date - start; finished = true; + if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded'); fn(err); } // for .resetTimeout() this.callback = done; - // async + // explicit async with `done` argument if (this.async) { + this.resetTimeout(); + try { this.fn.call(ctx, function(err){ if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + if (null != err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); + } else { + return done(new Error('done() invoked with non-Error: ' + err)); + } + } done(); }); } catch (err) { - done(err); + done(utils.getError(err)); } return; } @@ -4320,13 +4481,31 @@ Runnable.prototype.run = function(fn){ return done(new Error('--async-only option in use without declaring `done()`')); } - // sync + // sync or promise-returning try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); + if (this.pending) { + done(); + } else { + callFn(this.fn); + } } catch (err) { - fn(err); + done(utils.getError(err)); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result + .then(function() { + done() + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')) + }); + } else { + done(); + } } }; @@ -4354,7 +4533,9 @@ var globals = [ 'setInterval', 'clearInterval', 'XMLHttpRequest', - 'Date' + 'Date', + 'setImmediate', + 'clearImmediate' ]; /** @@ -4414,6 +4595,7 @@ F.prototype = EventEmitter.prototype; Runner.prototype = new F; Runner.prototype.constructor = Runner; + /** * Run tests with full titles matching `re`. Updates runner.total * with number of tests matched. @@ -4499,7 +4681,6 @@ Runner.prototype.checkGlobals = function(test){ var ok = this._globals; var globals = this.globalProps(); - var isNode = process.kill; var leaks; if (test) { @@ -4583,7 +4764,6 @@ Runner.prototype.hook = function(name, fn){ function next(i) { var hook = hooks[i]; if (!hook) return fn(); - if (self.failures && suite.bail()) return fn(); self.currentRunnable = hook; hook.ctx.currentTest = self.test; @@ -4729,6 +4909,7 @@ Runner.prototype.runTests = function(suite, fn){ , tests = suite.tests.slice() , test; + function hookErr(err, errSuite, after) { // before/after Each hook for errSuite failed: var orig = self.suite; @@ -4870,13 +5051,26 @@ Runner.prototype.runSuite = function(suite, fn){ */ Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); + if (err) { + debug('uncaught exception %s', err !== function () { + return this; + }.call(err) ? err : ( err.message || err )); + } else { + debug('uncaught undefined exception'); + err = utils.undefinedError(); + } err.uncaught = true; + + var runnable = this.currentRunnable; + if (!runnable) return; + + var wasAlreadyDone = runnable.state; this.fail(runnable, err); + runnable.clearTimeout(); + + if (wasAlreadyDone) return; + // recover from test if ('test' == runnable.type) { this.emit('test end', runnable); @@ -4936,7 +5130,7 @@ Runner.prototype.run = function(fn){ Runner.prototype.abort = function(){ debug('aborting'); this._abort = true; -} +}; /** * Filter leaks with the given globals flagged as `ok`. @@ -5000,7 +5194,6 @@ function filterLeaks(ok, globals) { }); // module: runner.js require.register("suite.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -5048,9 +5241,11 @@ exports.create = function(parent, title){ * @api private */ -function Suite(title, ctx) { +function Suite(title, parentContext) { this.title = title; - this.ctx = ctx; + var context = function() {}; + context.prototype = parentContext; + this.ctx = new context(); this.suites = []; this.tests = []; this.pending = false; @@ -5060,6 +5255,7 @@ function Suite(title, ctx) { this._afterAll = []; this.root = !title; this._timeout = 2000; + this._enableTimeouts = true; this._slow = 75; this._bail = false; } @@ -5073,6 +5269,7 @@ F.prototype = EventEmitter.prototype; Suite.prototype = new F; Suite.prototype.constructor = Suite; + /** * Return a clone of this `Suite`. * @@ -5085,6 +5282,7 @@ Suite.prototype.clone = function(){ debug('clone'); suite.ctx = this.ctx; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; @@ -5100,6 +5298,7 @@ Suite.prototype.clone = function(){ Suite.prototype.timeout = function(ms){ if (0 == arguments.length) return this._timeout; + if (ms.toString() === '0') this._enableTimeouts = false; if ('string' == typeof ms) ms = milliseconds(ms); debug('timeout %d', ms); this._timeout = parseInt(ms, 10); @@ -5107,6 +5306,21 @@ Suite.prototype.timeout = function(ms){ }; /** + * Set timeout `enabled`. + * + * @param {Boolean} enabled + * @return {Suite|Boolean} self or enabled + * @api private + */ + +Suite.prototype.enableTimeouts = function(enabled){ + if (arguments.length === 0) return this._enableTimeouts; + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** * Set slow `ms` or short-hand such as "2s". * * @param {Number|String} ms @@ -5125,7 +5339,7 @@ Suite.prototype.slow = function(ms){ /** * Sets whether to bail after first error. * - * @parma {Boolean} bail + * @param {Boolean} bail * @return {Suite|Number} for chaining * @api private */ @@ -5145,11 +5359,18 @@ Suite.prototype.bail = function(bail){ * @api private */ -Suite.prototype.beforeAll = function(fn){ +Suite.prototype.beforeAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeAll.push(hook); @@ -5165,11 +5386,18 @@ Suite.prototype.beforeAll = function(fn){ * @api private */ -Suite.prototype.afterAll = function(fn){ +Suite.prototype.afterAll = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterAll.push(hook); @@ -5185,11 +5413,18 @@ Suite.prototype.afterAll = function(fn){ * @api private */ -Suite.prototype.beforeEach = function(fn){ +Suite.prototype.beforeEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeEach.push(hook); @@ -5205,11 +5440,18 @@ Suite.prototype.beforeEach = function(fn){ * @api private */ -Suite.prototype.afterEach = function(fn){ +Suite.prototype.afterEach = function(title, fn){ if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); + if ('function' === typeof title) { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); + + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterEach.push(hook); @@ -5228,6 +5470,7 @@ Suite.prototype.afterEach = function(fn){ Suite.prototype.addSuite = function(suite){ suite.parent = this; suite.timeout(this.timeout()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); @@ -5246,6 +5489,7 @@ Suite.prototype.addSuite = function(suite){ Suite.prototype.addTest = function(test){ test.parent = this; test.timeout(this.timeout()); + test.enableTimeouts(this.enableTimeouts()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); @@ -5303,7 +5547,6 @@ Suite.prototype.eachTest = function(fn){ }); // module: suite.js require.register("test.js", function(module, exports, require){ - /** * Module dependencies. */ @@ -5339,6 +5582,7 @@ F.prototype = Runnable.prototype; Test.prototype = new F; Test.prototype.constructor = Test; + }); // module: test.js require.register("utils.js", function(module, exports, require){ @@ -5348,6 +5592,9 @@ require.register("utils.js", function(module, exports, require){ var fs = require('browser/fs') , path = require('browser/path') + , basename = path.basename + , exists = fs.existsSync || path.existsSync + , glob = require('browser/glob') , join = path.join , debug = require('browser/debug')('mocha:watch'); @@ -5513,16 +5760,19 @@ function ignored(path){ * @api private */ -exports.files = function(dir, ret){ +exports.files = function(dir, ext, ret){ ret = ret || []; + ext = ext || ['js']; + + var re = new RegExp('\\.(' + ext.join('|') + ')$'); fs.readdirSync(dir) .filter(ignored) .forEach(function(path){ path = join(dir, path); if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) { + exports.files(path, ext, ret); + } else if (path.match(re)) { ret.push(path); } }); @@ -5553,7 +5803,7 @@ exports.slug = function(str){ exports.clean = function(str) { str = str .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') - .replace(/^function *\(.*\) *{/, '') + .replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '') .replace(/\s+\}$/, ''); var spaces = str.match(/^\n?( *)/)[1].length @@ -5566,18 +5816,6 @@ exports.clean = function(str) { }; /** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; - -/** * Trim the given `str`. * * @param {String} str @@ -5624,7 +5862,7 @@ function highlight(js) { .replace(/('.*?')/gm, '<span class="string">$1</span>') .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') .replace(/(\d+)/gm, '<span class="number">$1</span>') - .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') + .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>') } @@ -5636,15 +5874,270 @@ function highlight(js) { */ exports.highlightTags = function(name) { - var code = document.getElementsByTagName(name); + var code = document.getElementById('mocha').getElementsByTagName(name); for (var i = 0, len = code.length; i < len; ++i) { code[i].innerHTML = highlight(code[i].innerHTML); } }; +/** + * If a value could have properties, and has none, this function is called, which returns + * a string representation of the empty value. + * + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @param {*} value Value to inspect + * @param {string} [type] The type of the value, if known. + * @returns {string} + */ +var emptyRepresentation = function emptyRepresentation(value, type) { + type = type || exports.type(value); + + switch(type) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +}; + +/** + * Takes some variable and asks `{}.toString()` what it thinks it is. + * @param {*} value Anything + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + * @api private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @returns {string} + */ +exports.type = function type(value) { + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return 'buffer'; + } + return Object.prototype.toString.call(value) + .replace(/^\[.+\s(.+?)\]$/, '$1') + .toLowerCase(); +}; + +/** + * @summary Stringify `value`. + * @description Different behavior depending on type of value. + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @see exports.type + * @param {*} value + * @return {string} + * @api private + */ + +exports.stringify = function(value) { + var prop, + type = exports.type(value); + + if (type === 'null' || type === 'undefined') { + return '[' + type + ']'; + } + + if (type === 'date') { + return '[Date: ' + value.toISOString() + ']'; + } + + if (!~exports.indexOf(['object', 'array', 'function'], type)) { + return value.toString(); + } + + for (prop in value) { + if (value.hasOwnProperty(prop)) { + return JSON.stringify(exports.canonicalize(value), null, 2).replace(/,(\n|$)/g, '$1'); + } + } + + return emptyRepresentation(value, type); +}; + +/** + * Return if obj is a Buffer + * @param {Object} arg + * @return {Boolean} + * @api private + */ +exports.isBuffer = function (arg) { + return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg); +}; + +/** + * @summary Return a new Thing that has the keys in sorted order. Recursive. + * @description If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @return {(Object|Array|Function|string|undefined)} + * @see {@link exports.stringify} + * @api private + */ + +exports.canonicalize = function(value, stack) { + var canonicalizedObj, + type = exports.type(value), + prop, + withStack = function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + }; + + stack = stack || []; + + if (exports.indexOf(stack, value) !== -1) { + return '[Circular]'; + } + + switch(type) { + case 'undefined': + canonicalizedObj = '[undefined]'; + break; + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function () { + canonicalizedObj = exports.map(value, function (item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'date': + canonicalizedObj = '[Date: ' + value.toISOString() + ']'; + break; + case 'function': + for (prop in value) { + canonicalizedObj = {}; + break; + } + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, type); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function () { + exports.forEach(exports.keys(value).sort(), function (key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'number': + case 'boolean': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value.toString(); + } + + return canonicalizedObj; +}; + +/** + * Lookup file names at the given `path`. + */ +exports.lookupFiles = function lookupFiles(path, extensions, recursive) { + var files = []; + var re = new RegExp('\\.(' + extensions.join('|') + ')$'); + + if (!exists(path)) { + if (exists(path + '.js')) { + path += '.js'; + } else { + files = glob.sync(path); + if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'"); + return files; + } + } + + try { + var stat = fs.statSync(path); + if (stat.isFile()) return path; + } + catch (ignored) { + return; + } + + fs.readdirSync(path).forEach(function(file){ + file = join(path, file); + try { + var stat = fs.statSync(file); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(file, extensions, recursive)); + } + return; + } + } + catch (ignored) { + return; + } + if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return; + files.push(file); + }); + + return files; +}; + +/** + * Generate an undefined error with a message warning the user. + * + * @return {Error} + */ + +exports.undefinedError = function(){ + return new Error('Caught undefined error, did you throw without specifying what?'); +}; + +/** + * Generate an undefined error if `err` is not defined. + * + * @param {Error} err + * @return {Error} + */ + +exports.getError = function(err){ + return err || exports.undefinedError(); +}; + + }); // module: utils.js // The global object is "self" in Web Workers. -global = (function() { return this; })(); +var global = (function() { return this; })(); /** * Save timer references to avoid Sinon interfering (see GH-237). @@ -5671,13 +6164,20 @@ process.stdout = {}; var uncaughtExceptionHandlers = []; +var originalOnerrorHandler = global.onerror; + /** * Remove uncaughtException listener. + * Revert to original onerror handler if previously defined. */ process.removeListener = function(e, fn){ if ('uncaughtException' == e) { - global.onerror = function() {}; + if (originalOnerrorHandler) { + global.onerror = originalOnerrorHandler; + } else { + global.onerror = function() {}; + } var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } } @@ -5780,12 +6280,13 @@ mocha.run = function(fn){ if (query.grep) mocha.grep(query.grep); if (query.invert) mocha.invert(); - return Mocha.prototype.run.call(mocha, function(){ + return Mocha.prototype.run.call(mocha, function(err){ // The DOM Document is not available in Web Workers. - if (global.document) { + var document = global.document; + if (document && document.getElementById('mocha') && options.noHighlighting !== true) { Mocha.utils.highlightTags('code'); } - if (fn) fn(); + if (fn) fn(err); }); }; @@ -5794,4 +6295,4 @@ mocha.run = function(fn){ */ Mocha.process = process; -})();
\ No newline at end of file +})(); diff --git a/tests/lib/mocha-2.1.0/package.json b/tests/lib/mocha-2.1.0/package.json new file mode 100644 index 0000000000..8c4a148b47 --- /dev/null +++ b/tests/lib/mocha-2.1.0/package.json @@ -0,0 +1,73 @@ +{ + "name": "mocha", + "version": "2.1.0", + "description": "simple, flexible, fun test framework", + "keywords": [ + "mocha", + "test", + "bdd", + "tdd", + "tap" + ], + "author": "TJ Holowaychuk <tj@vision-media.ca>", + "contributors": [ + "Joshua Appelman <joshua@jbna.nl>", + "Oleg Gaidarenko <markelog@gmail.com>", + "Christoffer Hallas <christoffer.hallas@gmail.com>", + "Christopher Hiller <chiller@badwing.com>", + "Travis Jeffery <tj@travisjeffery.com>", + "Johnathan Ong <me@jongleberry.com>", + "Guillermo Rauch <rauchg@gmail.com>", + "Nathan Rajlich <nathan@tootallnate.net>" + ], + "repository": { + "type": "git", + "url": "git://github.com/mochajs/mocha.git" + }, + "maintainers": [ + "travisjeffery <tj@travisjeffery.com>", + "jbnicolai <joshua@jbna.nl>", + "boneskull <chiller@badwing.com>" + ], + "main": "./index", + "browser": "./mocha.js", + "bin": { + "mocha": "./bin/mocha", + "_mocha": "./bin/_mocha" + }, + "engines": { + "node": ">= 0.8.x" + }, + "scripts": { + "test": "make test-all" + }, + "dependencies": { + "commander": "2.3.0", + "debug": "2.0.0", + "diff": "1.0.8", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.0" + }, + "devDependencies": { + "coffee-script": "~1.8.0", + "should": "~4.0.0" + }, + "files": [ + "bin", + "images", + "lib", + "index.js", + "mocha.css", + "mocha.js", + "LICENSE" + ], + "licenses": [ + { + "type": "MIT", + "url": "https://raw.github.com/mochajs/mocha/master/LICENSE" + } + ] +}
\ No newline at end of file diff --git a/tests/lib/mocha-1.17.1/support/compile.js b/tests/lib/mocha-2.1.0/support/compile.js index 39a53222e9..bea017ab51 100644 --- a/tests/lib/mocha-1.17.1/support/compile.js +++ b/tests/lib/mocha-2.1.0/support/compile.js @@ -1,4 +1,3 @@ - /** * Module dependencies. */ @@ -43,12 +42,14 @@ function parse(js) { function parseRequires(js) { return js - .replace(/require\('events'\)/g, "require('browser/events')") - .replace(/require\('debug'\)/g, "require('browser/debug')") - .replace(/require\('path'\)/g, "require('browser/path')") - .replace(/require\('diff'\)/g, "require('browser/diff')") - .replace(/require\('tty'\)/g, "require('browser/tty')") - .replace(/require\('fs'\)/g, "require('browser/fs')") + .replace(/require\('events'\)/g , "require('browser/events')") + .replace(/require\('debug'\)/g , "require('browser/debug')") + .replace(/require\('path'\)/g , "require('browser/path')") + .replace(/require\('diff'\)/g , "require('browser/diff')") + .replace(/require\('tty'\)/g , "require('browser/tty')") + .replace(/require\('escape-string-regexp'\)/g , "require('browser/escape-string-regexp')") + .replace(/require\('glob'\)/g , "require('browser/glob')") + .replace(/require\('fs'\)/g , "require('browser/fs')"); } /** diff --git a/tests/lib/mocha-2.1.0/support/foot.js b/tests/lib/mocha-2.1.0/support/foot.js new file mode 100644 index 0000000000..0319a0fe5f --- /dev/null +++ b/tests/lib/mocha-2.1.0/support/foot.js @@ -0,0 +1 @@ +})(); diff --git a/tests/lib/mocha-1.17.1/support/head.js b/tests/lib/mocha-2.1.0/support/head.js index 55d38175ec..55d38175ec 100644 --- a/tests/lib/mocha-1.17.1/support/head.js +++ b/tests/lib/mocha-2.1.0/support/head.js diff --git a/tests/lib/mocha-1.17.1/support/tail.js b/tests/lib/mocha-2.1.0/support/tail.js index 018add8d0c..e9c0c0d22f 100644 --- a/tests/lib/mocha-1.17.1/support/tail.js +++ b/tests/lib/mocha-2.1.0/support/tail.js @@ -1,5 +1,5 @@ // The global object is "self" in Web Workers. -global = (function() { return this; })(); +var global = (function() { return this; })(); /** * Save timer references to avoid Sinon interfering (see GH-237). @@ -26,13 +26,20 @@ process.stdout = {}; var uncaughtExceptionHandlers = []; +var originalOnerrorHandler = global.onerror; + /** * Remove uncaughtException listener. + * Revert to original onerror handler if previously defined. */ process.removeListener = function(e, fn){ if ('uncaughtException' == e) { - global.onerror = function() {}; + if (originalOnerrorHandler) { + global.onerror = originalOnerrorHandler; + } else { + global.onerror = function() {}; + } var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } } @@ -135,12 +142,13 @@ mocha.run = function(fn){ if (query.grep) mocha.grep(query.grep); if (query.invert) mocha.invert(); - return Mocha.prototype.run.call(mocha, function(){ + return Mocha.prototype.run.call(mocha, function(err){ // The DOM Document is not available in Web Workers. - if (global.document) { + var document = global.document; + if (document && document.getElementById('mocha') && options.noHighlighting !== true) { Mocha.utils.highlightTags('code'); } - if (fn) fn(); + if (fn) fn(err); }); }; diff --git a/tests/lib/mocha-2.1.0/test/acceptance/context.js b/tests/lib/mocha-2.1.0/test/acceptance/context.js new file mode 100644 index 0000000000..47c2db7083 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/acceptance/context.js @@ -0,0 +1,72 @@ +describe('Context', function(){ + beforeEach(function(){ + this.calls = ['before']; + }) + + describe('nested', function(){ + beforeEach(function(){ + this.calls.push('before two'); + }) + + it('should work', function(){ + this.calls.should.eql(['before', 'before two']); + this.calls.push('test'); + }) + + after(function(){ + this.calls.should.eql(['before', 'before two', 'test']); + this.calls.push('after two'); + }) + }) + + after(function(){ + this.calls.should.eql(['before', 'before two', 'test', 'after two']); + }) +}) + +describe('Context Siblings', function(){ + beforeEach(function(){ + this.calls = ['before']; + }) + + describe('sequestered sibling', function(){ + beforeEach(function(){ + this.calls.push('before two'); + this.hiddenFromSibling = 'This should be hidden'; + }) + + it('should work', function(){ + this.hiddenFromSibling.should.eql('This should be hidden') + }) + }) + + describe('sibling verifiction', function(){ + beforeEach(function(){ + this.calls.push('before sibling'); + }) + + it('should not have value set within a sibling describe', function(){ + 'This should be hidden'.should.not.eql(this.hiddenFromSibling); + this.visibleFromTestSibling = 'Visible from test sibling'; + }) + + it('should allow test siblings to modify shared context', function(){ + 'Visible from test sibling'.should.eql(this.visibleFromTestSibling); + }) + + it('should have reset this.calls before describe', function(){ + this.calls.should.eql(['before', 'before sibling']); + }) + }) + + after(function(){ + this.calls.should.eql(['before', 'before sibling']); + }) + +}) + +describe('timeout()', function(){ + it('should return the timeout', function(){ + this.timeout().should.equal(200); + }); +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/diffs.js b/tests/lib/mocha-2.1.0/test/acceptance/diffs.js index 17c3dfa470..c87ffe0cce 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/diffs.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/diffs.js @@ -1,4 +1,3 @@ - var fs = require('fs') , cssin = fs.readFileSync('test/acceptance/fixtures/css.in', 'ascii') , cssout = fs.readFileSync('test/acceptance/fixtures/css.out', 'ascii'); @@ -77,4 +76,18 @@ describe('diffs', function(){ // tobi.should.eql(loki); }); + + it('should show value diffs and not be affected by commas', function(){ + var obj1 = { a: 123 }; + var obj2 = { a: 123, b: 456 }; + + // obj1.should.equal(obj2); + }); + + it('should display diff by data and not like an objects', function(){ + var buf1 = new Buffer([0x01]); + var buf2 = new Buffer([0x02]); + +// buf1.should.equal(buf2); + }); }); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/duration.js b/tests/lib/mocha-2.1.0/test/acceptance/duration.js index 1f4c5c86f4..4f319b8a3c 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/duration.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/duration.js @@ -1,4 +1,3 @@ - describe('durations', function(){ describe('when slow', function(){ it('should highlight in red', function(done){ diff --git a/tests/lib/mocha-2.1.0/test/acceptance/failing/timeout.js b/tests/lib/mocha-2.1.0/test/acceptance/failing/timeout.js new file mode 100644 index 0000000000..e52dde4c24 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/acceptance/failing/timeout.js @@ -0,0 +1,17 @@ +describe('timeout', function(){ + this.timeout(1); + + it('should be honored with sync suites', function(){ + sleep(2); + }); + + it('should be honored with async suites', function(done){ + sleep(2); + done(); + }); + + function sleep(ms){ + var start = Date.now(); + while(start + ms > Date.now())continue; + } +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/fixtures/css.in b/tests/lib/mocha-2.1.0/test/acceptance/fixtures/css.in index 29fb2746e7..09a3ca5363 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/fixtures/css.in +++ b/tests/lib/mocha-2.1.0/test/acceptance/fixtures/css.in @@ -6,4 +6,4 @@ body { a { color: blue -}
\ No newline at end of file +} diff --git a/tests/lib/mocha-1.17.1/test/acceptance/fixtures/css.out b/tests/lib/mocha-2.1.0/test/acceptance/fixtures/css.out index 643692a4fc..53b3ec906e 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/fixtures/css.out +++ b/tests/lib/mocha-2.1.0/test/acceptance/fixtures/css.out @@ -10,4 +10,4 @@ a { foo { bar: 'baz'; -}
\ No newline at end of file +} diff --git a/tests/lib/mocha-1.17.1/test/acceptance/fs.js b/tests/lib/mocha-2.1.0/test/acceptance/fs.js index 102b8e1eac..cdd32166d5 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/fs.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/fs.js @@ -1,4 +1,3 @@ - var fs = require('fs'); describe('fs.readFile()', function(){ diff --git a/tests/lib/mocha-1.17.1/test/acceptance/glob/glob.js b/tests/lib/mocha-2.1.0/test/acceptance/glob/glob.js index b1127a2a11..3029ae82ee 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/glob/glob.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/glob/glob.js @@ -1,4 +1,3 @@ - describe('globbing test', function(){ it('should find this test', function(){ // see glob.sh for details diff --git a/tests/lib/mocha-1.17.1/test/acceptance/glob/glob.sh b/tests/lib/mocha-2.1.0/test/acceptance/glob/glob.sh index a2b8c6fea2..a2b8c6fea2 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/glob/glob.sh +++ b/tests/lib/mocha-2.1.0/test/acceptance/glob/glob.sh diff --git a/tests/lib/mocha-1.17.1/test/acceptance/globals.js b/tests/lib/mocha-2.1.0/test/acceptance/globals.js index 815eaee5c7..f8ef804b23 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/globals.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/globals.js @@ -1,4 +1,3 @@ - describe('global leaks', function(){ before(function(){ // uncomment to test diff --git a/tests/lib/mocha-1.17.1/test/acceptance/http.js b/tests/lib/mocha-2.1.0/test/acceptance/http.js index 750d1084f2..1dfa9146a4 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/http.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/http.js @@ -1,4 +1,3 @@ - var http = require('http'); var server = http.createServer(function(req, res){ @@ -10,8 +9,8 @@ server.listen(8888); describe('http', function(){ it('should provide an example', function(done){ http.get({ path: '/', port: 8888 }, function(res){ - res.should.have.status(200); + res.should.have.property('statusCode', 200); done(); }) }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/bdd.js b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/bdd.js index f536aa019a..bea1db2295 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/bdd.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/bdd.js @@ -1,4 +1,3 @@ - describe('Array', function(){ describe('#indexOf()', function(){ it('should return -1 when the value is not present', function(){ diff --git a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/exports.js b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/exports.js index c93bf28239..38093d50ea 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/exports.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/exports.js @@ -1,4 +1,3 @@ - var calls = []; exports.Array = { diff --git a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/qunit.js b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/qunit.js index dbdc5bb774..48aa21d602 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/qunit.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/qunit.js @@ -1,4 +1,3 @@ - function ok(expr, msg) { if (!expr) throw new Error(msg); } @@ -21,4 +20,4 @@ suite('String'); test('#length', function(){ ok('foo'.length == 3); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/tdd.js b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/tdd.js index 33311dd6fb..1c6885ed42 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/interfaces/tdd.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/interfaces/tdd.js @@ -1,4 +1,3 @@ - suite('Array', function(){ suite('#indexOf()', function(){ var initialValue = 32; diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/asyncOnly.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/asyncOnly.js index e03251f315..7b7086a7ea 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/asyncOnly.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/asyncOnly.js @@ -1,4 +1,3 @@ - describe('asyncOnly', function(){ it('should display an error', function(){ diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/bail.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/bail.js index 0628b2fbe2..a2c0c1396f 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/bail.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/bail.js @@ -1,4 +1,3 @@ - describe('bail', function(){ it('should only display this error', function(done){ throw new Error('this should be displayed'); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/cascade.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/cascade.js index 0f884a0031..b2dda4df11 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/cascade.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/cascade.js @@ -1,4 +1,3 @@ - describe('one', function(){ before(function(){ console.log('before one'); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/exit.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/exit.js index 4cace0b060..113e392300 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/exit.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/exit.js @@ -10,4 +10,9 @@ describe('exit', function(){ console.log('all done'); }, 2500) }) + + it('should kill all processes when SIGINT received', function () { + // uncomment to test + //while (true) {} + }); }) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/grep.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/grep.js index 9bfe0c5f66..ec9f78f3cd 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/grep.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/grep.js @@ -1,4 +1,3 @@ - describe('grep', function(){ describe('fast', function(){ it('should run fast', function(){ diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/many.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/many.js index c479ae43ee..26538bc66e 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/many.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/many.js @@ -24,4 +24,4 @@ describe('a load of tests', function(){ addTest(); } -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/nontty.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/nontty.js index cd96f1fc63..2372a660b9 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/nontty.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/nontty.js @@ -1,4 +1,3 @@ - describe('tests for non-tty', function(){ it('should pass', function(){ diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/bdd.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/bdd.js index 56627ab367..ff14dcdfe3 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/bdd.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/bdd.js @@ -1,4 +1,3 @@ - describe('should only run .only test in this bdd suite', function() { it('should not run this test', function() { var zero = 0; @@ -12,4 +11,4 @@ describe('should only run .only test in this bdd suite', function() { var zero = 0; zero.should.equal(1, 'this test should have been skipped'); }); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/qunit.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/qunit.js index e1d9ac1abb..07c240f617 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/qunit.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/qunit.js @@ -1,4 +1,3 @@ - function ok(expr, msg) { if (!expr) throw new Error(msg); } @@ -13,4 +12,4 @@ test.only('should run this test', function()Â { }); test('should run this test, not (includes the title of the .only test)', function() { ok(0 === 1, 'this test should have been skipped'); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/tdd.js b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/tdd.js index ed7e90637e..cb6429a3d6 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/misc/only/tdd.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/misc/only/tdd.js @@ -1,4 +1,3 @@ - suite('should only run .only test in this tdd suite', function() { test('should not run this test', function() { var zero = 0; @@ -12,4 +11,4 @@ suite('should only run .only test in this tdd suite', function() { var zero = 0; zero.should.equal(1, 'this test should have been skipped'); }); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/multiple.done.js b/tests/lib/mocha-2.1.0/test/acceptance/multiple.done.js index aeb88455e1..79874995f5 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/multiple.done.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/multiple.done.js @@ -1,4 +1,3 @@ - describe('multiple calls to done()', function(){ beforeEach(function(done){ done() @@ -13,4 +12,12 @@ describe('multiple calls to done()', function(){ // done(); }); }) -})
\ No newline at end of file + + it('should produce a reasonable trace', function (done) { + process.nextTick(function() { + done(); + // uncomment + // done() + }) + }); +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/pending.js b/tests/lib/mocha-2.1.0/test/acceptance/pending.js index cf738b27ba..4ef963709c 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/pending.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/pending.js @@ -1,4 +1,3 @@ - describe('pending', function(){ it('should be allowed') -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/require/a.js b/tests/lib/mocha-2.1.0/test/acceptance/require/a.js index 592d827449..592d827449 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/require/a.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/require/a.js diff --git a/tests/lib/mocha-1.17.1/test/acceptance/require/b.coffee b/tests/lib/mocha-2.1.0/test/acceptance/require/b.coffee index 045cc7591b..045cc7591b 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/require/b.coffee +++ b/tests/lib/mocha-2.1.0/test/acceptance/require/b.coffee diff --git a/tests/lib/mocha-1.17.1/test/acceptance/require/c.js b/tests/lib/mocha-2.1.0/test/acceptance/require/c.js index 4e681373a1..4e681373a1 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/require/c.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/require/c.js diff --git a/tests/lib/mocha-1.17.1/test/acceptance/require/d.coffee b/tests/lib/mocha-2.1.0/test/acceptance/require/d.coffee index d5ebbf60ed..d5ebbf60ed 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/require/d.coffee +++ b/tests/lib/mocha-2.1.0/test/acceptance/require/d.coffee diff --git a/tests/lib/mocha-1.17.1/test/acceptance/require/require.js b/tests/lib/mocha-2.1.0/test/acceptance/require/require.js index 74dd0eb597..20f3e6d6ef 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/require/require.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/require/require.js @@ -1,4 +1,3 @@ - describe('require test', function(){ it('should require args in order', function(){ var req = global.required; diff --git a/tests/lib/mocha-1.17.1/test/acceptance/required-tokens.js b/tests/lib/mocha-2.1.0/test/acceptance/required-tokens.js index 6944cee40c..6944cee40c 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/required-tokens.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/required-tokens.js diff --git a/tests/lib/mocha-1.17.1/test/acceptance/root.js b/tests/lib/mocha-2.1.0/test/acceptance/root.js index 49a1e7feb4..17738302da 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/root.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/root.js @@ -1,4 +1,3 @@ - var calls = []; before(function(){ @@ -9,4 +8,4 @@ describe('root', function(){ it('should be a valid suite', function(){ calls.should.eql(['before']); }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/sort/alpha.js b/tests/lib/mocha-2.1.0/test/acceptance/sort/alpha.js index 365f39cd19..7a5302994f 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/sort/alpha.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/sort/alpha.js @@ -4,4 +4,4 @@ describe('alpha', function(){ throw new Error('alpha was not executed first'); } }); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/sort/beta.js b/tests/lib/mocha-2.1.0/test/acceptance/sort/beta.js index 3de3e0ccad..0951f49038 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/sort/beta.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/sort/beta.js @@ -2,4 +2,4 @@ describe('beta', function(){ it('should be executed second', function(){ global.beta = 1; }); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/acceptance/test.coffee b/tests/lib/mocha-2.1.0/test/acceptance/test.coffee index b1c470a69e..8260940a1e 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/test.coffee +++ b/tests/lib/mocha-2.1.0/test/acceptance/test.coffee @@ -3,4 +3,4 @@ obj = foo: 'bar' describe 'coffeescript', -> it 'should work', -> - obj.should.eql foo: 'bar'
\ No newline at end of file + obj.should.eql foo: 'bar' diff --git a/tests/lib/mocha-1.17.1/test/acceptance/test.foo b/tests/lib/mocha-2.1.0/test/acceptance/test.foo index d00491fd7e..d00491fd7e 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/test.foo +++ b/tests/lib/mocha-2.1.0/test/acceptance/test.foo diff --git a/tests/lib/mocha-2.1.0/test/acceptance/throw.js b/tests/lib/mocha-2.1.0/test/acceptance/throw.js new file mode 100644 index 0000000000..ac74f22c4a --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/acceptance/throw.js @@ -0,0 +1,111 @@ +var mocha = require('../../') + , Suite = mocha.Suite + , Runner = mocha.Runner + , Test = mocha.Test; + +describe('a test that throws', function () { + var suite, runner; + + beforeEach(function(){ + suite = new Suite(null, 'root'); + runner = new Runner(suite); + }) + + this.timeout(50); + + describe('undefined', function (){ + it('should not pass if throwing sync and test is sync', function(done) { + var test = new Test('im sync and throw undefined sync', function(){ + throw undefined; + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }) + + it('should not pass if throwing sync and test is async', function(done){ + var test = new Test('im async and throw undefined sync', function(done2){ + throw undefined; + process.nexTick(done2); + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }); + + it('should not pass if throwing async and test is async', function(done){ + var test = new Test('im async and throw undefined async', function(done2){ + process.nexTick(function(){ + throw undefined; + done2(); + }); + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }) + }) + + describe('null', function (){ + it('should not pass if throwing sync and test is sync', function(done) { + var test = new Test('im sync and throw null sync', function(){ + throw null; + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }) + + it('should not pass if throwing sync and test is async', function(done){ + var test = new Test('im async and throw null sync', function(done2){ + throw null; + process.nexTick(done2); + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }); + + it('should not pass if throwing async and test is async', function(done){ + var test = new Test('im async and throw null async', function(done2){ + process.nexTick(function(){ + throw null; + done2(); + }); + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function(){ + runner.failures.should.equal(1); + test.state.should.equal('failed'); + done(); + }); + runner.run(); + }) + }) +})
\ No newline at end of file diff --git a/tests/lib/mocha-2.1.0/test/acceptance/timeout.js b/tests/lib/mocha-2.1.0/test/acceptance/timeout.js new file mode 100644 index 0000000000..f61c3ba566 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/acceptance/timeout.js @@ -0,0 +1,79 @@ +describe('timeouts', function(){ + beforeEach(function(done){ + // uncomment + // setTimeout(done, 3000); + done(); + }) + + it('should error on timeout', function(done){ + // uncomment + // setTimeout(done, 3000); + done(); + }) + + it('should allow overriding per-test', function(done){ + this.timeout(1000); + setTimeout(function(){ + done(); + }, 300); + }) + + describe('disabling', function(){ + it('should allow overriding per-test', function(done){ + this.enableTimeouts(false); + this.timeout(1); + setTimeout(done, 2); + }); + + it('should work with timeout(0)', function(done) { + this.timeout(0); + setTimeout(done, 1); + }) + + describe('using beforeEach', function() { + beforeEach(function () { + this.timeout(0); + }) + + it('should work with timeout(0)', function(done) { + setTimeout(done, 1); + }) + }) + + describe('using before', function() { + before(function () { + this.timeout(0); + }) + + it('should work with timeout(0)', function(done) { + setTimeout(done, 1); + }) + }) + + describe('using enableTimeouts(false)', function() { + this.timeout(4); + + it('should suppress timeout(4)', function(done) { + // The test is in the before() call. + this.enableTimeouts(false); + setTimeout(done, 50); + }) + }) + + describe('suite-level', function() { + this.timeout(0); + + it('should work with timeout(0)', function(done) { + setTimeout(done, 1); + }) + + describe('nested suite', function () { + it('should work with timeout(0)', function(done) { + setTimeout(done, 1); + }) + + }) + }) + }); + +}) diff --git a/tests/lib/mocha-1.17.1/test/acceptance/uncaught.js b/tests/lib/mocha-2.1.0/test/acceptance/uncaught.js index 7349595df2..f228266e45 100644 --- a/tests/lib/mocha-1.17.1/test/acceptance/uncaught.js +++ b/tests/lib/mocha-2.1.0/test/acceptance/uncaught.js @@ -1,4 +1,3 @@ - describe('uncaught', function(){ beforeEach(function(done){ process.nextTick(function(){ @@ -14,4 +13,4 @@ describe('uncaught', function(){ done(); }) }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-2.1.0/test/acceptance/utils.js b/tests/lib/mocha-2.1.0/test/acceptance/utils.js new file mode 100644 index 0000000000..4442c59213 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/acceptance/utils.js @@ -0,0 +1,263 @@ +var utils = require('../../lib/utils'); + +describe('lib/utils', function () { + describe('clean', function () { + it("should format a single line test function", function () { + var fn = [ + "function () {" + , " var a = 1;" + , "}" + ].join("\n"); + utils.clean(fn).should.equal("var a = 1;"); + }); + + it("should format a multi line test indented with spaces", function () { + // and no new lines after curly braces, shouldn't matter + var fn = [ + "function(){ var a = 1;" + , " var b = 2;" // this one has more spaces + , " var c = 3; }" + ].join("\n"); + utils.clean(fn).should.equal("var a = 1;\n var b = 2;\nvar c = 3;"); + }); + + it("should format a multi line test indented with tabs", function () { + var fn = [ + "function (arg1, arg2) {" + , "\tif (true) {" + , "\t\tvar a = 1;" + , "\t}" + , "}" + ].join("\n"); + utils.clean(fn).should.equal("if (true) {\n\tvar a = 1;\n}"); + }); + + it("should format functions saved in windows style - spaces", function () { + var fn = [ + "function (one) {" + , " do {", + , ' "nothing";', + , " } while (false);" + , ' }' + ].join("\r\n"); + utils.clean(fn).should.equal('do {\n "nothing";\n} while (false);'); + }); + + it("should format functions saved in windows style - tabs", function () { + var fn = [ + "function ( ) {" + , "\tif (false) {" + , "\t\tvar json = {" + , '\t\t\tone : 1' + , '\t\t};' + , "\t}" + , "}" + ].join("\r\n"); + utils.clean(fn).should.equal("if (false) {\n\tvar json = {\n\t\tone : 1\n\t};\n}"); + }); + + it("should format es6 arrow functions", function () { + var fn = [ + "() => {", + " var a = 1;", + "}" + ].join("\n"); + utils.clean(fn).should.equal("var a = 1;"); + }); + + it("should format es6 arrow functions with implicit return", function () { + var fn = "() => foo()"; + utils.clean(fn).should.equal("foo()"); + }); + }); + + describe('stringify', function(){ + + var stringify = utils.stringify; + + it('should canonicalize the object', function(){ + var travis = { name: 'travis', age: 24 }; + var travis2 = { age: 24, name: 'travis' }; + + stringify(travis).should.equal(stringify(travis2)); + }); + + it('should handle circular structures in objects', function(){ + var travis = { name: 'travis' }; + travis.whoami = travis; + + stringify(travis).should.equal('{\n "name": "travis"\n "whoami": "[Circular]"\n}'); + }); + + it('should handle circular structures in arrays', function(){ + var travis = ['travis']; + travis.push(travis); + + stringify(travis).should.equal('[\n "travis"\n "[Circular]"\n]'); + }); + + it('should handle circular structures in functions', function(){ + var travis = function () {}; + travis.fn = travis; + + stringify(travis).should.equal('{\n "fn": "[Circular]"\n}'); + }); + + + it('should handle various non-undefined, non-null, non-object, non-array, non-date, and non-function values', function () { + var regexp = new RegExp("(?:)"), + regExpObj = { regexp: regexp }, + regexpString = '/(?:)/'; + + stringify(regExpObj).should.equal('{\n "regexp": "' + regexpString + '"\n}'); + stringify(regexp).should.equal(regexpString); + + var number = 1, + numberObj = { number: number }, + numberString = '1'; + + stringify(numberObj).should.equal('{\n "number": ' + number + '\n}'); + stringify(number).should.equal(numberString); + + var boolean = false, + booleanObj = { boolean: boolean }, + booleanString = 'false'; + + stringify(booleanObj).should.equal('{\n "boolean": ' + boolean + '\n}'); + stringify(boolean).should.equal(booleanString); + + var string = 'sneepy', + stringObj = { string: string }; + + stringify(stringObj).should.equal('{\n "string": "' + string + '"\n}'); + stringify(string).should.equal(string); + + var nullValue = null, + nullObj = { 'null': null }, + nullString = '[null]'; + + stringify(nullObj).should.equal('{\n "null": null\n}'); + stringify(nullValue).should.equal(nullString); + + }); + + it('should handle arrays', function () { + var array = ['dave', 'dave', 'dave', 'dave'], + arrayObj = {array: array}, + arrayString = array.map(function () { + return ' "dave"'; + }).join('\n'); + + stringify(arrayObj).should.equal('{\n "array": [\n' + arrayString + '\n ]\n}'); + stringify(array).should.equal('[' + arrayString.replace(/\s+/g, '\n ') + '\n]'); + }); + + it('should handle functions', function () { + var fn = function() {}, + fnObj = {fn: fn}, + fnString = '[Function]'; + + stringify(fnObj).should.equal('{\n "fn": "' + fnString + '"\n}'); + stringify(fn).should.equal('[Function]'); + }); + + it('should handle empty objects', function () { + stringify({}).should.equal('{}'); + stringify({foo: {}}).should.equal('{\n "foo": {}\n}'); + }); + + it('should handle empty arrays', function () { + stringify([]).should.equal('[]'); + stringify({foo: []}).should.equal('{\n "foo": []\n}'); + }); + + it('should handle non-empty arrays', function () { + stringify(['a', 'b', 'c']).should.equal('[\n "a"\n "b"\n "c"\n]') + }); + + it('should handle empty functions (with no properties)', function () { + stringify(function(){}).should.equal('[Function]'); + stringify({foo: function() {}}).should.equal('{\n "foo": "[Function]"\n}'); + stringify({foo: function() {}, bar: 'baz'}).should.equal('{\n "bar": "baz"\n "foo": "[Function]"\n}'); + }); + + it('should handle functions w/ properties', function () { + var fn = function(){}; + fn.bar = 'baz'; + stringify(fn).should.equal('{\n "bar": "baz"\n}'); + stringify({foo: fn}).should.equal('{\n "foo": {\n "bar": "baz"\n }\n}'); + }); + + it('should handle undefined values', function () { + stringify({foo: undefined}).should.equal('{\n "foo": "[undefined]"\n}'); + stringify({foo: 'bar', baz: undefined}).should.equal('{\n "baz": "[undefined]"\n "foo": "bar"\n}'); + stringify().should.equal('[undefined]'); + }); + + it('should recurse', function () { +stringify({foo: {bar: {baz: {quux: {herp: 'derp'}}}}}).should.equal('{\n "foo": {\n "bar": {\n "baz": {\n "quux": {\n "herp": "derp"\n }\n }\n }\n }\n}'); + }); + + it('might get confusing', function () { + stringify(null).should.equal(stringify('[null]')); + }); + + it('should not freak out if it sees a primitive twice', function () { + stringify({foo: null, bar: null}).should.equal('{\n "bar": null\n "foo": null\n}'); + stringify({foo: 1, bar: 1}).should.equal('{\n "bar": 1\n "foo": 1\n}'); + }); + + it('should stringify dates', function () { + var date = new Date(0); + stringify(date).should.equal('[Date: 1970-01-01T00:00:00.000Z]'); + stringify({date: date}).should.equal('{\n "date": "[Date: 1970-01-01T00:00:00.000Z]"\n}'); + }); + }); + + describe('type', function () { + var type = utils.type; + it('should recognize various types', function () { + type({}).should.equal('object'); + type([]).should.equal('array'); + type(1).should.equal('number'); + type(Infinity).should.equal('number'); + type(null).should.equal('null'); + type(new Date()).should.equal('date'); + type(/foo/).should.equal('regexp'); + type('type').should.equal('string'); + type(global).should.equal('global'); + type(true).should.equal('boolean'); + }); + }); + + describe('lookupFiles', function () { + var fs = require('fs'), + path = require('path'), + existsSync = fs.existsSync || path.existsSync; + + beforeEach(function () { + fs.writeFileSync('/tmp/mocha-utils.js', 'yippy skippy ying yang yow'); + fs.symlinkSync('/tmp/mocha-utils.js', '/tmp/mocha-utils-link.js'); + }); + + it('should not choke on symlinks', function () { + utils.lookupFiles('/tmp', ['js'], false) + .should.containEql('/tmp/mocha-utils-link.js') + .and.containEql('/tmp/mocha-utils.js') + .and.have.lengthOf(2); + existsSync('/tmp/mocha-utils-link.js').should.be.true; + fs.renameSync('/tmp/mocha-utils.js', '/tmp/bob'); + existsSync('/tmp/mocha-utils-link.js').should.be.false; + utils.lookupFiles('/tmp', ['js'], false).should.eql([]); + }); + + afterEach(function () { + ['/tmp/mocha-utils.js', '/tmp/mocha-utils-link.js', '/tmp/bob'].forEach(function (path) { + try { + fs.unlinkSync(path); + } + catch (ignored) {} + }); + }); + }); +}); diff --git a/tests/lib/mocha-1.17.1/test/browser/array.js b/tests/lib/mocha-2.1.0/test/browser/array.js index 26404f6a90..26404f6a90 100644 --- a/tests/lib/mocha-1.17.1/test/browser/array.js +++ b/tests/lib/mocha-2.1.0/test/browser/array.js diff --git a/tests/lib/mocha-1.17.1/test/browser/index.html b/tests/lib/mocha-2.1.0/test/browser/index.html index f10732dd4e..f10732dd4e 100644 --- a/tests/lib/mocha-1.17.1/test/browser/index.html +++ b/tests/lib/mocha-2.1.0/test/browser/index.html diff --git a/tests/lib/mocha-1.17.1/test/browser/large.html b/tests/lib/mocha-2.1.0/test/browser/large.html index 1804b3a000..1804b3a000 100644 --- a/tests/lib/mocha-1.17.1/test/browser/large.html +++ b/tests/lib/mocha-2.1.0/test/browser/large.html diff --git a/tests/lib/mocha-1.17.1/test/browser/large.js b/tests/lib/mocha-2.1.0/test/browser/large.js index 1250abaef4..56757eae27 100644 --- a/tests/lib/mocha-1.17.1/test/browser/large.js +++ b/tests/lib/mocha-2.1.0/test/browser/large.js @@ -1,4 +1,3 @@ - var n = 30; while (n--) { describe('Array ' + n, function(){ @@ -46,4 +45,4 @@ describe('something', function(){ done(); }, 1); }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/browser/opts.html b/tests/lib/mocha-2.1.0/test/browser/opts.html index ec49a418fc..ec49a418fc 100644 --- a/tests/lib/mocha-1.17.1/test/browser/opts.html +++ b/tests/lib/mocha-2.1.0/test/browser/opts.html diff --git a/tests/lib/mocha-1.17.1/test/browser/opts.js b/tests/lib/mocha-2.1.0/test/browser/opts.js index dbbc9ff172..dbbc9ff172 100644 --- a/tests/lib/mocha-1.17.1/test/browser/opts.js +++ b/tests/lib/mocha-2.1.0/test/browser/opts.js diff --git a/tests/lib/mocha-2.1.0/test/color.js b/tests/lib/mocha-2.1.0/test/color.js new file mode 100644 index 0000000000..8c7167b0fa --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/color.js @@ -0,0 +1,17 @@ +var assert = require('assert'); +var child_process = require('child_process'); + +describe('Mocha', function() { + this.timeout(1000); + + it('should not output colors to pipe', function(cb) { + var command = 'bin/mocha --grep missing-test'; + child_process.exec(command, function(err, stdout, stderr) { + if (err) return cb(err); + + assert(stdout.indexOf('[90m') === -1); + + cb(null); + }); + }); +}); diff --git a/tests/lib/mocha-1.17.1/test/compiler/foo.js b/tests/lib/mocha-2.1.0/test/compiler/foo.js index b13101a77b..b13101a77b 100644 --- a/tests/lib/mocha-1.17.1/test/compiler/foo.js +++ b/tests/lib/mocha-2.1.0/test/compiler/foo.js diff --git a/tests/lib/mocha-1.17.1/test/grep.js b/tests/lib/mocha-2.1.0/test/grep.js index b4be3715f1..c2a88217f0 100644 --- a/tests/lib/mocha-1.17.1/test/grep.js +++ b/tests/lib/mocha-2.1.0/test/grep.js @@ -1,4 +1,3 @@ - var Mocha = require('../'); describe('Mocha', function(){ diff --git a/tests/lib/mocha-1.17.1/test/hook.async.js b/tests/lib/mocha-2.1.0/test/hook.async.js index fd62932c4b..ae6e642d24 100644 --- a/tests/lib/mocha-1.17.1/test/hook.async.js +++ b/tests/lib/mocha-2.1.0/test/hook.async.js @@ -1,4 +1,3 @@ - describe('async', function(){ var calls = []; diff --git a/tests/lib/mocha-1.17.1/test/hook.err.js b/tests/lib/mocha-2.1.0/test/hook.err.js index 873147ac02..d7e6c2e460 100644 --- a/tests/lib/mocha-1.17.1/test/hook.err.js +++ b/tests/lib/mocha-2.1.0/test/hook.err.js @@ -293,4 +293,4 @@ describe('hook error handling', function(){ }); }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/hook.sync.js b/tests/lib/mocha-2.1.0/test/hook.sync.js index c446c6aac7..1d40f5d0c0 100644 --- a/tests/lib/mocha-1.17.1/test/hook.sync.js +++ b/tests/lib/mocha-2.1.0/test/hook.sync.js @@ -1,4 +1,3 @@ - describe('serial', function(){ var calls = []; @@ -95,4 +94,4 @@ describe('serial', function(){ , 'parent after']); }) }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/hook.sync.nested.js b/tests/lib/mocha-2.1.0/test/hook.sync.nested.js index 94c00da5f7..b87c22298f 100644 --- a/tests/lib/mocha-1.17.1/test/hook.sync.nested.js +++ b/tests/lib/mocha-2.1.0/test/hook.sync.nested.js @@ -1,4 +1,3 @@ - describe('serial', function(){ describe('nested', function(){ var calls = []; diff --git a/tests/lib/mocha-1.17.1/test/hook.timeout.js b/tests/lib/mocha-2.1.0/test/hook.timeout.js index 5b158e27e6..155c1e95a6 100644 --- a/tests/lib/mocha-1.17.1/test/hook.timeout.js +++ b/tests/lib/mocha-2.1.0/test/hook.timeout.js @@ -1,4 +1,3 @@ - before(function(done){ this.timeout(100); setTimeout(done, 50); @@ -6,4 +5,4 @@ before(function(done){ it('should work', function(done) { done(); -});
\ No newline at end of file +}); diff --git a/tests/lib/mocha-1.17.1/test/http.meta.2.js b/tests/lib/mocha-2.1.0/test/http.meta.2.js index c0568ff349..31b779322f 100644 --- a/tests/lib/mocha-1.17.1/test/http.meta.2.js +++ b/tests/lib/mocha-2.1.0/test/http.meta.2.js @@ -1,4 +1,3 @@ - var http = require('http'); var server = http.createServer(function(req, res){ @@ -29,7 +28,7 @@ function get(url) { function request(done) { http.get({ path: url, port: 8899, headers: header }, function(res){ var buf = ''; - res.should.have.status(200); + res.should.have.property('statusCode', 200); res.setEncoding('utf8'); res.on('data', function(chunk){ buf += chunk }); res.on('end', function(){ @@ -53,6 +52,7 @@ function get(url) { expected = body; describe('GET ' + url, function(){ + this.timeout(500); if (fields) { describe('when given ' + fields, function(){ it('should respond with "' + body + '"', request); @@ -79,4 +79,4 @@ describe('http server', function(){ .set('Accept', 'application/json') .should .respond('["tobi","loki","jane"]') -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/http.meta.js b/tests/lib/mocha-2.1.0/test/http.meta.js index 1ab6767005..00c3f9c8b6 100644 --- a/tests/lib/mocha-1.17.1/test/http.meta.js +++ b/tests/lib/mocha-2.1.0/test/http.meta.js @@ -1,4 +1,3 @@ - var http = require('http'); var server = http.createServer(function(req, res){ @@ -25,7 +24,7 @@ function get(url, body, header) { return function(done){ http.get({ path: url, port: 8889, headers: header }, function(res){ var buf = ''; - res.should.have.status(200); + res.should.have.property('statusCode', 200); res.setEncoding('utf8'); res.on('data', function(chunk){ buf += chunk }); res.on('end', function(){ @@ -49,4 +48,4 @@ describe('http requests', function(){ it('should respond with users', get('/users', '["tobi","loki","jane"]', { Accept: 'application/json' })) }) -})
\ No newline at end of file +}) diff --git a/tests/lib/mocha-1.17.1/test/jsapi/index.js b/tests/lib/mocha-2.1.0/test/jsapi/index.js index 5b5c169ade..2dcdd6dc68 100644 --- a/tests/lib/mocha-1.17.1/test/jsapi/index.js +++ b/tests/lib/mocha-2.1.0/test/jsapi/index.js @@ -1,4 +1,3 @@ - var Mocha = require('../../') , path = require('path'); diff --git a/tests/lib/mocha-1.17.1/test/mocha.opts b/tests/lib/mocha-2.1.0/test/mocha.opts index 3c2f2cbd02..3c2f2cbd02 100644 --- a/tests/lib/mocha-1.17.1/test/mocha.opts +++ b/tests/lib/mocha-2.1.0/test/mocha.opts diff --git a/tests/lib/mocha-2.1.0/test/regression/issue1327/case.js b/tests/lib/mocha-2.1.0/test/regression/issue1327/case.js new file mode 100644 index 0000000000..295ec12413 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/regression/issue1327/case.js @@ -0,0 +1,14 @@ +var debug = require('debug')('mocha:issue1327'); +it("test 1", function() { + debug("This runs only once."); + process.nextTick(function() { + throw "Too bad"; + }); +}); +it("test 2", function() { + debug("This should run once - Previously wasn't called at all."); +}); +it("test 3", function() { + debug("This used to run twice."); + throw new Error("OUCH"); +}); diff --git a/tests/lib/mocha-2.1.0/test/regression/issue1327/control.js b/tests/lib/mocha-2.1.0/test/regression/issue1327/control.js new file mode 100644 index 0000000000..b77555da02 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/regression/issue1327/control.js @@ -0,0 +1,10 @@ +var assert = require("assert"), + fs = require("fs"); + +describe("GitHub issue #1327: expected behavior of case.js", function() { + it("should have run 3 tests", function() { + var results = JSON.parse(fs.readFileSync( + "test-outputs/issue1327/case-out.json")); + results.stats.tests.should.equal(3); + }); +}); diff --git a/tests/lib/mocha-2.1.0/test/reporters/base.js b/tests/lib/mocha-2.1.0/test/reporters/base.js new file mode 100644 index 0000000000..d285bf8845 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/reporters/base.js @@ -0,0 +1,136 @@ +var Base = require('../../lib/reporters/base'); + +describe('Base reporter', function () { + + it('should show diffs with showDiff property set', function () { + var err = new Error('test'), + stdout = [], + stdoutWrite = process.stdout.write, + errOut; + + err.actual = "a1"; + err.expected = "e1"; + err.showDiff = true; + var test = { + err: err, + fullTitle: function () { + return 'title'; + } + }; + + process.stdout.write = function (string) { + stdout.push(string); + }; + + Base.list([test]); + + process.stdout.write = stdoutWrite; + + errOut = stdout.join('\n'); + + errOut.should.match(/test/); + errOut.should.match(/actual/); + errOut.should.match(/expected/); + + }); + + + it('should not stringify strings', function () { + var err = new Error('test'), + stdout = [], + stdoutWrite = process.stdout.write, + errOut; + + err.actual = "a1"; + err.expected = "e2"; + err.showDiff = true; + var test = { + err: err, + fullTitle: function () { + return 'title'; + } + }; + + process.stdout.write = function (string) { + stdout.push(string); + }; + + Base.list([test]); + + process.stdout.write = stdoutWrite; + + errOut = stdout.join('\n'); + + errOut.should.not.match(/"/); + errOut.should.match(/test/); + errOut.should.match(/actual/); + errOut.should.match(/expected/); + + }); + + + it('should stringify objects', function () { + var err = new Error('test'), + stdout = [], + stdoutWrite = process.stdout.write, + errOut; + + err.actual = {key:"a1"}; + err.expected = {key:"e1"}; + err.showDiff = true; + var test = { + err: err, + fullTitle: function () { + return 'title'; + } + }; + + process.stdout.write = function (string) { + stdout.push(string); + }; + + Base.list([test]); + + process.stdout.write = stdoutWrite; + + errOut = stdout.join('\n'); + + errOut.should.match(/"key"/); + errOut.should.match(/test/); + errOut.should.match(/actual/); + errOut.should.match(/expected/); + + }); + + it('should not show diffs when showDiff property set', function () { + var err = new Error('test'), + stdout = [], + stdoutWrite = process.stdout.write, + errOut; + + err.actual = "a1"; + err.expected = "e1"; + err.showDiff = false; + var test = { + err: err, + fullTitle: function () { + return 'title'; + } + }; + + process.stdout.write = function (string) { + stdout.push(string); + }; + + Base.list([test]); + + process.stdout.write = stdoutWrite; + + errOut = stdout.join('\n'); + + errOut.should.match(/test/); + errOut.should.not.match(/actual/); + errOut.should.not.match(/expected/); + + }); +}); diff --git a/tests/lib/mocha-2.1.0/test/reporters/json.js b/tests/lib/mocha-2.1.0/test/reporters/json.js new file mode 100644 index 0000000000..f071e75480 --- /dev/null +++ b/tests/lib/mocha-2.1.0/test/reporters/json.js @@ -0,0 +1,61 @@ +var Mocha = require('../../') + , Suite = Mocha.Suite + , Runner = Mocha.Runner + , Test = Mocha.Test; + +describe('json reporter', function(){ + var suite, runner; + + beforeEach(function(){ + var mocha = new Mocha({ + reporter: 'json' + }); + suite = new Suite('JSON suite', 'root'); + runner = new Runner(suite); + var mochaReporter = new mocha._reporter(runner); + }) + + it('should have 1 test failure', function(done){ + var testTitle = 'json test 1'; + var error = { message: 'oh shit' }; + + suite.addTest(new Test(testTitle, function (done) { + done(new Error(error.message)); + })); + + runner.run(function(failureCount) { + failureCount.should.be.exactly(1); + runner.should.have.property('testResults'); + runner.testResults.should.have.property('failures'); + runner.testResults.failures.should.be.an.instanceOf(Array); + runner.testResults.failures.should.have.a.lengthOf(1); + + var failure = runner.testResults.failures[0]; + failure.should.have.property('title', testTitle); + failure.err.message.should.equal(error.message); + failure.should.have.properties('err'); + + done(); + }); + }) + + it('should have 1 test pending', function(done) { + var testTitle = 'json test 1'; + + suite.addTest(new Test(testTitle)); + + runner.run(function(failureCount) { + failureCount.should.be.exactly(0); + runner.should.have.property('testResults'); + runner.testResults.should.have.property('pending'); + runner.testResults.pending.should.be.an.instanceOf(Array); + runner.testResults.pending.should.have.a.lengthOf(1); + + var pending = runner.testResults.pending[0]; + pending.should.have.property('title', testTitle); + + done(); + }); + }) + +}) diff --git a/tests/lib/mocha-1.17.1/test/reporters/nyan.js b/tests/lib/mocha-2.1.0/test/reporters/nyan.js index cd6b4a96a8..8a54458961 100644 --- a/tests/lib/mocha-1.17.1/test/reporters/nyan.js +++ b/tests/lib/mocha-2.1.0/test/reporters/nyan.js @@ -1,6 +1,7 @@ var reporters = require('../../').reporters , NyanCat = reporters.Nyan; + describe('nyan face', function () { it('nyan face:(x .x) when "failures" at least one', function () { var nyanCat = new NyanCat({on: function(){}}); diff --git a/tests/lib/mocha-1.17.1/test/runnable.js b/tests/lib/mocha-2.1.0/test/runnable.js index 29d8fd6b96..dd05a7543b 100644 --- a/tests/lib/mocha-1.17.1/test/runnable.js +++ b/tests/lib/mocha-2.1.0/test/runnable.js @@ -1,4 +1,3 @@ - var mocha = require('../') , Runnable = mocha.Runnable , EventEmitter = require('events').EventEmitter; @@ -41,6 +40,14 @@ describe('Runnable(title, fn)', function(){ }) }) + describe('#enableTimeouts(enabled)', function(){ + it('should set enabled', function(){ + var run = new Runnable; + run.enableTimeouts(false); + run.enableTimeouts().should.equal(false); + }); + }); + describe('#slow(ms)', function(){ it('should set the slow threshold', function(){ var run = new Runnable; @@ -126,6 +133,17 @@ describe('Runnable(title, fn)', function(){ }) }) + describe('when timeouts are disabled', function() { + it('should not error with timeout', function(done) { + var test = new Runnable('foo', function(done){ + setTimeout(process.nextTick.bind(undefined, done), 2); + }); + test.timeout(1); + test.enableTimeouts(false); + test.run(done); + }); + }); + describe('when async', function(){ describe('without error', function(){ it('should invoke the callback', function(done){ @@ -206,6 +224,18 @@ describe('Runnable(title, fn)', function(){ done(); }); }) + + it('should not throw its own exception if passed a non-object', function (done) { + var test = new Runnable('foo', function(done) { + throw null; + process.nextTick(done); + }); + + test.run(function(err) { + err.message.should.equal('Caught undefined error, did you throw without specifying what?'); + done(); + }) + }); }) describe('when an error is passed', function(){ @@ -222,6 +252,32 @@ describe('Runnable(title, fn)', function(){ }) }) + describe('when done() is invoked with a non-Error object', function(){ + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(done){ + done({ error: 'Test error' }); + }); + + test.run(function(err){ + err.message.should.equal('done() invoked with non-Error: {"error":"Test error"}'); + done(); + }); + }) + }) + + describe('when done() is invoked with a string', function(){ + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(done){ + done('Test error'); + }); + + test.run(function(err){ + err.message.should.equal('done() invoked with non-Error: Test error'); + done(); + }); + }) + }) + it('should allow updating the timeout', function(done){ var callCount = 0; var increment = function() { @@ -242,5 +298,112 @@ describe('Runnable(title, fn)', function(){ it('should allow a timeout of 0') }) + describe('when fn returns a promise', function(){ + describe('when the promise is fulfilled with no value', function(){ + var fulfilledPromise = { + then: function (fulfilled, rejected) { + process.nextTick(fulfilled); + } + }; + + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(){ + return fulfilledPromise; + }); + + test.run(done); + }) + }) + + describe('when the promise is fulfilled with a value', function(){ + var fulfilledPromise = { + then: function (fulfilled, rejected) { + process.nextTick(function () { + fulfilled({}); + }); + } + }; + + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(){ + return fulfilledPromise; + }); + + test.run(done); + }) + }) + + describe('when the promise is rejected', function(){ + var expectedErr = new Error('fail'); + var rejectedPromise = { + then: function (fulfilled, rejected) { + process.nextTick(function () { + rejected(expectedErr); + }); + } + }; + + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(){ + return rejectedPromise; + }); + + test.run(function(err){ + err.should.equal(expectedErr); + done(); + }); + }) + }) + + describe('when the promise is rejected without a reason', function(){ + var expectedErr = new Error('Promise rejected with no or falsy reason'); + var rejectedPromise = { + then: function (fulfilled, rejected) { + process.nextTick(function () { + rejected(); + }); + } + }; + + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(){ + return rejectedPromise; + }); + + test.run(function(err){ + err.should.eql(expectedErr); + done(); + }); + }) + }) + + describe('when the promise takes too long to settle', function(){ + var foreverPendingPromise = { + then: function () { } + }; + + it('should give the timeout error', function(done){ + var test = new Runnable('foo', function(){ + return foreverPendingPromise; + }); + + test.timeout(10); + test.run(function(err){ + err.should.be.ok; + done(); + }); + }) + }) + }) + + describe('when fn returns a non-promise', function(){ + it('should invoke the callback', function(done){ + var test = new Runnable('foo', function(){ + return { then: "i ran my tests" }; + }); + + test.run(done); + }) + }) }) }) diff --git a/tests/lib/mocha-1.17.1/test/runner.js b/tests/lib/mocha-2.1.0/test/runner.js index 5a7589914d..a11aeedabf 100644 --- a/tests/lib/mocha-1.17.1/test/runner.js +++ b/tests/lib/mocha-2.1.0/test/runner.js @@ -1,4 +1,3 @@ - var mocha = require('../') , Suite = mocha.Suite , Runner = mocha.Runner @@ -53,12 +52,12 @@ describe('Runner', function(){ describe('.globalProps()', function(){ it('should include common non enumerable globals', function() { var props = runner.globalProps(); - props.should.include('setTimeout'); - props.should.include('clearTimeout'); - props.should.include('setInterval'); - props.should.include('clearInterval'); - props.should.include('Date'); - props.should.include('XMLHttpRequest'); + props.should.containEql('setTimeout'); + props.should.containEql('clearTimeout'); + props.should.containEql('setInterval'); + props.should.containEql('clearInterval'); + props.should.containEql('Date'); + props.should.containEql('XMLHttpRequest'); }); }); @@ -69,8 +68,8 @@ describe('Runner', function(){ it('should white-list globals', function(){ runner.globals(['foo', 'bar']); - runner.globals().should.include('foo'); - runner.globals().should.include('bar'); + runner.globals().should.containEql('foo'); + runner.globals().should.containEql('bar'); }) }) @@ -182,6 +181,20 @@ describe('Runner', function(){ }) }) + describe('.hook(name, fn)', function(){ + it('should execute hooks after failed test if suite bail is true', function(done){ + runner.fail({}); + suite.bail(true); + suite.afterEach(function(){ + suite.afterAll(function() { + done(); + }) + }); + runner.hook('afterEach', function(){}); + runner.hook('afterAll', function(){}); + }) + }) + describe('.fail(test, err)', function(){ it('should increment .failures', function(){ runner.failures.should.equal(0); diff --git a/tests/lib/mocha-1.17.1/test/suite.js b/tests/lib/mocha-2.1.0/test/suite.js index 8cacff8de9..011b3fb2b8 100644 --- a/tests/lib/mocha-1.17.1/test/suite.js +++ b/tests/lib/mocha-2.1.0/test/suite.js @@ -1,4 +1,3 @@ - var mocha = require('../') , Context = mocha.Context , Suite = mocha.Suite @@ -132,7 +131,7 @@ describe('Suite', function(){ describe('wraps the passed in function in a Hook', function(){ it('adds it to _beforeAll', function(){ - function fn(){} + var fn = function(){}; this.suite.beforeAll(fn); this.suite._beforeAll.should.have.length(1); @@ -140,6 +139,23 @@ describe('Suite', function(){ beforeAllItem.title.should.equal('"before all" hook'); beforeAllItem.fn.should.equal(fn); }); + + it('appends title to hook', function(){ + var fn = function(){}; + this.suite.beforeAll('test', fn); + + this.suite._beforeAll.should.have.length(1); + var beforeAllItem = this.suite._beforeAll[0]; + beforeAllItem.title.should.equal('"before all" hook: test'); + beforeAllItem.fn.should.equal(fn); + + function namedFn(){} + this.suite.beforeAll(namedFn); + this.suite._beforeAll.should.have.length(2); + beforeAllItem = this.suite._beforeAll[1]; + beforeAllItem.title.should.equal('"before all" hook: namedFn'); + beforeAllItem.fn.should.equal(namedFn); + }); }); }); @@ -150,7 +166,7 @@ describe('Suite', function(){ describe('wraps the passed in function in a Hook', function(){ it('adds it to _afterAll', function(){ - function fn(){} + var fn = function(){}; this.suite.afterAll(fn); this.suite._afterAll.should.have.length(1); @@ -158,6 +174,22 @@ describe('Suite', function(){ afterAllItem.title.should.equal('"after all" hook'); afterAllItem.fn.should.equal(fn); }); + it('appends title to hook', function(){ + var fn = function(){}; + this.suite.afterAll('test', fn); + + this.suite._afterAll.should.have.length(1); + var beforeAllItem = this.suite._afterAll[0]; + beforeAllItem.title.should.equal('"after all" hook: test'); + beforeAllItem.fn.should.equal(fn); + + function namedFn(){} + this.suite.afterAll(namedFn); + this.suite._afterAll.should.have.length(2); + beforeAllItem = this.suite._afterAll[1]; + beforeAllItem.title.should.equal('"after all" hook: namedFn'); + beforeAllItem.fn.should.equal(namedFn); + }); }); }); @@ -168,7 +200,7 @@ describe('Suite', function(){ describe('wraps the passed in function in a Hook', function(){ it('adds it to _beforeEach', function(){ - function fn(){} + var fn = function(){}; this.suite.beforeEach(fn); this.suite._beforeEach.should.have.length(1); @@ -176,6 +208,23 @@ describe('Suite', function(){ beforeEachItem.title.should.equal('"before each" hook'); beforeEachItem.fn.should.equal(fn); }); + + it('appends title to hook', function(){ + var fn = function(){}; + this.suite.beforeEach('test', fn); + + this.suite._beforeEach.should.have.length(1); + var beforeAllItem = this.suite._beforeEach[0]; + beforeAllItem.title.should.equal('"before each" hook: test'); + beforeAllItem.fn.should.equal(fn); + + function namedFn(){} + this.suite.beforeEach(namedFn); + this.suite._beforeEach.should.have.length(2); + beforeAllItem = this.suite._beforeEach[1]; + beforeAllItem.title.should.equal('"before each" hook: namedFn'); + beforeAllItem.fn.should.equal(namedFn); + }); }); }); @@ -186,7 +235,7 @@ describe('Suite', function(){ describe('wraps the passed in function in a Hook', function(){ it('adds it to _afterEach', function(){ - function fn(){} + var fn = function(){}; this.suite.afterEach(fn); this.suite._afterEach.should.have.length(1); @@ -194,6 +243,23 @@ describe('Suite', function(){ afterEachItem.title.should.equal('"after each" hook'); afterEachItem.fn.should.equal(fn); }); + + it('appends title to hook', function(){ + var fn = function(){}; + this.suite.afterEach('test', fn); + + this.suite._afterEach.should.have.length(1); + var beforeAllItem = this.suite._afterEach[0]; + beforeAllItem.title.should.equal('"after each" hook: test'); + beforeAllItem.fn.should.equal(fn); + + function namedFn(){} + this.suite.afterEach(namedFn); + this.suite._afterEach.should.have.length(2); + beforeAllItem = this.suite._afterEach[1]; + beforeAllItem.title.should.equal('"after each" hook: namedFn'); + beforeAllItem.fn.should.equal(namedFn); + }); }); }); diff --git a/tests/lib/mocha-1.17.1/test/utils.js b/tests/lib/mocha-2.1.0/test/utils.js index f5ea9dc1e7..3810c777b4 100644 --- a/tests/lib/mocha-1.17.1/test/utils.js +++ b/tests/lib/mocha-2.1.0/test/utils.js @@ -1,20 +1,26 @@ - var mocha = require('..'); var utils = mocha.utils; var clean = utils.clean; +var isBuffer = utils.isBuffer; -describe('utils', function(){ +describe('utils', function() { describe('.clean()', function(){ it('should remove the wrapping function declaration', function(){ clean('function (one, two, three) {\n//code\n}').should.equal('//code'); - }) + }); it('should remove space character indentation from the function body', function(){ clean(' //line1\n //line2').should.equal('//line1\n //line2'); - }) + }); it('should remove tab character indentation from the function body', function(){ clean('\t//line1\n\t\t//line2').should.equal('//line1\n\t//line2'); + }); + }); + describe('.isBuffer()', function(){ + it('should test if object is a Buffer', function() { + isBuffer(new Buffer([0x01])).should.equal(true); + isBuffer({}).should.equal(false); }) - }) -}) + }); +}); |