View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -6,12 +6,12 @@
from pyramid.view import view_config
from h.api import search as search_lib
from h.api.auth import get_user
from h.api.events import AnnotationEvent
from h.api.models import Annotation
from h.api.resources import Root
from h.api.resources import Annotations
import h.api.search
from h.api import nipsa
@@ -78,7 +78,7 @@ def search(request):
"""Search the database for annotations matching with the given query."""
# The search results are filtered for the authenticated user
user = get_user(request)
return h.api.search.search(request.params, user)
return search_lib.search(request.params, user)
@api_config(context=Root, name='access_token')
@@ -110,7 +110,7 @@ def annotations_index(request):
are ordered most recent first.
"""
user = get_user(request)
return h.api.search.index(user=user)
return search_lib.index(user=user)
@api_config(context=Annotations, request_method='POST', permission='create')
View
@@ -10,7 +10,7 @@ module.exports = function(config) {
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'chai-sinon'],
frameworks: ['mocha', 'chai', 'sinon'],
// list of files / patterns to load in the browser
@@ -25,6 +25,7 @@ module.exports = function(config) {
'lib/browser-action.js',
'lib/help-page.js',
'lib/hypothesis-chrome-extension.js',
'test/bootstrap.js',
'test/*-test.js',
'../../static/scripts/blocklist.js'
],
View
@@ -0,0 +1,2 @@
// Expose the sinon assertions.
sinon.assert.expose(assert, {prefix: null});
View
@@ -19,7 +19,7 @@ describe('BrowserAction', function () {
it('sets the active browser icon', function () {
var spy = fakeChromeBrowserAction.setIcon;
action.activate(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
path: BrowserAction.icons[TabState.states.ACTIVE],
});
@@ -28,7 +28,7 @@ describe('BrowserAction', function () {
it('sets the title of the browser icon', function () {
var spy = fakeChromeBrowserAction.setTitle;
action.activate(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
title: 'Hypothesis is active'
});
@@ -39,7 +39,7 @@ describe('BrowserAction', function () {
it('sets the inactive browser icon', function () {
var spy = fakeChromeBrowserAction.setIcon;
action.deactivate(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
path: BrowserAction.icons[TabState.states.INACTIVE],
});
@@ -48,7 +48,7 @@ describe('BrowserAction', function () {
it('sets the title of the browser icon', function () {
var spy = fakeChromeBrowserAction.setTitle;
action.deactivate(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
title: 'Hypothesis is inactive'
});
@@ -59,7 +59,7 @@ describe('BrowserAction', function () {
it('sets the inactive browser icon', function () {
var spy = fakeChromeBrowserAction.setIcon;
action.error(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
path: BrowserAction.icons[TabState.states.INACTIVE],
});
@@ -68,7 +68,7 @@ describe('BrowserAction', function () {
it('sets the title of the browser icon', function () {
var spy = fakeChromeBrowserAction.setTitle;
action.error(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
title: 'Hypothesis has failed to load'
});
@@ -77,7 +77,7 @@ describe('BrowserAction', function () {
it('shows a badge', function () {
var spy = fakeChromeBrowserAction.setBadgeText;
action.error(1);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
text: '!'
});
@@ -88,7 +88,7 @@ describe('BrowserAction', function () {
it('allows the state to be set via a constant', function () {
var spy = fakeChromeBrowserAction.setIcon;
action.setState(1, TabState.states.ACTIVE);
sinon.assert.calledWith(spy, {
assert.calledWith(spy, {
tabId: 1,
path: BrowserAction.icons[TabState.states.ACTIVE],
});
View
@@ -13,8 +13,8 @@ describe('HelpPage', function () {
describe('.showHelpForError', function () {
it('renders the "local-file" page when passed a LocalFileError', function () {
help.showHelpForError({id: 1, index: 1}, new h.LocalFileError('msg'));
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#local-file'
@@ -23,8 +23,8 @@ describe('HelpPage', function () {
it('renders the "no-file-access" page when passed a NoFileAccessError', function () {
help.showHelpForError({id: 1, index: 1}, new h.NoFileAccessError('msg'));
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#no-file-access'
@@ -33,8 +33,8 @@ describe('HelpPage', function () {
it('renders the "no-file-access" page when passed a RestrictedProtocolError', function () {
help.showHelpForError({id: 1, index: 1}, new h.RestrictedProtocolError('msg'));
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#restricted-protocol'
@@ -43,8 +43,8 @@ describe('HelpPage', function () {
it('renders the "blocked-site" page when passed a BlockedSiteError', function () {
help.showHelpForError({id: 1, index: 1}, new h.BlockedSiteError('msg'));
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#blocked-site'
@@ -61,8 +61,8 @@ describe('HelpPage', function () {
describe('.showLocalFileHelpPage', function () {
it('should load the help page with the "local-file" fragment', function () {
help.showLocalFileHelpPage({id: 1, index: 1});
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#local-file'
@@ -73,8 +73,8 @@ describe('HelpPage', function () {
describe('.showNoFileAccessHelpPage', function () {
it('should load the help page with the "no-file-access" fragment', function () {
help.showNoFileAccessHelpPage({id: 1, index: 1});
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#no-file-access'
@@ -85,8 +85,8 @@ describe('HelpPage', function () {
describe('.showRestrictedProtocolPage', function () {
it('should load the help page with the "restricted-protocol" fragment', function () {
help.showRestrictedProtocolPage({id: 1, index: 1});
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
index: 2,
openerTabId: 1,
url: 'CRX_PATH/help/index.html#restricted-protocol'
View
@@ -86,14 +86,14 @@ describe('HypothesisChromeExtension', function () {
it('sets up the state for tabs', function () {
tabs.push({id: 1, url: 'http://example.com'});
ext.install();
sinon.assert.calledWith(fakeTabState.deactivateTab, 1);
assert.calledWith(fakeTabState.deactivateTab, 1);
});
it('sets up the state for existing tabs', function () {
fakeTabState.isTabActive.returns(true);
tabs.push({id: 1, url: 'http://example.com'});
ext.install();
sinon.assert.calledWith(fakeTabState.activateTab, 1);
assert.calledWith(fakeTabState.activateTab, 1);
});
});
@@ -104,16 +104,16 @@ describe('HypothesisChromeExtension', function () {
it('opens a new tab pointing to the welcome page', function () {
ext.firstRun();
sinon.assert.called(fakeChromeTabs.create);
sinon.assert.calledWith(fakeChromeTabs.create, {
assert.called(fakeChromeTabs.create);
assert.calledWith(fakeChromeTabs.create, {
url: 'https://hypothes.is/welcome'
});
});
it('sets the browser state to active', function () {
ext.firstRun();
sinon.assert.called(fakeTabState.activateTab);
sinon.assert.calledWith(fakeTabState.activateTab, 1);
assert.called(fakeTabState.activateTab);
assert.calledWith(fakeTabState.activateTab, 1);
});
});
@@ -148,10 +148,10 @@ describe('HypothesisChromeExtension', function () {
it('sets up event listeners', function () {
ext.listen({addEventListener: sandbox.stub()});
sinon.assert.called(fakeChromeBrowserAction.onClicked.addListener);
sinon.assert.called(fakeChromeTabs.onCreated.addListener);
sinon.assert.called(fakeChromeTabs.onUpdated.addListener);
sinon.assert.called(fakeChromeTabs.onRemoved.addListener);
assert.called(fakeChromeBrowserAction.onClicked.addListener);
assert.called(fakeChromeTabs.onCreated.addListener);
assert.called(fakeChromeTabs.onUpdated.addListener);
assert.called(fakeChromeTabs.onRemoved.addListener);
});
describe('when a tab is created', function () {
@@ -162,7 +162,7 @@ describe('HypothesisChromeExtension', function () {
it('clears the new tab state', function () {
onCreatedHandler({id: 1, url: 'http://example.com/foo.html'});
sinon.assert.calledWith(fakeTabState.clearTab, 1);
assert.calledWith(fakeTabState.clearTab, 1);
});
});
@@ -181,43 +181,43 @@ describe('HypothesisChromeExtension', function () {
var tab = createTab();
fakeTabState.isTabActive.withArgs(1).returns(true);
onUpdatedHandler(tab.id, {status: 'complete'}, tab);
sinon.assert.calledWith(fakeSidebarInjector.injectIntoTab, tab);
assert.calledWith(fakeSidebarInjector.injectIntoTab, tab);
});
it('clears the tab state if the sidebar is not active', function () {
var tab = {id: 1, url: 'http://example.com/foo.html', status: 'complete'};
fakeTabState.isTabActive.withArgs(1).returns(false);
onUpdatedHandler(tab.id, {status: 'complete'}, tab);
sinon.assert.calledWith(fakeTabState.clearTab, tab.id);
assert.calledWith(fakeTabState.clearTab, tab.id);
});
it('updates the browser action to the active state when active', function () {
var tab = createTab();
fakeTabState.isTabActive.withArgs(1).returns(true);
onUpdatedHandler(tab.id, {status: 'complete'}, tab);
sinon.assert.called(fakeBrowserAction.activate);
sinon.assert.calledWith(fakeBrowserAction.activate, tab.id);
assert.called(fakeBrowserAction.activate);
assert.calledWith(fakeBrowserAction.activate, tab.id);
});
it('updates the browser action to the inactive state when inactive', function () {
var tab = createTab();
fakeTabState.isTabActive.withArgs(1).returns(false);
onUpdatedHandler(tab.id, {status: 'complete'}, tab);
sinon.assert.calledWith(fakeBrowserAction.deactivate, tab.id);
assert.calledWith(fakeBrowserAction.deactivate, tab.id);
});
it('restores the tab state if errored', function () {
var tab = createTab();
fakeTabState.isTabErrored.returns(true);
onUpdatedHandler(tab.id, {status: 'complete'}, tab);
sinon.assert.calledWith(fakeTabState.restorePreviousState, 1);
assert.calledWith(fakeTabState.restorePreviousState, 1);
});
it('does nothing until the tab status is complete', function () {
var tab = createTab();
fakeTabState.isTabActive.withArgs(1).returns(true);
onUpdatedHandler(tab.id, {status: 'loading'}, tab);
sinon.assert.notCalled(fakeSidebarInjector.injectIntoTab);
assert.notCalled(fakeSidebarInjector.injectIntoTab);
});
});
@@ -229,7 +229,7 @@ describe('HypothesisChromeExtension', function () {
it('clears the tab', function () {
onRemovedHandler(1);
sinon.assert.calledWith(fakeTabState.clearTab, 1);
assert.calledWith(fakeTabState.clearTab, 1);
});
});
@@ -241,15 +241,15 @@ describe('HypothesisChromeExtension', function () {
it('activate the tab if the tab is inactive', function () {
fakeTabState.isTabInactive.returns(true);
onClickedHandler({id: 1, url: 'http://example.com/foo.html'});
sinon.assert.called(fakeTabState.activateTab);
sinon.assert.calledWith(fakeTabState.activateTab, 1);
assert.called(fakeTabState.activateTab);
assert.calledWith(fakeTabState.activateTab, 1);
});
it('deactivate the tab if the tab is active', function () {
fakeTabState.isTabActive.returns(true);
onClickedHandler({id: 1, url: 'http://example.com/foo.html'});
sinon.assert.called(fakeTabState.deactivateTab);
sinon.assert.calledWith(fakeTabState.deactivateTab, 1);
assert.called(fakeTabState.deactivateTab);
assert.calledWith(fakeTabState.deactivateTab, 1);
});
describe('when a tab has an local-file error', function () {
@@ -261,8 +261,8 @@ describe('HypothesisChromeExtension', function () {
// Trigger failed render.
onUpdatedHandler(tab.id, {status: 'complete'}, tab).then(function () {
sinon.assert.called(fakeTabState.errorTab);
sinon.assert.calledWith(fakeTabState.errorTab, 1);
assert.called(fakeTabState.errorTab);
assert.calledWith(fakeTabState.errorTab, 1);
resolve();
});
});
@@ -274,8 +274,8 @@ describe('HypothesisChromeExtension', function () {
fakeTabState.isTabErrored.withArgs(1).returns(true);
onClickedHandler(tab);
sinon.assert.called(fakeHelpPage.showHelpForError);
sinon.assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.LocalFileError));
assert.called(fakeHelpPage.showHelpForError);
assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.LocalFileError));
});
});
@@ -288,8 +288,8 @@ describe('HypothesisChromeExtension', function () {
// Trigger failed render.
onUpdatedHandler(tab.id, {status: 'complete'}, tab).then(function () {
sinon.assert.called(fakeTabState.errorTab);
sinon.assert.calledWith(fakeTabState.errorTab, 1);
assert.called(fakeTabState.errorTab);
assert.calledWith(fakeTabState.errorTab, 1);
});
});
@@ -300,8 +300,8 @@ describe('HypothesisChromeExtension', function () {
fakeTabState.isTabErrored.withArgs(1).returns(true);
onClickedHandler(tab);
sinon.assert.called(fakeHelpPage.showHelpForError);
sinon.assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.NoFileAccessError));
assert.called(fakeHelpPage.showHelpForError);
assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.NoFileAccessError));
});
});
@@ -314,8 +314,8 @@ describe('HypothesisChromeExtension', function () {
// Trigger failed render.
onUpdatedHandler(tab.id, {status: 'complete'}, tab).then(function () {
sinon.assert.called(fakeTabState.errorTab);
sinon.assert.calledWith(fakeTabState.errorTab, 1);
assert.called(fakeTabState.errorTab);
assert.calledWith(fakeTabState.errorTab, 1);
});
});
@@ -326,8 +326,8 @@ describe('HypothesisChromeExtension', function () {
fakeTabState.isTabErrored.withArgs(1).returns(true);
onClickedHandler(tab);
sinon.assert.called(fakeHelpPage.showHelpForError);
sinon.assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.RestrictedProtocolError));
assert.called(fakeHelpPage.showHelpForError);
assert.calledWith(fakeHelpPage.showHelpForError, tab, sinon.match.instanceOf(h.RestrictedProtocolError));
});
});
@@ -346,64 +346,64 @@ describe('HypothesisChromeExtension', function () {
it('updates the browser icon', function () {
onChangeHandler(1, 'active', 'inactive');
sinon.assert.calledWith(fakeBrowserAction.setState, 1, 'active');
assert.calledWith(fakeBrowserAction.setState, 1, 'active');
});
it('updates the TabStore if the tab has not errored', function () {
onChangeHandler(1, 'active', 'inactive');
sinon.assert.calledWith(fakeTabStore.set, 1, 'active');
assert.calledWith(fakeTabStore.set, 1, 'active');
});
it('does not update the TabStore if the tab has errored', function () {
fakeTabState.isTabErrored.returns(true);
onChangeHandler(1, 'errored', 'inactive');
sinon.assert.notCalled(fakeTabStore.set);
assert.notCalled(fakeTabStore.set);
});
it('injects the sidebar if the tab has been activated', function () {
fakeTabState.isTabActive.returns(true);
onChangeHandler(1, 'active', 'inactive');
sinon.assert.calledWith(fakeSidebarInjector.injectIntoTab, tab);
assert.calledWith(fakeSidebarInjector.injectIntoTab, tab);
});
it('removes the sidebar if the tab has been deactivated', function () {
fakeTabState.isTabInactive.returns(true);
onChangeHandler(1, 'inactive', 'active');
sinon.assert.calledWith(fakeSidebarInjector.removeFromTab, tab);
assert.calledWith(fakeSidebarInjector.removeFromTab, tab);
});
it('does nothing with the sidebar if the tab is errored', function () {
fakeTabState.isTabErrored.returns(true);
onChangeHandler(1, 'errored', 'inactive');
sinon.assert.notCalled(fakeSidebarInjector.injectIntoTab);
sinon.assert.notCalled(fakeSidebarInjector.removeFromTab);
assert.notCalled(fakeSidebarInjector.injectIntoTab);
assert.notCalled(fakeSidebarInjector.removeFromTab);
});
it('does nothing if the tab is still loading', function () {
fakeChromeTabs.get = sandbox.stub().yields({id: 1, status: 'loading'});
onChangeHandler(1, 'active', 'inactive');
sinon.assert.notCalled(fakeSidebarInjector.injectIntoTab);
assert.notCalled(fakeSidebarInjector.injectIntoTab);
});
it('removes the tab from the store if the tab was closed', function () {
onChangeHandler(1, null, 'inactive');
sinon.assert.called(fakeTabStore.unset);
sinon.assert.calledWith(fakeTabStore.unset);
assert.called(fakeTabStore.unset);
assert.calledWith(fakeTabStore.unset);
});
describe('when a tab with an error is updated', function () {
it('resets the tab error state when no longer errored', function () {
var tab = {id: 1, url: 'file://foo.html', status: 'complete'};
onChangeHandler(1, 'active', 'errored');
sinon.assert.called(fakeTabErrorCache.unsetTabError);
sinon.assert.calledWith(fakeTabErrorCache.unsetTabError, 1);
assert.called(fakeTabErrorCache.unsetTabError);
assert.calledWith(fakeTabErrorCache.unsetTabError, 1);
});
it('resets the tab error state when the tab is closed', function () {
var tab = {id: 1, url: 'file://foo.html', status: 'complete'};
onChangeHandler(1, null, 'errored');
sinon.assert.called(fakeTabErrorCache.unsetTabError);
sinon.assert.calledWith(fakeTabErrorCache.unsetTabError, 1);
assert.called(fakeTabErrorCache.unsetTabError);
assert.calledWith(fakeTabErrorCache.unsetTabError, 1);
});
});
});
View
@@ -54,7 +54,7 @@ describe('SidebarInjector', function () {
var promise = injector.injectIntoTab({id: 1, url: url}).then(
assertReject, function (err) {
assert.instanceOf(err, h.RestrictedProtocolError);
sinon.assert.notCalled(spy);
assert.notCalled(spy);
}
);
this.server.respond();
@@ -70,7 +70,7 @@ describe('SidebarInjector', function () {
var promise = injector.injectIntoTab({id: 1, url: url});
this.server.respond();
return promise.then(function () {
sinon.assert.calledWith(spy, 1, {
assert.calledWith(spy, 1, {
url: 'CRX_PATH/content/web/viewer.html?file=' + encodeURIComponent(url)
});
});
@@ -85,11 +85,11 @@ describe('SidebarInjector', function () {
var promise = injector.injectIntoTab({id: 1, url: url});
this.server.respond();
return promise.then(function () {
sinon.assert.callCount(spy, 2);
sinon.assert.calledWith(spy, 1, {
assert.callCount(spy, 2);
assert.calledWith(spy, 1, {
code: sinon.match('/public/config.js')
});
sinon.assert.calledWith(spy, 1, {
assert.calledWith(spy, 1, {
code: sinon.match('/public/embed.js')
});
});
@@ -104,8 +104,8 @@ describe('SidebarInjector', function () {
var promise = injector.injectIntoTab({id: 1, url: url}).then(
function () {
sinon.assert.called(spy);
sinon.assert.calledWith(spy, 1, {
assert.called(spy);
assert.calledWith(spy, 1, {
url: 'CRX_PATH/content/web/viewer.html?file=' + encodeURIComponent('file://foo.pdf')
});
}
@@ -144,7 +144,7 @@ describe('SidebarInjector', function () {
it('retuns an error before loading the config', function () {
var url = 'file://foo.html';
var promise = injector.injectIntoTab({id: 1, url: url}).then(assertReject, function (err) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
this.server.respond();
return promise;
@@ -159,7 +159,7 @@ describe('SidebarInjector', function () {
this.server.respond();
return promise.then(
function onFulfill() {
sinon.assert.called(fakeChromeTabs.executeScript);
assert.called(fakeChromeTabs.executeScript);
},
function onRejected(reason) {
assert(false, "The promise should not be rejected");
@@ -172,7 +172,7 @@ describe('SidebarInjector', function () {
this.server.respond();
return promise.then(
function onFulfill() {
sinon.assert.called(fakeChromeTabs.executeScript);
assert.called(fakeChromeTabs.executeScript);
},
function onRejected(reason) {
assert(false, "The promise should not be rejected");
@@ -187,7 +187,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
@@ -200,7 +200,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
@@ -213,7 +213,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
@@ -226,7 +226,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
@@ -239,7 +239,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
@@ -252,7 +252,7 @@ describe('SidebarInjector', function () {
assert(false, "The promise should not be fulfilled");
},
function onRejected(reason) {
sinon.assert.notCalled(fakeChromeTabs.executeScript);
assert.notCalled(fakeChromeTabs.executeScript);
});
});
});
@@ -264,7 +264,7 @@ describe('SidebarInjector', function () {
var url = 'chrome://extensions/';
return injector.removeFromTab({id: 1, url: url}).then(function () {
sinon.assert.notCalled(spy);
assert.notCalled(spy);
});
});
@@ -275,7 +275,7 @@ describe('SidebarInjector', function () {
var url = protocol + '//foobar/';
return injector.removeFromTab({id: 1, url: url}).then(function () {
sinon.assert.notCalled(spy);
assert.notCalled(spy);
});
});
});
@@ -286,7 +286,7 @@ describe('SidebarInjector', function () {
var url = 'CRX_PATH/content/web/viewer.html?file=' + encodeURIComponent('http://example.com/foo.pdf');
return injector.removeFromTab({id: 1, url: url}).then(function () {
sinon.assert.calledWith(spy, 1, {
assert.calledWith(spy, 1, {
url: 'http://example.com/foo.pdf'
});
});
@@ -297,7 +297,7 @@ describe('SidebarInjector', function () {
it('injects a destroy script into the page', function () {
var stub = fakeChromeTabs.executeScript.yields([true]);
return injector.removeFromTab({id: 1, url: 'http://example.com/foo.html'}).then(function () {
sinon.assert.calledWith(stub, 1, {
assert.calledWith(stub, 1, {
code: sinon.match('/public/destroy.js')
});
});
View
@@ -40,14 +40,14 @@ describe('TabState', function () {
it('triggers an onchange handler', function () {
state.activateTab(2);
sinon.assert.calledWith(onChange, 2, TabState.states.ACTIVE, null);
assert.calledWith(onChange, 2, TabState.states.ACTIVE, null);
});
it('options.force can be used to re-trigger the current state', function () {
state.activateTab(2);
state.activateTab(2, {force: true});
sinon.assert.calledWith(onChange, 2, TabState.states.ACTIVE, null);
sinon.assert.calledTwice(onChange);
assert.calledWith(onChange, 2, TabState.states.ACTIVE, null);
assert.calledTwice(onChange);
});
});
@@ -59,14 +59,14 @@ describe('TabState', function () {
it('triggers an onchange handler', function () {
state.deactivateTab(2);
sinon.assert.calledWith(onChange, 2, TabState.states.INACTIVE, null);
assert.calledWith(onChange, 2, TabState.states.INACTIVE, null);
});
it('options.force can be used to re-trigger the current state', function () {
state.deactivateTab(2);
state.deactivateTab(2, {force: true});
sinon.assert.calledWith(onChange, 2, TabState.states.INACTIVE, null);
sinon.assert.calledTwice(onChange);
assert.calledWith(onChange, 2, TabState.states.INACTIVE, null);
assert.calledTwice(onChange);
});
});
@@ -78,14 +78,14 @@ describe('TabState', function () {
it('triggers an onchange handler', function () {
state.errorTab(2);
sinon.assert.calledWith(onChange, 2, TabState.states.ERRORED, null);
assert.calledWith(onChange, 2, TabState.states.ERRORED, null);
});
it('options.force can be used to re-trigger the current state', function () {
state.errorTab(2);
state.errorTab(2, {force: true});
sinon.assert.calledWith(onChange, 2, TabState.states.ERRORED, null);
sinon.assert.calledTwice(onChange);
assert.calledWith(onChange, 2, TabState.states.ERRORED, null);
assert.calledTwice(onChange);
});
});
@@ -99,7 +99,7 @@ describe('TabState', function () {
it('triggers an onchange handler', function () {
state.clearTab(1);
sinon.assert.calledWith(onChange, 1, null);
assert.calledWith(onChange, 1, null);
});
});
@@ -123,7 +123,7 @@ describe('TabState', function () {
state.errorTab(1);
state.deactivateTab(1);
state.deactivateTab(1, {force: true});
sinon.assert.calledWith(onChange, 1, TabState.states.INACTIVE, TabState.states.ERRORED);
assert.calledWith(onChange, 1, TabState.states.INACTIVE, TabState.states.ERRORED);
});
});
@@ -152,7 +152,7 @@ describe('TabState', function () {
it('provides the previous value to the handler', function () {
state.errorTab(1);
state.deactivateTab(1);
sinon.assert.calledWith(onChange, 1, TabState.states.INACTIVE, TabState.states.ERRORED);
assert.calledWith(onChange, 1, TabState.states.INACTIVE, TabState.states.ERRORED);
});
});
});
View
@@ -37,21 +37,21 @@ describe('TabStore', function () {
it('inserts a JSON string into the store for the tab id', function () {
var expected = JSON.stringify({1: 'active'});
store.set(1, 'active');
sinon.assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
});
it('adds new properties to the serialized object with each new call', function () {
var expected = JSON.stringify({1: 'active', 2: 'inactive'});
store.set(1, 'active');
store.set(2, 'inactive');
sinon.assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
});
it('overrides existing properties on the serialized object', function () {
var expected = JSON.stringify({1: 'inactive'});
store.set(1, 'active');
store.set(1, 'inactive');
sinon.assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
assert.calledWith(fakeLocalStorage.setItem, 'state', expected);
});
});
@@ -63,7 +63,7 @@ describe('TabStore', function () {
it('removes a property from the serialized object', function () {
store.unset(1);
sinon.assert.called(fakeLocalStorage.setItem, '{}');
assert.called(fakeLocalStorage.setItem, '{}');
});
});
View
@@ -1,4 +1,4 @@
{inject, module} = require('angular-mock')
{inject, module} = angular.mock
describe 'h:AccountController', ->
$scope = null
View
@@ -1,4 +1,4 @@
{inject, module} = require('angular-mock')
{inject, module} = angular.mock
sandbox = sinon.sandbox.create()
class MockSession
View
@@ -136,7 +136,7 @@ module.exports = class Guest extends Annotator
@wrapper = @element
.on 'click', (event) =>
if !@selectedTargets?.length
@triggerHideFrame()
this.hideFrame()
this
# These methods aren't used in the iframe-hosted configuration of Annotator.
@@ -200,22 +200,13 @@ module.exports = class Guest extends Annotator
return animationPromise ->
range = Annotator.Range.sniff(anchor.range)
normedRange = range.normalize(root)
highlights = highlighter.highlightRange(normedRange)
rect = highlighter.getBoundingClientRect(highlights)
$(highlights).data('annotation', anchor.annotation)
anchor.highlights = highlights
anchor.pos =
left: rect.left + window.scrollX
top: rect.top + window.scrollY
return anchor
sync = (anchors) ->
# Store the results of anchoring.
annotation.$anchors = ({pos} for {pos} in anchors)
annotation.$orphan = anchors.length > 0
for anchor in anchors
if anchor.range?
@@ -253,8 +244,8 @@ module.exports = class Guest extends Annotator
anchor = locate(target).then(highlight)
anchors.push(anchor)
# Wait for all the anchoring tasks to complete then call sync.
Promise.all(anchors).then(sync)
annotation.$anchors = Promise.all(anchors)
annotation.$anchors.then(sync)
return annotation
@@ -359,7 +350,7 @@ module.exports = class Guest extends Annotator
@selectedRanges = []
selectAnnotations: (annotations, toggle) =>
this.triggerShowFrame()
this.showFrame()
if toggle
this.toggleAnnotationSelection annotations
else
@@ -412,11 +403,11 @@ module.exports = class Guest extends Annotator
@visibleHighlights = shouldShowHighlights
# Open the sidebar
triggerShowFrame: ->
showFrame: ->
@crossframe?.notify method: 'open'
# Close the sidebar
triggerHideFrame: ->
hideFrame: ->
@crossframe?.notify method: 'back'
onAdderMouseup: (event) ->
@@ -435,5 +426,5 @@ module.exports = class Guest extends Annotator
this.createHighlight()
when 'comment'
this.createAnnotation()
this.triggerShowFrame()
this.showFrame()
Annotator.Util.getGlobal().getSelection().removeAllRanges()
View
@@ -35,9 +35,9 @@ class Annotator.Plugin.Toolbar extends Annotator.Plugin
event.stopPropagation()
collapsed = @annotator.frame.hasClass('annotator-collapsed')
if collapsed
@annotator.triggerShowFrame()
@annotator.showFrame()
else
@annotator.triggerHideFrame()
@annotator.hideFrame()
,
"title": "Hide Highlights"
"class": "h-icon-visibility"
View
@@ -4,17 +4,6 @@ Guest = require('../guest')
anchoring = require('../anchoring/html')
highlighter = require('../highlighter')
assert = chai.assert
sinon.assert.expose(assert, prefix: '')
waitForSync = (annotation) ->
if annotation.$anchors?
return Promise.resolve()
else
return new Promise(setTimeout).then(-> waitForSync(annotation))
describe 'Guest', ->
sandbox = null
fakeCrossFrame = null
@@ -322,7 +311,7 @@ describe 'Guest', ->
guest = createGuest()
annotation = target: []
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.isFalse(annotation.$orphan)
done()
@@ -331,7 +320,7 @@ describe 'Guest', ->
annotation = target: [{selector: "test"}]
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.isFalse(annotation.$orphan)
done()
@@ -340,7 +329,7 @@ describe 'Guest', ->
annotation = target: [{selector: 'broken selector'}]
sandbox.stub(anchoring, 'anchor').returns(Promise.reject())
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.isTrue(annotation.$orphan)
done()
@@ -352,24 +341,21 @@ describe 'Guest', ->
update: sinon.stub()
annotation = {}
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.called(guest.plugins.BucketBar.update)
assert.called(guest.plugins.CrossFrame.sync)
done()
it 'saves the anchor positions on the annotation', (done) ->
it 'saves a promise of the anchors on the annotation', (done) ->
guest = createGuest()
highlights = [document.createElement('span')]
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
clientRect = {top: 100, left: 200}
window.scrollX = 50
window.scrollY = 25
sandbox.stub(highlighter, 'getBoundingClientRect').returns(clientRect)
annotation = guest.setupAnnotation({target: [{selector: []}]})
waitForSync(annotation).then ->
assert.equal(annotation.$anchors.length, 1)
pos = annotation.$anchors[0].pos
assert.equal(pos.top, 125)
assert.equal(pos.left, 250)
sandbox.stub(highlighter, 'highlightRange').returns(highlights)
target = [{selector: []}]
annotation = guest.setupAnnotation({target: [target]})
assert.instanceOf(annotation.$anchors, Promise)
annotation.$anchors.then (anchors) ->
assert.equal(anchors.length, 1)
done()
it 'adds the anchor to the "anchors" instance property"', (done) ->
@@ -379,7 +365,7 @@ describe 'Guest', ->
sandbox.stub(highlighter, 'highlightRange').returns(highlights)
target = [{selector: []}]
annotation = guest.setupAnnotation({target: [target]})
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.equal(guest.anchors.length, 1)
assert.strictEqual(guest.anchors[0].annotation, annotation)
assert.strictEqual(guest.anchors[0].target, target)
@@ -395,7 +381,7 @@ describe 'Guest', ->
guest.anchors = [{annotation, target, highlights}]
removeHighlights = sandbox.stub(highlighter, 'removeHighlights')
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.equal(guest.anchors.length, 0)
assert.calledWith(removeHighlights, highlights)
done()
@@ -405,10 +391,10 @@ describe 'Guest', ->
annotation = target: [{selector: "test"}]
stub = sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
delete annotation.$anchors
guest.setupAnnotation(annotation)
waitForSync(annotation).then ->
annotation.$anchors.then ->
assert.equal(guest.anchors.length, 1)
assert.calledOnce(stub)
done()
View
@@ -3,10 +3,6 @@ $ = Annotator.$
highlighter = require('../highlighter')
assert = chai.assert
sinon.assert.expose(assert, prefix: '')
describe "highlightRange", ->
it 'wraps a highlight span around the given range', ->
txt = document.createTextNode('test highlight span')
View
@@ -62,23 +62,26 @@ module.exports = class AppController
$scope.accountDialog.visible = false
cleanupAnnotations = ->
# Clean up any annotations that need to be unloaded.
# Clean up all the annotations
for id, container of $scope.threading.idTable when container.message
# Remove annotations the user is not authorized to view.
if not permissions.permits 'read', container.message, auth.user
# Keep drafts. When logging out, drafts are already discarded.
if drafts.contains(container.message)
continue
else
$scope.$emit('annotationDeleted', container.message)
drafts.remove container.message
$scope.$watch 'sort.name', (name) ->
return unless name
predicate = switch name
when 'Newest' then ['-!!message', '-message.updated']
when 'Oldest' then ['-!!message', 'message.updated']
when 'Location' then [
'-!!message'
'message.$anchors[0].pos.top'
'message.$anchors[0].pos.left'
]
when 'Location' then (thread) ->
if thread.message?
for target in thread.message.target ? []
for selector in target.selector ? []
if selector.type is 'TextPositionSelector'
return selector.start
return Number.POSITIVE_INFINITY
$scope.sort = {name, predicate}
$scope.$watch (-> auth.user), (newVal, oldVal) ->
View
@@ -19,7 +19,7 @@ module.exports = class CrossFrame
createAnnotationSync = ->
whitelist = [
'$anchors', '$highlight', '$orphan',
'$highlight', '$orphan',
'target', 'document', 'uri'
]
options =
View
@@ -1,7 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
describe 'annotation', ->
$compile = null
View
@@ -1,8 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
angular = require('angular')
{module, inject} = angular.mock
describe 'form-input', ->
$compile = null
View
@@ -1,8 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
angular = require('angular')
{module, inject} = angular.mock
describe 'form-validate', ->
$compile = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'match', ->
$compile = null
View
@@ -1,6 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
VISIBILITY_KEY ='hypothesis.visibility'
VISIBILITY_PUBLIC = 'public'
View
@@ -1,6 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
describe 'share-dialog', ->
$scope = null
View
@@ -1,7 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
describe 'simple-search', ->
$compile = null
@@ -50,15 +47,15 @@ describe 'simple-search', ->
isolate.searchtext = "Test query"
isolate.$digest()
$element.find('form').triggerHandler('submit')
sinon.assert.calledWith($scope.update, "Test query")
assert.calledWith($scope.update, "Test query")
it 'invokes callbacks when the input model changes', ->
$scope.query = "Test query"
$scope.$digest()
sinon.assert.calledOnce($scope.update)
assert.calledOnce($scope.update)
$scope.query = ""
$scope.$digest()
sinon.assert.calledOnce($scope.clear)
assert.calledOnce($scope.clear)
it 'adds a class to the form when there is no input value', ->
$form = $element.find('.simple-search-form')
View
@@ -1,6 +1,5 @@
var angularMock = require('angular-mock');
var module = angularMock.module;
var inject = angularMock.inject;
var module = angular.mock.module;
var inject = angular.mock.inject;
describe('spinner', function () {
var $animate = null;
View
@@ -1,7 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
describe 'h:directives.status-button', ->
$scope = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'thread', ->
$compile = null
View
@@ -1,9 +1,5 @@
var angularMock = require('angular-mock');
var module = angularMock.module;
var inject = angularMock.inject;
var assert = chai.assert;
sinon.assert.expose(assert, {prefix: null});
var module = angular.mock.module;
var inject = angular.mock.inject;
describe('persona', function () {
var filter = null;
View
@@ -1,9 +1,5 @@
var angularMock = require('angular-mock');
var module = angularMock.module;
var inject = angularMock.inject;
var assert = chai.assert;
sinon.assert.expose(assert, {prefix: null});
var module = angular.mock.module;
var inject = angular.mock.inject;
describe('urlencode', function () {
var filter = null;
View
@@ -12,7 +12,8 @@ module.exports = function(config) {
frameworks: [
'browserify',
'mocha',
'chai-sinon'
'chai',
'sinon'
],
// list of files / patterns to load in the browser
@@ -40,6 +41,7 @@ module.exports = function(config) {
// Test deps
'vendor/angular-mocks.js',
'../../templates/client/*.html',
'test/bootstrap.js',
// Tests
'**/*-test.coffee',
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'annotationMapper', ->
sandbox = sinon.sandbox.create()
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'AnnotationSync', ->
sandbox = sinon.sandbox.create()
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'AnnotationUIController', ->
$scope = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'AnnotationUISync', ->
sandbox = sinon.sandbox.create()
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'annotationUI', ->
annotationUI = null
View
@@ -1,4 +1,4 @@
{inject, module} = require('angular-mock')
{inject, module} = angular.mock
describe "AnnotationViewerController", ->
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'AppController', ->
$controller = null
@@ -46,6 +46,7 @@ describe 'AppController', ->
}
fakeDrafts = {
contains: sandbox.stub()
remove: sandbox.spy()
all: sandbox.stub().returns([])
discard: sandbox.spy()
@@ -113,7 +114,6 @@ describe 'AppController', ->
beforeEach inject (_$controller_, $rootScope) ->
$controller = _$controller_
$scope = $rootScope.$new()
$scope.$digest = sinon.spy()
afterEach ->
sandbox.restore()
@@ -179,3 +179,25 @@ describe 'AppController', ->
payload: [ remoteAnnotation ]
assert.calledWith $scope.$emit, "annotationDeleted", localAnnotation
it 'deletes annotations, but not drafts, on user change', ->
createController()
$scope.$emit = sinon.spy()
annotation1 = id: 'abaca'
annotation2 = id: 'deadbeef'
fakeThreading.register(annotation1)
fakeThreading.register(annotation2)
fakeDrafts.contains.withArgs(annotation1).returns(true)
fakeDrafts.contains.withArgs(annotation2).returns(false)
fakeAuth.user = null
$scope.$digest()
fakeAuth.user = 'acct:loki@example.com'
$scope.$digest()
assert.neverCalledWith($scope.$emit, 'annotationDeleted', annotation1)
assert.calledWith($scope.$emit, 'annotationDeleted', annotation2)
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'h', ->
fakeAnnotator = null
View
@@ -1,7 +1,6 @@
describe('parseUrl', function () {
'use strict';
var assert = chai.assert;
var blocklist = require('../blocklist');
var parseUrl = blocklist.parseUrl;
View
@@ -0,0 +1,2 @@
// Expose the sinon assertions.
sinon.assert.expose(assert, {prefix: null});
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
Channel = require('jschannel')
describe 'Bridge', ->
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'CrossFrame', ->
sandbox = sinon.sandbox.create()
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'Discovery', ->
sandbox = sinon.sandbox.create()
View
@@ -1,10 +1,6 @@
"use strict";
var mock = require('angular-mock');
var assert = chai.assert;
sinon.assert.expose(assert, {prefix: null});
var mock = angular.mock;
describe('h:features', function () {
var $httpBackend;
View
@@ -1,8 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
angular = require('angular')
{module, inject} = angular.mock
describe 'form-respond', ->
$scope = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'host', ->
sandbox = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
sandbox = sinon.sandbox.create()
describe 'identityProvider', ->
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'localStorage', ->
fakeWindow = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'h:permissions', ->
sandbox = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'searchFilter', ->
sandbox = null
@@ -8,7 +8,6 @@ describe 'searchFilter', ->
angular.module('h', [])
.service('searchFilter', require('../search-filter'))
beforeEach module('h')
beforeEach ->
View
@@ -1,6 +1,6 @@
"use strict";
var mock = require('angular-mock');
var mock = angular.mock;
describe('h:session', function () {
var $httpBackend;
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'store', ->
$httpBackend = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'StreamController', ->
$controller = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'streamer', ->
WebSocket = null
View
@@ -1,6 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
describe 'tags', ->
TAGS_LIST_KEY = 'hypothesis.user.tags.list'
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'Threading', ->
instance = null
View
@@ -1,6 +1,4 @@
{module, inject} = require('angular-mock')
assert = chai.assert
{module, inject} = angular.mock
minute = 60
hour = minute * 60
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'unicode', ->
unicode = null
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
poem =
tiger: 'Tiger! Tiger! burning bright
View
@@ -1,4 +1,4 @@
{module, inject} = require('angular-mock')
{module, inject} = angular.mock
describe 'WidgetController', ->
$scope = null
View
@@ -68,5 +68,3 @@ module.exports = class WidgetController
$scope.hasFocus = (annotation) ->
!!($scope.focusedAnnotations ? {})[annotation?.$$tag]
$scope.notOrphan = (container) -> !container?.message?.$orphan
View
@@ -71,3 +71,11 @@ $thread-padding: 1em;
.thread-load-more {
clear: both;
}
.thread-anchor-notice {
// XXX: negative margins here make me sad; maybe refactor container
background-color: lighten($gray-light, 38%);
border-top: solid 1px $gray-lighter;
margin: 0 -1em -1em -1em;
padding: 1em;
}
View
@@ -45,3 +45,7 @@
ng-show="vm.shouldShowAsReply()">
</li>
</ul>
<footer class="thread-anchor-notice" ng-if="vm.container.message.$orphan">
<em>We're sorry, but we can't find the exact position of this annotation.</em>
</footer>
View
@@ -41,7 +41,7 @@
ng-mouseenter="focus(child.message)"
ng-click="scrollTo(child.message)"
ng-mouseleave="focus()"
ng-repeat="child in threadRoot.children | filter:notOrphan | orderBy : sort.predicate"
ng-repeat="child in threadRoot.children | orderBy : sort.predicate"
ng-show="shouldShowThread(child) && (count('edit') || count('match') || !threadFilter.active()) || vm.isNew()">
</li>
</ul>
View
@@ -29,15 +29,15 @@
"jscs": "^1.13.1",
"karma": "^0.12.17",
"karma-browserify": "^3.0.3",
"karma-chai-sinon": "^0.1.5",
"karma-chai": "^0.1.0",
"karma-cli": "0.0.4",
"karma-mocha": "^0.1.4",
"karma-ng-html2js-preprocessor": "^0.1.0",
"karma-phantomjs-launcher": "^0.1.4",
"karma-sinon": "^1.0.4",
"mocha": "^1.20.1",
"phantomjs": "^1.9.7",
"sinon": "^1.15.4",
"sinon-chai": "^2.8.0"
"sinon": "^1.15.4"
},
"engines": {
"node": "0.10.x"
@@ -65,7 +65,6 @@
"annotator": "./h/static/scripts/vendor/annotator.js",
"annotator-auth": "./h/static/scripts/vendor/annotator.auth.js",
"angular": "./h/static/scripts/vendor/angular.js",
"angular-mock": "./h/static/scripts/vendor/angular-mocks.js",
"dom-anchor-fragment": "./node_modules/dom-anchor-fragment/dist/dom-anchor-fragment.js",
"dom-anchor-text-position": "./node_modules/dom-anchor-text-position/dist/dom-anchor-text-position.js",
"dom-anchor-text-quote": "./node_modules/dom-anchor-text-quote/dist/dom-anchor-text-quote.js",
@@ -96,7 +95,6 @@
"jquery"
]
},
"angular-mock": "global:angular.mock",
"dom-anchor-fragment": "domAnchorFragment",
"dom-anchor-text-position": {
"depends": [