Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
61
gaia/apps/search/build/build.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var utils = require('utils');
|
||||
|
||||
var SearchAppBuilder = function() {
|
||||
};
|
||||
|
||||
function pickAsset(root, filename, ppx) {
|
||||
if (ppx !== '1') {
|
||||
var suffix = '@' + ppx + 'x';
|
||||
var hdpiFile = filename.replace(/(\.[a-z]+$)/, suffix + '$1');
|
||||
var file = utils.getFile(root.path, hdpiFile);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return utils.getFile(root.path, filename);
|
||||
}
|
||||
|
||||
SearchAppBuilder.prototype.setOptions = function(options) {
|
||||
this.stageDir = utils.getFile(options.STAGE_APP_DIR);
|
||||
this.appDir = utils.getFile(options.APP_DIR);
|
||||
this.buildDir = utils.getFile(this.appDir.path, '/build');
|
||||
this.distDirPath = options.GAIA_DISTRIBUTION_DIR;
|
||||
};
|
||||
|
||||
SearchAppBuilder.prototype.initTopsitesJSON = function() {
|
||||
var defaultJSONpath =
|
||||
utils.joinPath(this.appDir.path, 'build', 'topsites.json');
|
||||
var defaultJson = utils.getJSON(utils.getFile(defaultJSONpath));
|
||||
|
||||
defaultJson.forEach(function(site) {
|
||||
if (site.tilePath) {
|
||||
var file = pickAsset(this.buildDir, site.tilePath,
|
||||
this.options.GAIA_DEV_PIXELS_PER_PX);
|
||||
var icon = utils.getFileAsDataURI(file);
|
||||
site.tile = icon;
|
||||
delete site.tilePath;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
var file = utils.getFile(this.stageDir.path, 'js', 'inittopsites.json');
|
||||
var fileContents = utils.getDistributionFileContent('topsites', defaultJson,
|
||||
this.distDirPath);
|
||||
utils.writeContent(file, fileContents);
|
||||
};
|
||||
|
||||
SearchAppBuilder.prototype.execute = function(options) {
|
||||
this.options = options;
|
||||
this.setOptions(options);
|
||||
if (options.PROFILE_FOLDER !== 'profile-test') {
|
||||
this.initTopsitesJSON();
|
||||
}
|
||||
};
|
||||
|
||||
exports.execute = function(options) {
|
||||
utils.copyToStage(options);
|
||||
(new SearchAppBuilder()).execute(options);
|
||||
};
|
||||
BIN
gaia/apps/search/build/mozilla.org.jpg
Executable file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
gaia/apps/search/build/mozilla.org@1.5x.jpg
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
gaia/apps/search/build/mozilla.org@2.25x.jpg
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
gaia/apps/search/build/mozilla.org@2x.jpg
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
gaia/apps/search/build/mozillasupport.jpg
Executable file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
gaia/apps/search/build/mozillasupport@1.5x.jpg
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
gaia/apps/search/build/mozillasupport@2.25x.jpg
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
gaia/apps/search/build/mozillasupport@2x.jpg
Executable file
|
After Width: | Height: | Size: 17 KiB |
14
gaia/apps/search/build/topsites.json
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"title": "Mozilla",
|
||||
"url": "https://mozilla.org",
|
||||
"frecency": 0,
|
||||
"tilePath": "mozilla.org.jpg"
|
||||
},
|
||||
{
|
||||
"title": "Mozilla Support",
|
||||
"url": "https://support.mozilla.org",
|
||||
"frecency": 0,
|
||||
"tilePath": "mozillasupport.jpg"
|
||||
}
|
||||
]
|
||||
115
gaia/apps/search/index.html
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0">
|
||||
<link rel="stylesheet" href="style/search.css">
|
||||
<link rel="stylesheet" href="shared/elements/gaia_grid/style/external.css">
|
||||
<link rel="stylesheet" href="shared/style/progress_activity.css">
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/search.{locale}.properties">
|
||||
<link rel="localization" href="/shared/elements/gaia_grid/locales/gaia_grid.{locale}.properties">
|
||||
|
||||
<script defer src="shared/js/l10n.js"></script>
|
||||
<script defer src="shared/js/lazy_loader.js"></script>
|
||||
<script defer src="shared/js/async_storage.js"></script>
|
||||
<script defer src="shared/js/utilities.js"></script>
|
||||
<script defer src="shared/js/settings_listener.js"></script>
|
||||
<script defer src="shared/js/format.js"></script>
|
||||
<script defer src="shared/js/search_provider.js"></script>
|
||||
<!-- <script defer src="shared/js/search_providers.json"></script> -->
|
||||
|
||||
<!-- Confirm dialog component -->
|
||||
<script defer src="/shared/js/component_utils.js"></script>
|
||||
<script defer src="/shared/elements/gaia_buttons/script.js"></script>
|
||||
<script defer src="/shared/elements/gaia_confirm/script.js"></script>
|
||||
<!--- gaia_grid required scripts -->
|
||||
<script defer src="shared/js/homescreens/homescreen_settings.js"></script>
|
||||
<script defer src="shared/js/homescreens/confirm_dialog_helper.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/grid_view.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/grid_icon_renderer.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/grid_layout.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/grid_zoom.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/script.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/grid_item.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/bookmark.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/marketplace_app.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/divider.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/mozapp.js"></script>
|
||||
<script defer src="shared/elements/gaia_grid/js/items/placeholder.js"></script>
|
||||
|
||||
<script defer src="shared/js/async_storage.js"></script>
|
||||
<script defer src="shared/js/url_helper.js"></script>
|
||||
<script defer src="shared/js/web_manifest_helper.js"></script>
|
||||
<script defer src="shared/js/utilities.js"></script>
|
||||
<script defer src="shared/js/dedupe.js"></script>
|
||||
<script defer src="js/places_idb.js"></script>
|
||||
<script defer src="shared/js/sync_datastore.js"></script>
|
||||
<script defer src="shared/js/icons_helper.js"></script>
|
||||
<script defer src="shared/js/metrics_helper.js"></script>
|
||||
<script defer src="js/contextmenu.js"></script>
|
||||
<script defer src="js/search.js"></script>
|
||||
<script defer src="js/providers/provider.js"></script>
|
||||
<script defer src="js/providers/grid_provider.js"></script>
|
||||
|
||||
<!-- Ordering of the following files will impact which queries we send first -->
|
||||
<script defer src="js/providers/marketplace.js"></script>
|
||||
<script defer src="js/providers/suggestions.js"></script>
|
||||
<script defer src="js/providers/bookmarks.js"></script>
|
||||
<script defer src="js/providers/places.js"></script>
|
||||
<script defer src="js/providers/local_apps.js"></script>
|
||||
|
||||
<!-- Include the app icon in the application.zip -->
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_84.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_126.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_142.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_189.png"/>
|
||||
<link rel="resource" type="image/png" href="shared/resources/branding/browser_284.png"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="search-results" class="hidden">
|
||||
<div id="offline-message">
|
||||
<span id="settings-connectivity"></span>
|
||||
<span data-l10n-id="offline-message"></span>
|
||||
</div>
|
||||
|
||||
<section id="contacts" class="local" data-l10n-id="contacts"
|
||||
aria-label="Contacts"></section>
|
||||
|
||||
<section id="icons-wrapper" class="apps" data-l10n-id="apps" aria-label="Apps">
|
||||
<gaia-grid cols="4" text-rows="3" id="icons"></gaia-grid>
|
||||
</section>
|
||||
|
||||
<h2 id="places-header" class="hidden"><span data-l10n-id="history"></span></h2>
|
||||
<section id="places" class="local" data-l10n-id="places"
|
||||
aria-label="Places"></section>
|
||||
|
||||
<div id="suggestions-wrapper">
|
||||
<h2>
|
||||
<select id="suggestions-select"></select>
|
||||
<label for="suggestions-select" id="suggestions-provider"></label>
|
||||
<span id="suggestions-provider-dropdown"></span>
|
||||
</h2>
|
||||
|
||||
<div id="suggestions-notice-wrapper" hidden>
|
||||
<p data-l10n-id="suggestions-settings-notice">
|
||||
<span id="settings-link"></span>
|
||||
</p>
|
||||
<button id="suggestions-notice-confirm" data-l10n-id="ok"></button>
|
||||
</div>
|
||||
|
||||
<section id="suggestions"
|
||||
class="suggestions" data-l10n-id="suggestions"
|
||||
aria-label="Suggestions"></section>
|
||||
<div class="loading"><progress></progress></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<menu id="contextmenu" hidden>
|
||||
<menuitem id="add-to-homescreen" data-l10n-id="add-to-home-screen" type="button"></menuitem>
|
||||
</menu>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
92
gaia/apps/search/js/contextmenu.js
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
'use strict';
|
||||
/* global MozActivity */
|
||||
/* global Bookmarks */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* The search context menu handles bookmarking of search
|
||||
* results when the users long presses them.
|
||||
*/
|
||||
function Contextmenu() {
|
||||
this.menu = document.getElementById('contextmenu');
|
||||
this.grid = document.getElementById('icons');
|
||||
|
||||
this.bookmarkButton = this.menu.querySelector('#add-to-homescreen');
|
||||
|
||||
this.grid.addEventListener('contextmenu', this);
|
||||
this.bookmarkButton.addEventListener('click', this);
|
||||
}
|
||||
|
||||
Contextmenu.prototype = {
|
||||
|
||||
/**
|
||||
* The current icon target.
|
||||
*/
|
||||
icon: null,
|
||||
|
||||
handleEvent: function(e) {
|
||||
switch(e.type) {
|
||||
case 'contextmenu':
|
||||
var identifier = e.target.dataset.identifier;
|
||||
var icon = this.grid.getIcon(identifier);
|
||||
|
||||
// Only show for bookmark targets
|
||||
if (!icon || icon.detail.type !== 'bookmark') {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = icon.detail.url;
|
||||
if (Bookmarks.get(url)) {
|
||||
// Bookmark already installed in device
|
||||
return;
|
||||
}
|
||||
|
||||
// In order to benefit from the system contextmenu in such a way that
|
||||
// it overlaps search bar, let's create a contextmenu attribute on
|
||||
// the fly, and remove it once the event dispatching is done.
|
||||
e.target.setAttribute('contextmenu', 'contextmenu');
|
||||
|
||||
// Stop the grid from listening to events to prevent it from
|
||||
// receiving a touchend event and launching the icon. This is
|
||||
// restored on next tick.
|
||||
this.grid.stop();
|
||||
|
||||
setTimeout(() => {
|
||||
e.target.removeAttribute('contextmenu');
|
||||
this.grid.start();
|
||||
});
|
||||
|
||||
this.icon = icon;
|
||||
break;
|
||||
|
||||
case 'click':
|
||||
/* jshint nonew: false */
|
||||
/**
|
||||
* We could have several cases for icons in the search grid:
|
||||
* - Icons from e.me: this.icon.icon will be url
|
||||
* - Icons from places: this.icon.icon can be:
|
||||
* - a blob, cached icon
|
||||
* - a icon url
|
||||
* To solve this, added an extra feature attribute: iconUrl,
|
||||
* so GaiaGridItems with this extra attribute in the detail
|
||||
* will have a way to keep track of the original url.
|
||||
*/
|
||||
var iconUrl = this.icon.detail.iconUrl || this.icon.icon;
|
||||
new MozActivity({
|
||||
name: 'save-bookmark',
|
||||
data: {
|
||||
type: 'url',
|
||||
url: this.icon.detail.url,
|
||||
name: this.icon.name,
|
||||
icon: iconUrl
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Contextmenu = Contextmenu;
|
||||
|
||||
}(window));
|
||||
197
gaia/apps/search/js/newtab.js
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
'use strict';
|
||||
/* global asyncStorage, MozActivity, SettingsListener */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
var isManuallyPrivate = window.location.search.includes('private=1');
|
||||
var isManuallyRegular = window.location.search.includes('private=0');
|
||||
var isPrivateByDefault = false;
|
||||
|
||||
SettingsListener.observe('browser.private.default', false, function (value) {
|
||||
if (value) {
|
||||
isPrivateByDefault = true;
|
||||
} else {
|
||||
isPrivateByDefault = false;
|
||||
}
|
||||
|
||||
exports.newtab.togglePrivacyMode();
|
||||
});
|
||||
|
||||
/**
|
||||
* The main Newtab page object.
|
||||
* Instantiates places to populate history and top sites.
|
||||
*/
|
||||
function Newtab() {
|
||||
// Initialize the parent port connection
|
||||
var self = this;
|
||||
navigator.mozApps.getSelf().onsuccess = function() {
|
||||
var app = this.result;
|
||||
app.connect('search-results').then(function onConnAccepted(ports) {
|
||||
ports.forEach(function(port) {
|
||||
self._port = port;
|
||||
});
|
||||
self.init();
|
||||
}, function onConnectionRejected(reason) {
|
||||
console.log('Error connecting: ' + reason + '\n');
|
||||
});
|
||||
};
|
||||
|
||||
this.togglePrivacyMode();
|
||||
}
|
||||
|
||||
Newtab.prototype = {
|
||||
|
||||
/**
|
||||
* A reference to the Places provider.
|
||||
*/
|
||||
provider: null,
|
||||
|
||||
/**
|
||||
* Initializes top sites and history.
|
||||
*/
|
||||
init: function() {
|
||||
this.provider.init();
|
||||
this.provider.searchObj = this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests a screenshot of the page from the system app.
|
||||
*/
|
||||
requestScreenshot: function(url) {
|
||||
this._port.postMessage({
|
||||
'action': 'request-screenshot',
|
||||
'url': url
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests that the system app opens a new private window.
|
||||
*/
|
||||
requestPrivateWindow: function() {
|
||||
this._port.postMessage({
|
||||
'action': 'private-window'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the privacy mode.
|
||||
*
|
||||
* When private browsing _IS_ the default and we _ARE NOT_ manually being
|
||||
* regular or when private browsing _IS NOT_ the default and we _ARE_
|
||||
* manually bring private: Makes this window look like a private window by
|
||||
* applying the necessary theme color and background. The window can't be
|
||||
* truly private, otherwise we wouldn't have access to local history and
|
||||
* bookmarks.
|
||||
*
|
||||
* When private browsing _IS_ the default and we _ARE_ manually regular:
|
||||
* Makes this window look normal by removing private theme settings.
|
||||
*/
|
||||
togglePrivacyMode: function() {
|
||||
var themeMeta = document.querySelector('meta[name="theme-color"]');
|
||||
var themeColor = document.createElement('meta');
|
||||
|
||||
if ((isPrivateByDefault && !isManuallyRegular) ||
|
||||
(!isPrivateByDefault && isManuallyPrivate)) {
|
||||
|
||||
if (themeMeta) {
|
||||
document.head.removeChild(themeMeta);
|
||||
}
|
||||
|
||||
themeColor.setAttribute('name', 'theme-color');
|
||||
themeColor.setAttribute('content', '#392E54');
|
||||
document.head.appendChild(themeColor);
|
||||
document.body.classList.add('private');
|
||||
|
||||
// Show the private dialog if needed.
|
||||
asyncStorage.getItem('shouldSuppressPrivateDialog', value => {
|
||||
if (value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.privateBrowserDialog = document.getElementById(
|
||||
'private-window-dialog');
|
||||
this.privateBrowserDialog.removeAttribute('hidden');
|
||||
|
||||
this.privateBrowserDialogClose = document.getElementById(
|
||||
'private-window-hide-dialog');
|
||||
this.privateBrowserDialogClose.addEventListener('click', this);
|
||||
|
||||
this.privateBrowserLearnMore = document.getElementById(
|
||||
'private-learn-more');
|
||||
this.privateBrowserLearnMore.addEventListener('click', this);
|
||||
});
|
||||
}
|
||||
|
||||
if (isPrivateByDefault && isManuallyRegular) {
|
||||
if (themeMeta) {
|
||||
document.head.removeChild(themeMeta);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* General event handler.
|
||||
*/
|
||||
handleEvent: function(e) {
|
||||
switch(e.target) {
|
||||
case this.privateBrowserDialogClose:
|
||||
this.hidePrivateBrowserDialog();
|
||||
break;
|
||||
case this.privateBrowserLearnMore:
|
||||
this.learnAboutPrivateBrowsing(e);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the private browser dialog.
|
||||
*/
|
||||
hidePrivateBrowserDialog: function() {
|
||||
this.privateBrowserDialog.setAttribute('hidden', true);
|
||||
|
||||
// Set async storage value if we have checked the box so we don't
|
||||
// attempt to show it again.
|
||||
var checkbox = this.privateBrowserDialog.querySelector('gaia-checkbox');
|
||||
if (checkbox.checked) {
|
||||
asyncStorage.setItem('shouldSuppressPrivateDialog', true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the learn more dialog.
|
||||
*/
|
||||
learnAboutPrivateBrowsing: function(e) {
|
||||
e.preventDefault();
|
||||
/* jshint nonew: false */
|
||||
new MozActivity({
|
||||
name: 'view',
|
||||
data: {
|
||||
type: 'url',
|
||||
url: e.target.href
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.newtab = new Newtab();
|
||||
|
||||
/**
|
||||
* Stub search object to populate providers.
|
||||
* TODO: We should split up the places provider into some data layer where
|
||||
* the search or newtab page could leverage it.
|
||||
*/
|
||||
exports.Search = {
|
||||
provider: function(provider) {
|
||||
exports.newtab.provider = provider;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a browser to a URL.
|
||||
* @param {String} url The url to navigate to
|
||||
*/
|
||||
navigate: function(url) {
|
||||
new MozActivity({name:'view',data:{type:'url',url:url}});
|
||||
}
|
||||
};
|
||||
|
||||
})(window);
|
||||
180
gaia/apps/search/js/places_idb.js
Executable file
|
|
@ -0,0 +1,180 @@
|
|||
/* globals Promise, asyncStorage, IDBKeyRange */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var DB_NAME = 'places_idb_store';
|
||||
var DB_VERSION = 3;
|
||||
|
||||
var PLACES_STORE = 'places';
|
||||
var VISITS_STORE = 'visits';
|
||||
|
||||
function PlacesIdbStore() {}
|
||||
|
||||
PlacesIdbStore.prototype = {
|
||||
|
||||
db: null,
|
||||
|
||||
latestRevision: null,
|
||||
|
||||
init: function() {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var req = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||
req.onupgradeneeded = self.upgradeSchema;
|
||||
req.onsuccess = function(e) {
|
||||
self.db = e.target.result;
|
||||
asyncStorage.getItem('latest-revision', function(value) {
|
||||
self.latestRevision = value || 0;
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
upgradeSchema: function(e) {
|
||||
var db = e.target.result;
|
||||
var fromVersion = e.oldVersion;
|
||||
|
||||
if (fromVersion < 1) {
|
||||
var places = db.createObjectStore(PLACES_STORE, { keyPath: 'url' });
|
||||
places.createIndex('frecency', 'frecency', { unique: false });
|
||||
places.createIndex('visited', 'visited', { unique: false });
|
||||
}
|
||||
|
||||
if (fromVersion < 2) {
|
||||
asyncStorage.removeItem('latest-revision');
|
||||
var visits = db.createObjectStore(VISITS_STORE, { keyPath: 'date' });
|
||||
visits.createIndex('date', 'date', { unique: true });
|
||||
}
|
||||
|
||||
if (fromVersion < 3) {
|
||||
var oStore = e.target.transaction.objectStore(VISITS_STORE);
|
||||
oStore.createIndex('url', 'url', { unique: false });
|
||||
}
|
||||
},
|
||||
|
||||
add: function(id, data, rev) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db
|
||||
.transaction([PLACES_STORE, VISITS_STORE], 'readwrite');
|
||||
|
||||
if (data.visits && data.visits.length === 0) {
|
||||
// Removed via browsing history clearing, let's remove it.
|
||||
this._removeUrl(id).then(this.saveAndResolve(rev, resolve));
|
||||
} else {
|
||||
txn.objectStore(PLACES_STORE).put(data);
|
||||
|
||||
if (!data.visits) {
|
||||
data.visits = Number.isFinite(data.visited) ? [data.visited] : [];
|
||||
}
|
||||
|
||||
var visitsStore = txn.objectStore(VISITS_STORE);
|
||||
data.visits.filter(Number.isFinite).forEach(function(date) {
|
||||
visitsStore.put({
|
||||
date: date,
|
||||
url: data.url,
|
||||
title: data.title,
|
||||
icons: data.icons
|
||||
});
|
||||
});
|
||||
|
||||
txn.oncomplete = this.saveAndResolve(rev, resolve);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addPlace: function(place) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([PLACES_STORE], 'readwrite');
|
||||
txn.objectStore(PLACES_STORE).put(place);
|
||||
txn.oncomplete = resolve;
|
||||
});
|
||||
},
|
||||
|
||||
remove: function(id, rev) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._removeUrl(id).then(this.saveAndResolve(rev, resolve));
|
||||
});
|
||||
},
|
||||
|
||||
_removeUrl: function(id) {
|
||||
return Promise.all([
|
||||
// Remove the url from places store.
|
||||
new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([PLACES_STORE], 'readwrite');
|
||||
txn.objectStore(PLACES_STORE).delete(id);
|
||||
txn.oncomplete = resolve;
|
||||
}),
|
||||
|
||||
// Clear visits from this url.
|
||||
new Promise((resolve, reject) => {
|
||||
var txn = this.db.transaction([VISITS_STORE], 'readwrite');
|
||||
var visitsStore = txn.objectStore(VISITS_STORE);
|
||||
var urlIndex = visitsStore.index('url');
|
||||
var req = urlIndex.openCursor(IDBKeyRange.only(id));
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
if (cursor) {
|
||||
visitsStore.delete(cursor.primaryKey);
|
||||
cursor.continue();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
clear: function(rev) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var txn = this.db
|
||||
.transaction([PLACES_STORE, VISITS_STORE], 'readwrite');
|
||||
txn.objectStore(PLACES_STORE).clear();
|
||||
txn.objectStore(VISITS_STORE).clear();
|
||||
txn.oncomplete = this.saveAndResolve(rev, resolve);
|
||||
});
|
||||
},
|
||||
|
||||
saveAndResolve: function(rev, resolve) {
|
||||
return function() {
|
||||
asyncStorage.setItem('latest-revision', rev);
|
||||
resolve();
|
||||
};
|
||||
},
|
||||
|
||||
readStore: function(store, index, limit, done, filter) {
|
||||
var results = [];
|
||||
var txn = this.db.transaction(store, 'readonly');
|
||||
var oStore = txn.objectStore(store);
|
||||
|
||||
oStore.index(index).openCursor(null, 'prev').onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
if (!filter || filter(cursor.value)) {
|
||||
results.push(cursor.value);
|
||||
}
|
||||
if (results.length < limit) {
|
||||
cursor.continue();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
txn.oncomplete = function() {
|
||||
done(results);
|
||||
};
|
||||
},
|
||||
|
||||
read: function(index, limit, done, filter) {
|
||||
this.readStore(PLACES_STORE, index, limit, done, filter);
|
||||
},
|
||||
|
||||
readVisits: function(limit, done, filter) {
|
||||
this.readStore(VISITS_STORE, 'date', limit, done, filter);
|
||||
}
|
||||
};
|
||||
|
||||
exports.PlacesIdbStore = PlacesIdbStore;
|
||||
|
||||
})(window);
|
||||
72
gaia/apps/search/js/providers/bookmarks.js
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
/* globals DataGridProvider, GaiaGrid, Search,
|
||||
Promise, SyncDataStore, InMemoryStore */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var STORE_NAME = 'bookmarks_store';
|
||||
|
||||
function formatBookmark(bookmark) {
|
||||
return {
|
||||
data: new GaiaGrid.Bookmark({
|
||||
id: bookmark.url,
|
||||
name: bookmark.name || bookmark.url,
|
||||
url: bookmark.url,
|
||||
icon: bookmark.icon
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function Bookmarks() {}
|
||||
|
||||
Bookmarks.prototype = {
|
||||
|
||||
__proto__: DataGridProvider.prototype,
|
||||
|
||||
name: 'Bookmarks',
|
||||
|
||||
init: function() {
|
||||
DataGridProvider.prototype.init.apply(this, arguments);
|
||||
this.persistStore = new InMemoryStore();
|
||||
this.syncStore = new SyncDataStore(STORE_NAME, this.persistStore, 'url');
|
||||
return this.syncStore.sync();
|
||||
},
|
||||
|
||||
get: function(url) {
|
||||
var results = this.persistStore.results;
|
||||
|
||||
for (var idx in results) {
|
||||
var item = results[idx];
|
||||
if (item.url === url) {
|
||||
return formatBookmark(item);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
search: function(filter) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var renderResults = [];
|
||||
filter = filter.toLowerCase();
|
||||
for(var elem in self.persistStore.results) {
|
||||
var bookmark = self.persistStore.results[elem];
|
||||
var name = ('name' in bookmark) && bookmark.name.toLowerCase();
|
||||
if ((!bookmark.url || bookmark.url.indexOf(filter) == -1) &&
|
||||
(!name || name.indexOf(filter) == -1)) {
|
||||
continue;
|
||||
}
|
||||
renderResults.push(formatBookmark(bookmark));
|
||||
}
|
||||
resolve(renderResults);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.Bookmarks = new Bookmarks();
|
||||
Search.provider(exports.Bookmarks);
|
||||
|
||||
}(window));
|
||||
77
gaia/apps/search/js/providers/contacts.js
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
(function() {
|
||||
|
||||
'use strict';
|
||||
/* global MozActivity, Promise, Provider, Search */
|
||||
|
||||
function Contacts() {
|
||||
}
|
||||
|
||||
Contacts.prototype = {
|
||||
|
||||
__proto__: Provider.prototype,
|
||||
|
||||
name: 'Contacts',
|
||||
|
||||
click: function(e) {
|
||||
var target = e.target;
|
||||
|
||||
Search.close();
|
||||
/* jshint nonew: false */
|
||||
new MozActivity({
|
||||
name: 'open',
|
||||
data: {
|
||||
type: 'webcontacts/contact',
|
||||
params: {
|
||||
'id': target.dataset.contactId
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
search: function(input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var options = {
|
||||
filterValue: input,
|
||||
filterBy: ['givenName'],
|
||||
filterOp: 'startsWith'
|
||||
};
|
||||
|
||||
this.clear();
|
||||
|
||||
var request = navigator.mozContacts.find(options);
|
||||
request.onsuccess = (function() {
|
||||
var results = request.result;
|
||||
if (!results.length) {
|
||||
return reject();
|
||||
}
|
||||
|
||||
var renderResults = [];
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
var result = results[i];
|
||||
for (var j = 0; j < result.name.length; j++) {
|
||||
var renderObj = {
|
||||
title: result.name[j],
|
||||
dataset: {
|
||||
contactId: result.id
|
||||
}
|
||||
};
|
||||
|
||||
if (result.photo) {
|
||||
renderObj.icon = result.photo[0];
|
||||
}
|
||||
|
||||
renderResults.push(renderObj);
|
||||
}
|
||||
}
|
||||
resolve(renderResults);
|
||||
}).bind(this);
|
||||
|
||||
request.onerror = function() {
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Search.provider(new Contacts());
|
||||
|
||||
}());
|
||||
33
gaia/apps/search/js/providers/grid_provider.js
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
/* global Provider */
|
||||
|
||||
'use strict';
|
||||
|
||||
function DataGridProvider() {
|
||||
}
|
||||
|
||||
DataGridProvider.prototype = {
|
||||
|
||||
__proto__: Provider.prototype,
|
||||
|
||||
name: 'DataGridProvider',
|
||||
|
||||
isGridProvider: true,
|
||||
|
||||
init: function() {
|
||||
this.grid = document.getElementById('icons');
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
this.grid.clear();
|
||||
},
|
||||
|
||||
render: function(results) {
|
||||
results.forEach(function(config, index) {
|
||||
this.grid.add(config.data);
|
||||
}, this);
|
||||
|
||||
this.grid.render({
|
||||
skipDivider: true
|
||||
});
|
||||
}
|
||||
};
|
||||
170
gaia/apps/search/js/providers/local_apps.js
Executable file
|
|
@ -0,0 +1,170 @@
|
|||
/* global Search, DataGridProvider, GaiaGrid, Promise */
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
function LocalApps() {
|
||||
this.apps = {};
|
||||
this.appListing = [];
|
||||
this.blacklist;
|
||||
|
||||
var mozApps = navigator.mozApps.mgmt;
|
||||
|
||||
mozApps.oninstall = e => {
|
||||
var app = e.application;
|
||||
var processApp = () => {
|
||||
this.apps[app.manifestURL] = app;
|
||||
this.createAppListing();
|
||||
};
|
||||
|
||||
if (app.downloading) {
|
||||
app.ondownloadapplied = processApp;
|
||||
} else {
|
||||
processApp();
|
||||
}
|
||||
};
|
||||
|
||||
mozApps.onuninstall = e => {
|
||||
delete this.apps[e.application.manifestURL];
|
||||
this.createAppListing();
|
||||
};
|
||||
|
||||
mozApps.getAll().onsuccess = e => {
|
||||
e.target.result.forEach(app => {
|
||||
this.apps[app.manifestURL] = app;
|
||||
});
|
||||
this.createBlacklist().then(() => {
|
||||
this.createAppListing();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
LocalApps.prototype = {
|
||||
|
||||
__proto__: DataGridProvider.prototype,
|
||||
|
||||
name: 'LocalApps',
|
||||
|
||||
dedupes: true,
|
||||
dedupeStrategy: 'exact',
|
||||
|
||||
createBlacklist: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var self = this;
|
||||
var key = 'app.launch_path.blacklist';
|
||||
var req = navigator.mozSettings.createLock().get(key);
|
||||
req.onsuccess = function onsuccess() {
|
||||
self.blacklist = req.result[key] || [];
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
createAppListing: function() {
|
||||
var appListing = [];
|
||||
var blacklist = this.blacklist;
|
||||
|
||||
var manifestURLs = Object.keys(this.apps);
|
||||
|
||||
manifestURLs.forEach(function eachManifest(manifestURL) {
|
||||
var app = this.apps[manifestURL];
|
||||
var manifest = app.manifest || app.updateManifest;
|
||||
|
||||
var HIDDEN_ROLES = [
|
||||
'system', 'input', 'homescreen', 'search', 'theme', 'addon',
|
||||
'langpack'
|
||||
];
|
||||
if (HIDDEN_ROLES.indexOf(manifest.role) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var entryPoints = manifest.entry_points;
|
||||
if (entryPoints) {
|
||||
for (var i in entryPoints) {
|
||||
var entry = entryPoints[i];
|
||||
entry.entryPoint = i;
|
||||
entry.manifestURL = app.manifestURL;
|
||||
appListing.push(entry);
|
||||
}
|
||||
} else {
|
||||
manifest.manifestURL = app.manifestURL;
|
||||
appListing.push(manifest);
|
||||
}
|
||||
|
||||
if (blacklist) {
|
||||
blacklist.forEach(function(blackentry) {
|
||||
for (var i in appListing) {
|
||||
var app = appListing[i];
|
||||
if (app.launch_path === blackentry) {
|
||||
appListing.splice(i, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.appListing = appListing;
|
||||
},
|
||||
|
||||
removeAppListing: function(app) {
|
||||
for (var i in this.appListing) {
|
||||
var manifest = this.appListing[i];
|
||||
if (manifest.manifestURL === app.manifestURL) {
|
||||
this.appListing.splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
search: function(input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var results = this.find(input);
|
||||
var formatted = [];
|
||||
|
||||
results.forEach(function eachResult(result) {
|
||||
formatted.push({
|
||||
dedupeId: result.app.manifestURL,
|
||||
data: new GaiaGrid.Mozapp(result.app, result.entryPoint)
|
||||
});
|
||||
}, this);
|
||||
|
||||
resolve(formatted);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if an app manifest matches a query.
|
||||
*/
|
||||
matches: function(manifest, query) {
|
||||
query = query.toLowerCase();
|
||||
|
||||
// Get the localized name from the query.
|
||||
var shortName = manifest.short_name || '';
|
||||
var userLang = document.documentElement.lang;
|
||||
var locales = manifest.locales;
|
||||
var localized = locales && locales[userLang] && locales[userLang].name;
|
||||
localized = localized || '';
|
||||
|
||||
return shortName.toLowerCase().indexOf(query) != -1 ||
|
||||
manifest.name.toLowerCase().indexOf(query) != -1 ||
|
||||
localized.toLowerCase().indexOf(query) != -1;
|
||||
},
|
||||
|
||||
find: function(query) {
|
||||
var results = [];
|
||||
this.appListing.forEach(function(manifest) {
|
||||
if (this.matches(manifest, query)) {
|
||||
results.push({
|
||||
app: this.apps[manifest.manifestURL],
|
||||
entryPoint: manifest.entryPoint
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
Search.provider(new LocalApps());
|
||||
|
||||
}());
|
||||
85
gaia/apps/search/js/providers/marketplace.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
/* global Search, DataGridProvider, GaiaGrid, Promise */
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
const NUM_DISPLAY = 4;
|
||||
var apiUrl = null;
|
||||
|
||||
function Marketplace() {}
|
||||
|
||||
Marketplace.prototype = {
|
||||
|
||||
__proto__: DataGridProvider.prototype,
|
||||
|
||||
name: 'Marketplace',
|
||||
|
||||
dedupes: true,
|
||||
dedupeStrategy: 'exact',
|
||||
remote: true,
|
||||
|
||||
init: function() {
|
||||
|
||||
DataGridProvider.prototype.init.apply(this, arguments);
|
||||
var urlKey = 'search.marketplace.url';
|
||||
var req = navigator.mozSettings.createLock().get(urlKey);
|
||||
req.onsuccess = function () {
|
||||
if (req.result[urlKey]) {
|
||||
apiUrl = req.result[urlKey]
|
||||
.replace('{limit}', NUM_DISPLAY)
|
||||
.replace('{lang}', document.documentElement.lang);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
search: function(input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.abort();
|
||||
|
||||
if (!input || !apiUrl) {
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', apiUrl.replace('{q}', encodeURIComponent(input)), true);
|
||||
req.onload = (function onload() {
|
||||
var results = JSON.parse(req.responseText);
|
||||
if (!results.length) {
|
||||
resolve([]); return;
|
||||
}
|
||||
|
||||
var length = Math.min(NUM_DISPLAY, results.length);
|
||||
var formatted = [];
|
||||
for (var i = 0; i < length; i++) {
|
||||
var app = results[i];
|
||||
formatted.push({
|
||||
dedupeId: app.manifest_url,
|
||||
data: new GaiaGrid.MarketPlaceApp({
|
||||
id: app.manifest_url,
|
||||
name: navigator.mozL10n.get('install-marketplace-title', {
|
||||
title: app.name
|
||||
}),
|
||||
icons: app.icons,
|
||||
slug: app.slug
|
||||
})
|
||||
});
|
||||
}
|
||||
resolve(formatted);
|
||||
}).bind(this);
|
||||
req.onerror = function onerror() {
|
||||
console.log('Marketplace error.');
|
||||
reject();
|
||||
};
|
||||
req.ontimeout = function ontimeout() {
|
||||
console.log('Marketplace timeout.');
|
||||
reject();
|
||||
};
|
||||
req.send();
|
||||
this.request = req;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Search.provider(new Marketplace());
|
||||
}());
|
||||
400
gaia/apps/search/js/providers/places.js
Executable file
|
|
@ -0,0 +1,400 @@
|
|||
/* globals Provider, SyncDataStore, Promise, IconsHelper */
|
||||
/* globals Search, PlacesIdbStore */
|
||||
/* globals DateHelper */
|
||||
/* globals asyncStorage */
|
||||
/* globals LazyLoader */
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var _ = navigator.mozL10n.get;
|
||||
|
||||
// Maximum number of results to show show for a single query
|
||||
var MAX_AWESOME_RESULTS = 4;
|
||||
var MAX_HISTORY_RESULTS = 20;
|
||||
var MAX_TOPSITES_RESULTS = 6;
|
||||
|
||||
// Name of the datastore we pick up places from
|
||||
var STORE_NAME = 'places';
|
||||
|
||||
var topSitesWrapper = document.getElementById('top-sites');
|
||||
var historyWrapper = document.getElementById('history');
|
||||
|
||||
// These elements are only included in the newtab page.
|
||||
if (topSitesWrapper && historyWrapper) {
|
||||
topSitesWrapper.addEventListener('click', itemClicked);
|
||||
historyWrapper.addEventListener('click', itemClicked);
|
||||
}
|
||||
|
||||
var cachedLink = document.createElement('a');
|
||||
function parseUrl(url) {
|
||||
cachedLink.href = url;
|
||||
return cachedLink;
|
||||
}
|
||||
|
||||
// Storage for locally fetched icons
|
||||
var icons = {};
|
||||
var iconUrls = {};
|
||||
|
||||
function getIcon(place) {
|
||||
|
||||
if (place.url in icons && icons[place.url]) {
|
||||
return icons[place.url];
|
||||
}
|
||||
|
||||
IconsHelper.getIcon(place.url, null, place).then(icon => {
|
||||
saveIcon(place.url, icon);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function saveIcon(key, url) {
|
||||
if (key in icons) {
|
||||
return;
|
||||
}
|
||||
icons[key] = null;
|
||||
iconUrls[key] = url;
|
||||
fetchIcon(url, function(err, icon) {
|
||||
if (!err) {
|
||||
icons[key] = icon;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchIcon(uri, callback) {
|
||||
var xhr = new XMLHttpRequest({mozSystem: true});
|
||||
xhr.open('GET', uri, true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.addEventListener('load', function() {
|
||||
// Check icon was successfully downloded
|
||||
// 0 is due to https://bugzilla.mozilla.org/show_bug.cgi?id=716491
|
||||
if (!(xhr.status === 200 || xhr.status === 0)) {
|
||||
return callback(new Error('error_downloading'));
|
||||
}
|
||||
|
||||
var blob = xhr.response;
|
||||
|
||||
if (blob.size > this.MAX_ICON_SIZE) {
|
||||
return callback(new Error('image_to_large'));
|
||||
}
|
||||
|
||||
// Only save the icon if it can be loaded as an image bigger than 0px
|
||||
var img = document.createElement('img');
|
||||
img.src = window.URL.createObjectURL(blob);
|
||||
|
||||
img.onload = function() {
|
||||
window.URL.revokeObjectURL(img.src);
|
||||
if (img.naturalWidth <= 0) {
|
||||
return callback(new Error('Cannot load image'));
|
||||
}
|
||||
callback(null, blob);
|
||||
};
|
||||
|
||||
img.onerror = function() {
|
||||
window.URL.revokeObjectURL(img.src);
|
||||
return callback(new Error('Cannot load image'));
|
||||
};
|
||||
});
|
||||
|
||||
xhr.onerror = function(err) {
|
||||
return callback(new Error('Cannot load uri'));
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function itemClicked(e) {
|
||||
if (e.target.dataset.url) {
|
||||
Search.navigate(e.target.dataset.url);
|
||||
}
|
||||
}
|
||||
|
||||
var listTemplate = createList();
|
||||
|
||||
function createList() {
|
||||
var list = document.createElement('ul');
|
||||
list.setAttribute('role', 'listbox');
|
||||
return list;
|
||||
}
|
||||
|
||||
function incrementHistoryThreshold(timestamp, currentThreshold, thresholds) {
|
||||
var newThreshold = currentThreshold += 1;
|
||||
if (timestamp < thresholds[newThreshold]) {
|
||||
return incrementHistoryThreshold(timestamp, newThreshold, thresholds);
|
||||
}
|
||||
return newThreshold;
|
||||
}
|
||||
|
||||
function drawHistoryHeading(parent, threshold, timestamp) {
|
||||
|
||||
var LABELS = [
|
||||
'future',
|
||||
'today',
|
||||
'yesterday',
|
||||
'last-7-days',
|
||||
'this-month',
|
||||
'last-6-months',
|
||||
'older-than-6-months'
|
||||
];
|
||||
|
||||
var text = '';
|
||||
|
||||
// Special case for month headings
|
||||
if (threshold == 5 && timestamp) {
|
||||
var date = new Date(timestamp);
|
||||
var now = new Date();
|
||||
text = _('month-' + date.getMonth());
|
||||
if (date.getFullYear() != now.getFullYear()) {
|
||||
text += ' ' + date.getFullYear();
|
||||
}
|
||||
} else {
|
||||
text = _(LABELS[threshold]);
|
||||
}
|
||||
|
||||
var h3 = document.createElement('h3');
|
||||
var spanNode = document.createElement('span');
|
||||
spanNode.textContent = text;
|
||||
var ul = listTemplate.cloneNode(true);
|
||||
h3.appendChild(spanNode);
|
||||
parent.appendChild(h3);
|
||||
parent.appendChild(ul);
|
||||
}
|
||||
|
||||
function updateIcon(visit, iconWrapper) {
|
||||
var iconDom = iconWrapper.querySelector('img');
|
||||
IconsHelper.getIcon(visit.url, null, visit).then((icon) => {
|
||||
if (icon && iconDom) {
|
||||
iconDom.onload = function () {
|
||||
iconWrapper.classList.remove('empty');
|
||||
iconDom.style.display = 'block';
|
||||
};
|
||||
iconDom.src = icon;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildHistory(visits) {
|
||||
|
||||
var thresholds = [
|
||||
Date.now(), // 0. Now
|
||||
DateHelper.todayStarted(), // 1. Today
|
||||
DateHelper.yesterdayStarted(), // 2. Yesterday
|
||||
DateHelper.thisWeekStarted(), // 3. This week
|
||||
DateHelper.thisMonthStarted(), // 4. This month
|
||||
DateHelper.lastSixMonthsStarted(), // 5. Six months
|
||||
0 // 6. Epoch!
|
||||
];
|
||||
|
||||
var threshold = 0;
|
||||
var month = null;
|
||||
var year = null;
|
||||
|
||||
var fragment = document.createDocumentFragment();
|
||||
|
||||
visits.forEach(function(visit) {
|
||||
// Draw new heading if new threshold reached
|
||||
if (visit.date > 0 && visit.date < thresholds[threshold]) {
|
||||
threshold = incrementHistoryThreshold(visit.date,
|
||||
threshold, thresholds);
|
||||
// Special case for month headings
|
||||
if (threshold != 5) {
|
||||
drawHistoryHeading(fragment, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
if (threshold === 5) {
|
||||
var timestampDate = new Date(visit.date);
|
||||
if (timestampDate.getMonth() != month ||
|
||||
timestampDate.getFullYear() != year) {
|
||||
month = timestampDate.getMonth();
|
||||
year = timestampDate.getFullYear();
|
||||
drawHistoryHeading(fragment, threshold, visit.date);
|
||||
}
|
||||
}
|
||||
|
||||
visit.meta = visit.url;
|
||||
visit.dataset = { url: visit.url };
|
||||
var dom = exports.Places.buildResultsDom([visit]);
|
||||
var iconDom = dom.querySelector('.icon');
|
||||
fragment.appendChild(dom);
|
||||
updateIcon(visit, iconDom);
|
||||
});
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function showStartPage() {
|
||||
|
||||
if (!topSitesWrapper || !historyWrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
var store = exports.Places.persistStore;
|
||||
|
||||
var urls = [];
|
||||
store.readVisits(MAX_HISTORY_RESULTS, function(results) {
|
||||
var docFragment = buildHistory(results);
|
||||
historyWrapper.innerHTML = '';
|
||||
historyWrapper.appendChild(docFragment);
|
||||
}, function filter(visit) {
|
||||
var isStored = visit.url in urls;
|
||||
urls[visit.url] = true;
|
||||
return !isStored;
|
||||
});
|
||||
|
||||
store.read('frecency', MAX_TOPSITES_RESULTS, function(results) {
|
||||
var docFragment = document.createDocumentFragment();
|
||||
results.forEach(function(x) {
|
||||
docFragment.appendChild(formatTopResult(x));
|
||||
});
|
||||
topSitesWrapper.innerHTML = '';
|
||||
topSitesWrapper.appendChild(docFragment);
|
||||
});
|
||||
}
|
||||
|
||||
function formatTopResult(result) {
|
||||
var div = document.createElement('div');
|
||||
var span = document.createElement('span');
|
||||
if (result.title) {
|
||||
span.setAttribute('dir', 'auto');
|
||||
span.textContent = result.title;
|
||||
} else {
|
||||
span.setAttribute('dir', 'ltr');
|
||||
span.textContent = result.url;
|
||||
}
|
||||
div.dataset.url = result.url;
|
||||
div.classList.add('top-site');
|
||||
div.appendChild(span);
|
||||
div.setAttribute('role', 'link');
|
||||
|
||||
if (result.screenshot || result.tile) {
|
||||
var img = result.screenshot || result.tile;
|
||||
var imgUrl = (typeof img === 'string') ? img : URL.createObjectURL(img);
|
||||
div.style.backgroundImage = 'url(' + imgUrl + ')';
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function matchesFilter(value, filter) {
|
||||
return !filter || (value && value.match(new RegExp(filter, 'i')) !== null);
|
||||
}
|
||||
|
||||
function formatPlace(placeObj, filter) {
|
||||
|
||||
var result = {
|
||||
'title': placeObj.title,
|
||||
'meta': placeObj.url,
|
||||
'icon': getIcon(placeObj),
|
||||
'dataset': {
|
||||
'url': placeObj.url
|
||||
},
|
||||
'label': placeObj.title
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function Places() {}
|
||||
|
||||
Places.prototype = {
|
||||
|
||||
__proto__: Provider.prototype,
|
||||
|
||||
name: 'Places',
|
||||
|
||||
click: itemClicked,
|
||||
|
||||
init: function() {
|
||||
|
||||
// If not on new tab page, set places div as container
|
||||
if (!topSitesWrapper || !historyWrapper) {
|
||||
this.header = document.getElementById(this.name.toLowerCase() +
|
||||
'-header');
|
||||
this.container = document.getElementById(this.name.toLowerCase());
|
||||
this.container.addEventListener('click', this.click.bind(this));
|
||||
}
|
||||
|
||||
this.persistStore = new PlacesIdbStore();
|
||||
|
||||
this.persistStore.init().then(() => {
|
||||
|
||||
this.syncStore =
|
||||
new SyncDataStore(STORE_NAME, this.persistStore, 'url');
|
||||
|
||||
this.syncStore.filter = function(place) {
|
||||
return place.url.startsWith('app://') ||
|
||||
place.url === 'about:blank';
|
||||
};
|
||||
this.syncStore.onChange = function() {
|
||||
showStartPage();
|
||||
};
|
||||
// Make init return a promise, so we know when
|
||||
// we did the sync. Used right now for testing
|
||||
// porpoises.
|
||||
var rev = this.persistStore.latestRevision || 0;
|
||||
return this.syncStore.sync(rev).then(() => {
|
||||
return new Promise(resolve => {
|
||||
|
||||
function done() {
|
||||
showStartPage();
|
||||
resolve();
|
||||
}
|
||||
|
||||
asyncStorage.getItem('have-preloaded-sites', (havePreloaded) => {
|
||||
if (!havePreloaded) {
|
||||
this.preloadTopSites().then(() => {
|
||||
asyncStorage.setItem('have-preloaded-sites', true);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
saveSites: function(sites) {
|
||||
return Promise.all(sites.map(site => {
|
||||
site.frecency = -2;
|
||||
return this.persistStore.addPlace(site);
|
||||
}));
|
||||
},
|
||||
|
||||
preloadTopSites: function() {
|
||||
// load default build top sites
|
||||
return LazyLoader.getJSON('/js/inittopsites.json').then(sites => {
|
||||
return this.saveSites(sites);
|
||||
});
|
||||
},
|
||||
|
||||
search: function(filter) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var matchedOrigins = {};
|
||||
this.persistStore.read('frecency', MAX_AWESOME_RESULTS, (results) => {
|
||||
resolve(results.map(function(result) {
|
||||
return formatPlace(result, filter);
|
||||
}));
|
||||
}, function filterFun(result) {
|
||||
if (result.frecency <= 0) {
|
||||
return false;
|
||||
}
|
||||
var url = parseUrl(result.url);
|
||||
var matches = !(url.hostname in matchedOrigins) &&
|
||||
(matchesFilter(result.title, filter) ||
|
||||
matchesFilter(result.url, filter));
|
||||
if (matches) {
|
||||
matchedOrigins[url.hostname] = true;
|
||||
}
|
||||
return matches;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.Places = new Places();
|
||||
Search.provider(exports.Places);
|
||||
|
||||
}(window));
|
||||
168
gaia/apps/search/js/providers/provider.js
Executable file
|
|
@ -0,0 +1,168 @@
|
|||
/* globals IconsHelper */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base Provider class
|
||||
*/
|
||||
function Provider() {
|
||||
}
|
||||
|
||||
Provider.prototype = {
|
||||
|
||||
/**
|
||||
* Name of the provider
|
||||
* Overridden at the child provider level
|
||||
*/
|
||||
name: 'Provider',
|
||||
|
||||
/**
|
||||
* Whether or not this provider dedupes results.
|
||||
*/
|
||||
dedupes: false,
|
||||
|
||||
/**
|
||||
* Initializes the provider container and adds listeners
|
||||
*/
|
||||
init: function(searchObj) {
|
||||
this.searchObj = searchObj;
|
||||
this.container = document.getElementById(this.name.toLowerCase());
|
||||
this.container.addEventListener('click', this.click.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the rendered results of this provider from the app grid
|
||||
*/
|
||||
clear: function() {
|
||||
this.container.innerHTML = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler when a result is clicked
|
||||
*/
|
||||
click: function() {},
|
||||
|
||||
/**
|
||||
* Aborts any in-progress request.
|
||||
*/
|
||||
abort: function() {
|
||||
if (this.request && this.request.abort) {
|
||||
this.request.abort();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an icon once it has been loaded.
|
||||
*
|
||||
* @param {Object} config A configuration object.
|
||||
* @param {Object} iconWrapper An iconWrapper DOM object.
|
||||
*/
|
||||
updateIcon: function(config, iconWrapper) {
|
||||
var iconDom = iconWrapper.querySelector('img');
|
||||
IconsHelper.getIcon(config.dataset.url, null, config).then((icon) => {
|
||||
if (icon && iconDom) {
|
||||
iconDom.onload = function () {
|
||||
iconWrapper.classList.remove('empty');
|
||||
iconDom.style.display = 'block';
|
||||
};
|
||||
iconDom.src = icon;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders a set of results.
|
||||
* Each result may contain the following attributes:
|
||||
* - title: The title of the app.
|
||||
* - meta: Secondary content to show for the result.
|
||||
* - icon: The icon of the result.
|
||||
* - dataset: Data attributes to apply to the result.
|
||||
* - label: Aria-label for the result.
|
||||
* - description: Additional description for the result.
|
||||
*/
|
||||
buildResultsDom: function(results) {
|
||||
//<div class="result" data-url="mozilla.com" role="link"
|
||||
// aria-label="My Urlasljd alskdja lsdjka sldjk"
|
||||
// aria-describedby="description-0">
|
||||
// <div class="icon">
|
||||
// <img role="presentation" src="..." />
|
||||
// </div>
|
||||
// <div class="urlwrapper">
|
||||
// <span class="title">My Urlasljd <span class="highlight">alskd</span>
|
||||
// ja lsdjka sldjk</span>
|
||||
// <small id="description-0" class="url"
|
||||
// aria-label="http://url.com">http://url.com</small>
|
||||
// </div>
|
||||
//</div>
|
||||
|
||||
var frag = document.createDocumentFragment();
|
||||
results.forEach(function(config, index) {
|
||||
|
||||
var result = document.createElement('div');
|
||||
var iconWrapper = document.createElement('div');
|
||||
var icon = document.createElement('img');
|
||||
var description = document.createElement('div');
|
||||
var title = document.createElement('span');
|
||||
var meta = document.createElement('small');
|
||||
|
||||
result.classList.add('result');
|
||||
iconWrapper.classList.add('icon');
|
||||
description.classList.add('description');
|
||||
title.classList.add('title');
|
||||
meta.classList.add('meta');
|
||||
|
||||
for (var i in config.dataset) {
|
||||
result.dataset[i] = config.dataset[i];
|
||||
}
|
||||
|
||||
if (config.title) {
|
||||
title.setAttribute('dir', 'auto');
|
||||
title.textContent = config.title;
|
||||
} else {
|
||||
title.setAttribute('dir', 'ltr');
|
||||
title.textContent = config.url;
|
||||
}
|
||||
|
||||
if (config.meta) {
|
||||
meta.textContent = config.meta;
|
||||
// Expose meta infrormation as a helpful description for each result.
|
||||
if (config.description) {
|
||||
meta.id = this.name + '-description-' + index;
|
||||
meta.setAttribute('aria-label', config.description);
|
||||
result.setAttribute('aria-describedby', meta.id);
|
||||
}
|
||||
}
|
||||
|
||||
icon.setAttribute('role', 'presentation');
|
||||
if (config.icon) {
|
||||
icon.src = window.URL.createObjectURL(config.icon);
|
||||
} else {
|
||||
iconWrapper.classList.add('empty');
|
||||
}
|
||||
|
||||
result.setAttribute('role', 'link');
|
||||
// Either use an explicit label or, if not present, title.
|
||||
result.setAttribute('aria-label', config.label || config.title);
|
||||
|
||||
description.appendChild(title);
|
||||
description.appendChild(meta);
|
||||
iconWrapper.appendChild(icon);
|
||||
result.appendChild(iconWrapper);
|
||||
result.appendChild(description);
|
||||
frag.appendChild(result);
|
||||
|
||||
if (!config.icon) {
|
||||
this.updateIcon(config, iconWrapper);
|
||||
}
|
||||
}, this);
|
||||
return frag;
|
||||
},
|
||||
|
||||
render: function(results) {
|
||||
var dom = this.buildResultsDom(results);
|
||||
this.container.appendChild(dom);
|
||||
if (this.header) {
|
||||
results.length ? this.header.classList.remove('hidden') :
|
||||
this.header.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
140
gaia/apps/search/js/providers/suggestions.js
Executable file
|
|
@ -0,0 +1,140 @@
|
|||
(function() {
|
||||
|
||||
'use strict';
|
||||
/* globals Promise, Provider, Search, MetricsHelper */
|
||||
/* globals LazyLoader */
|
||||
/* globals SearchProvider */
|
||||
|
||||
var suggestionsWrapper = document.getElementById('suggestions-wrapper');
|
||||
var suggestionsProvider = document.getElementById('suggestions-provider');
|
||||
var suggestionsSelect = document.getElementById('suggestions-select');
|
||||
|
||||
function providerUpdated() {
|
||||
|
||||
navigator.mozL10n.setAttributes(suggestionsProvider, 'search-header', {
|
||||
provider: SearchProvider('title').toUpperCase()
|
||||
});
|
||||
|
||||
var providers = SearchProvider.providers();
|
||||
var selectFragment = document.createDocumentFragment();
|
||||
var optionNode = document.createElement('option');
|
||||
|
||||
Object.keys(providers).forEach(provider => {
|
||||
var option = optionNode.cloneNode();
|
||||
option.value = provider;
|
||||
option.text = providers[provider].title;
|
||||
if (provider === SearchProvider.selected()) {
|
||||
option.selected = true;
|
||||
}
|
||||
selectFragment.appendChild(option);
|
||||
});
|
||||
|
||||
suggestionsSelect.innerHTML = '';
|
||||
suggestionsSelect.appendChild(selectFragment);
|
||||
}
|
||||
|
||||
function encodeTerms(str, search) {
|
||||
return str.replace('{searchTerms}', encodeURIComponent(search));
|
||||
}
|
||||
|
||||
function Suggestions() {}
|
||||
|
||||
Suggestions.prototype = {
|
||||
|
||||
__proto__: Provider.prototype,
|
||||
|
||||
name: 'Suggestions',
|
||||
|
||||
currentSearch: null,
|
||||
|
||||
init: function() {
|
||||
this.metrics = new MetricsHelper();
|
||||
this.metrics.init();
|
||||
|
||||
Provider.prototype.init.apply(this, arguments);
|
||||
|
||||
suggestionsSelect.addEventListener('change', function(e) {
|
||||
SearchProvider.setProvider(e.target.value);
|
||||
});
|
||||
|
||||
SearchProvider.ready().then(() => {
|
||||
providerUpdated();
|
||||
SearchProvider.providerUpdated(() => {
|
||||
providerUpdated();
|
||||
this.redoSearch();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
redoSearch: function() {
|
||||
if (!this.currentSearch) {
|
||||
return;
|
||||
}
|
||||
this.search(this.currentSearch).then(results => {
|
||||
this.render(results);
|
||||
});
|
||||
},
|
||||
|
||||
click: function(e) {
|
||||
this.metrics.report('websearch', SearchProvider('title'));
|
||||
var suggestion = e.target.dataset.suggestion;
|
||||
var url = encodeTerms(SearchProvider('searchUrl'), suggestion);
|
||||
Search.navigate(url);
|
||||
},
|
||||
|
||||
search: function(input, preventRemote) {
|
||||
|
||||
this.currentSearch = preventRemote ? null : input;
|
||||
this.render([input]);
|
||||
|
||||
if (!navigator.onLine || preventRemote) {
|
||||
return Promise.resolve([input]);
|
||||
}
|
||||
|
||||
suggestionsWrapper.dataset.loading = true;
|
||||
|
||||
return SearchProvider.ready().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var url = encodeTerms(SearchProvider('suggestUrl'), input);
|
||||
LazyLoader.getJSON(url, true).then(result => {
|
||||
var results = result[1];
|
||||
// We add an item to search the entered term as well
|
||||
results.unshift(input);
|
||||
resolve(results);
|
||||
}).catch(function() {
|
||||
suggestionsWrapper.dataset.loading = false;
|
||||
reject();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function(items) {
|
||||
var ul = document.createElement('ul');
|
||||
ul.setAttribute('role', 'listbox');
|
||||
|
||||
items.forEach(function each(item) {
|
||||
var li = document.createElement('li');
|
||||
li.dataset.suggestion = li.textContent = item;
|
||||
li.setAttribute('role', 'option');
|
||||
ul.appendChild(li);
|
||||
});
|
||||
|
||||
suggestionsWrapper.dataset.loading = false;
|
||||
this.container.innerHTML = '';
|
||||
|
||||
if (ul.childNodes.length) {
|
||||
this.container.appendChild(ul);
|
||||
}
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
Provider.prototype.clear.apply(this, arguments);
|
||||
this.currentSearch = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Search.provider(new Suggestions());
|
||||
|
||||
}());
|
||||
398
gaia/apps/search/js/search.js
Executable file
|
|
@ -0,0 +1,398 @@
|
|||
(function() {
|
||||
|
||||
'use strict';
|
||||
/* global asyncStorage */
|
||||
/* global Contextmenu */
|
||||
/* global Search */
|
||||
/* global SearchDedupe */
|
||||
/* global SettingsListener */
|
||||
/* global UrlHelper */
|
||||
/* global SearchProvider */
|
||||
/* global MetricsHelper */
|
||||
/* global MozActivity */
|
||||
|
||||
// timeout before notifying providers
|
||||
var SEARCH_DELAY = 500;
|
||||
var MAX_GRID_SIZE = 4;
|
||||
|
||||
window.Search = {
|
||||
|
||||
_port: null,
|
||||
|
||||
providers: {},
|
||||
|
||||
gridCount: 0,
|
||||
|
||||
searchResults: document.getElementById('search-results'),
|
||||
|
||||
offlineMessage: document.getElementById('offline-message'),
|
||||
suggestionsWrapper: document.getElementById('suggestions-wrapper'),
|
||||
grid: document.getElementById('icons'),
|
||||
gridWrapper: document.getElementById('icons-wrapper'),
|
||||
|
||||
suggestionsEnabled: false,
|
||||
|
||||
/**
|
||||
* Used to display a notice on how to configure the search provider
|
||||
* on first use
|
||||
*/
|
||||
suggestionNotice: document.getElementById('suggestions-notice-wrapper'),
|
||||
get settingsLink() {
|
||||
return document.getElementById('settings-link');
|
||||
},
|
||||
|
||||
toShowNotice: null,
|
||||
NOTICE_KEY: 'notice-shown',
|
||||
|
||||
init: function() {
|
||||
|
||||
this.dedupe = new SearchDedupe();
|
||||
|
||||
this.metrics = new MetricsHelper();
|
||||
this.metrics.init();
|
||||
|
||||
// Initialize the parent port connection
|
||||
var self = this;
|
||||
navigator.mozApps.getSelf().onsuccess = function() {
|
||||
var app = this.result;
|
||||
app.connect('search-results').then(function onConnAccepted(ports) {
|
||||
ports.forEach(function(port) {
|
||||
self._port = port;
|
||||
});
|
||||
setConnectionHandler();
|
||||
}, function onConnectionRejected(reason) {
|
||||
console.log('Error connecting: ' + reason + '\n');
|
||||
});
|
||||
};
|
||||
|
||||
function setConnectionHandler() {
|
||||
navigator.mozSetMessageHandler('connection',
|
||||
function(connectionRequest) {
|
||||
var keyword = connectionRequest.keyword;
|
||||
var port = connectionRequest.port;
|
||||
if (keyword === 'search') {
|
||||
port.onmessage = self.dispatchMessage.bind(self);
|
||||
port.start();
|
||||
}
|
||||
});
|
||||
initializeProviders();
|
||||
}
|
||||
|
||||
function initializeProviders() {
|
||||
for (var i in self.providers) {
|
||||
self.providers[i].init(self);
|
||||
}
|
||||
}
|
||||
|
||||
var enabledKey = 'search.suggestions.enabled';
|
||||
SettingsListener.observe(enabledKey, true, function(enabled) {
|
||||
this.suggestionsEnabled = enabled;
|
||||
}.bind(this));
|
||||
|
||||
this.initNotice();
|
||||
this.initConnectivityCheck();
|
||||
|
||||
this.contextmenu = new Contextmenu();
|
||||
window.addEventListener('resize', this.resize);
|
||||
window.addEventListener('scroll', this.onScroll);
|
||||
navigator.mozSetMessageHandler('activity',
|
||||
this.handleActivityEvents.bind(this));
|
||||
},
|
||||
|
||||
resize: function() {
|
||||
if (this.grid && this.grid.render) {
|
||||
this.grid.render({
|
||||
rerender: true,
|
||||
skipDivider: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Typically an input keeps focus when the user scrolls, here we
|
||||
// want to grab focus and manually dismiss the keyboard.
|
||||
onScroll: function() {
|
||||
window.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a search provider
|
||||
*/
|
||||
provider: function(provider) {
|
||||
if (!(provider.name in this.providers)) {
|
||||
this.providers[provider.name] = provider;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispatches messages to handlers in the Search class
|
||||
*/
|
||||
dispatchMessage: function(msg) {
|
||||
if (typeof this[msg.data.action] === 'function') {
|
||||
this[msg.data.action](msg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the user changes the search query
|
||||
*/
|
||||
change: function(msg) {
|
||||
|
||||
clearTimeout(this.changeTimeout);
|
||||
|
||||
this.showSearchResults();
|
||||
|
||||
var input = msg.data.input;
|
||||
var providers = this.providers;
|
||||
|
||||
this.changeTimeout = setTimeout(() => {
|
||||
this.clear();
|
||||
this.dedupe.reset();
|
||||
|
||||
Object.keys(providers).forEach((providerKey) => {
|
||||
var provider = providers[providerKey];
|
||||
|
||||
var preventRemoteFetch =
|
||||
UrlHelper.isURL(input) ||
|
||||
msg.data.isPrivateBrowser ||
|
||||
!this.suggestionsEnabled;
|
||||
|
||||
if (provider.remote && preventRemoteFetch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (provider.name === 'Suggestions') {
|
||||
var toShow = input.length > 2 &&
|
||||
this.toShowNotice &&
|
||||
this.suggestionsEnabled &&
|
||||
this.suggestionNotice.hidden &&
|
||||
navigator.onLine;
|
||||
if (toShow) {
|
||||
this.suggestionNotice.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
provider.search(input, preventRemoteFetch).then((results) => {
|
||||
this.collect(provider, results);
|
||||
}, error => { if (error) console.warn('Search provider ' + provider.name + ': ' + error); });
|
||||
});
|
||||
}, SEARCH_DELAY);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a notice to the user informaing them of how to configure
|
||||
* search providers, should only be shown once.
|
||||
*/
|
||||
initNotice: function() {
|
||||
|
||||
var confirm = document.getElementById('suggestions-notice-confirm');
|
||||
confirm.addEventListener('click', this.discardNotice.bind(this, true));
|
||||
|
||||
var settingsLink = this.settingsLink;
|
||||
if (settingsLink) {
|
||||
settingsLink
|
||||
.addEventListener('click', this.openSettings.bind(this));
|
||||
}
|
||||
|
||||
asyncStorage.getItem(this.NOTICE_KEY, function(value) {
|
||||
if (this.toShowNotice === null) {
|
||||
this.toShowNotice = !value;
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
openSettings: function() {
|
||||
this.discardNotice();
|
||||
/* jshint nonew: false */
|
||||
new MozActivity({
|
||||
name: 'configure',
|
||||
data: {
|
||||
target: 'device',
|
||||
section: 'search'
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
discardNotice: function(focus) {
|
||||
this.suggestionNotice.hidden = true;
|
||||
this.toShowNotice = false;
|
||||
asyncStorage.setItem(this.NOTICE_KEY, true);
|
||||
if (focus) {
|
||||
this._port.postMessage({'action': 'focus'});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Expands the search experience when the user taps on a suggestion
|
||||
* or submits a query.
|
||||
*/
|
||||
expandSearch: function(query) {
|
||||
this.clear();
|
||||
this.providers.WebResults.fullscreen(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Collects results from a search provider.
|
||||
* If the provider de-duplicates results, filter through them
|
||||
* and dedupe them. Otherwise, render.
|
||||
* @param {Array} results The results of the provider search.
|
||||
*/
|
||||
collect: function(provider, results) {
|
||||
|
||||
if (provider.dedupes) {
|
||||
results = this.dedupe.reduce(results, provider.dedupeStrategy);
|
||||
}
|
||||
|
||||
if (provider.isGridProvider &&
|
||||
(results.length + this.gridCount) > MAX_GRID_SIZE) {
|
||||
var spaces = MAX_GRID_SIZE - this.gridCount;
|
||||
if (spaces < 1) {
|
||||
this.abort();
|
||||
return;
|
||||
}
|
||||
results.splice(spaces, (results.length - spaces));
|
||||
}
|
||||
|
||||
if (provider.isGridProvider) {
|
||||
this.gridCount += results.length;
|
||||
}
|
||||
|
||||
this.gridWrapper.classList.toggle('hidden', !this.gridCount);
|
||||
provider.render(results);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the user trigger a search activity
|
||||
*/
|
||||
handleActivityEvents: function(activity) {
|
||||
var activityName = activity.source.name;
|
||||
if (activityName === 'search') {
|
||||
this.submit({data: {
|
||||
input: activity.source.data.keyword
|
||||
}});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the user submits the search form
|
||||
*/
|
||||
submit: function(msg) {
|
||||
|
||||
this.discardNotice();
|
||||
|
||||
var input = msg.data.input;
|
||||
|
||||
// Not a valid URL, could be a search term
|
||||
if (UrlHelper.isNotURL(input)) {
|
||||
this.metrics.report('websearch', SearchProvider('title'));
|
||||
|
||||
var url = SearchProvider('searchUrl')
|
||||
.replace('{searchTerms}', encodeURIComponent(input));
|
||||
this.navigate(url);
|
||||
return;
|
||||
}
|
||||
|
||||
var hasScheme = UrlHelper.hasScheme(input);
|
||||
|
||||
// No scheme, prepend basic protocol and return
|
||||
if (!hasScheme) {
|
||||
input = 'http://' + input;
|
||||
}
|
||||
|
||||
this.navigate(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear results from each provider.
|
||||
*/
|
||||
clear: function(msg) {
|
||||
this.abort();
|
||||
for (var i in this.providers) {
|
||||
this.providers[i].clear();
|
||||
}
|
||||
this.gridCount = 0;
|
||||
this.suggestionNotice.hidden = true;
|
||||
},
|
||||
|
||||
showSearchResults: function() {
|
||||
if (this.searchResults) {
|
||||
this.searchResults.classList.remove('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Aborts all in-progress provider requests.
|
||||
*/
|
||||
abort: function() {
|
||||
clearTimeout(this.changeTimeout);
|
||||
for (var i in this.providers) {
|
||||
this.providers[i].abort();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Messages the parent container to close
|
||||
*/
|
||||
close: function() {
|
||||
this.abort();
|
||||
this._port.postMessage({'action': 'hide'});
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a browser to a URL
|
||||
* @param {String} url The url to navigate to
|
||||
*/
|
||||
navigate: function(url) {
|
||||
new MozActivity({name:'view',data:{type:'url',url:url}});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to the system app to update the input value
|
||||
*/
|
||||
setInput: function(input) {
|
||||
this._port.postMessage({
|
||||
'action': 'input',
|
||||
'input': input
|
||||
});
|
||||
this.expandSearch(input);
|
||||
},
|
||||
|
||||
initConnectivityCheck: function() {
|
||||
var self = this;
|
||||
function onConnectivityChange() {
|
||||
if (navigator.onLine) {
|
||||
self.searchResults.classList.remove('offline');
|
||||
} else {
|
||||
self.searchResults.classList.add('offline');
|
||||
}
|
||||
}
|
||||
|
||||
this.offlineMessage.addEventListener(
|
||||
'click', function() {
|
||||
var activity = new window.MozActivity({
|
||||
name: 'configure',
|
||||
data: {
|
||||
target: 'device',
|
||||
section: 'root',
|
||||
filterBy: 'connectivity'
|
||||
}
|
||||
});
|
||||
activity.onsuccess = function() {
|
||||
/*
|
||||
XXX: Since this activity inmediately returns
|
||||
success, we cannot go back to the search bar.
|
||||
Keeping a reference of the activity once this
|
||||
is fixed.
|
||||
*/
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
window.addEventListener('offline', onConnectivityChange);
|
||||
window.addEventListener('online', onConnectivityChange);
|
||||
onConnectivityChange();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('load', Search.init.bind(Search));
|
||||
|
||||
})();
|
||||
18
gaia/apps/search/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "search",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["apps/search/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/search"
|
||||
}
|
||||
}
|
||||
48
gaia/apps/search/locales/search.en-US.properties
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
# LOCALIZATION NOTE {{title}} is name of a Marketplace Application to be installed
|
||||
install-marketplace-title = Install {{ title }}
|
||||
history = HISTORY
|
||||
top-sites = TOP SITES
|
||||
suggestions.ariaLabel = Suggestions
|
||||
contacts.ariaLabel = Contacts
|
||||
places.ariaLabel = Places
|
||||
apps.ariaLabel = Apps
|
||||
|
||||
# LOCALIZATION NOTE Search and Settings are names of applications. In this case, there is a section titled 'Search' within the Settings application.
|
||||
suggestions-settings-notice = Search suggestions are enabled. The characters you type when entering a search are being sent to the search engine. You can disable search suggestions in the device <span>Settings</span>.
|
||||
ok = OK
|
||||
|
||||
add-to-home-screen.label = Add to Home Screen
|
||||
|
||||
offline-message = No internet connection
|
||||
|
||||
# History headings
|
||||
future=FUTURE
|
||||
today=TODAY
|
||||
yesterday=YESTERDAY
|
||||
last-7-days=LAST 7 DAYS
|
||||
this-month=THIS MONTH
|
||||
last-6-months=LAST 6 MONTHS
|
||||
older-than-6-months=OLDER THAN 6 MONTHS
|
||||
|
||||
# Months
|
||||
month-0=JANUARY
|
||||
month-1=FEBRUARY
|
||||
month-2=MARCH
|
||||
month-3=APRIL
|
||||
month-4=MAY
|
||||
month-5=JUNE
|
||||
month-6=JULY
|
||||
month-7=AUGUST
|
||||
month-8=SEPTEMBER
|
||||
month-9=OCTOBER
|
||||
month-10=NOVEMBER
|
||||
month-11=DECEMBER
|
||||
|
||||
search-header={{ provider }} SEARCH
|
||||
|
||||
# Private Browsing
|
||||
private-window-header=You’re browsing privately
|
||||
private-window-info1=In private windows, {{brandShortName}} won’t keep any browser history, cookies, or temporary Internet files. However, files you download and bookmarks you make will be kept.
|
||||
private-window-info2=While this device won’t have a record of your browsing history, your Internet service provider can still track the pages you visit.
|
||||
private-window-dont-tell=Don’t tell me again
|
||||
private-learn-more=Learn more
|
||||
84
gaia/apps/search/manifest.webapp
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"name": "Browser",
|
||||
"description": "Gaia System Web Browser",
|
||||
"type": "certified",
|
||||
"role": "search",
|
||||
"launch_path": "/newtab.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Browser",
|
||||
"description": "Gaia System Web Browser"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Browser",
|
||||
"description": "Gaia System Web Browser"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Navigateur",
|
||||
"description": "Navigateur web système de Gaia"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Browser",
|
||||
"description": "Gaia System Web Browser"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"permissions": {
|
||||
"themeable": {},
|
||||
"mobileconnection": {},
|
||||
"webapps-manage": {},
|
||||
"open-remote-window": {},
|
||||
"settings": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"systemXHR": {},
|
||||
"contacts": {
|
||||
"access": "readonly"
|
||||
},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {}
|
||||
},
|
||||
"connections": {
|
||||
"search": {
|
||||
"handler_path": "index.html",
|
||||
"description": "Notifies the search app on query change.",
|
||||
"rules": {}
|
||||
}
|
||||
},
|
||||
"datastores-access": {
|
||||
"places": {
|
||||
"access": "readonly",
|
||||
"description": "Stores data about browsing history."
|
||||
},
|
||||
"bookmarks_store": {
|
||||
"access": "readonly",
|
||||
"description": "Stores data about bookmarks"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"84": "/shared/resources/branding/firefox-browser.png",
|
||||
"126": "/shared/resources/branding/firefox-browser.png",
|
||||
"142": "/shared/resources/branding/firefox-browser.png",
|
||||
"189": "/shared/resources/branding/firefox-browser.png",
|
||||
"256": "/shared/resources/branding/firefox-browser.png",
|
||||
"284": "/shared/resources/branding/firefox-browser.png"
|
||||
},
|
||||
"chrome": {
|
||||
"navigation": true
|
||||
},
|
||||
"activities": {
|
||||
"search": {
|
||||
"filters": {
|
||||
"type": "text/plain",
|
||||
"keyword": {
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"disposition": "window",
|
||||
"href": "/index.html"
|
||||
}
|
||||
}
|
||||
}
|
||||
51
gaia/apps/search/newtab.html
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="theme-color" content="#fff">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0">
|
||||
<link rel="stylesheet" href="style/newtab.css">
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="/shared/locales/branding/branding.{locale}.properties">
|
||||
<link rel="localization" href="locales/search.{locale}.properties">
|
||||
|
||||
<script defer src="shared/js/l10n.js"></script>
|
||||
<script defer src="shared/js/lazy_loader.js"></script>
|
||||
<script defer src="shared/js/async_storage.js"></script>
|
||||
<script defer src="js/places_idb.js"></script>
|
||||
<script defer src="shared/js/sync_datastore.js"></script>
|
||||
<script defer src="shared/js/url_helper.js"></script>
|
||||
<script defer src="shared/js/utilities.js"></script>
|
||||
<script defer src="shared/js/settings_listener.js"></script>
|
||||
|
||||
<script src="shared/js/component_utils.js"></script>
|
||||
<script src="shared/elements/gaia_buttons/script.js"></script>
|
||||
<script src="shared/elements/gaia_checkbox/script.js"></script>
|
||||
|
||||
<script defer src="js/newtab.js"></script>
|
||||
<script defer src="shared/js/icons_helper.js"></script>
|
||||
<script defer src="js/providers/provider.js"></script>
|
||||
<script defer src="js/providers/grid_provider.js"></script>
|
||||
<script defer src="js/providers/places.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="private-window-dialog" hidden>
|
||||
<h1 data-l10n-id="private-window-header"></h1>
|
||||
<p data-l10n-id="private-window-info1"></p>
|
||||
<p data-l10n-id="private-window-info2"></p>
|
||||
<p>
|
||||
<a id="private-learn-more" target="_blank" href="https://support.mozilla.org/kb/private-browsing-firefox-os" data-l10n-id="private-learn-more"></a>
|
||||
</p>
|
||||
<gaia-checkbox class="inline">
|
||||
<label data-l10n-id="private-window-dont-tell"></label>
|
||||
</gaia-checkbox>
|
||||
<gaia-buttons class="inline">
|
||||
<button type="submit" data-l10n-id="ok" id="private-window-hide-dialog" class="full"></button>
|
||||
</gaia-buttons>
|
||||
</div>
|
||||
<h2 id="top-sites-header"><span data-l10n-id="top-sites"></span></h2>
|
||||
<section id="top-sites" aria-labelledby="top-sites-header"></section>
|
||||
<section id="history" class="local"></section>
|
||||
</body>
|
||||
</html>
|
||||
10
gaia/apps/search/package.json
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"webide": {
|
||||
"prepackage": {
|
||||
"command": "make",
|
||||
"env": ["APP=search"],
|
||||
"cwd": "../.."
|
||||
},
|
||||
"packageDir": "../../build_stage/search/"
|
||||
}
|
||||
}
|
||||
BIN
gaia/apps/search/shared/resources/branding/firefox-browser.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
gaia/apps/search/shared/resources/branding/vulpes-symbol.png
Normal file
|
After Width: | Height: | Size: 769 KiB |
BIN
gaia/apps/search/style/icons/search.png
Executable file
|
After Width: | Height: | Size: 364 B |
BIN
gaia/apps/search/style/icons/search@1.5x.png
Executable file
|
After Width: | Height: | Size: 477 B |
BIN
gaia/apps/search/style/icons/search@2.25x.png
Executable file
|
After Width: | Height: | Size: 732 B |
BIN
gaia/apps/search/style/icons/search@2x.png
Executable file
|
After Width: | Height: | Size: 659 B |
BIN
gaia/apps/search/style/icons/settings.png
Executable file
|
After Width: | Height: | Size: 216 B |
BIN
gaia/apps/search/style/icons/settings@1.5x.png
Executable file
|
After Width: | Height: | Size: 316 B |
BIN
gaia/apps/search/style/icons/settings@2.25x.png
Executable file
|
After Width: | Height: | Size: 406 B |
BIN
gaia/apps/search/style/icons/settings@2x.png
Executable file
|
After Width: | Height: | Size: 371 B |
BIN
gaia/apps/search/style/images/pb_dialog.png
Executable file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
gaia/apps/search/style/images/pb_dialog@1.5x.png
Executable file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
gaia/apps/search/style/images/pb_dialog@2.25x.png
Executable file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
gaia/apps/search/style/images/pb_dialog@2x.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
218
gaia/apps/search/style/newtab.css
Executable file
|
|
@ -0,0 +1,218 @@
|
|||
/*csslint empty-rules: false*/
|
||||
/* See bug 1053721 for csslint empty rule requirement. */
|
||||
:root {
|
||||
--primary-background-color: #fff;
|
||||
--results-header-color: #a6a6a6;
|
||||
--divider-color: #e5e5e5;
|
||||
--history-result-title-color: #4d4d4d;
|
||||
}
|
||||
|
||||
body.private {
|
||||
--primary-background-color: #392E54;
|
||||
--results-header-color: #858585;
|
||||
--divider-color: #454545;
|
||||
--history-result-title-color: #9e9e9e;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-size: 10px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
html[dir="ltr"] { text-align: left; }
|
||||
html[dir="rtl"] { text-align: right; }
|
||||
|
||||
h2,
|
||||
#history h3:first-child {
|
||||
text-align: center;
|
||||
border-top: 0.2rem solid var(--divider-color);
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0.9rem;
|
||||
margin-bottom: -0.9rem;
|
||||
color: var(--results-header-color);
|
||||
font-style: normal;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
h2 span,
|
||||
#history h3:first-child span {
|
||||
position: relative;
|
||||
top: -0.9rem;
|
||||
background: var(--primary-background-color);
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#top-sites {
|
||||
overflow: auto;
|
||||
margin-top: 1rem;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#top-sites .top-site:nth-child(2n) {
|
||||
margin-inline-start: 1rem;
|
||||
}
|
||||
|
||||
.top-site {
|
||||
display: block;
|
||||
width: calc(50% - 0.5rem);
|
||||
height: 9rem;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
border: 0.1rem solid rgba(133,133,133, 0.8);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html[dir="ltr"] .top-site { float: left; }
|
||||
html[dir="rtl"] .top-site { float: right; }
|
||||
|
||||
.top-site span {
|
||||
box-sizing: border-box;
|
||||
font-size: 1.3rem;
|
||||
font-style: italic;
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 0.2rem;
|
||||
pointer-events: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#history {
|
||||
margin-top: 2.1rem;
|
||||
}
|
||||
|
||||
#history .result {
|
||||
font-size: 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 0.1rem solid rgba(0, 0, 0, 0.1);
|
||||
padding: 1rem 0.7rem;
|
||||
overflow: hidden;
|
||||
color: var(--history-result-title-color);
|
||||
}
|
||||
|
||||
#history .result * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#history .result .description {
|
||||
width: calc(100% - 1.8rem);
|
||||
}
|
||||
|
||||
#history .icon {
|
||||
margin-top: 0.3rem;
|
||||
margin-bottom: 0;
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0.8rem;
|
||||
display: block;
|
||||
flex: 0 0 1.6rem;
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
}
|
||||
|
||||
#history .icon img {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#history .icon.empty {
|
||||
border: 0.1rem dashed #5A5A5A;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
#history .meta {
|
||||
font-size: 1.2rem;
|
||||
color: #5f5f5f;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
#history .title, #history .meta {
|
||||
display: block;
|
||||
white-space:nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
section h3 {
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
font-style: italic;
|
||||
color: #858585;
|
||||
font-weight: normal;
|
||||
margin-top: 1rem;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/* Pseudo-private view */
|
||||
body.private {
|
||||
background: var(--primary-background-color);
|
||||
}
|
||||
|
||||
/* Private browser dialog */
|
||||
#private-window-dialog {
|
||||
min-height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 15rem 2rem 0;
|
||||
-moz-box-sizing: border-box;
|
||||
background: var(--primary-background-color) url(images/pb_dialog.png) no-repeat center 2rem / 10.2rem 11.6rem;
|
||||
}
|
||||
|
||||
#private-window-dialog[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#private-window-dialog h1 {
|
||||
font-size: 2rem;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
#private-window-dialog a,
|
||||
#private-window-dialog p {
|
||||
color: #fff;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.8rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
#private-window-dialog a {
|
||||
color: #00caf2;
|
||||
}
|
||||
|
||||
#private-window-dialog gaia-checkbox {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#private-window-dialog gaia-checkbox label {
|
||||
color: #fff;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
BIN
gaia/apps/search/style/preloaded/favicons/1_NYTimes.png
Executable file
|
After Width: | Height: | Size: 294 B |
BIN
gaia/apps/search/style/preloaded/favicons/2_Behance.png
Executable file
|
After Width: | Height: | Size: 367 B |
BIN
gaia/apps/search/style/preloaded/favicons/3_Gizmodo.png
Executable file
|
After Width: | Height: | Size: 423 B |
BIN
gaia/apps/search/style/preloaded/favicons/4_Vouge.png
Executable file
|
After Width: | Height: | Size: 396 B |
BIN
gaia/apps/search/style/preloaded/screenshots/1_Mozilla.jpg
Executable file
|
After Width: | Height: | Size: 23 KiB |
BIN
gaia/apps/search/style/preloaded/screenshots/2_IGN.jpg
Executable file
|
After Width: | Height: | Size: 36 KiB |
BIN
gaia/apps/search/style/preloaded/screenshots/3_CNN.jpg
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
gaia/apps/search/style/preloaded/screenshots/4_500px.jpg
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
gaia/apps/search/style/preloaded/screenshots/5_49ers.jpg
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
gaia/apps/search/style/preloaded/screenshots/6_ESPN.jpg
Executable file
|
After Width: | Height: | Size: 29 KiB |
321
gaia/apps/search/style/search.css
Executable file
|
|
@ -0,0 +1,321 @@
|
|||
/* Only include gaia-theme to get CSS variables, the transition-duration one
|
||||
in particular. Other parts of gaia-theme are not used. */
|
||||
@import url('../shared/elements/gaia-theme/gaia-theme.css');
|
||||
|
||||
/**
|
||||
* Disable selection by default except for elements with
|
||||
* "contenteditable".
|
||||
*/
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
html[dir="ltr"] { text-align: left; }
|
||||
html[dir="rtl"] { text-align: right; }
|
||||
|
||||
[contenteditable="true"] {
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
body, a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
#offline-message {
|
||||
font-size: 1.4rem;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
margin-inline-end: 1rem;
|
||||
height: 4.2rem;
|
||||
padding-bottom: 1.3rem;
|
||||
text-align: center;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#settings-connectivity {
|
||||
background: url(/style/icons/settings.png) no-repeat 50% 50% / 1.4rem;
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
margin-top: -0.1rem;
|
||||
padding-inline-end: 0.8rem;
|
||||
}
|
||||
|
||||
#search-results {
|
||||
padding-top: 1.3rem;
|
||||
}
|
||||
|
||||
#search-results.offline #suggestions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#search-results.offline #offline-message {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#suggestions-provider {
|
||||
color: #00aac5;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
padding-inline-end: 2rem;
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
|
||||
#suggestions-provider-dropdown {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: -.4rem;
|
||||
margin-inline-start: -1.4rem;
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #00aac5 transparent;
|
||||
}
|
||||
html[dir="ltr"] #suggestions-provider-dropdown {
|
||||
border-width: 0 0 .8rem .8rem;
|
||||
}
|
||||
html[dir="rtl"] #suggestions-provider-dropdown {
|
||||
border-width: 0 .8rem .8rem 0;
|
||||
}
|
||||
|
||||
|
||||
#suggestions {
|
||||
font-size: 1.6rem;
|
||||
overflow-x: hidden;
|
||||
margin-inline-end: 1.6rem;
|
||||
}
|
||||
|
||||
#suggestions ul {
|
||||
list-style: none;
|
||||
white-space: nowrap;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
#suggestions li {
|
||||
height: 6rem;
|
||||
line-height: 6rem;
|
||||
margin: 0;
|
||||
border-bottom: 0.1rem solid rgba(255, 255, 255, 0.05);
|
||||
background: url(/style/icons/search.png) no-repeat;
|
||||
background-size: 3rem;
|
||||
padding-inline-start: 4.5rem;
|
||||
width: 100%;
|
||||
unicode-bidi: -moz-plaintext;
|
||||
}
|
||||
html[dir="ltr"] #suggestions li { background-position: left 0.8rem center; }
|
||||
html[dir="rtl"] #suggestions li { background-position: right 0.8rem center; }
|
||||
|
||||
section .result * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
border-top: 0.3rem solid #5f5f5f;
|
||||
font-size: 1.4rem;
|
||||
color: #858585;
|
||||
margin-top: 0.9rem;
|
||||
margin-bottom: -0.9rem;
|
||||
margin-inline-start: 0.5rem;
|
||||
margin-inline-end: 0.6rem;
|
||||
}
|
||||
|
||||
/* The select box is hidden behind search provider label since we
|
||||
only want to see the dialog prompt, not the actual select box */
|
||||
#suggestions-select {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
h2 span, h2 label {
|
||||
position: relative;
|
||||
top: -0.9rem;
|
||||
background: #333;
|
||||
padding: 0 1rem;
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#places-header {
|
||||
margin-top: 0.9rem;
|
||||
}
|
||||
|
||||
#places-header.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.local .result {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.8rem 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.local .result:after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
content: " ";
|
||||
width: calc(100% - 1rem);
|
||||
height: 0.1rem;
|
||||
background: #535353;
|
||||
left: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.local .result:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.local .result:active {
|
||||
background-color: #3e666e;
|
||||
}
|
||||
|
||||
.local .icon {
|
||||
margin: 0;
|
||||
margin-top: 0.4rem;
|
||||
margin-inline-end: 1.6rem;
|
||||
display: block;
|
||||
flex: 0 0 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.local .icon img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.local .icon.empty {
|
||||
border: 1px dashed #5A5A5A;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.local .icon.empty img {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.local .title, .local .meta {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.local .description {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.local .title {
|
||||
color: #e7e7e7;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.local .meta {
|
||||
color: #858585;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.local .highlight {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* web */
|
||||
section.apps {
|
||||
/* Hide the whitespace between inline elements */
|
||||
font-size: 0;
|
||||
height: 10rem;
|
||||
overflow: hidden;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
gaia-grid {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: left; /* workaround the fact gaia-grid has a direction: 'ltr' */
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#suggestions-notice-wrapper {
|
||||
background: rgba(45, 45, 45, 0.94);
|
||||
z-index: 100;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
margin: 1rem 1.5rem 0;
|
||||
}
|
||||
|
||||
#suggestions-notice-wrapper p {
|
||||
padding: 1.5rem;
|
||||
font-size: 1.6rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#suggestions-notice-confirm {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: #00AACC;
|
||||
background: none;
|
||||
border: none;
|
||||
border-top: 0.1rem solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 0;
|
||||
padding: 1rem;
|
||||
font-size: 16px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#settings-link {
|
||||
color: #00AACC;
|
||||
}
|
||||
|
||||
#suggestions-notice-wrapper::before {
|
||||
content: ' ';
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
border-style: solid;
|
||||
border-width: 0 1rem 1rem;
|
||||
border-color: transparent transparent rgba(45, 45, 45, 0.94) transparent;
|
||||
top: -1rem;
|
||||
margin-inline-start: calc(50% - 1rem);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* show up to 2-line labels in result icons */
|
||||
#search-results .icon .title {
|
||||
text-shadow: none !important;
|
||||
height: calc(3.88rem + 0.2rem);
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
padding: 2.5rem 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-loading=true] .loading {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#places .meta {
|
||||
direction: ltr;
|
||||
}
|
||||
html[dir="ltr"] #places .meta { text-align: left; }
|
||||
html[dir="rtl"] #places .meta { text-align: right; }
|
||||
27
gaia/apps/search/test/build/integration/search_test.js
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true, mocha: true */
|
||||
/* global suiteSetup */
|
||||
|
||||
var helper = require('helper');
|
||||
|
||||
suite('Search tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
test('APP=search make', function(done) {
|
||||
helper.exec('APP=search make', function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var zipPath = process.cwd() +
|
||||
'/profile/webapps/search.gaiamobile.org/application.zip';
|
||||
var checkList = ['manifest.webapp'];
|
||||
|
||||
checkList.forEach(function(filePath) {
|
||||
helper.checkFilePathInZip(zipPath, filePath);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
19
gaia/apps/search/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"__dirname",
|
||||
"module",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished",
|
||||
"require",
|
||||
"define",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
74
gaia/apps/search/test/marionette/app_installed_test.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var Rocketbar =
|
||||
require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var createAppServer =
|
||||
require('../../../homescreen/test/marionette/server/parent');
|
||||
|
||||
marionette('Search - Installed Apps Test', function() {
|
||||
var clientOptions = require(__dirname + '/client_options.js');
|
||||
var client = marionette.client({
|
||||
profile: clientOptions,
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var appInstall, home, rocketbar, search, server, system;
|
||||
|
||||
setup(function(done) {
|
||||
appInstall = new AppInstall(client);
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
var app = __dirname + '/fixtures/installed_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
test('app result after install/uninstall', function() {
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
rocketbar.enterText('Calendar');
|
||||
search.goToResults();
|
||||
|
||||
// Should only have the 'stock' calendar app.
|
||||
client.waitFor(function() {
|
||||
return search.countGridResults() === 1;
|
||||
});
|
||||
|
||||
// Exit rocketbar and install the app.
|
||||
client.switchToFrame();
|
||||
system.tapHome();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// Search again for the same term.
|
||||
rocketbar.appTitleFocus();
|
||||
rocketbar.enterText('Calendar');
|
||||
search.goToResults();
|
||||
|
||||
// Should now contain the newly installed app as well.
|
||||
client.waitFor(function() {
|
||||
return search.countGridResults() === 2;
|
||||
});
|
||||
|
||||
// Remove the installed app.
|
||||
client.switchToFrame();
|
||||
system.tapHome();
|
||||
appInstall.uninstall(server.packageManifestURL);
|
||||
home.confirmDialog('remove');
|
||||
|
||||
// Should now contain only a single app.
|
||||
rocketbar.appTitleFocus();
|
||||
rocketbar.enterText('Calendar');
|
||||
search.goToResults();
|
||||
client.waitFor(function() {
|
||||
return search.countGridResults() === 1;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
48
gaia/apps/search/test/marionette/app_search_test.js
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
|
||||
marionette('Search - App search', function() {
|
||||
var profile = require(__dirname + '/client_options.js');
|
||||
// Causing a crash in this test, so disable apz.
|
||||
profile.prefs = {
|
||||
'layers.async-pan-zoom.enabled': false
|
||||
};
|
||||
var client = marionette.client({
|
||||
profile: profile,
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
test('Search apps from Rocketbar', function() {
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
rocketbar.enterText('calendar');
|
||||
search.goToResults();
|
||||
var calendarIdentifier = 'app://calendar.gaiamobile.org/manifest.webapp';
|
||||
var result = search.checkResult(calendarIdentifier, 'Calendar');
|
||||
result.tap();
|
||||
search.goToApp('app://calendar.gaiamobile.org');
|
||||
});
|
||||
|
||||
test('Search for app with entry point', function() {
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
rocketbar.enterText('Phone');
|
||||
search.goToResults();
|
||||
var phoneIdentifier =
|
||||
'app://communications.gaiamobile.org/manifest.webapp-dialer';
|
||||
var result = search.checkResult(phoneIdentifier, 'Phone');
|
||||
result.tap();
|
||||
search.goToApp('app://communications.gaiamobile.org', 'dialer');
|
||||
});
|
||||
|
||||
});
|
||||
95
gaia/apps/search/test/marionette/browser_test.js
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
'use strict';
|
||||
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
|
||||
var assert = require('chai').assert;
|
||||
|
||||
marionette('Browser test', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var search, system, server, home, rocketbar;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('verticalhome');
|
||||
search = client.loader.getAppClass('search');
|
||||
system = client.loader.getAppClass('system');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
test('Test title injecting html', function() {
|
||||
var url = server.url('xsstitle.html');
|
||||
rocketbar.goToURL(url);
|
||||
client.helper.wait(1500);
|
||||
system.closeBrowserByUrl(url);
|
||||
|
||||
// Open browser app.
|
||||
client.apps.launch(search.URL);
|
||||
client.apps.switchToApp(search.URL);
|
||||
|
||||
client.waitFor(function() {
|
||||
return search.getHistoryResults().length === 1;
|
||||
});
|
||||
|
||||
var title = client.executeScript(function() {
|
||||
return document.querySelector('#history .title').innerHTML;
|
||||
});
|
||||
|
||||
assert.equal(title, '<em>test</em>');
|
||||
});
|
||||
|
||||
test('Large Icon', function() {
|
||||
var url = server.url('largeicon.html');
|
||||
rocketbar.goToURL(url);
|
||||
client.helper.wait(500);
|
||||
system.closeBrowserByUrl(url);
|
||||
|
||||
// Open browser app.
|
||||
client.apps.launch(search.URL);
|
||||
client.apps.switchToApp(search.URL);
|
||||
|
||||
client.waitFor(function() {
|
||||
return search.getHistoryResults().length === 1;
|
||||
});
|
||||
|
||||
var width = client.executeScript(function() {
|
||||
var icon = document.querySelector('#history .icon');
|
||||
return icon.clientWidth;
|
||||
});
|
||||
|
||||
assert.equal(width, 16);
|
||||
});
|
||||
|
||||
test('Ensure fallback to url when no place title', function() {
|
||||
var url = server.url('notitle.html');
|
||||
rocketbar.goToURL(url);
|
||||
client.helper.wait(500);
|
||||
system.closeBrowserByUrl(url);
|
||||
|
||||
// Open browser app.
|
||||
client.apps.launch(search.URL);
|
||||
client.apps.switchToApp(search.URL);
|
||||
|
||||
client.waitFor(function() {
|
||||
return search.getTopSites().length === 1;
|
||||
});
|
||||
|
||||
var topSite = search.getTopSites()[0];
|
||||
|
||||
assert.equal(topSite.text(), url);
|
||||
});
|
||||
});
|
||||
74
gaia/apps/search/test/marionette/change_providers_test.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Search - Switch Providers Test', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system, server;
|
||||
|
||||
var providers;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
providers = {
|
||||
version: search.searchDataVersion(),
|
||||
providers: {
|
||||
'first': {
|
||||
title: 'first',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_one.json')
|
||||
},
|
||||
'second': {
|
||||
title: 'second',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_two.json')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.settings.set('search.suggestions.enabled', true);
|
||||
client.settings.set('search.cache', providers);
|
||||
client.settings.set('search.provider', 'first');
|
||||
});
|
||||
|
||||
test('Test switching suggestion provider', function() {
|
||||
|
||||
home.waitForLaunch();
|
||||
|
||||
rocketbar.homescreenFocus();
|
||||
search.triggerFirstRun(rocketbar);
|
||||
|
||||
rocketbar.enterText('sometext');
|
||||
search.goToResults();
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.suggestions).length === 2;
|
||||
});
|
||||
|
||||
var select = search.switchProvidersSelect;
|
||||
client.helper.tapSelectOption(select, 'second');
|
||||
|
||||
client.switchToFrame();
|
||||
search.goToResults();
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.suggestions).length === 3;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
6
gaia/apps/search/test/marionette/client_options.js
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
settings: {
|
||||
'devtools.pseudolocalization.enabled': false,
|
||||
'language.current': 'en-US'
|
||||
}
|
||||
};
|
||||
43
gaia/apps/search/test/marionette/contacts_search_test.js
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Contacts = require(
|
||||
'../../../communications/contacts/test/marionette/lib/contacts');
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Search - Contact search', function() {
|
||||
var client = marionette.client();
|
||||
|
||||
var contacts, search, rocketbar;
|
||||
|
||||
setup(function() {
|
||||
rocketbar = new Rocketbar(client);
|
||||
contacts = new Contacts(client);
|
||||
search = client.loader.getAppClass('search');
|
||||
});
|
||||
|
||||
test.skip('able to search contact from rocketbar', function() {
|
||||
var contactTel = 1231231234;
|
||||
contacts.launch();
|
||||
contacts.addContact({
|
||||
givenName: 'HelloWorld',
|
||||
tel: contactTel
|
||||
});
|
||||
client.apps.close(Contacts.URL, 'contacts');
|
||||
|
||||
client.switchToFrame();
|
||||
|
||||
search.doSearch('HelloWorld');
|
||||
|
||||
search.goToResults();
|
||||
|
||||
search.checkResult('firstContact', 'HelloWorld');
|
||||
|
||||
search.goToApp(Contacts.URL, 'contacts');
|
||||
|
||||
var firstTel = client.helper
|
||||
.waitForElement(Contacts.Selectors.detailsTelButtonFirst);
|
||||
assert.equal(firstTel.text(), contactTel);
|
||||
});
|
||||
|
||||
});
|
||||
13
gaia/apps/search/test/marionette/fixtures/favicon.html
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="shortcut icon"
|
||||
href="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
|
||||
<title>Sample page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
10
gaia/apps/search/test/marionette/fixtures/installed_app/index.html
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>iwrotethis</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
15
gaia/apps/search/test/marionette/fixtures/installed_app/manifest.webapp
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "Calendar2",
|
||||
"description": "Just a dummy app for testing what happens when we install something.",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gaia/apps/search/test/marionette/fixtures/installed_app/style/icons/Blank.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
10
gaia/apps/search/test/marionette/fixtures/notitle.html
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
11
gaia/apps/search/test/marionette/fixtures/sample.html
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sample page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
1
gaia/apps/search/test/marionette/fixtures/suggestions_one.json
Executable file
|
|
@ -0,0 +1 @@
|
|||
["foo", ["bar"]]
|
||||
1
gaia/apps/search/test/marionette/fixtures/suggestions_two.json
Executable file
|
|
@ -0,0 +1 @@
|
|||
["foo", ["bar", "baz"]]
|
||||
11
gaia/apps/search/test/marionette/fixtures/xsstitle.html
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><em>test</em></title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
39
gaia/apps/search/test/marionette/home_button_test.js
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
|
||||
marionette('Search - Home Button Press', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
test('Home button returns to home screen', function() {
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
rocketbar.enterText('calendar');
|
||||
|
||||
// Ensure search results are displayed.
|
||||
search.goToResults();
|
||||
var calendarIdentifier = 'app://calendar.gaiamobile.org/manifest.webapp';
|
||||
search.checkResult(calendarIdentifier, 'Calendar');
|
||||
|
||||
// Emulate the home button and ensure the home screen is displayed.
|
||||
client.switchToFrame();
|
||||
assert.ok(client.findElement(search.Selectors.iframe).displayed());
|
||||
system.tapHome();
|
||||
client.waitFor(function(){
|
||||
return !client.findElement(search.Selectors.iframe).displayed();
|
||||
});
|
||||
});
|
||||
});
|
||||
164
gaia/apps/search/test/marionette/lib/search.js
Executable file
|
|
@ -0,0 +1,164 @@
|
|||
'use strict';
|
||||
/* global module */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var PROVIDERS_VERSION = 3;
|
||||
/**
|
||||
* Abstraction around search app.
|
||||
* @constructor
|
||||
*/
|
||||
function Search(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type String Origin of search app
|
||||
*/
|
||||
Search.URL = 'app://search.gaiamobile.org';
|
||||
|
||||
Search.Selectors = {
|
||||
allGridResults: 'gaia-grid .icon',
|
||||
iframe: 'iframe[mozapptype="search"]',
|
||||
firstAppContainer: 'gaia-grid',
|
||||
firstApp: 'gaia-grid .icon',
|
||||
firstContact: '#contacts div',
|
||||
firstContactContainer: '#contacts',
|
||||
firstPlace: '#places div .title',
|
||||
firstPlaceContainer: '#places',
|
||||
firstRunConfirm: '#suggestions-notice-confirm',
|
||||
privateWindow: '#private-window',
|
||||
topSites: '.top-site',
|
||||
topSitesHeader: '#top-sites-header',
|
||||
historyResults: '#history .result',
|
||||
suggestions: '#suggestions li',
|
||||
switchProviders: '#suggestions-select',
|
||||
privateDialog: '#private-window-dialog',
|
||||
privateDialogClose: '#private-window-hide-dialog',
|
||||
privateDialogCheckbox: '#private-window-dialog gaia-checkbox'
|
||||
};
|
||||
|
||||
Search.prototype = {
|
||||
|
||||
URL: Search.URL,
|
||||
NEW_TAB_URL: Search.URL + '/newtab.html',
|
||||
Selectors: Search.Selectors,
|
||||
|
||||
privateBrowserUrl: 'app://search.gaiamobile.org/newtab.html?private=1',
|
||||
nonPrivateBrowserUrl: 'app://search.gaiamobile.org/newtab.html?private=0',
|
||||
|
||||
/**
|
||||
* Navigates to the search results frame.
|
||||
*/
|
||||
goToResults: function() {
|
||||
var frameSelector = Search.Selectors.iframe;
|
||||
var iframe = this.client.helper.waitForElement(frameSelector);
|
||||
this.client.helper.waitForElement(iframe);
|
||||
this.client.switchToFrame(iframe);
|
||||
this.client.helper.waitForElement('body');
|
||||
},
|
||||
|
||||
/**
|
||||
* Return selector for the grid item with a n identifier
|
||||
*/
|
||||
getResultSelector: function(identifier) {
|
||||
return '.icon[data-identifier="' + identifier + '"]';
|
||||
},
|
||||
|
||||
/**
|
||||
* Return selector for the history list item by URL
|
||||
*/
|
||||
getHistoryResultSelector: function(url) {
|
||||
return '.result[data-url="' + url + '"]';
|
||||
},
|
||||
|
||||
/**
|
||||
* Return grid results for a particular identifier
|
||||
*/
|
||||
getResult: function(identifier) {
|
||||
var selector = '.icon[data-identifier="' + identifier + '"]';
|
||||
return this.client.findElements(selector);
|
||||
},
|
||||
|
||||
getHistoryResults: function() {
|
||||
return this.client.findElements(Search.Selectors.historyResults);
|
||||
},
|
||||
|
||||
getTopSites: function() {
|
||||
return this.client.findElements(Search.Selectors.topSites);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks that we have a result for a given app in the results list.
|
||||
*/
|
||||
checkResult: function(identifier, expected) {
|
||||
var selectors = Search.Selectors;
|
||||
var selector = '.icon[data-identifier*="' + identifier + '"]';
|
||||
|
||||
this.client.helper.waitForElement(selectors.firstAppContainer);
|
||||
var result = this.client.helper.waitForElement(selector);
|
||||
assert.equal(expected, result.text());
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Counts all grid search results.
|
||||
*/
|
||||
countGridResults: function() {
|
||||
return this.client.findElements(Search.Selectors.allGridResults).length;
|
||||
},
|
||||
|
||||
/**
|
||||
* On first run a warning is shown to users on Search app configuration
|
||||
* trigger this notice and confirm it.
|
||||
*/
|
||||
triggerFirstRun: function(rocketbar) {
|
||||
rocketbar.enterText('a');
|
||||
this.goToResults();
|
||||
|
||||
this.client.executeScript(function() {
|
||||
window.wrappedJSObject.Search.toShowNotice = false;
|
||||
});
|
||||
|
||||
this.client.switchToFrame();
|
||||
rocketbar.enterText('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigates to a specific app/entry point
|
||||
* Waits for the body to be loaded
|
||||
*/
|
||||
goToApp: function() {
|
||||
this.client.switchToFrame();
|
||||
this.client.apps.switchToApp.apply(this.client.apps, arguments);
|
||||
this.client.helper.waitForElement('body');
|
||||
},
|
||||
|
||||
searchDataVersion: function() {
|
||||
return PROVIDERS_VERSION;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a reference to the provider select using findElement.
|
||||
* This waits for the element to be available, but not visible on the apge.
|
||||
*/
|
||||
get switchProvidersSelect() {
|
||||
// Fail finding elements quickly.
|
||||
var quickly = this.client.scope({
|
||||
searchTimeout: 20
|
||||
});
|
||||
|
||||
var element;
|
||||
|
||||
try {
|
||||
element = quickly.findElement(Search.Selectors.switchProviders);
|
||||
} catch(e) {
|
||||
return this.switchProvidersSelect;
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = Search;
|
||||
86
gaia/apps/search/test/marionette/notice_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Search - Notice Test', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system, server;
|
||||
|
||||
var providers;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
providers = {
|
||||
version: search.searchDataVersion(),
|
||||
providers: {
|
||||
'first': {
|
||||
title: 'first',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_one.json')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.settings.set('search.suggestions.enabled', true);
|
||||
client.settings.set('search.cache', providers);
|
||||
client.settings.set('search.provider', 'first');
|
||||
});
|
||||
|
||||
test('Search Notice', function() {
|
||||
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
|
||||
var confirmSelector = search.Selectors.firstRunConfirm;
|
||||
// Notice should not be displayed if we type < 3 characters
|
||||
rocketbar.enterText('ab');
|
||||
rocketbar.enterText('cd');
|
||||
search.goToResults();
|
||||
assert.ok(!client.findElement(confirmSelector).displayed());
|
||||
|
||||
// Should not show notice if suggestions are disabled
|
||||
client.settings.set('search.suggestions.enabled', false);
|
||||
client.switchToFrame();
|
||||
rocketbar.enterText('abc');
|
||||
search.goToResults();
|
||||
client.helper.waitForElementToDisappear(confirmSelector);
|
||||
|
||||
// Notice should be displayed if we show > 3 characters
|
||||
client.settings.set('search.suggestions.enabled', true);
|
||||
client.switchToFrame();
|
||||
rocketbar.enterText('abc');
|
||||
search.goToResults();
|
||||
client.helper.waitForElement(confirmSelector);
|
||||
|
||||
// Notice should be dismissed if we press enter
|
||||
client.switchToFrame();
|
||||
var searchText = 'abc';
|
||||
rocketbar.enterText(searchText, true);
|
||||
rocketbar.switchToSearchFrame(server.url('sample.html'), searchText);
|
||||
client.switchToFrame();
|
||||
home.pressHomeButton();
|
||||
rocketbar.homescreenFocus();
|
||||
search.goToResults();
|
||||
assert.ok(!client.findElement(confirmSelector).displayed());
|
||||
});
|
||||
|
||||
});
|
||||
130
gaia/apps/search/test/marionette/places_search_test.js
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Places tests', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, server, rocketbar, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('verticalhome');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
test('Test url searching', function() {
|
||||
|
||||
var url = server.url('sample.html');
|
||||
var url2 = server.url('favicon.html');
|
||||
|
||||
// Lauch the rocketbar and trigger its first run notice
|
||||
home.waitForLaunch();
|
||||
home.focusRocketBar();
|
||||
search.triggerFirstRun(rocketbar);
|
||||
|
||||
// Input a url and press enter to visit
|
||||
rocketbar.enterText(url + '\uE006');
|
||||
rocketbar.switchToBrowserFrame(url);
|
||||
|
||||
// Go home
|
||||
client.switchToFrame();
|
||||
home.pressHomeButton();
|
||||
client.apps.switchToApp(home.URL);
|
||||
|
||||
// Redo search for url
|
||||
home.focusRocketBar();
|
||||
rocketbar.enterText(url);
|
||||
search.goToResults();
|
||||
var id = search.getHistoryResultSelector(url);
|
||||
var result = client.helper.waitForElement(id);
|
||||
|
||||
// Click result and check app loads
|
||||
result.click();
|
||||
client.switchToFrame();
|
||||
rocketbar.switchToBrowserFrame(url);
|
||||
|
||||
// Go home
|
||||
client.switchToFrame();
|
||||
home.pressHomeButton();
|
||||
client.apps.switchToApp(home.URL);
|
||||
|
||||
// Input a different url and press enter to visit
|
||||
home.focusRocketBar();
|
||||
rocketbar.enterText(url2 + '\uE006');
|
||||
rocketbar.switchToBrowserFrame(url2);
|
||||
});
|
||||
|
||||
test.skip('Search for a string that doesnt match visited url', function() {
|
||||
var url = server.url('sample.html');
|
||||
search.triggerFirstRun(rocketbar);
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url + '\uE006');
|
||||
rocketbar.waitForBrowserFrame();
|
||||
client.switchToFrame();
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText('non_matching_string');
|
||||
search.goToResults();
|
||||
assert.equal(client.findElements(search.Selectors.firstPlace).length, 0);
|
||||
});
|
||||
|
||||
test.skip('Ensures urls visited twice only show in results once', function() {
|
||||
var url = server.url('sample.html');
|
||||
search.triggerFirstRun(rocketbar);
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url + '\uE006');
|
||||
rocketbar.waitForBrowserFrame();
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url + '\uE006');
|
||||
rocketbar.waitForBrowserFrame();
|
||||
client.switchToFrame();
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url);
|
||||
search.goToResults();
|
||||
|
||||
// Wait to get the correct amount of results
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.firstPlace).length === 1;
|
||||
}.bind(this));
|
||||
|
||||
// Wait for a second and check we dont get extra results
|
||||
client.helper.wait(1000);
|
||||
assert.equal(client.findElements(search.Selectors.firstPlace).length, 1);
|
||||
});
|
||||
|
||||
test.skip('Ensure favicon is loaded', function() {
|
||||
var url = server.url('favicon.html');
|
||||
search.triggerFirstRun(rocketbar);
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url + '\uE006');
|
||||
rocketbar.waitForBrowserFrame();
|
||||
|
||||
client.waitFor(function() {
|
||||
client.switchToFrame();
|
||||
rocketbar.focus();
|
||||
rocketbar.enterText(url);
|
||||
search.goToResults();
|
||||
var result = client.helper.waitForElement('#places div .icon');
|
||||
return !result.getAttribute('class').match('empty');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
64
gaia/apps/search/test/marionette/private_browser_dialog_test.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Private Browser Trigger', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
settings: {
|
||||
'browser.private.default': true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var search, system;
|
||||
|
||||
setup(function() {
|
||||
search = client.loader.getAppClass('search');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
function launchPrivateWindow() {
|
||||
var frame = system.waitForLaunch(search.URL);
|
||||
client.switchToFrame(frame);
|
||||
}
|
||||
|
||||
test('launches private window', function() {
|
||||
// Assert that the dialog is visible and is dismissable.
|
||||
launchPrivateWindow();
|
||||
var dialog = client.helper.waitForElement(search.Selectors.privateDialog);
|
||||
client.helper.waitForElement(search.Selectors.privateDialogClose)
|
||||
.scriptWith(function(el) {
|
||||
el.wrappedJSObject.dispatchEvent(new CustomEvent('click'));
|
||||
});
|
||||
client.helper.waitForElementToDisappear(dialog);
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.close();
|
||||
});
|
||||
client.switchToFrame();
|
||||
|
||||
// Assert that the dialog is visible if we relaunch the search app.
|
||||
launchPrivateWindow();
|
||||
dialog = client.helper.waitForElement(search.Selectors.privateDialog);
|
||||
// Tap the checkbox and close the dialog.
|
||||
client.helper.waitForElement(search.Selectors.privateDialogCheckbox)
|
||||
.scriptWith(function(el) {
|
||||
el.wrappedJSObject.checked = true;
|
||||
});
|
||||
client.helper.waitForElement(search.Selectors.privateDialogClose)
|
||||
.scriptWith(function(el) {
|
||||
el.wrappedJSObject.dispatchEvent(new CustomEvent('click'));
|
||||
});
|
||||
client.helper.waitForElementToDisappear(dialog);
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.close();
|
||||
});
|
||||
client.switchToFrame();
|
||||
|
||||
// The next time we re-launch the browser the dialog should not appear.
|
||||
launchPrivateWindow();
|
||||
assert.ok(client.helper.waitForElement(
|
||||
search.Selectors.topSitesHeader).displayed());
|
||||
});
|
||||
});
|
||||
48
gaia/apps/search/test/marionette/private_browsing_events_test.js
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
marionette('Private Browsing', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
settings: {
|
||||
'browser.private.default': true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var search, system;
|
||||
|
||||
setup(function() {
|
||||
search = client.loader.getAppClass('search');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
});
|
||||
|
||||
test('launches private window', function() {
|
||||
client.executeScript(function () {
|
||||
window.dispatchEvent(new CustomEvent('new-private-window'));
|
||||
});
|
||||
|
||||
client.helper.waitForElement('body');
|
||||
|
||||
var iframe = system.getAppIframe(search.privateBrowserUrl);
|
||||
|
||||
client.waitFor(function() {
|
||||
return iframe.getAttribute('src') === search.privateBrowserUrl;
|
||||
});
|
||||
});
|
||||
|
||||
test('launches non-private window', function() {
|
||||
client.executeScript(function () {
|
||||
window.dispatchEvent(new CustomEvent('new-non-private-window'));
|
||||
});
|
||||
|
||||
client.helper.waitForElement('body');
|
||||
|
||||
var iframe = system.getAppIframe(search.nonPrivateBrowserUrl);
|
||||
|
||||
client.waitFor(function() {
|
||||
return iframe.getAttribute('src') === search.nonPrivateBrowserUrl;
|
||||
});
|
||||
});
|
||||
});
|
||||
126
gaia/apps/search/test/marionette/search_rocketbar_test.js
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Search - Rocketbar Test', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system, server;
|
||||
|
||||
var providers, searchTemplate;
|
||||
var phoneIdentifier =
|
||||
'app://communications.gaiamobile.org/manifest.webapp-dialer';
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
searchTemplate = server.url('sample.html') + '?q=';
|
||||
providers = {
|
||||
version: search.searchDataVersion(),
|
||||
providers: {
|
||||
'first': {
|
||||
title: 'first',
|
||||
searchUrl: searchTemplate + '{searchTerms}',
|
||||
suggestUrl: server.url('suggestions_one.json')
|
||||
}
|
||||
}
|
||||
};
|
||||
mockSearch();
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
});
|
||||
|
||||
function mockSearch() {
|
||||
client.settings.set('search.suggestions.enabled', true);
|
||||
client.settings.set('search.cache', providers);
|
||||
client.settings.set('search.provider', 'first');
|
||||
}
|
||||
|
||||
function assertThatUrlContains(text) {
|
||||
rocketbar.appTitleFocus();
|
||||
client.waitFor(function() {
|
||||
var value = rocketbar.input.getAttribute('value');
|
||||
var searchText = text.replace(' ', '%20');
|
||||
return value === (searchTemplate + searchText);
|
||||
});
|
||||
}
|
||||
|
||||
test('General walkthrough', function() {
|
||||
search.triggerFirstRun(rocketbar);
|
||||
var textToSearch = 'a test';
|
||||
|
||||
// Clear button shouldnt be visible when no text entered
|
||||
assert.ok(!rocketbar.clear.displayed());
|
||||
|
||||
// Search for an app ane make sure it exists
|
||||
rocketbar.enterText('Phone');
|
||||
search.goToResults();
|
||||
search.checkResult(phoneIdentifier, 'Phone');
|
||||
|
||||
// Press rocketbar close button, ensure the homescreen is
|
||||
// now displayed
|
||||
client.switchToFrame();
|
||||
rocketbar.cancel.click();
|
||||
client.apps.switchToApp(home.URL);
|
||||
client.waitFor(function() {
|
||||
return home.visibleIcons.length && home.visibleIcons[0].displayed();
|
||||
});
|
||||
|
||||
// When we previously pressed close, when rocketbar reopens value
|
||||
// should be empty
|
||||
rocketbar.homescreenFocus();
|
||||
assert.equal(rocketbar.input.getAttribute('value'), '');
|
||||
|
||||
// Search for an app again, this time press close after searching
|
||||
rocketbar.enterText('Phone');
|
||||
home.pressHomeButton();
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.visibleIcons.length && home.visibleIcons[0].displayed();
|
||||
});
|
||||
|
||||
// If we press home button during a search, next time we focus the rocketbar
|
||||
// previous result should be displayed
|
||||
rocketbar.homescreenFocus();
|
||||
search.goToResults();
|
||||
search.checkResult(phoneIdentifier, 'Phone');
|
||||
|
||||
// Clear button should be visible when text entered
|
||||
client.switchToFrame();
|
||||
assert.ok(rocketbar.clear.displayed());
|
||||
|
||||
// Press clear button, input
|
||||
rocketbar.clear.click();
|
||||
assert.equal(rocketbar.input.getAttribute('value'), '');
|
||||
assert.ok(!rocketbar.clear.displayed());
|
||||
|
||||
// Perform a search
|
||||
rocketbar.enterText(textToSearch, true);
|
||||
rocketbar.switchToBrowserFrame(searchTemplate);
|
||||
});
|
||||
|
||||
test('Browser search', function() {
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
var textToSearch = 'a test';
|
||||
rocketbar.performSearchInBrowser(textToSearch);
|
||||
assertThatUrlContains(textToSearch);
|
||||
});
|
||||
|
||||
});
|
||||
74
gaia/apps/search/test/marionette/suggestions_disabled_test.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Search - Suggestions disabled', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system, server;
|
||||
|
||||
var providers;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
providers = {
|
||||
version: search.searchDataVersion(),
|
||||
providers: {
|
||||
'first': {
|
||||
title: 'first',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_one.json')
|
||||
},
|
||||
'second': {
|
||||
title: 'second',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_two.json')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.settings.set('search.suggestions.enabled', false);
|
||||
client.settings.set('search.cache', providers);
|
||||
client.settings.set('search.provider', 'first');
|
||||
});
|
||||
|
||||
test('Test switching suggestion provider', function() {
|
||||
|
||||
home.waitForLaunch();
|
||||
|
||||
rocketbar.homescreenFocus();
|
||||
search.triggerFirstRun(rocketbar);
|
||||
|
||||
rocketbar.enterText('sometext');
|
||||
search.goToResults();
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.suggestions).length === 1;
|
||||
});
|
||||
|
||||
var select = search.switchProvidersSelect;
|
||||
client.helper.tapSelectOption(select, 'second');
|
||||
|
||||
client.switchToFrame();
|
||||
search.goToResults();
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.suggestions).length === 1;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
68
gaia/apps/search/test/marionette/suggestions_test.js
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/rocketbar.js');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Search - Suggestions Test', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, search, rocketbar, system, server;
|
||||
|
||||
var providers;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
search = client.loader.getAppClass('search');
|
||||
rocketbar = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
providers = {
|
||||
version: search.searchDataVersion(),
|
||||
providers: {
|
||||
'first': {
|
||||
title: 'first',
|
||||
searchUrl: server.url('sample.html'),
|
||||
suggestUrl: server.url('suggestions_one.json')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.settings.set('search.suggestions.enabled', true);
|
||||
client.settings.set('search.cache', providers);
|
||||
client.settings.set('search.provider', 'first');
|
||||
});
|
||||
|
||||
test('Test suggestions', function() {
|
||||
|
||||
home.waitForLaunch();
|
||||
rocketbar.homescreenFocus();
|
||||
|
||||
search.triggerFirstRun(rocketbar);
|
||||
rocketbar.enterText('sometext');
|
||||
search.goToResults();
|
||||
|
||||
// Ensure we get 2 results (hardcoded in the provider results)
|
||||
client.waitFor(function() {
|
||||
return client.findElements(search.Selectors.suggestions).length === 2;
|
||||
});
|
||||
|
||||
// Ensure clicking on a result opens the browser correctly
|
||||
var first = client.helper.waitForElement('#suggestions li');
|
||||
first.click();
|
||||
|
||||
client.switchToFrame();
|
||||
rocketbar.switchToBrowserFrame(providers.providers.first.searchUrl);
|
||||
});
|
||||
|
||||
});
|
||||
17
gaia/apps/search/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
80
gaia/apps/search/test/unit/contextmenu_test.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
require('/js/contextmenu.js');
|
||||
|
||||
suite('search/contextmenu', function() {
|
||||
|
||||
var subject = null,
|
||||
realBookmarks = null,
|
||||
isBookmarked = false;
|
||||
|
||||
suiteSetup(function() {
|
||||
realBookmarks = window.Bookmarks;
|
||||
window.Bookmarks = {
|
||||
get: function() {
|
||||
return isBookmarked;
|
||||
}
|
||||
};
|
||||
loadBodyHTML('/index.html');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.Bookmarks = realBookmarks;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.useFakeTimers();
|
||||
subject = new window.Contextmenu();
|
||||
});
|
||||
|
||||
suite('handleEvent', function() {
|
||||
test('contextmenu icon not bookmarked', function() {
|
||||
var target = document.createElement('div');
|
||||
target.getIcon = function() {
|
||||
return {
|
||||
detail: {
|
||||
type: 'bookmark'
|
||||
}
|
||||
};
|
||||
};
|
||||
target.start = function() {};
|
||||
target.stop = function() {};
|
||||
subject.grid = target;
|
||||
|
||||
var fakeContextMenuEvent = {
|
||||
type: 'contextmenu',
|
||||
target: target
|
||||
};
|
||||
|
||||
var startStub = this.sinon.stub(subject.grid, 'start');
|
||||
var stopStub = this.sinon.stub(subject.grid, 'stop');
|
||||
subject.handleEvent(fakeContextMenuEvent);
|
||||
assert.ok(stopStub.calledOnce);
|
||||
this.sinon.clock.tick();
|
||||
assert.ok(startStub.calledOnce);
|
||||
});
|
||||
|
||||
test('contextmenu icon already bookmarked', function() {
|
||||
var target = document.createElement('div');
|
||||
target.getIcon = function() {
|
||||
return {
|
||||
detail: {
|
||||
type: 'bookmark'
|
||||
}
|
||||
};
|
||||
};
|
||||
target.stop = function() {};
|
||||
subject.grid = target;
|
||||
|
||||
var fakeContextMenuEvent = {
|
||||
type: 'contextmenu',
|
||||
target: target
|
||||
};
|
||||
|
||||
isBookmarked = true;
|
||||
var stopStub = this.sinon.stub(subject.grid, 'stop');
|
||||
subject.handleEvent(fakeContextMenuEvent);
|
||||
assert.isFalse(stopStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
13
gaia/apps/search/test/unit/mock_provider.js
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
/* global Promise */
|
||||
|
||||
var MockProvider = function(name) {
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
MockProvider.prototype.init = function() {};
|
||||
MockProvider.prototype.clear = function() {};
|
||||
MockProvider.prototype.search = function() {
|
||||
return new Promise(() => {});
|
||||
};
|
||||
MockProvider.prototype.abort = function() {};
|
||||
17
gaia/apps/search/test/unit/mock_search.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
window.Search = {
|
||||
_port: null,
|
||||
providers: {},
|
||||
init: function() {},
|
||||
provider: function(provider) {
|
||||
this.providers[provider.name] = provider;
|
||||
},
|
||||
onSearchInput: function() {},
|
||||
close: function() {},
|
||||
navigate: function() {},
|
||||
setInput: function() {},
|
||||
collect: function(provider, results) {
|
||||
provider.render(results);
|
||||
}
|
||||
};
|
||||
169
gaia/apps/search/test/unit/places_idb_test.js
Executable file
|
|
@ -0,0 +1,169 @@
|
|||
/* globals MocksHelper */
|
||||
/* globals asyncStorage */
|
||||
/* globals PlacesIdbStore */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_async_storage.js');
|
||||
|
||||
requireApp('search/js/places_idb.js');
|
||||
|
||||
var mocksHelper = new MocksHelper([
|
||||
'asyncStorage'
|
||||
]).init();
|
||||
|
||||
suite('search/places_idb', function() {
|
||||
|
||||
var subject;
|
||||
|
||||
var mozilla = 'http://mozilla.org';
|
||||
var google = 'http://google.com';
|
||||
var yahoo = 'http://yahoo.com';
|
||||
|
||||
function place(url, frecency, visited, visits) {
|
||||
frecency = frecency || 1;
|
||||
visited = visited || 0;
|
||||
visits = visits || [visited];
|
||||
return {
|
||||
url: url,
|
||||
frecency: frecency,
|
||||
visited: visited,
|
||||
visits: visits
|
||||
};
|
||||
}
|
||||
|
||||
mocksHelper.attachTestHelpers();
|
||||
|
||||
setup(done => {
|
||||
asyncStorage.getItem = function(key, callback) {
|
||||
callback(null);
|
||||
};
|
||||
|
||||
subject = new PlacesIdbStore();
|
||||
|
||||
subject.init().then(function() {
|
||||
subject.clear().then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test frecency', function(done) {
|
||||
Promise.all([
|
||||
subject.add('url', place(mozilla, 1, 0)),
|
||||
subject.add('url', place(google, 3, 1)),
|
||||
subject.add('url', place(yahoo, 2, 2)),
|
||||
subject.add('url', place(mozilla, 4, 3))
|
||||
]).then(function() {
|
||||
subject.read('frecency', 5, function(results) {
|
||||
assert.equal(results.length, 3);
|
||||
assert.equal(results[0].url, mozilla);
|
||||
assert.equal(results[1].url, google);
|
||||
assert.equal(results[2].url, yahoo);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test history', function(done) {
|
||||
Promise.all([
|
||||
subject.add('url', place(mozilla, 1, 0)),
|
||||
subject.add('url', place(google, 3, 1)),
|
||||
subject.add('url', place(yahoo, 2, 2)),
|
||||
subject.add('url', place(mozilla, 4, 3))
|
||||
]).then(function() {
|
||||
subject.read('visited', 5, function(results) {
|
||||
assert.equal(results.length, 3);
|
||||
assert.equal(results[0].url, mozilla);
|
||||
assert.equal(results[1].url, yahoo);
|
||||
assert.equal(results[2].url, google);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test frecency filter', function(done) {
|
||||
Promise.all([
|
||||
subject.add('url', place(google, -1, 0)),
|
||||
subject.add('url', place(yahoo, -2, 2)),
|
||||
subject.add('url', place(mozilla, 1, 3))
|
||||
]).then(function() {
|
||||
subject.read('frecency', 5, function(results) {
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].url, mozilla);
|
||||
done();
|
||||
}, function(place) {
|
||||
return place.frecency > 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test filter', function(done) {
|
||||
Promise.all([
|
||||
subject.add('url', place(mozilla, 1, 0)),
|
||||
subject.add('url', place(google, 3, 1)),
|
||||
subject.add('url', place(yahoo, 2, 2)),
|
||||
subject.add('url', place(mozilla, 4, 3))
|
||||
]).then(function() {
|
||||
subject.read('visited', 5, function(results) {
|
||||
assert.equal(results.length, 1);
|
||||
assert.equal(results[0].url, mozilla);
|
||||
done();
|
||||
}, function(place) {
|
||||
return place.url === mozilla;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Test visits', function(done) {
|
||||
Promise.all([
|
||||
subject.add('url', place(mozilla, 1, 0, [0])),
|
||||
subject.add('url', place(google, 3, 0, [4, 2])),
|
||||
subject.add('url', place(yahoo, 2, 0, [7, 3])),
|
||||
subject.add('url', place(mozilla, 4, 0, [6, 5, 1, 0]))
|
||||
]).then(function() {
|
||||
subject.readVisits(7, function(results) {
|
||||
assert.equal(results.length, 7);
|
||||
assert.equal(results[0].url, yahoo);
|
||||
assert.equal(results[1].url, mozilla);
|
||||
assert.equal(results[2].url, mozilla);
|
||||
assert.equal(results[3].url, google);
|
||||
assert.equal(results[4].url, yahoo);
|
||||
assert.equal(results[5].url, google);
|
||||
assert.equal(results[6].url, mozilla);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Adding empty visits should delete the record', function(done) {
|
||||
Promise.all([
|
||||
subject.add(mozilla, place(mozilla, 1, 1, [1])),
|
||||
subject.add(mozilla, place(mozilla, 1, 2, []))
|
||||
]).then(() => {
|
||||
subject.read('visited', 5, results => {
|
||||
assert.equal(results.length, 0);
|
||||
subject.readVisits(5, results => {
|
||||
assert.equal(results.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Adding empty visits should delete the record', function(done) {
|
||||
Promise.all([
|
||||
subject.add(mozilla, place(mozilla, 1, 1, [1])),
|
||||
subject.remove(mozilla)
|
||||
]).then(() => {
|
||||
subject.read('visited', 5, results => {
|
||||
assert.equal(results.length, 0);
|
||||
subject.readVisits(5, results => {
|
||||
assert.equal(results.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
99
gaia/apps/search/test/unit/providers/bookmarks_test.js
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
/* global MockNavigatorDatastore, MockDatastore, Promise */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
||||
require('/shared/js/utilities.js');
|
||||
requireApp('search/test/unit/mock_search.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
requireApp('search/js/providers/grid_provider.js');
|
||||
|
||||
// Required files for the grid and a mozapp result
|
||||
require('/shared/js/l10n.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_layout.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_view.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
require('/shared/elements/gaia_grid/js/items/bookmark.js');
|
||||
require('/shared/js/sync_datastore.js');
|
||||
|
||||
suite('search/providers/providers', function() {
|
||||
var subject;
|
||||
var realDatastore;
|
||||
var promiseDone = Promise.resolve({ operation: 'done' });
|
||||
|
||||
var ICON = 'https://mozorg.cdn.mozilla.net/media/img/favicon.ico';
|
||||
|
||||
suiteSetup(function() {
|
||||
realDatastore = navigator.getDataStores;
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
MockNavigatorDatastore._records = {};
|
||||
|
||||
MockDatastore.sync = function() {
|
||||
var cursor = {
|
||||
next: function() {
|
||||
cursor.next = () => promiseDone;
|
||||
|
||||
return Promise.resolve({
|
||||
operation: 'add',
|
||||
data: {
|
||||
url: 'http://mozilla.org',
|
||||
name: 'Homepage',
|
||||
icon: ICON
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
return cursor;
|
||||
};
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.getDataStores = realDatastore;
|
||||
});
|
||||
|
||||
setup(function(done) {
|
||||
requireApp('search/js/providers/bookmarks.js', function() {
|
||||
subject = window.Bookmarks;
|
||||
subject.grid = document.createElement('gaia-grid');
|
||||
promiseDone.then(function() {
|
||||
subject.init().then(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('search', function() {
|
||||
test('renders data url', function(done) {
|
||||
subject.search('mozilla').then((results) => {
|
||||
assert.equal(results[0].data.detail.url, 'http://mozilla.org');
|
||||
assert.equal(results[0].data.detail.name, 'Homepage');
|
||||
assert.equal(results[0].data.detail.id, 'http://mozilla.org');
|
||||
assert.equal(results[0].data.detail.icon, ICON);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('search name', function(done) {
|
||||
subject.search('homepage').then((results) => {
|
||||
assert.equal(results[0].data.detail.name, 'Homepage');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('get', function() {
|
||||
test('ok', function() {
|
||||
var bookmark = subject.get('http://mozilla.org');
|
||||
assert.equal(bookmark.data.detail.url, 'http://mozilla.org');
|
||||
assert.equal(bookmark.data.detail.name, 'Homepage');
|
||||
assert.equal(bookmark.data.detail.id, 'http://mozilla.org');
|
||||
assert.equal(bookmark.data.detail.icon, ICON);
|
||||
});
|
||||
|
||||
test('fail', function() {
|
||||
var bookmark = subject.get('http://catanddog.org');
|
||||
assert.isNull(bookmark);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
94
gaia/apps/search/test/unit/providers/contacts_test.js
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
'use strict';
|
||||
/* global MocksHelper, MockMozContacts, MockContactsList, MockMozActivity,
|
||||
Search */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_moz_activity.js');
|
||||
require('/shared/js/url_helper.js');
|
||||
requireApp('search/test/unit/mock_search.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
require('/shared/test/unit/mocks/mock_mozContacts.js');
|
||||
require('/apps/communications/contacts/test/unit/mock_contacts_list.js');
|
||||
|
||||
var mocksForMarketplaceProvider = new MocksHelper([
|
||||
'MozActivity'
|
||||
]).init();
|
||||
|
||||
suite('search/providers/contacts', function() {
|
||||
mocksForMarketplaceProvider.attachTestHelpers();
|
||||
|
||||
var fakeElement, stubById, subject;
|
||||
|
||||
suiteSetup(function() {
|
||||
navigator.mozContacts = MockMozContacts;
|
||||
sinon.stub(navigator.mozContacts, 'find', function() {
|
||||
return {
|
||||
set onsuccess(cb) {
|
||||
cb(MockContactsList());
|
||||
},
|
||||
set onerror(cb) {
|
||||
},
|
||||
result: MockContactsList()
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
setup(function(done) {
|
||||
fakeElement = document.createElement('div');
|
||||
fakeElement.style.cssText = 'height: 100px; display: block;';
|
||||
stubById = this.sinon.stub(document, 'getElementById')
|
||||
.returns(fakeElement.cloneNode(true));
|
||||
requireApp('search/js/providers/contacts.js', function() {
|
||||
subject = Search.providers.Contacts;
|
||||
subject.init();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stubById.restore();
|
||||
});
|
||||
|
||||
suite('click', function() {
|
||||
test('launches the web activity', function() {
|
||||
var searchCloseStub = this.sinon.stub(Search, 'close');
|
||||
subject.click({
|
||||
target: {
|
||||
dataset: {
|
||||
contactId: 'mozilla'
|
||||
}
|
||||
}
|
||||
});
|
||||
var activityInfo = MockMozActivity.calls[0];
|
||||
assert.equal(activityInfo.name, 'open');
|
||||
assert.equal(activityInfo.data.params.id, 'mozilla');
|
||||
assert.ok(searchCloseStub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('search', function() {
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(URL, 'createObjectURL');
|
||||
});
|
||||
|
||||
test('clears results', function() {
|
||||
var stub = this.sinon.stub(subject, 'clear');
|
||||
subject.search(null, function() {});
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
|
||||
test('contact is rendered', function(done) {
|
||||
subject.search('stub content').then((results) => {
|
||||
Search.collect(subject, results);
|
||||
var contact = subject.container.querySelector('.result');
|
||||
assert.equal(contact.querySelector('.title').innerHTML, 'Pepito A');
|
||||
assert.equal(contact.getAttribute('aria-label'), 'Pepito A');
|
||||
assert.equal(contact.getAttribute('role'), 'link');
|
||||
assert.equal(contact.querySelector('.icon img').getAttribute('role'),
|
||||
'presentation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
156
gaia/apps/search/test/unit/providers/local_apps_test.js
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
'use strict';
|
||||
/* global Search, MockNavigatormozApps, MockNavigatorSettings */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_apps.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
requireApp('search/test/unit/mock_search.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
requireApp('search/js/providers/grid_provider.js');
|
||||
|
||||
suite('search/providers/local_apps', function() {
|
||||
|
||||
var realMozApps;
|
||||
var realMozSettings;
|
||||
var _apps, _blValue;
|
||||
|
||||
suiteSetup(function() {
|
||||
_apps = [{
|
||||
manifest: {
|
||||
entry_points: {
|
||||
'fakeapp1-1': {
|
||||
launch_path: '/fakeapp1-1/index.html',
|
||||
name: 'Mozilla Fake App 1 - 1'
|
||||
},
|
||||
'fakeapp1-2': {
|
||||
launch_path: '/fakeapp1-2/index.html',
|
||||
name: 'Mozilla Fake App 1 - 2'
|
||||
}
|
||||
},
|
||||
name: 'Mozilla Fake App 1'
|
||||
},
|
||||
manifestURL: 'http://fakeapp1/manifest.webapp'
|
||||
},{
|
||||
manifest: {
|
||||
launch_path: '/fakeapp2/index.html',
|
||||
name: 'Mozilla Fake App 2'
|
||||
},
|
||||
manifestURL: 'http://fakeapp2/manifest.webapp'
|
||||
},{
|
||||
manifest: {
|
||||
launch_path: '/fakeapp3/index.html',
|
||||
name: 'Mooilla Fake App 3',
|
||||
short_name: 'shorty'
|
||||
}
|
||||
},{
|
||||
manifest: {
|
||||
launch_path: '/fakeapp4/index.html',
|
||||
name: 'Blah blah',
|
||||
locales: {
|
||||
foobar: {
|
||||
name: 'localized app'
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
_blValue = ['/fakeapp1-1/index.html'];
|
||||
|
||||
realMozApps = navigator.mozApps;
|
||||
navigator.mozApps = MockNavigatormozApps;
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
|
||||
navigator.mozApps.mApps = _apps;
|
||||
navigator.mozSettings.createLock().set({
|
||||
'app.launch_path.blacklist': _blValue
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozApps = realMozApps;
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
|
||||
|
||||
suite('Blacklist', function() {
|
||||
|
||||
var subject;
|
||||
|
||||
setup(function(done) {
|
||||
requireApp('search/js/providers/local_apps.js', function() {
|
||||
subject = Search.providers.LocalApps;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockNavigatormozApps.mTeardown();
|
||||
});
|
||||
|
||||
test('CreateBlacklist returns correct blacklist', function(done) {
|
||||
subject.createBlacklist().then(() => {
|
||||
assert.equal(subject.blacklist, _blValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Search', function() {
|
||||
|
||||
var subject;
|
||||
|
||||
setup(function(done) {
|
||||
requireApp('search/js/providers/local_apps.js', function() {
|
||||
subject = Search.providers.LocalApps;
|
||||
subject.apps = _apps;
|
||||
subject.blacklist = _blValue;
|
||||
subject.createAppListing();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockNavigatormozApps.mTeardown();
|
||||
});
|
||||
|
||||
test('Search returns correct applications', function() {
|
||||
var results = subject.find('moz');
|
||||
assert.equal(results.length, 2);
|
||||
});
|
||||
|
||||
test('Search returns correct applications', function() {
|
||||
var results = subject.find('mo');
|
||||
assert.equal(results.length, 3);
|
||||
});
|
||||
|
||||
test('Search for localized apps', function() {
|
||||
document.documentElement.lang = 'foobar';
|
||||
var results = subject.find('localized');
|
||||
assert.equal(results.length, 1);
|
||||
});
|
||||
|
||||
test('Search returns correct applications', function() {
|
||||
var results = subject.find('shorty');
|
||||
assert.equal(results.length, 1);
|
||||
});
|
||||
|
||||
test('hidden role which downloads is not listed', function() {
|
||||
var resultLength = subject.appListing.length;
|
||||
var mockApp = {
|
||||
manifestURL: 'marketplace/langpack',
|
||||
downloading: true,
|
||||
manifest: {
|
||||
role: 'langpack'
|
||||
}
|
||||
};
|
||||
navigator.mozApps.mgmt.oninstall({
|
||||
application: mockApp
|
||||
});
|
||||
mockApp.ondownloadapplied();
|
||||
assert.equal(resultLength, subject.appListing.length);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
108
gaia/apps/search/test/unit/providers/marketplace_test.js
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
'use strict';
|
||||
/* global MocksHelper, Search */
|
||||
/* global MockNavigatorSettings */
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/test/unit/mocks/mock_moz_activity.js');
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
|
||||
requireApp('search/test/unit/mock_search.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
requireApp('search/js/providers/grid_provider.js');
|
||||
|
||||
// Required files for the grid and a marketplaceapp result
|
||||
require('/shared/js/l10n.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_layout.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_view.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
require('/shared/elements/gaia_grid/js/items/bookmark.js');
|
||||
require('/shared/elements/gaia_grid/js/items/marketplace_app.js');
|
||||
|
||||
var mocksForMarketplaceProvider = new MocksHelper([
|
||||
'MozActivity'
|
||||
]).init();
|
||||
|
||||
suite('search/providers/marketplace', function() {
|
||||
mocksForMarketplaceProvider.attachTestHelpers();
|
||||
|
||||
var fakeElement, stubById, subject, realMozSettings;
|
||||
|
||||
setup(function(done) {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
|
||||
navigator.mozSettings.createLock().set({
|
||||
'search.marketplace.url': 'http://localhost?{q}'
|
||||
});
|
||||
|
||||
fakeElement = document.createElement('div');
|
||||
fakeElement.style.cssText = 'height: 100px; display: block;';
|
||||
stubById = this.sinon.stub(document, 'getElementById')
|
||||
.returns(fakeElement.cloneNode(true));
|
||||
requireApp('search/js/providers/marketplace.js', function() {
|
||||
subject = Search.providers.Marketplace;
|
||||
subject.init();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stubById.restore();
|
||||
});
|
||||
|
||||
suite('mock response', function() {
|
||||
|
||||
var marketplaceContent = [{
|
||||
'manifest_url': 'http://fakeapp1.mozilla.org/manifest.webapp',
|
||||
'name': 'FIRST Marketplace App',
|
||||
'icon': ''
|
||||
}, {
|
||||
'manifest_url': 'http://fakeapp2.mozilla.org/manifest.webapp',
|
||||
'name': 'SECOND Marketplace App',
|
||||
'icon': ''
|
||||
}];
|
||||
|
||||
var requests = [];
|
||||
var xhr;
|
||||
|
||||
setup(function() {
|
||||
xhr = sinon.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
xhr.onCreate = function(req) {
|
||||
setTimeout(function() {
|
||||
requests.push(req);
|
||||
req.responseText = JSON.stringify(marketplaceContent);
|
||||
req.status = 200;
|
||||
req.onload();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.request = null;
|
||||
xhr.restore();
|
||||
});
|
||||
|
||||
test('escapes search terms', function(done) {
|
||||
subject.search('foo#').then(function(results) {
|
||||
assert.equal(requests[0].url, 'http://localhost?foo%23');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders all results', function(done) {
|
||||
subject.search('fake').then(results => {
|
||||
assert.equal(results.length, 2);
|
||||
done();
|
||||
});
|
||||
|
||||
// setTimeout to ensure that the search microtask fires.
|
||||
setTimeout(function() {
|
||||
var req = requests[0];
|
||||
req.responseText = JSON.stringify(marketplaceContent);
|
||||
req.onload();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
68
gaia/apps/search/test/unit/providers/provider_test.js
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
/* global Provider, MocksHelper */
|
||||
|
||||
requireApp('search/shared/js/everythingme/eme.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
require('/shared/test/unit/mocks/mock_icons_helper.js');
|
||||
|
||||
var mocks = new MocksHelper([
|
||||
'IconsHelper'
|
||||
]).init();
|
||||
|
||||
suite('search/providers/provider', function() {
|
||||
|
||||
var fakeElement, stubById, subject;
|
||||
mocks.attachTestHelpers();
|
||||
|
||||
setup(function() {
|
||||
fakeElement = document.createElement('div');
|
||||
fakeElement.style.cssText = 'height: 100px; display: block;';
|
||||
stubById = this.sinon.stub(document, 'getElementById')
|
||||
.returns(fakeElement.cloneNode(true));
|
||||
subject = new Provider();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stubById.restore();
|
||||
});
|
||||
|
||||
suite('constructor', function() {
|
||||
test('everything looks ok', function() {
|
||||
assert.ok(subject.name);
|
||||
});
|
||||
});
|
||||
|
||||
suite('init', function() {
|
||||
test('sets up container', function() {
|
||||
subject.init();
|
||||
assert.ok(subject.container);
|
||||
});
|
||||
});
|
||||
|
||||
suite('render', function() {
|
||||
test('escapes HTML', function() {
|
||||
subject.init();
|
||||
subject.render([{
|
||||
title: '<hello>',
|
||||
meta: '<world>',
|
||||
dataset: {}
|
||||
}]);
|
||||
|
||||
var title = subject.container.querySelector('.title');
|
||||
assert.equal(title.innerHTML, '<hello>');
|
||||
|
||||
var meta = subject.container.querySelector('.meta');
|
||||
assert.equal(meta.innerHTML, '<world>');
|
||||
});
|
||||
});
|
||||
|
||||
suite('clear', function() {
|
||||
test('clears container', function() {
|
||||
subject.init();
|
||||
subject.container.innerHTML = 'not empty';
|
||||
subject.clear();
|
||||
assert.equal(subject.container.innerHTML, '');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
37
gaia/apps/search/test/unit/providers/suggestions_test.js
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
/* global MocksHelper, Search */
|
||||
|
||||
requireApp('search/test/unit/mock_search.js');
|
||||
requireApp('search/js/providers/provider.js');
|
||||
require('/shared/js/metrics_helper.js');
|
||||
requireApp('search/test/unit/mock_provider.js');
|
||||
require('/shared/test/unit/mocks/mock_search_provider.js');
|
||||
|
||||
var mocksForSuggestionsProvider = new MocksHelper([
|
||||
'SearchProvider'
|
||||
]).init();
|
||||
|
||||
suite('search/providers/suggestions', function() {
|
||||
mocksForSuggestionsProvider.attachTestHelpers();
|
||||
var subject;
|
||||
requireApp('search/js/providers/suggestions.js', function(done) {
|
||||
subject = Search.providers.Suggestions;
|
||||
subject.init();
|
||||
done();
|
||||
});
|
||||
|
||||
suite('click', function() {
|
||||
test('should report to metrics', function() {
|
||||
var stub = this.sinon.stub(window.MetricsHelper.prototype, 'report');
|
||||
subject.click({
|
||||
target: {
|
||||
dataset: {
|
||||
suggestions: 'mozilla'
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.ok(stub.calledWith('websearch', 'testProvider'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
734
gaia/apps/search/test/unit/search_test.js
Executable file
|
|
@ -0,0 +1,734 @@
|
|||
'use strict';
|
||||
/* global MockNavigatormozApps, MockNavigatormozSetMessageHandler,
|
||||
Search, MockProvider, MockasyncStorage, Promise, MocksHelper */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_apps.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_set_message_handler.js');
|
||||
require('/shared/test/unit/mocks/mock_moz_activity.js');
|
||||
require('/shared/test/unit/mocks/mock_search_provider.js');
|
||||
require('/shared/js/url_helper.js');
|
||||
require('/shared/js/dedupe.js');
|
||||
require('/js/contextmenu.js');
|
||||
require('/shared/js/metrics_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_async_storage.js');
|
||||
requireApp('search/test/unit/mock_provider.js');
|
||||
|
||||
var mocksForSearch = new MocksHelper([
|
||||
'SearchProvider'
|
||||
]).init();
|
||||
|
||||
suite('search/search', function() {
|
||||
var realAsyncStorage;
|
||||
var realMozApps;
|
||||
var realSetMessageHandler;
|
||||
var realOnLine;
|
||||
|
||||
var clock;
|
||||
mocksForSearch.attachTestHelpers();
|
||||
|
||||
function removeProvider(provider) {
|
||||
delete Search.providers[provider.name];
|
||||
}
|
||||
|
||||
suiteSetup(function(done) {
|
||||
loadBodyHTML('/index.html');
|
||||
|
||||
realSetMessageHandler = navigator.mozSetMessageHandler;
|
||||
navigator.mozSetMessageHandler = MockNavigatormozSetMessageHandler;
|
||||
|
||||
realMozApps = navigator.mozApps;
|
||||
navigator.mozApps = MockNavigatormozApps;
|
||||
|
||||
realAsyncStorage = window.asyncStorage;
|
||||
window.asyncStorage = MockasyncStorage;
|
||||
|
||||
window.SettingsListener = {
|
||||
observe: function() {}
|
||||
};
|
||||
|
||||
clock = sinon.useFakeTimers();
|
||||
|
||||
requireApp('search/js/search.js', function() {
|
||||
// assert.equal(Search.toShowNotice, null);
|
||||
// Search.toShowNotice = false;
|
||||
|
||||
Search._port = { postMessage: function() {} };
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSetMessageHandler = realSetMessageHandler;
|
||||
navigator.mozApps = realMozApps;
|
||||
window.asyncStorage = realAsyncStorage;
|
||||
clock.restore();
|
||||
delete window.SettingsListener;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockNavigatormozSetMessageHandler.mSetup();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockNavigatormozSetMessageHandler.mTeardown();
|
||||
MockNavigatormozApps.mTeardown();
|
||||
});
|
||||
|
||||
suite('init', function() {
|
||||
test('will call provider init method', function() {
|
||||
var initCalled;
|
||||
this.sinon.spy(window.MetricsHelper.prototype, 'init');
|
||||
this.sinon.spy(navigator, 'mozSetMessageHandler');
|
||||
|
||||
Search.providers = [{
|
||||
init: function() {
|
||||
initCalled = true;
|
||||
},
|
||||
search: function() {},
|
||||
abort: function() {}
|
||||
}];
|
||||
|
||||
Search.init();
|
||||
MockNavigatormozApps.mTriggerLastRequestSuccess();
|
||||
assert.equal(MockNavigatormozApps.mLastConnectionKeyword,
|
||||
'search-results');
|
||||
MockNavigatormozApps.mLastConnectionCallback([]);
|
||||
assert.ok(initCalled);
|
||||
Search.providers = [];
|
||||
assert.isFalse(Search.suggestionsWrapper.classList.contains('offline'));
|
||||
assert.ok(window.MetricsHelper.prototype.init.calledOnce);
|
||||
assert.ok(navigator.mozSetMessageHandler.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('offline', function() {
|
||||
suiteSetup(function() {
|
||||
realOnLine = Object.getOwnPropertyDescriptor(navigator, 'onLine');
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
configurable: true,
|
||||
get: function() { return false; }
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
if (realOnLine) {
|
||||
Object.defineProperty(navigator, 'onLine', realOnLine);
|
||||
}
|
||||
});
|
||||
|
||||
test('init while offline', function() {
|
||||
this.sinon.spy(Search, 'initConnectivityCheck');
|
||||
Search.init();
|
||||
|
||||
assert.ok(Search.initConnectivityCheck.calledOnce);
|
||||
assert.isTrue(Search.searchResults.classList.contains('offline'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('provider', function() {
|
||||
test('increments number of providers', function() {
|
||||
function numProviders() {
|
||||
return Object.keys(Search.providers).length;
|
||||
}
|
||||
|
||||
var count = numProviders();
|
||||
Search.provider({
|
||||
name: 'Foo'
|
||||
});
|
||||
assert.equal(count + 1, numProviders());
|
||||
|
||||
removeProvider({
|
||||
name: 'Foo'
|
||||
});
|
||||
assert.equal(count, numProviders());
|
||||
});
|
||||
});
|
||||
|
||||
suite('initNotice', function() {
|
||||
test('gets the settingsLink on every call', function() {
|
||||
var stub = this.sinon.stub(document, 'getElementById').returns({
|
||||
addEventListener: this.sinon.spy()
|
||||
});
|
||||
Search.initNotice();
|
||||
Search.initNotice();
|
||||
assert.isTrue(stub.withArgs('settings-link').callCount === 2);
|
||||
});
|
||||
});
|
||||
|
||||
suite('scrolling focus', function() {
|
||||
test('we grab focus when scrolling results', function() {
|
||||
var stub = this.sinon.stub(window, 'focus');
|
||||
window.dispatchEvent(new window.Event('scroll'));
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('dispatchMessage', function() {
|
||||
test('dispatches messages based on action', function() {
|
||||
var stub = this.sinon.stub(Search, 'change');
|
||||
Search.dispatchMessage({
|
||||
data: {
|
||||
action: 'change',
|
||||
input: 'http://mozilla.org'
|
||||
}
|
||||
});
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('change', function() {
|
||||
setup(function() {
|
||||
var fakeProvider = {
|
||||
name: 'Foo',
|
||||
search: function() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
abort: function() {},
|
||||
clear: function() {}
|
||||
};
|
||||
Search.provider(fakeProvider);
|
||||
});
|
||||
|
||||
test('only searches once if called twice rapidly', function() {
|
||||
var stub = this.sinon.stub(Search.providers.Foo, 'search').returns(
|
||||
new Promise(() => {}));
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'a'
|
||||
}
|
||||
});
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'a'
|
||||
}
|
||||
});
|
||||
assert.ok(stub.notCalled);
|
||||
clock.tick(1000); // For typing timeout
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
|
||||
test('full URLs do not get sent', function() {
|
||||
var remoteProvider = {
|
||||
name: 'remoteguy',
|
||||
remote: true,
|
||||
search: function() {},
|
||||
abort: function() {},
|
||||
clear: function() {}
|
||||
};
|
||||
Search.provider(remoteProvider);
|
||||
Search.suggestionsEnabled = true;
|
||||
|
||||
var stub = this.sinon.stub(Search.providers.remoteguy, 'search');
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'http://mozilla.org'
|
||||
}
|
||||
});
|
||||
clock.tick(1000); // For typing timeout
|
||||
assert.ok(stub.notCalled);
|
||||
});
|
||||
|
||||
test('aborting will cancel search timeout', function() {
|
||||
var stub = this.sinon.stub(Search.providers.Foo, 'search');
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'a'
|
||||
}
|
||||
});
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'a'
|
||||
}
|
||||
});
|
||||
assert.ok(stub.notCalled);
|
||||
Search.abort();
|
||||
clock.tick(1000); // For typing timeout
|
||||
assert.ok(stub.notCalled);
|
||||
});
|
||||
|
||||
test('when a private browser', function() {
|
||||
var remoteProvider = {
|
||||
name: 'remoteguy',
|
||||
remote: true,
|
||||
search: function() {},
|
||||
abort: function() {},
|
||||
clear: function() {}
|
||||
};
|
||||
Search.provider(remoteProvider);
|
||||
Search.suggestionsEnabled = true;
|
||||
|
||||
var stub = this.sinon.stub(Search.providers.remoteguy, 'search');
|
||||
Search.change({
|
||||
data: {
|
||||
input: 'search me',
|
||||
isPrivateBrowser: true
|
||||
}
|
||||
});
|
||||
clock.tick(1000); // For typing timeout
|
||||
assert.ok(stub.notCalled);
|
||||
});
|
||||
});
|
||||
|
||||
suite('submit', function() {
|
||||
test('Navigates to a URL', function() {
|
||||
var stub = this.sinon.stub(Search, 'navigate');
|
||||
Search.dispatchMessage({
|
||||
data: {
|
||||
action: 'submit',
|
||||
input: 'http://mozilla.org'
|
||||
}
|
||||
});
|
||||
clock.tick(1000); // For typing timeout
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
|
||||
test('Fires correct Event to metrics search for search term', function() {
|
||||
var stub = this.sinon.stub(window.MetricsHelper.prototype, 'report');
|
||||
|
||||
Search.dispatchMessage({
|
||||
data: {
|
||||
action: 'submit',
|
||||
input: 'searchterm'
|
||||
}
|
||||
});
|
||||
clock.tick(1000); // For typing timeout
|
||||
|
||||
assert.ok(stub.calledWith('websearch', 'testProvider'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('clear', function() {
|
||||
var called = 0;
|
||||
|
||||
suiteSetup(function() {
|
||||
Search.providers = {
|
||||
Fake: {
|
||||
clear: function() {
|
||||
called++;
|
||||
},
|
||||
abort: function() {}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
test('calls the provider.clear method', function() {
|
||||
Search.clear();
|
||||
assert.equal(called, 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('abort', function() {
|
||||
test('calls abort method of provider', function() {
|
||||
Search.providers = {
|
||||
Fake: {
|
||||
clear: function() {},
|
||||
abort: function() {}
|
||||
}
|
||||
};
|
||||
var abortStub = this.sinon.stub(Search.providers.Fake, 'abort');
|
||||
Search.abort();
|
||||
assert.ok(abortStub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('close', function() {
|
||||
test('posts a message to the port', function() {
|
||||
var stub = this.sinon.stub(Search._port, 'postMessage');
|
||||
Search.close();
|
||||
assert.ok(stub.calledWith({action: 'hide'}));
|
||||
});
|
||||
});
|
||||
|
||||
suite('navigate', function() {
|
||||
test('Open activity is fired', function() {
|
||||
var url = 'http://mozilla.org';
|
||||
var stub = this.sinon.stub(window, 'open');
|
||||
Search.navigate(url);
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('expandSearch', function() {
|
||||
setup(function() {
|
||||
Search.providers = {
|
||||
WebResults: {
|
||||
clear: function() {},
|
||||
abort: function() {},
|
||||
search: function() {
|
||||
return new Promise(() => {});
|
||||
},
|
||||
fullscreen: function() {}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
test('calls search for WebResults', function() {
|
||||
var stub = this.sinon.stub(Search.providers.WebResults,'fullscreen');
|
||||
Search.expandSearch();
|
||||
assert.ok(stub.calledOnce);
|
||||
});
|
||||
});
|
||||
|
||||
suite('setInput', function() {
|
||||
test('posts a message to the port', function() {
|
||||
var stub = this.sinon.stub(Search._port, 'postMessage');
|
||||
this.sinon.stub(Search, 'expandSearch');
|
||||
Search.setInput('foo');
|
||||
assert.ok(stub.calledWith({action: 'input', input: 'foo'}));
|
||||
});
|
||||
});
|
||||
|
||||
suite('handleActivityEvents', function() {
|
||||
test('handle search activity', function() {
|
||||
this.sinon.stub(Search, 'submit');
|
||||
Search.handleActivityEvents({
|
||||
source: {
|
||||
name: 'search',
|
||||
data: {keyword: 'mozilla'}
|
||||
}
|
||||
});
|
||||
assert.ok(Search.submit.calledWith({data: {
|
||||
input: 'mozilla'
|
||||
}}));
|
||||
});
|
||||
});
|
||||
|
||||
suite('collect', function() {
|
||||
|
||||
setup(function() {
|
||||
Search.dedupe.exactResults = {};
|
||||
Search.dedupe.fuzzyResults = {};
|
||||
});
|
||||
|
||||
// Suppport functions
|
||||
function exactProvider() {
|
||||
return {
|
||||
dedupes: true,
|
||||
dedupeStrategy: 'exact',
|
||||
render: function() {}
|
||||
};
|
||||
}
|
||||
|
||||
function fuzzyProvider() {
|
||||
return {
|
||||
dedupes: true,
|
||||
dedupeStrategy: 'fuzzy',
|
||||
render: function() {}
|
||||
};
|
||||
}
|
||||
|
||||
test('provider does not de-dupe', function() {
|
||||
var results = [
|
||||
{dedupeId: 'a'},
|
||||
{dedupeId: 'b'}
|
||||
];
|
||||
|
||||
var provider = {
|
||||
dedupes: false,
|
||||
render: function() {}
|
||||
};
|
||||
|
||||
var renderStub = this.sinon.stub(provider, 'render');
|
||||
Search.collect(provider, results);
|
||||
assert.equal(renderStub.getCall(0).args[0].length, 2);
|
||||
});
|
||||
|
||||
test('when no de-duplication necessary', function() {
|
||||
var provider1 = exactProvider();
|
||||
var provider2 = exactProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, [{dedupeId: 'a'}]);
|
||||
Search.collect(provider2, [{dedupeId: 'b'}]);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 1);
|
||||
});
|
||||
|
||||
test('de-duplicates an exact manifestURL', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://mozilla.org/manifest.webapp'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://mozilla.org/manifest.webapp'}
|
||||
];
|
||||
|
||||
var provider1 = exactProvider();
|
||||
var provider2 = exactProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('de-duplicates a fuzzy match /w same domain', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://mozilla.org/manifest.webapp'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://mozilla.org/awesome/app'}
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('de-duplicates a fuzzy match /w subdomain', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://mozilla.org/manifest.webapp'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://omggame.mozilla.org/awesome/app'}
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('attempt to avoid de-duplicating second level domains', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://mozilla.co.org/manifest.webapp'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://bob.co.org/awesome/app'}
|
||||
];
|
||||
|
||||
var provider1 = exactProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 1);
|
||||
});
|
||||
|
||||
test('installed hosted app /w web result', function() {
|
||||
var results1 = [
|
||||
{dedupeId:
|
||||
'https://m.facebook.com/openwebapp/manifest.webapp'
|
||||
}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'https://www.facebook.com/'}
|
||||
];
|
||||
|
||||
var provider1 = exactProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('mis-matching domain against manifest url', function() {
|
||||
var results1 = [
|
||||
{dedupeId:
|
||||
'https://bits.wikimedia.org/wikipediamobilefirefoxos/manifest.webapp'
|
||||
}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://en.m.wikipedia.org'}
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('common domain parts filtered out', function() {
|
||||
Search.dedupe.fuzzyResults = {
|
||||
'touch': true,
|
||||
'mobile': true
|
||||
};
|
||||
|
||||
var results1 = [
|
||||
{dedupeId: 'https://touch.www.linkedin.com/login.html'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'https://touch.mozilla.org/fake.html'}
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 1);
|
||||
});
|
||||
|
||||
test('bug 1030713 - subdomain dededupe matching', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://translate.google.com'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://mail.google.com/tasks'},
|
||||
{dedupeId: 'http://drive.google.com/keep'},
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 2);
|
||||
});
|
||||
|
||||
test('bug 1030713 - subdomain dededupe matching with path', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'http://drive.google.com/keep'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'http://mail.google.com/tasks'},
|
||||
{dedupeId: 'http://translate.google.com'},
|
||||
];
|
||||
|
||||
var provider1 = fuzzyProvider();
|
||||
var provider2 = fuzzyProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 2);
|
||||
});
|
||||
|
||||
test('exact provider does not de-dupe against itself', function() {
|
||||
var results = [
|
||||
{dedupeId: 'https://mozilla.org/index.html'},
|
||||
{dedupeId: 'https://mozilla.org/sauce.html'}
|
||||
];
|
||||
|
||||
var provider = exactProvider();
|
||||
|
||||
var renderStub = this.sinon.stub(provider, 'render');
|
||||
Search.collect(provider, results);
|
||||
assert.equal(renderStub.getCall(0).args[0].length, 2);
|
||||
});
|
||||
|
||||
test('fuzzy provider does not de-dupe against itself', function() {
|
||||
var results = [
|
||||
{dedupeId: 'https://mozilla.org/index.html'},
|
||||
{dedupeId: 'https://mozilla.org/sauce.html'}
|
||||
];
|
||||
|
||||
var provider = fuzzyProvider();
|
||||
|
||||
var renderStub = this.sinon.stub(provider, 'render');
|
||||
Search.collect(provider, results);
|
||||
assert.equal(renderStub.getCall(0).args[0].length, 2);
|
||||
});
|
||||
|
||||
test('exact provider ignores querystring', function() {
|
||||
var results1 = [
|
||||
{dedupeId: 'https://mozilla.org/index.html?ignoreme=true'}
|
||||
];
|
||||
|
||||
var results2 = [
|
||||
{dedupeId: 'https://mozilla.org/index.html'}
|
||||
];
|
||||
|
||||
var provider1 = exactProvider();
|
||||
var provider2 = exactProvider();
|
||||
|
||||
var renderStub1 = this.sinon.stub(provider1, 'render');
|
||||
var renderStub2 = this.sinon.stub(provider2, 'render');
|
||||
Search.collect(provider1, results1);
|
||||
Search.collect(provider2, results2);
|
||||
assert.equal(renderStub1.getCall(0).args[0].length, 1);
|
||||
assert.equal(renderStub2.getCall(0).args[0].length, 0);
|
||||
});
|
||||
|
||||
test('Dont search remote providers when suggestions disabled', function() {
|
||||
var localProvider = new MockProvider('local');
|
||||
var remoteProvider = new MockProvider('remote');
|
||||
remoteProvider.remote = true;
|
||||
|
||||
var remoteStub = this.sinon.stub(remoteProvider, 'search')
|
||||
.returns(new Promise(() => {}));
|
||||
var localStub = this.sinon.stub(localProvider, 'search')
|
||||
.returns(new Promise(() => {}));
|
||||
|
||||
Search.provider(localProvider);
|
||||
Search.provider(remoteProvider);
|
||||
|
||||
Search.suggestionsEnabled = false;
|
||||
|
||||
Search.change({data: {input: 'test'}});
|
||||
clock.tick(1000);
|
||||
|
||||
assert.ok(remoteStub.notCalled);
|
||||
assert.ok(localStub.calledOnce);
|
||||
|
||||
removeProvider(localProvider);
|
||||
removeProvider(remoteProvider);
|
||||
});
|
||||
|
||||
test('Search all providers when suggestions enabled', function() {
|
||||
var localProvider = new MockProvider('local');
|
||||
var remoteProvider = new MockProvider('remote');
|
||||
remoteProvider.remote = true;
|
||||
|
||||
var remoteStub = this.sinon.stub(remoteProvider, 'search')
|
||||
.returns(new Promise(() => {}));
|
||||
var localStub = this.sinon.stub(localProvider, 'search')
|
||||
.returns(new Promise(() => {}));
|
||||
|
||||
Search.provider(localProvider);
|
||||
Search.provider(remoteProvider);
|
||||
|
||||
Search.suggestionsEnabled = true;
|
||||
Search.change({data: {input: 'test'}});
|
||||
clock.tick(1000);
|
||||
|
||||
assert.ok(remoteStub.calledOnce);
|
||||
assert.ok(localStub.calledOnce);
|
||||
|
||||
removeProvider(localProvider);
|
||||
removeProvider(remoteProvider);
|
||||
});
|
||||
});
|
||||
});
|
||||
4
gaia/apps/search/test/unit/setup.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
require('/shared/test/unit/load_body_html_helper.js');
|
||||
78
gaia/apps/search/test/unit/utilities_test.js
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
/* globals DateHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
requireApp('search/shared/js/utilities.js');
|
||||
|
||||
suite('Date Helper', function() {
|
||||
|
||||
function assertMidnight(date) {
|
||||
assert.equal(date.getHours(), 0);
|
||||
assert.equal(date.getMinutes(), 0);
|
||||
assert.equal(date.getSeconds(), 0);
|
||||
assert.equal(date.getMilliseconds(), 0);
|
||||
}
|
||||
|
||||
suite('started', function() {
|
||||
|
||||
test('todayStarted', function() {
|
||||
var now = new Date();
|
||||
var todayStartedTimestamp = DateHelper.todayStarted();
|
||||
var todayStarted = new Date(todayStartedTimestamp);
|
||||
assert.equal(todayStarted.getFullYear(), now.getFullYear());
|
||||
assert.equal(todayStarted.getMonth(), now.getMonth());
|
||||
assert.equal(todayStarted.getDate(), now.getDate());
|
||||
assertMidnight(todayStarted);
|
||||
});
|
||||
|
||||
test('yesterdayStarted', function() {
|
||||
var now = new Date();
|
||||
var yesterdayStartedTimestamp = DateHelper.yesterdayStarted();
|
||||
var yesterdayStarted = new Date(yesterdayStartedTimestamp);
|
||||
assert.equal(true, yesterdayStartedTimestamp <= now.valueOf());
|
||||
assertMidnight(yesterdayStarted);
|
||||
|
||||
});
|
||||
|
||||
test('thisWeekStarted', function() {
|
||||
var now = new Date();
|
||||
var weekStartedTimestamp = DateHelper.thisWeekStarted();
|
||||
var weekStarted = new Date(weekStartedTimestamp);
|
||||
assert.equal(true, weekStartedTimestamp <= now.valueOf());
|
||||
assert.equal(weekStarted.getDay(), 0);
|
||||
assertMidnight(weekStarted);
|
||||
});
|
||||
|
||||
test('thisMonthStarted', function() {
|
||||
var now = new Date();
|
||||
var monthStartedTimestamp = DateHelper.thisMonthStarted();
|
||||
var monthStarted = new Date(monthStartedTimestamp);
|
||||
assert.equal(true, monthStartedTimestamp <= now.valueOf());
|
||||
assert.equal(monthStarted.getFullYear(), now.getFullYear());
|
||||
assert.equal(monthStarted.getMonth(), now.getMonth());
|
||||
assert.equal(monthStarted.getDate(), 1);
|
||||
assertMidnight(monthStarted);
|
||||
});
|
||||
|
||||
test('lastSixMonthsStarted', function() {
|
||||
var sixMonthsStarted = DateHelper.lastSixMonthsStarted();
|
||||
var now = new Date().valueOf();
|
||||
var delta = now - sixMonthsStarted;
|
||||
assert.equal(true, delta >= 2629743830 * 6);
|
||||
assert.equal(true, delta < 2629743830 * 7);
|
||||
});
|
||||
|
||||
test('thisYearStarted', function() {
|
||||
var now = new Date();
|
||||
var yearStartedTimestamp = DateHelper.thisYearStarted();
|
||||
var yearStarted = new Date(yearStartedTimestamp);
|
||||
assert.equal(true, yearStartedTimestamp <= now.valueOf());
|
||||
assert.equal(yearStarted.getFullYear(), now.getFullYear());
|
||||
assert.equal(yearStarted.getMonth(), 0);
|
||||
assert.equal(yearStarted.getDate(), 1);
|
||||
assertMidnight(yearStarted);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||