This commit is contained in:
jrburke 2011-04-15 14:34:58 -07:00
Родитель 2953b4b4f9 06a960584d
Коммит 8878255ff7
19 изменённых файлов: 2581 добавлений и 590 удалений

Просмотреть файл

@ -36,7 +36,10 @@ function ($, object, fn, dispatch, rdapi, accounts) {
this.svcAccount = svcAccount;
this.callbacks = [];
this.lastUpdated = this.fromStore().lastUpdated;
this.fromStore(fn.bind(this, function (data) {
this.lastUpdated = data;
}));
// Time check is one day.
this.timeCheck = 24 * 60 * 60 * 1000;
@ -70,9 +73,11 @@ function ($, object, fn, dispatch, rdapi, accounts) {
/**
* Retrieves stored contacts. Should only be used internally or by subclasses.
*/
fromStore: function () {
fromStore: function (callback) {
var acct = this.svcAccount;
return accounts.getData(acct.domain, acct.userid, acct.username, 'contacts') || {};
accounts.getData(acct.domain, acct.userid, acct.username, 'contacts', function (data) {
callback(data || {});
});
},
/**
@ -112,53 +117,62 @@ function ($, object, fn, dispatch, rdapi, accounts) {
*/
notify: function (callback) {
this.callbacks.push(callback);
this.contacts = this.fromStore().list;
this.fromStore(fn.bind(this, function (data) {
if (!this.contacts || this.needFetch()) {
this.fetch();
} else {
this.notifyCallbacks();
}
this.contacts = data.list;
if (!this.contacts || this.needFetch()) {
this.fetch();
} else {
this.notifyCallbacks();
}
}));
},
fetch: function () {
var acct = this.svcAccount,
svcData = accounts.getService(acct.domain, acct.userid, acct.username);
var acct = this.svcAccount;
rdapi('contacts/' + acct.domain, {
type: 'POST',
domain: acct.domain,
data: {
username: acct.username,
userid: acct.userid,
startindex: 0,
maxresults: 500,
account: JSON.stringify(svcData)
},
//Only wait for 10 seconds, then give up.
timeout: 10000,
success: fn.bind(this, function (json) {
//Transform data to a form usable by the front end.
if (json && !json.error) {
var entries = json.result.entry;
accounts.getService(acct.domain, acct.userid,
acct.username, fn.bind(this, function (svcData) {
this.contacts = this.getFormattedContacts(entries);
this.lastUpdated = (new Date()).getTime();
rdapi('contacts/' + acct.domain, {
type: 'POST',
data: {
username: acct.username,
userid: acct.userid,
startindex: 0,
maxresults: 500,
account: JSON.stringify(svcData)
},
//Only wait for 10 seconds, then give up.
timeout: 10000,
success: fn.bind(this, function (json) {
//Transform data to a form usable by the front end.
if (json && !json.error) {
var entries = json.result.entry;
this.toStore({
list: this.contacts
});
}
}),
error: fn.bind(this, function (xhr, textStatus, errorThrown) {
// does not matter what the error is, just eat it and hide
// the UI showing a wait.
// If xhr.status === 503, could do a retry, and dispatch a
// 'serverErrorPossibleRetry', but wait for UX to be worked out
// in https://bugzilla.mozilla.org/show_bug.cgi?id=642653
this.notifyCallbacks();
})
});
this.getFormattedContacts(entries,
fn.bind(this, function (contacts) {
this.contacts = contacts;
this.lastUpdated = (new Date()).getTime();
this.toStore({
list: this.contacts
});
})
);
}
}),
error: fn.bind(this, function (xhr, textStatus, errorThrown) {
// does not matter what the error is, just eat it and hide
// the UI showing a wait.
// If xhr.status === 503, could do a retry, and dispatch a
// 'serverErrorPossibleRetry', but wait for UX to be worked out
// in https://bugzilla.mozilla.org/show_bug.cgi?id=642653
this.notifyCallbacks();
})
});
}));
},
notifyCallbacks: function () {
@ -184,9 +198,11 @@ function ($, object, fn, dispatch, rdapi, accounts) {
/**
* Translates contact data from server into a format used on the client.
* @param {Array} entries
* @returns {Array}
* @param {Function} callback called once contacts are formatted. Could
* involve asyn operations. The callback will receive an array of contacts.
*
*/
getFormattedContacts: function (entries) {
getFormattedContacts: function (entries, callback) {
var data = [];
entries.forEach(function (entry) {
if (entry.accounts && entry.accounts.length) {
@ -200,7 +216,7 @@ function ($, object, fn, dispatch, rdapi, accounts) {
});
}
});
return data;
callback(data);
},
/**

Просмотреть файл

@ -63,7 +63,6 @@ function (object, Contacts, $, accounts, fn) {
incorporate: function (contactsText) {
var acct = this.svcAccount,
newContacts = [],
storedContacts,
contacts = contactsText.split(',');
contacts.forEach(fn.bind(this, function (contact) {
@ -83,50 +82,53 @@ function (object, Contacts, $, accounts, fn) {
if (newContacts.length) {
// update storage of manually entered contacts.
storedContacts = accounts.getData(acct.domain,
acct.userid,
acct.username,
'enteredContacts') || [];
accounts.getData(acct.domain, acct.userid, acct.username,
'enteredContacts', fn.bind(this, function (storedContacts) {
storedContacts = storedContacts.concat(newContacts);
accounts.setData(acct.domain, acct.userid, acct.username,
'enteredContacts', storedContacts);
storedContacts = storedContacts || [];
// update the master merged list of contacts.
this.contacts = this.contacts.concat(newContacts);
this.toStore({
list: this.contacts
});
storedContacts = storedContacts.concat(newContacts);
accounts.setData(acct.domain, acct.userid, acct.username,
'enteredContacts', storedContacts);
// update the master merged list of contacts.
this.contacts = this.contacts.concat(newContacts);
this.toStore({
list: this.contacts
});
}));
}
},
getFormattedContacts: function (entries) {
getFormattedContacts: function (entries, callback) {
var data = [],
acct = this.svcAccount,
storedContacts = accounts.getData(acct.domain,
acct.userid,
acct.username,
'enteredContacts');
acct = this.svcAccount;
// convert server data to the right format.
entries.forEach(function (entry) {
if (entry.emails && entry.emails.length) {
entry.emails.forEach(function (email) {
var displayName = entry.displayName ? entry.displayName : email.value;
data.push({
displayName: displayName,
email: email.value
});
accounts.getData(acct.domain, acct.userid, acct.username,
'enteredContacts', fn.bind(this, function (storedContacts) {
// convert server data to the right format.
entries.forEach(function (entry) {
if (entry.emails && entry.emails.length) {
entry.emails.forEach(function (email) {
var displayName = entry.displayName ?
entry.displayName : email.value;
data.push({
displayName: displayName,
email: email.value
});
});
}
});
}
});
// add in any manually saved email addresses.
if (storedContacts) {
data = data.concat(storedContacts);
}
// add in any manually saved email addresses.
if (storedContacts) {
data = data.concat(storedContacts);
}
return data;
callback(data);
})
);
}
};
});

Просмотреть файл

@ -35,187 +35,29 @@ function (storage, dispatch, rdapi, services) {
(username && cache.username === username));
}
function fromJson(value) {
if (value) {
value = JSON.parse(value);
function pubAccountsChanged() {
if (opener && !opener.closed) {
dispatch.pub('accountsChanged', null, opener);
}
return value;
dispatch.pub('accountsChanged');
}
var store = storage(), impl,
changeTypes = {
//localStorage is the most robust, since the change in localStorage
//can be listened to across windows.
// Listen for chrome storage changes, and if it is for
// serviceCache, translate that into a higher level message
// understood by the web modules.
dispatch.sub('storeNotifyChange', function (data) {
if (data.key === 'serviceCache') {
pubAccountsChanged();
}
});
'localStorage': {
dispatch.sub('storeNotifyRemoveAll', function (data) {
// If the storage has all been removed, then need to update
// since the serviceCache was also removed.
pubAccountsChanged();
});
accounts: function (ok, error) {
// accounts now simply provides existing accounts retreived during
// the oauth dances
var serviceCache = fromJson(store.serviceCache) || [];
//Call ok callback with current knowledge. If there is a change in the
//account info, then the changed event will be triggered later.
if (ok) {
ok(serviceCache);
}
},
update: function (account_data) {
// XXX TODO
// get the account and push it into localstore, don't overwrite, we
// get one account at a time here
// We write into serviceCache which will be used by api calls
// to send the auth keys
var serviceCache = fromJson(store.serviceCache) || [],
existing = false,
profile, p, a, acct;
// we store the entire object in serviceCache
if (serviceCache) {
for (a = 0; a < serviceCache.length; a++) {
if (isCacheMatch(serviceCache[a], account_data.domain,
account_data.userid, account_data.username)) {
serviceCache[a] = account_data;
existing = true;
break;
}
}
}
if (!existing) {
serviceCache.push(account_data);
}
store.serviceCache = JSON.stringify(serviceCache);
impl.changed();
},
remove: function (domain, userid, username) {
var serviceCache = fromJson(store.serviceCache),
i, cache, a, p, s, svc;
if (serviceCache) {
for (i = 0; (cache = serviceCache[i]); i++) {
if (isCacheMatch(cache, domain, userid, username)) {
serviceCache.splice(i, 1);
break;
}
}
store.serviceCache = JSON.stringify(serviceCache);
}
// clear the contacts cache
svc = services.domains[domain];
// remove this clearCache call when 3.6 is removed.
svc.clearCache(store);
// Delete auxillary data.
impl.clearData(domain, userid, username);
impl.changed();
},
/**
* Set auxillary data related to an account. Deleted when the account
* is deleted.
*/
setData: function (domain, userid, username, name, value) {
var key = [domain, userid, username].join('|') + 'Data',
data = fromJson(store[key]) || {};
if (value === undefined || value === null) {
delete data[name];
} else {
data[name] = value;
}
store[key] = JSON.stringify(data);
return value;
},
/**
* Get auxillary data related to an account.
*/
getData: function (domain, userid, username, name) {
var key = [domain, userid, username].join('|') + 'Data',
data = fromJson(store[key]) || {};
return data ? data[name] : null;
},
/**
* Clears auxillary data related to an account. Deleted when the account
* is deleted.
*/
clearData: function (domain, userid, username) {
var key = [domain, userid, username].join('|') + 'Data';
delete store[key];
},
getService: function (domain, userid, username) {
var serviceCache = fromJson(store.serviceCache),
i, cache;
if (serviceCache) {
for (i = 0; (cache = serviceCache[i]); i++) {
if (isCacheMatch(cache, domain, userid, username)) {
return cache;
}
}
}
return null;
},
changed: function () {
store.accountChanged = (new Date()).getTime();
//Force the onchange events to occur. Sometimes the storage
//events do not fire?
if (opener && !opener.closed) {
dispatch.pub('accountsChanged', null, opener);
}
dispatch.pub('accountsChanged');
},
onChange: function (action) {
//Listen to storage changes, and if a the accountChanged key
//changes, refresh.
var lastValue = store.accountChanged;
window.addEventListener('storage', function (evt) {
//Only refresh if the accounts were changed.
if (store.accountChanged !== lastValue) {
action();
}
}, false);
//Also use direct notification in case storage events fail.
dispatch.sub('accountsChanged', action);
}
},
//Some extensions mess with localStorage, so in that case, fall back to
//using dispatching.
'memory': {
accounts: function (ok, error) {
},
changed: function () {
//Use dispatching. Dispatch to current window, but also to an opener
//if available.
store.accountChanged = (new Date()).getTime();
if (opener) {
dispatch.pub('accountsChanged', null, opener);
}
dispatch.pub('accountsChanged');
},
onChange: function (action) {
dispatch.sub('accountsChanged', action);
}
}
};
impl = changeTypes[storage.type];
var store = storage();
/**
* Gets the accounts. Can use a cached value.
@ -223,15 +65,46 @@ function (storage, dispatch, rdapi, services) {
* @param {Function} error function to call if an error.
*/
function accounts(ok, error) {
return impl.accounts(ok, error);
store.get('serviceCache', function (data) {
if (ok) {
ok(data || []);
}
});
}
/**
* Updates the accounts from a json account object.
* @param {Object} cookie object to update from
*/
accounts.update = function (account_data) {
impl.update(account_data);
accounts.update = function (accountData) {
// get the account and push it into storage, don't overwrite, we
// get one account at a time here
// We write into serviceCache which will be used by api calls
// to send the auth keys
store.get('serviceCache', function (serviceCache) {
var existing = false,
a;
serviceCache = serviceCache || [];
// we store the entire object in serviceCache
if (serviceCache) {
for (a = 0; a < serviceCache.length; a++) {
if (isCacheMatch(serviceCache[a], accountData.domain,
accountData.userid, accountData.username)) {
serviceCache[a] = accountData;
existing = true;
break;
}
}
}
if (!existing) {
serviceCache.push(accountData);
}
store.set('serviceCache', serviceCache);
});
};
/**
@ -240,8 +113,24 @@ function (storage, dispatch, rdapi, services) {
* @param {string} userid
* @param {string} username
*/
accounts.remove = function (account, userid, username) {
impl.remove(account, userid, username);
accounts.remove = function (domain, userid, username) {
store.get('serviceCache', function (serviceCache) {
var i, cache;
if (serviceCache) {
for (i = 0; (cache = serviceCache[i]); i++) {
if (isCacheMatch(cache, domain, userid, username)) {
serviceCache.splice(i, 1);
break;
}
}
store.set('serviceCache', serviceCache);
}
// Delete auxillary data.
accounts.clearData(domain, userid, username);
});
};
/**
@ -249,9 +138,23 @@ function (storage, dispatch, rdapi, services) {
* @param {string} domain
* @param {string} userid
* @param {string} username
* @param {Function} callback the callback to call once date is retrieved.
*/
accounts.getService = function (account, userid, username) {
return impl.getService(account, userid, username);
accounts.getService = function (domain, userid, username, callback) {
store.get('serviceCache', function (serviceCache) {
var i, cache;
if (serviceCache) {
for (i = 0; (cache = serviceCache[i]); i++) {
if (isCacheMatch(cache, domain, userid, username)) {
callback(cache);
return;
}
}
}
callback(null);
});
};
/**
@ -259,7 +162,7 @@ function (storage, dispatch, rdapi, services) {
* info is no longer valid/expired.
*/
accounts.clear = function () {
delete store.serviceCache;
store.remove('serviceCache');
};
/**
@ -267,9 +170,27 @@ function (storage, dispatch, rdapi, services) {
* @param {string} domain
* @param {string} userid
* @param {string} username
* @param {string} name the data key name
* @param {Object} value the data to store at that key name.
*/
accounts.setData = function (account, userid, username, name, value) {
return impl.setData(account, userid, username, name, value);
accounts.setData = function (domain, userid, username, name, value) {
var key = [domain, userid, username].join('|') + 'Data';
store.get('key', function (data) {
data = data || {};
if (value === undefined || value === null) {
delete data[name];
} else {
data[name] = value;
}
store.set(key, data);
});
return value;
};
/**
@ -277,9 +198,17 @@ function (storage, dispatch, rdapi, services) {
* @param {string} domain
* @param {string} userid
* @param {string} username
* @param {string} name the key name for the auxillary data.
* @param {Function} callbac the callback to call when the data is retreived.
*/
accounts.getData = function (account, userid, username, name) {
return impl.getData(account, userid, username, name);
accounts.getData = function (domain, userid, username, name, callback) {
var key = [domain, userid, username].join('|') + 'Data';
store.get(key, function (data) {
data = data || {};
callback(data[name] || null);
});
};
/**
@ -288,15 +217,9 @@ function (storage, dispatch, rdapi, services) {
* @param {string} userid
* @param {string} username
*/
accounts.clearData = function (account, userid, username) {
return impl.clearData(account, userid, username);
};
/**
* Called when the cache of accounts has changed.
*/
accounts.changed = function () {
return impl.changed();
accounts.clearData = function (domain, userid, username) {
var key = [domain, userid, username].join('|') + 'Data';
store.remove(key);
};
/**
@ -311,7 +234,7 @@ function (storage, dispatch, rdapi, services) {
* Call it with no args to get the default behavior, page reload.
*/
accounts.onChange = function (action) {
return impl.onChange(action || defaultAction);
dispatch.sub('accountsChanged', action || defaultAction);
};
return accounts;

Просмотреть файл

@ -13,8 +13,8 @@
* dojo._toDom()
*/
define([ 'require', './object', './jig', 'module'],
function (require, object, jig, module) {
define([ 'require', './object', './fn', './jig', 'module'],
function (require, object, fn, jig, module) {
var tempNode,
baseAttrName = 'data-' + module.id.replace(/\//g, '-') + '-' +
@ -35,31 +35,70 @@ function (require, object, jig, module) {
init: function (data, relNode, position) {
object.mixin(this, data, true);
var asyncCreate, onFinishCreate;
//Start widget lifecycle
if (this.onCreate) {
this.onCreate();
asyncCreate = this.onCreate();
}
if (this.template) {
this.node = this.render();
if (this.onRender) {
this.onRender(relNode);
onFinishCreate = fn.bind(this, function () {
if (this.template) {
this.node = this.render();
if (this.onRender) {
this.onRender(relNode);
}
}
}
if (relNode && this.node) {
if (position === 'before') {
relNode.parentNode.insertBefore(this.node, relNode);
} else if (position === 'after') {
relNode.parentNode.insertBefore(this.node, relNode.nextSibling);
} else if (position === 'prepend' && relNode.firstChild) {
relNode.insertBefore(this.node, relNode.firstChild);
} else {
relNode.appendChild(this.node);
if (relNode && this.node) {
if (position === 'before') {
relNode.parentNode.insertBefore(this.node, relNode);
} else if (position === 'after') {
relNode.parentNode.insertBefore(this.node, relNode.nextSibling);
} else if (position === 'prepend' && relNode.firstChild) {
relNode.insertBefore(this.node, relNode.firstChild);
} else {
relNode.appendChild(this.node);
}
}
});
if (asyncCreate) {
asyncCreate.then(onFinishCreate);
this.asyncCreate = asyncCreate;
} else {
onFinishCreate();
}
},
// poor man deferred thingy for now. May want to swap out
// with a real promise implementation at some point.
makeCreateCallback: function () {
var p = {
_callbacks: [],
then: function (callback) {
if ('value' in p) {
callback(p.value);
} else {
p._callbacks.push(callback);
}
},
resolve: function (value) {
if ('value' in p) {
throw new Error ('Async widget already resolved');
} else {
// Setting the value
p.value = value;
p._callbacks.forEach(function (callback) {
callback(value);
});
}
}
};
return p;
},
render: function (relativeNode) {
var doc, child, renderedNode, id;
if (this.template) {

Просмотреть файл

@ -33,7 +33,8 @@
*/
define(['jquery'], function ($) {
var origin = location.protocol + "//" + location.host;
var origin = location.protocol + "//" + location.host,
wins = [];
return {
sub: function (topic, callback, win, targetOrigin) {
@ -72,10 +73,35 @@ define(['jquery'], function ($) {
pub: function (topic, data, win) {
win = win || window;
win.postMessage(JSON.stringify({
var text = JSON.stringify({
topic: topic,
data: data
}), origin);
}),
i, otherWin;
// Notify primary target.
win.postMessage(text, origin);
// notify other windows too, can go away if settings work is done
// in share panel.
if (wins.length) {
for (i = 0; (otherWin = wins[i]); i++) {
if (otherWin.closed) {
wins.splice(i, 1);
i -= 1;
} else {
otherWin.postMessage(text, origin);
}
}
}
},
// Used by settings page so that it can get all the same disptaches as
// the share panel, important since data storage is primarily accessed and
// data update events triggred in the share panel window. This code can
// go away if the settings work is done in the share panel.
trackWindow: function (win) {
wins.push(win);
}
};
});

Просмотреть файл

@ -22,17 +22,15 @@
* */
/*jslint indent: 2 */
/*global define: false, window: false, location: true, localStorage: false,
/*global define: false, window: false, location: true,
opener: false, setTimeout: false, navigator: false */
'use strict';
define([ 'blade/object', 'storage'],
function (object, storage) {
define([ 'blade/object'],
function (object) {
var newHotness = parseFloat(navigator.userAgent.split('Firefox/')[1]) >= 4,
store = storage(),
svcs, prop;
var svcs, prop;
function SvcBase(name, options) {
if (!name) {
@ -54,29 +52,6 @@ function (object, storage) {
}
SvcBase.constructor = SvcBase;
SvcBase.prototype = {
clearCache: function (store) {
//This first delete is only needed for 3.6 support.
//Remove the call in accounts.js when it is removed.
delete store[this.type + 'Contacts'];
},
//This method can be removed once 3.6 support is dropped.
getContacts: function (store) {
if (store[this.type + 'Contacts']) {
var contacts = JSON.parse(store[this.type + 'Contacts']);
return contacts;
}
return null;
},
//This method can be removed once 3.6 support is dropped.
setContacts: function (store, contacts) {
store[this.type + 'Contacts'] = JSON.stringify(contacts);
},
// stub function that should not return data for non-mail services
// for the FF 3.6 extension.
get36FormattedContacts: function () {
return null;
}
};
/* common functionality for email based services */
@ -276,14 +251,6 @@ function (object, storage) {
for (prop in svcs.domains) {
if (svcs.domains.hasOwnProperty(prop)) {
svcs.domainList.push(prop);
// Clear out the old contacts model.
// TODO: Remove this once the 3.6 add-on/UI is finally
// shut off.
if (newHotness) {
delete store[svcs.domains[prop].type + 'Contacts'];
delete store.contactsModelVersion;
}
}
}

Просмотреть файл

@ -25,7 +25,7 @@
/*global define: false, location: false */
"use strict";
define(['storage', 'blade/url'], function (storage, url) {
define(['blade/url'], function (url) {
var cache = {};
function shareOptions(str) {
@ -40,10 +40,9 @@ define(['storage', 'blade/url'], function (storage, url) {
}
var options = {},
store = storage(),
vimeoCdnRegExp = /vimeocdn\.com\//,
vimeoSourceRegExp = /clip_id=(\d+)/,
urlArgs, prop, source, videoId;
urlArgs, source, videoId;
if (str) {
urlArgs = url.queryToObject(str);
@ -84,22 +83,6 @@ define(['storage', 'blade/url'], function (storage, url) {
}
//END domain-specific hacks.
//Save the extension version in the localStorage, for use in
//other pages like settings.
if (options.version) {
store.extensionVersion = options.version;
}
//Save the preferences in localStorage, for use in
//other ppages like setting.
if (options.prefs) {
for (prop in options.prefs) {
if (options.prefs.hasOwnProperty(prop)) {
store['prefs.' + prop] = options.prefs[prop];
}
}
}
cache[str] = options;
return options;

Просмотреть файл

@ -25,28 +25,90 @@
/*global define: false, localStorage: false */
"use strict";
define([], function () {
var store = localStorage, type = 'localStorage';
define(['dispatch'], function (dispatch) {
var internalStore = {},
callbacks = {},
store;
//Capability detect for localStorage. At least one add-on does weird things
//with it.
try {
store.tempVar = 'temp';
//Test reading.
if (store.tempVar === 'temp') {}
//Test deletion.
delete store.tempVar;
} catch (e) {
//Just use a simple in-memory object. Not as nice, but code will still work.
store = {};
type = 'memory';
// Temporary workaround to allow separate tab of settings to still have
// access to the chrome storage. Not a good idea to do long term.
if (opener && !opener.closed && opener.require &&
(store = opener.require('storage'))) {
return store;
}
store = {
get: function (key, callback) {
var keyCallbacks;
if (key in internalStore) {
// favor internal storage when available.
callback(internalStore[key]);
} else {
// hold on to the callback until there is an answer.
keyCallbacks = callbacks[key];
if (!keyCallbacks) {
keyCallbacks = callbacks[key] = [];
// ask the real storage for it.
dispatch.pub('storeGet', key);
}
keyCallbacks.push(callback);
}
},
set: function (key, value) {
internalStore[key] = value;
dispatch.pub('storeSet', {
key: key,
value: value
});
},
remove: function (key) {
delete internalStore[key];
dispatch.pub('storeRemove', key);
}
};
dispatch.sub('storeGetReturn', function (data) {
var key = data.key,
value = data.value,
keyCallbacks = callbacks[key];
internalStore[key] = value;
if (keyCallbacks) {
keyCallbacks.forEach(function (callback) {
callback(value);
});
delete callbacks[key];
}
});
dispatch.sub('storeNotifyChange', function (data) {
if (data.value === null) {
delete internalStore[data.key];
} else {
internalStore[data.key] = data.value;
}
});
dispatch.sub('storeNotifyRemoveAll', function (data) {
// Reset the internal store
internalStore = {};
});
function storage() {
return store;
}
storage.type = type;
storage.type = 'chrome';
return storage;
});

Просмотреть файл

@ -80,6 +80,8 @@
<div id="settings" class="panel hidden">
<h1>Configure your sharing settings</h1>
Commented out for now.
<!--
<ul>
<li class="hbox">
<span class="key">F1</span>
@ -91,6 +93,7 @@
<input type="checkbox" id="bookmarking">
</li>
</ul>
-->
</div>
<div id="advanced" class="panel hidden">

Просмотреть файл

@ -33,9 +33,6 @@ function (require, $, fn, rdapi, oauth, jig,
dispatch, storage, accounts, dotCompare, url,
services, placeholder) {
var store = storage(),
shortenPrefs = store.shortenPrefs,
isGreaterThan072 = dotCompare(store.extensionVersion, "0.7.3") > -1,
isGreaterThan073 = dotCompare(store.extensionVersion, "0.7.4") > -1,
options = url.queryToObject(location.href.split('#')[1] || '') || {},
existingAccounts = {},
showNew = options.show === 'new';
@ -75,7 +72,9 @@ function (require, $, fn, rdapi, oauth, jig,
json.forEach(function (account) {
// protect against old style account data
if (typeof(account.profile) === 'undefined') return;
if (typeof(account.profile) === 'undefined') {
return;
}
html += jig('#accountTemplate', account.profile);
// remember which accounts already have an entry
@ -93,11 +92,9 @@ function (require, $, fn, rdapi, oauth, jig,
html = '';
services.domainList.forEach(function (domain) {
var data = services.domains[domain];
if (isGreaterThan073 || !existingAccounts[domain]) {
data.domain = domain;
data.enableSignOut = !data.forceLogin && existingAccounts[domain];
html += jig('#addTemplate', services.domains[domain]);
}
data.domain = domain;
data.enableSignOut = !data.forceLogin && existingAccounts[domain];
html += jig('#addTemplate', services.domains[domain]);
});
if (html) {
@ -129,7 +126,7 @@ function (require, $, fn, rdapi, oauth, jig,
var shortenDom = $('#shortenForm'),
bitlyCheckboxDom = $('#bitlyCheckbox'),
pref, node;
node;
//Function placed inside this function to get access to DOM variables.
@ -199,7 +196,7 @@ function (require, $, fn, rdapi, oauth, jig,
function resetShortenData() {
clearShortenData();
delete store.shortenPrefs;
store.remove('shortenPrefs');
hideShortenForm();
}
@ -209,13 +206,15 @@ function (require, $, fn, rdapi, oauth, jig,
$("#wrapper").css({ "min-height" : (h) });
});
if (shortenPrefs) {
shortenPrefs = JSON.parse(shortenPrefs);
setShortenData(shortenPrefs);
showShortenForm();
} else {
hideShortenForm();
}
store.get('shortenPrefs', function (shortenPrefs) {
if (shortenPrefs) {
shortenPrefs = JSON.parse(shortenPrefs);
setShortenData(shortenPrefs);
showShortenForm();
} else {
hideShortenForm();
}
});
$('body')
.delegate('#bitlyCheckbox', 'click', function (evt) {
@ -242,15 +241,15 @@ function (require, $, fn, rdapi, oauth, jig,
dataType: 'json',
success: function (json) {
if (json.status_code === 200 && json.data.valid) {
store.shortenPrefs = JSON.stringify(data);
store.set('shortenPrefs', JSON.stringify(data));
} else {
$('#bitlyNotValid').removeClass('hidden');
delete store.shortenPrefs;
store.remove('shortenPrefs');
}
},
error: function (xhr, textStatus, errorThrown) {
$('#bitlyNotValid').removeClass('hidden');
delete store.shortenPrefs;
store.remove('shortenPrefs');
}
});
@ -274,7 +273,7 @@ function (require, $, fn, rdapi, oauth, jig,
if (success) {
//Make sure to bring the user back to this service if
//the auth is successful.
store.lastSelection = selectionName;
store.set('lastSelection', selectionName);
} else {
showStatus('statusOAuthFailed');
}
@ -295,31 +294,8 @@ function (require, $, fn, rdapi, oauth, jig,
accounts.clear();
}
evt.preventDefault();
})
.delegate('#settings [type="checkbox"]', 'click', function (evt) {
//Listen for changes in prefs and update localStorage, inform opener
//of changes.
var node = evt.target,
prefId = node.id,
value = node.checked;
store['prefs.' + prefId] = value;
if (opener && !opener.closed) {
dispatch.pub('prefChanged', {
name: prefId,
value: value
}, opener);
}
});
//Set up state of the prefs.
pref = store['prefs.use_accel_key'];
pref = pref ? JSON.parse(pref) : false;
$('#use_accel_key')[0].checked = pref || false;
pref = store['prefs.bookmarking'];
pref = pref ? JSON.parse(pref) : false;
$('#bookmarking')[0].checked = pref || false;
// create ellipsis for gecko
$(function () {
$(".overflow").textOverflow(null, true);
@ -328,12 +304,8 @@ function (require, $, fn, rdapi, oauth, jig,
// tabs
// Only show settings if extension can actually handle setting of them.
// Same for advanced.
if (isGreaterThan072) {
$('li[data-tab="settings"]').removeClass('hidden');
}
if (isGreaterThan073) {
$('li[data-tab="advanced"]').removeClass('hidden');
}
$('li[data-tab="settings"]').removeClass('hidden');
$('li[data-tab="advanced"]').removeClass('hidden');
$('body')
// Set up tab switching behavior.
@ -382,5 +354,15 @@ function (require, $, fn, rdapi, oauth, jig,
'&output=json&' +
'q=http%3A%2F%2Fmozillalabs.com%2Fmessaging%2Ffeed%2F';
$('head')[0].appendChild(node);
// Make sure this window gets all events, particularly related to storage.
// This can go away if the settings work is done inside the share panel.
// Use a setTimeout because the opener could be reloading, for instance,
// after an account is added.
if (opener && !opener.closed && opener.require) {
setTimeout(function () {
opener.require('dispatch').trackWindow(window);
}, 1000);
}
});
});

Просмотреть файл

@ -23,9 +23,15 @@
/*jslint plusplus: false, indent: 2, nomen: false */
/*global require: false, define: false, location: true, window: false, alert: false,
document: false, setTimeout: false, localStorage: false */
document: false, setTimeout: false, localStorage: false, parent: false */
"use strict";
// Allow tests to plug into the page by notify them if this is a test.
if (location.hash === '#test') {
parent.postMessage(JSON.stringify({topic: 'registerForTests'}),
location.protocol + "//" + location.host);
}
require({
paths: {
widgets: '../share/panel/scripts/widgets'
@ -43,7 +49,7 @@ function (require, $, object, fn, rdapi, oauth,
DebugPanel, AccountPanel, dotCompare) {
var actions = services.domains,
options, bodyDom, timer, pageInfo, sendData, showNew,
options, bodyDom, pageInfo, sendData, showNew,
onFirstShareState = null,
accountPanels = [],
store = storage(),
@ -55,7 +61,6 @@ function (require, $, object, fn, rdapi, oauth,
statusSharing: true,
statusShared: true
},
isGreaterThan076 = dotCompare(store.extensionVersion, "0.7.7") > -1,
// If it has been more than a day,
// refresh the UI, record a timestamp for it.
refreshStamp = (new Date()).getTime(),
@ -242,7 +247,7 @@ function (require, $, object, fn, rdapi, oauth,
} else if (json.error) {
showStatus('statusError', json.error.message);
} else {
store.lastSelection = actions[sendData.domain].type;
store.set('lastSelection', actions[sendData.domain].type);
showStatusShared();
//Be sure to delete sessionRestore data
accountPanels.forEach(function (panel) {
@ -282,53 +287,58 @@ function (require, $, object, fn, rdapi, oauth,
sendData = data;
var svcData = accounts.getService(data.domain, data.userid, data.username),
svcConfig = services.domains[data.domain],
shortenPrefs = store.shortenPrefs,
shortenData;
// get any shortener prefs before trying to send.
store.get('shortenPrefs', function (shortenPrefs) {
sendData.account = JSON.stringify(svcData);
accounts.getService(data.domain, data.userid, data.username,
function (svcData) {
// hide the panel now, but only if the extension can show status
// itself (0.7.7 or greater)
updateChromeStatus(SHARE_START);
if (isGreaterThan076) {
hide();
}
var svcConfig = services.domains[data.domain],
shortenData;
//First see if a bitly URL is needed.
if (svcConfig.shorten && shortenPrefs) {
shortenData = {
format: 'json',
longUrl: sendData.link
};
sendData.account = JSON.stringify(svcData);
// Unpack the user prefs
shortenPrefs = JSON.parse(shortenPrefs);
// hide the panel now, but only if the extension can show status
// itself (0.7.7 or greater)
updateChromeStatus(SHARE_START);
hide();
if (shortenPrefs) {
object.mixin(shortenData, shortenPrefs, true);
}
//First see if a bitly URL is needed.
if (svcConfig.shorten && shortenPrefs) {
shortenData = {
format: 'json',
longUrl: sendData.link
};
// Make sure the server does not try to shorten.
delete sendData.shorten;
// Unpack the user prefs
shortenPrefs = JSON.parse(shortenPrefs);
$.ajax({
url: 'http://api.bitly.com/v3/shorten',
type: 'GET',
data: shortenData,
dataType: 'json',
success: function (json) {
sendData.shorturl = json.data.url;
callSendApi();
},
error: function (xhr, textStatus, errorThrown) {
showStatus('statusShortenerError', errorThrown);
if (shortenPrefs) {
object.mixin(shortenData, shortenPrefs, true);
}
// Make sure the server does not try to shorten.
delete sendData.shorten;
$.ajax({
url: 'http://api.bitly.com/v3/shorten',
type: 'GET',
data: shortenData,
dataType: 'json',
success: function (json) {
sendData.shorturl = json.data.url;
callSendApi();
},
error: function (xhr, textStatus, errorThrown) {
showStatus('statusShortenerError', errorThrown);
}
});
} else {
callSendApi();
}
}
});
} else {
callSendApi();
}
);
});
}
/**
@ -343,67 +353,106 @@ function (require, $, object, fn, rdapi, oauth,
$('#shareui').removeClass('hidden');
//Figure out what accounts we do have
accounts.forEach(function (account) {
// protect against old style account data
if (typeof(account.profile) === 'undefined') {
return;
}
store.get('lastSelection', function (lastSelection) {
store.get('accountAdded', function (accountAdded) {
var domain = account.profile.accounts[0].domain,
data, PanelCtor;
var asyncCount = 0,
asyncConstructionDone = false,
accountPanel;
if (domain && actions[domain]) {
//Make sure to see if there is a match for last selection
if (actions[domain].type === store.lastSelection) {
lastSelectionMatch = i;
// Finishes account creation. Actually runs *after* the work done
// below this function. Need a function callback since AccountPanel
// construction is async.
function finishCreate() {
asyncCount -= 1;
// Could still be waiting for other async creations. If so, wait.
if (asyncCount > 0 || !asyncConstructionDone) {
return;
}
// add the account panels now
accountsDom.append(fragment);
//Add debug panel if it is allowed.
if (options.prefs.system === 'dev') {
debugPanel = new DebugPanel({}, accountsDom[0]);
}
checkBase64Preview();
//If no matching accounts match the last selection clear it.
if (lastSelectionMatch < 0 && !accountAdded && lastSelection) {
store.remove('lastSelection');
lastSelectionMatch = 0;
}
// which domain was last active?
$("#accounts").accordion({ active: lastSelectionMatch });
//Reset the just added state now that accounts have been configured one time.
if (accountAdded) {
store.remove('accountAdded');
}
//Inform extension the content size has changed, but use a delay,
//to allow any reflow/adjustments.
setTimeout(function () {
dispatch.pub('sizeToContent');
}, 100);
}
data = actions[domain];
data.domain = domain;
//Figure out what accounts we do have
accounts.forEach(function (account) {
// protect against old style account data
if (typeof(account.profile) === 'undefined') {
return;
}
// Get the contructor function for the panel.
PanelCtor = require(panelOverlayMap[domain] || 'widgets/AccountPanel');
var domain = account.profile.accounts[0].domain,
data, PanelCtor;
accountPanels.push(new PanelCtor({
options: options,
account: account,
svc: data
}, fragment));
}
if (domain && actions[domain]) {
//Make sure to see if there is a match for last selection
if (actions[domain].type === lastSelection) {
lastSelectionMatch = i;
}
i++;
data = actions[domain];
data.domain = domain;
// Get the contructor function for the panel.
PanelCtor = require(panelOverlayMap[domain] || 'widgets/AccountPanel');
accountPanel = new PanelCtor({
options: options,
account: account,
svc: data
}, fragment);
// if an async creation, then wait until all are created before
// proceeding with UI construction.
if (accountPanel.asyncCreate) {
asyncCount += 1;
accountPanel.asyncCreate.then(finishCreate);
}
accountPanels.push(accountPanel);
}
i++;
});
asyncConstructionDone = true;
// The async creation could have finished if all the data values
// for the account panels were already cached. If so, then finish
// out the UI construction.
if (!asyncCount) {
finishCreate();
}
});
});
// add the account panels now
accountsDom.append(fragment);
//Add debug panel if it is allowed.
if (options.prefs.system === 'dev') {
debugPanel = new DebugPanel({}, accountsDom[0]);
}
checkBase64Preview();
//If no matching accounts match the last selection clear it.
if (lastSelectionMatch < 0 && !store.accountAdded && store.lastSelection) {
delete store.lastSelection;
lastSelectionMatch = 0;
}
// which domain was last active?
$("#accounts").accordion({ active: lastSelectionMatch });
//Reset the just added state now that accounts have been configured one time.
if (store.accountAdded) {
delete store.accountAdded;
}
//Inform extension the content size has changed, but use a delay,
//to allow any reflow/adjustments.
setTimeout(function () {
dispatch.pub('sizeToContent');
}, 100);
}
function updateAccounts(accounts) {
@ -441,34 +490,11 @@ function (require, $, object, fn, rdapi, oauth,
}
} else {
showStatus('statusSettings');
//Clean up storage
services.domainList.forEach(function (domain) {
delete store[services.domains[domain].type + 'Contacts'];
});
dispatch.pub('sizeToContent');
}
}
//For the "new items" link, only show it for x number of days after showing it.
//NOTE: when updating for newer releases, delete the old value from the
//storage.
delete store.newTimerV1;
delete store.newTimerV2;
timer = store.newTimerV3;
if (!timer) {
store.newTimerV3 = (new Date()).getTime();
showNew = true;
} else {
timer = JSON.parse(timer);
//If time since first seen is greater than three days, hide the new link.
if ((new Date()).getTime() - timer < (3 * 24 * 60 * 60 * 1000)) {
showNew = true;
}
}
// Set up initialization work for the first share state passing.
onFirstShareState = function () {
// Wait until DOM ready to start the DOM work.

Просмотреть файл

@ -77,10 +77,11 @@ function (object, Widget, $, template,
shareTypeLabel: 'send to'
},
onCreate: function () {
onCreate: function (onAsynCreateDone) {
var profile = this.account.profile,
name = profile.displayName,
userName, savedOptions;
userName,
onFinishCreate = this.makeCreateCallback();
//Set up the svcAccount property
this.svcAccount = profile.accounts[0];
@ -94,58 +95,65 @@ function (object, Widget, $, template,
//Check for saved data. Only use if the URL
//and the account match
savedOptions = store[this.storeId];
if (savedOptions) {
savedOptions = JSON.parse(savedOptions);
store.get(this.storeId, fn.bind(this, function (savedOptions) {
if (savedOptions) {
savedOptions = JSON.parse(savedOptions);
if (this.theGameHasChanged(savedOptions)) {
this.clearSavedData();
savedOptions = null;
} else {
//Mix in the savedOptions with options.
this.options = object.create(this.options, [savedOptions]);
if (this.theGameHasChanged(savedOptions)) {
this.clearSavedData();
savedOptions = null;
} else {
//Mix in the savedOptions with options.
this.options = object.create(this.options, [savedOptions]);
}
}
}
//Set up the photo property
this.photo = profile.photos && profile.photos[0] && profile.photos[0].value;
//Set up the photo property
this.photo = profile.photos && profile.photos[0] && profile.photos[0].value;
//Set up nicer display name
// XXX for email services, we should show the email account, but we
// cannot rely on userid being a 'pretty' name we can display
userName = this.svcAccount.username;
if (userName && userName !== name) {
name = name + " (" + userName + ")";
}
this.displayName = name;
// Figure out what module will handle contacts.
this.contactsName = (this.svc.overlays &&
this.svc.overlays[this.contactsName]) ||
this.contactsName;
//Listen for options changes and update the account.
this.optionsChangedSub = dispatch.sub('optionsChanged', fn.bind(this, function (options) {
this.options = options;
this.optionsChanged();
}));
//Listen for updates to base64Preview
this.base64PreviewSub = dispatch.sub('base64Preview', fn.bind(this, function (dataUrl) {
$('[name="picture_base64"]', this.bodyNode).val(jigFuncs.rawBase64(dataUrl));
}));
// listen for successful send, and if so, update contacts list, if
// the send matches this account.
this.sendCompleteSub = dispatch.sub('sendComplete', fn.bind(this, function (data) {
var acct = this.svcAccount;
if (data.to && acct.domain === data.domain &&
acct.userid === data.userid &&
acct.username === data.username) {
this.contacts.incorporate(data.to);
//Set up nicer display name
// XXX for email services, we should show the email account, but we
// cannot rely on userid being a 'pretty' name we can display
userName = this.svcAccount.username;
if (userName && userName !== name) {
name = name + " (" + userName + ")";
}
this.displayName = name;
// Figure out what module will handle contacts.
this.contactsName = (this.svc.overlays &&
this.svc.overlays[this.contactsName]) ||
this.contactsName;
//Listen for options changes and update the account.
this.optionsChangedSub = dispatch.sub('optionsChanged', fn.bind(this, function (options) {
this.options = options;
this.optionsChanged();
}));
//Listen for updates to base64Preview
this.base64PreviewSub = dispatch.sub('base64Preview', fn.bind(this, function (dataUrl) {
$('[name="picture_base64"]', this.bodyNode).val(jigFuncs.rawBase64(dataUrl));
}));
// listen for successful send, and if so, update contacts list, if
// the send matches this account.
this.sendCompleteSub = dispatch.sub('sendComplete', fn.bind(this, function (data) {
var acct = this.svcAccount;
if (data.to && acct.domain === data.domain &&
acct.userid === data.userid &&
acct.username === data.username) {
this.contacts.incorporate(data.to);
}
}));
// indicate async creation is done.
onFinishCreate.resolve();
}));
// return onFinishCreate to indicate this is an async creation
return onFinishCreate;
},
destroy: function () {
@ -242,7 +250,7 @@ function (object, Widget, $, template,
clearSavedData: function () {
this.memStore = {};
delete store[this.storeId];
store.remove(this.storeId);
//Also clear up the form data.
var root = $(this.bodyNode);
@ -258,7 +266,7 @@ function (object, Widget, $, template,
saveData: function () {
var data = this.getFormData();
store[this.storeId] = JSON.stringify(data);
store.set(this.storeId, data);
},
validate: function (sendData) {

Просмотреть файл

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Fake Chrome Container</title>
<style>
iframe {
width: 350px;
height: 500px;
display: block;
float: left;
}
#chromeActions {
float: left;
padding: 20px;
}
#chromeActions button {
display: block;
margin: 10px;
}
</style>
<script data-main="chrome.js" src="../../1/scripts/requireplugins-jquery.js" charset="utf-8"></script>
</head>
<body>
<h1>Fake Chrome Container</h1>
<iframe id="testFrame" src="../../blank.html"></iframe>
<form id="chromeActions">
<button id="reloadPanel">Reload Panel</button>
<button id="logStore">Log Data Store to Console</button>
<button id="clearStore">Clear Data Store</button>
</form>
</body>
</html>

192
web/dev/tests/1/chrome.js Normal file
Просмотреть файл

@ -0,0 +1,192 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Raindrop.
*
* The Initial Developer of the Original Code is
* Mozilla Messaging, Inc..
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* */
/*jslint indent: 2 */
/*global define: false, window: false, console: false */
"use strict";
define(['jquery', 'dispatch'], function ($, dispatch) {
// debug
window.addEventListener('message', function (evt) {
console.log("GOT POSTMESSAGE: ", evt.data, evt);
}, false);
var sub = dispatch.sub,
testWindow, chrome,
dataStore = (localStorage.chromeTestStore &&
JSON.parse(localStorage.chromeTestStore)) || {},
origin = location.protocol + "//" + location.host;
// expose the data store for inspection in firebug
window.dataStore = dataStore;
chrome = {
saveStore: function () {
localStorage.chromeTestStore = JSON.stringify(dataStore);
},
logStore: function () {
console.log('Chrome dataStore: ', dataStore);
},
clearStore: function () {
delete localStorage.chromeTestStore;
dataStore = {};
},
loadPanel: function () {
testWindow.location = '../../1/share/panel/#test';
},
reloadPanel: function () {
testWindow.location.reload(true);
}
};
// Helpers that deal with the target window subscriptions.
function targetPub(topic, data) {
dispatch.pub(topic, data, testWindow);
}
// Listen for the event that the panel will give in test mode.
sub('registerForTests', function () {
var subs = {
panelReady: function () {
targetPub('shareState', {
status: 0,
open: true,
options: {
version: '0.7.2',
title: 'Firefox web browser',
description: 'All about firefox',
medium: null,
source: null,
url: 'http://www.mozilla.com/en-US/firefox/fx/',
canonicalUrl: null,
shortUrl: null,
previews: [{
http_url: 'http://mozcom-cdn.mozilla.net/img/firefox-100.jpg'
}],
siteName: '',
prefs: {
system: 'dev',
bookmarking: true,
use_accel_key: true
}
}
});
},
storeGet: function (key) {
var value = dataStore[key];
//JSON wants null.
if (value === undefined) {
value = null;
}
targetPub('storeGetReturn', {
key: key,
value: value
});
},
storeSet: function (data) {
dataStore[data.key] = data.value;
chrome.saveStore();
targetPub('storeNotifyChange', {
key: data.key,
value: data.value
});
},
storeRemove: function (key) {
delete dataStore[key];
chrome.saveStore();
targetPub('storeNotifyChange', {
key: key,
value: null
});
}
},
prop;
// register all events.
testWindow.addEventListener('message', function (evt) {
if (evt.origin === origin) {
var message;
try {
var message = JSON.parse(evt.data);
} catch (e) {
console.error('Could not JSON parse: ' + evt.data);
}
if (message && message.topic) {
if (subs[message.topic]) {
subs[message.topic](message.data);
} else {
// actually quite a few of these, uncomment if you want a play
// by play of topics going through the testWindow.
console.log("Unhandled topic: " + message.topic, message.data);
}
}
}
}, false);
});
window.addEventListener('load', function (evt) {
testWindow = $('#testFrame')[0].contentWindow;
// load the share panel
chrome.loadPanel();
// bind some event listeners, these just bind button IDs to trigger
// the same-named method on this chrome module.
$('body').delegate('#chromeActions button', 'click', function (evt) {
var id = evt.target.id;
if (id && chrome[id]) {
chrome[id]();
}
evt.preventDefault();
});
}, false);
// There is a chance it is already ready (particularly in webkit),
// so try to grab it now.
testWindow = $('#testFrame');
if (testWindow && (testWindow = testWindow[0])) {
testWindow = testWindow.contentWindow;
chrome.loadPanel();
} else {
testWindow = null;
}
// Return a module, then accessible via commandline as
// require('chrome')
return chrome;
});

Просмотреть файл

@ -0,0 +1,7 @@
# QUnit test for Firefox Share
qunit.js and qunit.css were fetched from http://code.jquery.com/qunit/git/
on 2011-24-03 at 3:40PM Pacific time.
Just open up index.html to run the tests. But serve the file from a web server,
do not run it locally, so that things like XMLHttpRequests can work properly.

Просмотреть файл

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="qunit.css" type="text/css" media="screen" />
<script src="../../scripts/requireplugins-jquery.js"></script>
<script src="qunit.js"></script>
<script>
// Tell QUnit to wait until everything is loaded.
QUnit.stop();
require({
baseUrl: '../../scripts'
});
require([
// List test files here.
'test-dotCompare.js'
], function () {
require.ready(function () {
// All modules loaded, and DOM ready, go!
QUnit.start();
});
});
</script>
</head>
<body>
<h1 id="qunit-header">QUnit test for Firefox Share</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,215 @@
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

1442
web/dev/tests/qunit/qunit.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,23 @@
/*jslint strict: false, indent: 2 */
/*global define: false, module: false, test: false, equals: false */
define(['dotCompare'], function (dotCompare) {
module('dotCompare');
test('A is bigger than B', function () {
equals(1, dotCompare('0.7.4', '0.7.1'));
equals(1, dotCompare('1.7.4', '0.8.5'));
});
test('B is bigger than A', function () {
equals(-1, dotCompare('0.7.4', '0.8.1'));
equals(-1, dotCompare('0.8.5', '1.7.4'));
});
test('A is equal to B', function () {
equals(0, dotCompare('0.7.4', '0.7.4'));
equals(0, dotCompare('0.0.1', '0.0.1'));
});
});