Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
056a32bb
Unverified
Commit
056a32bb
authored
Mar 05, 2020
by
Lyza Gardner
Committed by
GitHub
Mar 05, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1875 from hypothesis/split-annotations-service
Split `loadAnnotationsService` out of `annotationsService`
parents
ac8fcfbc
c1b99fec
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
385 additions
and
364 deletions
+385
-364
sidebar-content.js
src/sidebar/components/sidebar-content.js
+2
-2
sidebar-content-test.js
src/sidebar/components/test/sidebar-content-test.js
+9
-9
index.js
src/sidebar/index.js
+5
-0
annotations.js
src/sidebar/services/annotations.js
+25
-79
load-annotations.js
src/sidebar/services/load-annotations.js
+68
-0
annotations-test.js
src/sidebar/services/test/annotations-test.js
+9
-274
load-annotations-test.js
src/sidebar/services/test/load-annotations-test.js
+267
-0
No files found.
src/sidebar/components/sidebar-content.js
View file @
056a32bb
...
...
@@ -6,7 +6,7 @@ import * as tabs from '../util/tabs';
function
SidebarContentController
(
$scope
,
analytics
,
a
nnotationsService
,
loadA
nnotationsService
,
store
,
frameSync
,
rootThread
,
...
...
@@ -120,7 +120,7 @@ function SidebarContentController(
}
const
searchUris
=
store
.
searchUris
();
a
nnotationsService
.
load
(
searchUris
,
currentGroupId
);
loadA
nnotationsService
.
load
(
searchUris
,
currentGroupId
);
},
true
);
...
...
src/sidebar/components/test/sidebar-content-test.js
View file @
056a32bb
...
...
@@ -20,7 +20,7 @@ describe('sidebar.components.sidebar-content', function() {
let
store
;
let
ctrl
;
let
fakeAnalytics
;
let
fake
Annotations
;
let
fake
LoadAnnotationsService
;
let
fakeFrameSync
;
let
fakeRootThread
;
let
fakeSettings
;
...
...
@@ -56,7 +56,7 @@ describe('sidebar.components.sidebar-content', function() {
reconnect
:
sandbox
.
stub
(),
};
fake
Annotations
=
{
fake
LoadAnnotationsService
=
{
load
:
sinon
.
stub
(),
};
...
...
@@ -68,7 +68,7 @@ describe('sidebar.components.sidebar-content', function() {
$provide
.
value
(
'frameSync'
,
fakeFrameSync
);
$provide
.
value
(
'rootThread'
,
fakeRootThread
);
$provide
.
value
(
'streamer'
,
fakeStreamer
);
$provide
.
value
(
'
annotationsService'
,
fakeAnnotations
);
$provide
.
value
(
'
loadAnnotationsService'
,
fakeLoadAnnotationsService
);
$provide
.
value
(
'settings'
,
fakeSettings
);
});
});
...
...
@@ -167,7 +167,7 @@ describe('sidebar.components.sidebar-content', function() {
function
connectFrameAndPerformInitialFetch
()
{
setFrames
([{
uri
:
'https://a-page.com'
}]);
$scope
.
$digest
();
fake
Annotations
.
load
.
reset
();
fake
LoadAnnotationsService
.
load
.
reset
();
}
it
(
'generates the thread list'
,
()
=>
{
...
...
@@ -184,7 +184,7 @@ describe('sidebar.components.sidebar-content', function() {
$scope
.
$digest
();
assert
.
calledWith
(
fake
Annotations
.
load
,
fake
LoadAnnotationsService
.
load
,
[
'https://a-page.com'
,
'https://new-frame.com'
],
'group-id'
);
...
...
@@ -203,7 +203,7 @@ describe('sidebar.components.sidebar-content', function() {
$scope
.
$digest
();
assert
.
calledWith
(
fake
Annotations
.
load
,
fake
LoadAnnotationsService
.
load
,
[
'https://a-page.com'
],
'group-id'
);
...
...
@@ -219,7 +219,7 @@ describe('sidebar.components.sidebar-content', function() {
store
.
updateSession
(
newProfile
);
$scope
.
$digest
();
assert
.
notCalled
(
fake
Annotations
.
load
);
assert
.
notCalled
(
fake
LoadAnnotationsService
.
load
);
});
});
...
...
@@ -250,7 +250,7 @@ describe('sidebar.components.sidebar-content', function() {
store
.
addAnnotations
=
sinon
.
stub
();
setFrames
([{
uri
:
uri
}]);
$scope
.
$digest
();
fake
Annotations
.
load
=
sinon
.
stub
();
fake
LoadAnnotationsService
.
load
=
sinon
.
stub
();
});
it
(
'should load annotations for the new group'
,
()
=>
{
...
...
@@ -260,7 +260,7 @@ describe('sidebar.components.sidebar-content', function() {
$scope
.
$digest
();
assert
.
calledWith
(
fake
Annotations
.
load
,
fake
LoadAnnotationsService
.
load
,
[
'http://example.com'
],
'different-group'
);
...
...
src/sidebar/index.js
View file @
056a32bb
...
...
@@ -179,6 +179,7 @@ import featuresService from './services/features';
import
flashService
from
'./services/flash'
;
import
frameSyncService
from
'./services/frame-sync'
;
import
groupsService
from
'./services/groups'
;
import
loadAnnotationsService
from
'./services/load-annotations'
;
import
localStorageService
from
'./services/local-storage'
;
import
permissionsService
from
'./services/permissions'
;
import
persistedDefaultsService
from
'./services/persisted-defaults'
;
...
...
@@ -221,6 +222,7 @@ function startAngularApp(config) {
.
register
(
'flash'
,
flashService
)
.
register
(
'frameSync'
,
frameSyncService
)
.
register
(
'groups'
,
groupsService
)
.
register
(
'loadAnnotationsService'
,
loadAnnotationsService
)
.
register
(
'localStorage'
,
localStorageService
)
.
register
(
'permissions'
,
permissionsService
)
.
register
(
'persistedDefaults'
,
persistedDefaultsService
)
...
...
@@ -307,6 +309,9 @@ function startAngularApp(config) {
.
service
(
'flash'
,
()
=>
container
.
get
(
'flash'
))
.
service
(
'frameSync'
,
()
=>
container
.
get
(
'frameSync'
))
.
service
(
'groups'
,
()
=>
container
.
get
(
'groups'
))
.
service
(
'loadAnnotationsService'
,
()
=>
container
.
get
(
'loadAnnotationsService'
)
)
.
service
(
'permissions'
,
()
=>
container
.
get
(
'permissions'
))
.
service
(
'persistedDefaults'
,
()
=>
container
.
get
(
'persistedDefaults'
))
.
service
(
'rootThread'
,
()
=>
container
.
get
(
'rootThread'
))
...
...
src/sidebar/services/annotations.js
View file @
056a32bb
import
SearchClient
from
'../search-client'
;
/**
* A service for creating, manipulating and persisting annotations and their
* application-store representations. Interacts with API services as needed.
*/
import
*
as
metadata
from
'../util/annotation-metadata'
;
import
{
defaultPermissions
,
...
...
@@ -9,14 +13,26 @@ import { generateHexString } from '../util/random';
import
uiConstants
from
'../ui-constants'
;
// @ngInject
export
default
function
annotationsService
(
annotationMapper
,
api
,
store
,
streamer
,
streamFilter
)
{
let
searchClient
=
null
;
export
default
function
annotationsService
(
api
,
store
)
{
/**
* Apply changes for the given `annotation` from its draft in the store (if
* any) and return a new object with those changes integrated.
*/
function
applyDraftChanges
(
annotation
)
{
const
changes
=
{};
const
draft
=
store
.
getDraft
(
annotation
);
if
(
draft
)
{
changes
.
tags
=
draft
.
tags
;
changes
.
text
=
draft
.
text
;
changes
.
permissions
=
draft
.
isPrivate
?
privatePermissions
(
annotation
.
user
)
:
sharedPermissions
(
annotation
.
user
,
annotation
.
group
);
}
// Integrate changes from draft into object to be persisted
return
{
...
annotation
,
...
changes
};
}
/**
* Extend new annotation objects with defaults and permissions.
...
...
@@ -100,75 +116,6 @@ export default function annotationsService(
});
}
/**
* Load annotations for all URIs and groupId.
*
* @param {string[]} uris
* @param {string} groupId
*/
function
load
(
uris
,
groupId
)
{
annotationMapper
.
unloadAnnotations
(
store
.
savedAnnotations
());
// Cancel previously running search client.
if
(
searchClient
)
{
searchClient
.
cancel
();
}
if
(
uris
.
length
>
0
)
{
searchAndLoad
(
uris
,
groupId
);
streamFilter
.
resetFilter
().
addClause
(
'/uri'
,
'one_of'
,
uris
);
streamer
.
setConfig
(
'filter'
,
{
filter
:
streamFilter
.
getFilter
()
});
}
}
function
searchAndLoad
(
uris
,
groupId
)
{
searchClient
=
new
SearchClient
(
api
.
search
,
{
incremental
:
true
,
});
searchClient
.
on
(
'results'
,
results
=>
{
if
(
results
.
length
)
{
annotationMapper
.
loadAnnotations
(
results
);
}
});
searchClient
.
on
(
'error'
,
error
=>
{
console
.
error
(
error
);
});
searchClient
.
on
(
'end'
,
()
=>
{
// Remove client as it's no longer active.
searchClient
=
null
;
store
.
frames
().
forEach
(
function
(
frame
)
{
if
(
0
<=
uris
.
indexOf
(
frame
.
uri
))
{
store
.
updateFrameAnnotationFetchStatus
(
frame
.
uri
,
true
);
}
});
store
.
annotationFetchFinished
();
});
store
.
annotationFetchStarted
();
searchClient
.
get
({
uri
:
uris
,
group
:
groupId
});
}
/**
* Apply changes for the given `annotation` from its draft in the store (if
* any) and return a new object with those changes integrated.
*/
function
applyDraftChanges
(
annotation
)
{
const
changes
=
{};
const
draft
=
store
.
getDraft
(
annotation
);
if
(
draft
)
{
changes
.
tags
=
draft
.
tags
;
changes
.
text
=
draft
.
text
;
changes
.
permissions
=
draft
.
isPrivate
?
privatePermissions
(
annotation
.
user
)
:
sharedPermissions
(
annotation
.
user
,
annotation
.
group
);
}
// Integrate changes from draft into object to be persisted
return
{
...
annotation
,
...
changes
};
}
/**
* Create a reply to `annotation` by the user `userid` and add to the store.
*
...
...
@@ -225,7 +172,6 @@ export default function annotationsService(
return
{
create
,
load
,
reply
,
save
,
};
...
...
src/sidebar/services/load-annotations.js
0 → 100644
View file @
056a32bb
/**
* A service for fetching annotations, filtered by document URIs and group.
*/
import
SearchClient
from
'../search-client'
;
// @ngInject
export
default
function
loadAnnotationsService
(
annotationMapper
,
api
,
store
,
streamer
,
streamFilter
)
{
let
searchClient
=
null
;
/**
* Load annotations for all URIs and groupId.
*
* @param {string[]} uris
* @param {string} groupId
*/
function
load
(
uris
,
groupId
)
{
annotationMapper
.
unloadAnnotations
(
store
.
savedAnnotations
());
// Cancel previously running search client.
if
(
searchClient
)
{
searchClient
.
cancel
();
}
if
(
uris
.
length
>
0
)
{
searchAndLoad
(
uris
,
groupId
);
streamFilter
.
resetFilter
().
addClause
(
'/uri'
,
'one_of'
,
uris
);
streamer
.
setConfig
(
'filter'
,
{
filter
:
streamFilter
.
getFilter
()
});
}
}
function
searchAndLoad
(
uris
,
groupId
)
{
searchClient
=
new
SearchClient
(
api
.
search
,
{
incremental
:
true
,
});
searchClient
.
on
(
'results'
,
results
=>
{
if
(
results
.
length
)
{
annotationMapper
.
loadAnnotations
(
results
);
}
});
searchClient
.
on
(
'error'
,
error
=>
{
console
.
error
(
error
);
});
searchClient
.
on
(
'end'
,
()
=>
{
// Remove client as it's no longer active.
searchClient
=
null
;
store
.
frames
().
forEach
(
function
(
frame
)
{
if
(
0
<=
uris
.
indexOf
(
frame
.
uri
))
{
store
.
updateFrameAnnotationFetchStatus
(
frame
.
uri
,
true
);
}
});
store
.
annotationFetchFinished
();
});
store
.
annotationFetchStarted
();
searchClient
.
get
({
uri
:
uris
,
group
:
groupId
});
}
return
{
load
,
};
}
src/sidebar/services/test/annotations-test.js
View file @
056a32bb
import
EventEmitter
from
'tiny-emitter'
;
import
*
as
fixtures
from
'../../test/annotation-fixtures'
;
import
uiConstants
from
'../../ui-constants'
;
import
annotationsService
from
'../annotations'
;
import
{
$imports
}
from
'../annotations'
;
let
searchClients
;
let
longRunningSearchClient
=
false
;
class
FakeSearchClient
extends
EventEmitter
{
constructor
(
searchFn
,
opts
)
{
super
();
assert
.
ok
(
searchFn
);
searchClients
.
push
(
this
);
this
.
cancel
=
sinon
.
stub
();
this
.
incremental
=
!!
opts
.
incremental
;
this
.
get
=
sinon
.
spy
(
query
=>
{
assert
.
ok
(
query
.
uri
);
for
(
let
i
=
0
;
i
<
query
.
uri
.
length
;
i
++
)
{
const
uri
=
query
.
uri
[
i
];
this
.
emit
(
'results'
,
[{
id
:
uri
+
'123'
,
group
:
'__world__'
}]);
this
.
emit
(
'results'
,
[{
id
:
uri
+
'456'
,
group
:
'private-group'
}]);
}
if
(
!
longRunningSearchClient
)
{
this
.
emit
(
'end'
);
}
});
}
}
import
annotationsService
,
{
$imports
}
from
'../annotations'
;
describe
(
'annotationService'
,
()
=>
{
let
fakeStore
;
describe
(
'annotationsService'
,
()
=>
{
let
fakeApi
;
let
fakeAnnotationMapper
;
let
fakeStreamer
;
let
fakeStreamFilter
;
let
fakeMetadata
;
let
fakeUris
;
let
fakeGroupId
;
let
fakeStore
;
let
fakeDefaultPermissions
;
let
fakePrivatePermissions
;
let
fakeSharedPermissions
;
beforeEach
(()
=>
{
sinon
.
stub
(
console
,
'error'
);
searchClients
=
[];
longRunningSearchClient
=
false
;
fakeAnnotationMapper
=
{
loadAnnotations
:
sinon
.
stub
(),
unloadAnnotations
:
sinon
.
stub
(),
};
let
svc
;
beforeEach
(()
=>
{
fakeApi
=
{
annotation
:
{
create
:
sinon
.
stub
().
resolves
(
fixtures
.
defaultAnnotation
()),
update
:
sinon
.
stub
().
resolves
(
fixtures
.
defaultAnnotation
()),
},
search
:
sinon
.
stub
(),
};
fakeDefaultPermissions
=
sinon
.
stub
();
fakePrivatePermissions
=
sinon
.
stub
().
returns
({
read
:
[
'acct:foo@bar.com'
],
update
:
[
'acct:foo@bar.com'
],
delete
:
[
'acct:foo@bar.com'
],
});
fakeSharedPermissions
=
sinon
.
stub
().
returns
({
read
:
[
'group:__world__'
],
});
fakePrivatePermissions
=
sinon
.
stub
();
fakeSharedPermissions
=
sinon
.
stub
();
fakeMetadata
=
{
isAnnotation
:
sinon
.
stub
(),
...
...
@@ -86,38 +36,18 @@ describe('annotationService', () => {
fakeStore
=
{
addAnnotations
:
sinon
.
stub
(),
annotationFetchFinished
:
sinon
.
stub
(),
annotationFetchStarted
:
sinon
.
stub
(),
createDraft
:
sinon
.
stub
(),
deleteNewAndEmptyDrafts
:
sinon
.
stub
(),
focusedGroupId
:
sinon
.
stub
(),
frames
:
sinon
.
stub
(),
getDefault
:
sinon
.
stub
(),
getDraft
:
sinon
.
stub
().
returns
(
null
),
profile
:
sinon
.
stub
().
returns
({}),
removeDraft
:
sinon
.
stub
(),
savedAnnotations
:
sinon
.
stub
(),
selectTab
:
sinon
.
stub
(),
setCollapsed
:
sinon
.
stub
(),
updateFrameAnnotationFetchStatus
:
sinon
.
stub
(),
};
fakeStreamer
=
{
setConfig
:
sinon
.
stub
(),
connect
:
sinon
.
stub
(),
reconnect
:
sinon
.
stub
(),
};
fakeStreamFilter
=
{
resetFilter
:
sinon
.
stub
().
returns
({
addClause
:
sinon
.
stub
(),
}),
getFilter
:
sinon
.
stub
().
returns
({}),
};
fakeUris
=
[
'http://example.com'
];
fakeGroupId
=
'group-id'
;
$imports
.
$mock
({
'../search-client'
:
FakeSearchClient
,
'../util/annotation-metadata'
:
fakeMetadata
,
'../util/permissions'
:
{
defaultPermissions
:
fakeDefaultPermissions
,
...
...
@@ -125,31 +55,16 @@ describe('annotationService', () => {
sharedPermissions
:
fakeSharedPermissions
,
},
});
svc
=
annotationsService
(
fakeApi
,
fakeStore
);
});
afterEach
(()
=>
{
console
.
error
.
restore
();
$imports
.
$restore
();
});
function
service
()
{
fakeStore
.
frames
.
returns
(
fakeUris
.
map
(
uri
=>
{
return
{
uri
:
uri
};
})
);
return
annotationsService
(
fakeAnnotationMapper
,
fakeApi
,
fakeStore
,
fakeStreamer
,
fakeStreamFilter
);
}
describe
(
'create'
,
()
=>
{
let
now
;
let
svc
;
const
getLastAddedAnnotation
=
()
=>
{
if
(
fakeStore
.
addAnnotations
.
callCount
<=
0
)
{
...
...
@@ -161,7 +76,6 @@ describe('annotationService', () => {
beforeEach
(()
=>
{
now
=
new
Date
();
svc
=
service
();
fakeStore
.
focusedGroupId
.
returns
(
'mygroup'
);
fakeStore
.
profile
.
returns
({
...
...
@@ -307,180 +221,7 @@ describe('annotationService', () => {
});
});
describe
(
'load'
,
()
=>
{
it
(
'unloads any existing annotations'
,
()
=>
{
// When new clients connect, all existing annotations should be unloaded
// before reloading annotations for each currently-connected client.
fakeStore
.
savedAnnotations
.
returns
([
{
id
:
fakeUris
[
0
]
+
'123'
},
{
id
:
fakeUris
[
0
]
+
'456'
},
]);
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
unloadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'123'
}),
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'456'
}),
]);
});
it
(
'loads all annotations for a URI'
,
()
=>
{
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'456'
}),
]);
});
it
(
'loads all annotations for a frame with multiple URIs'
,
()
=>
{
const
uri
=
'http://example.com/test.pdf'
;
const
fingerprint
=
'urn:x-pdf:fingerprint'
;
fakeUris
=
[
uri
,
fingerprint
];
const
svc
=
service
();
// Override the default frames set by the service call above.
fakeStore
.
frames
.
returns
([
{
uri
:
uri
,
metadata
:
{
documentFingerprint
:
'fingerprint'
,
link
:
[
{
href
:
fingerprint
,
},
{
href
:
uri
,
},
],
},
},
]);
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fingerprint
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
+
'456'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fingerprint
+
'456'
}),
]);
});
it
(
'loads all annotations for all URIs'
,
()
=>
{
fakeUris
=
[
'http://example.com'
,
'http://foobar.com'
];
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
[
fakeUris
[
0
]
+
'123'
,
fakeUris
[
0
]
+
'456'
,
fakeUris
[
1
]
+
'123'
,
fakeUris
[
1
]
+
'456'
,
].
forEach
(
uri
=>
{
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
}),
]);
});
});
it
(
'updates annotation fetch status for all frames'
,
()
=>
{
fakeUris
=
[
'http://example.com'
,
'http://foobar.com'
];
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeStore
.
updateFrameAnnotationFetchStatus
,
fakeUris
[
0
],
true
);
assert
.
calledWith
(
fakeStore
.
updateFrameAnnotationFetchStatus
,
fakeUris
[
1
],
true
);
});
it
(
'fetches annotations for the specified group'
,
()
=>
{
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
searchClients
[
0
].
get
,
{
uri
:
fakeUris
,
group
:
fakeGroupId
,
});
});
it
(
'loads annotations in batches'
,
()
=>
{
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
ok
(
searchClients
[
0
].
incremental
);
});
it
(
"cancels previously search client if it's still running"
,
()
=>
{
const
svc
=
service
();
// Issue a long running load annotations request.
longRunningSearchClient
=
true
;
svc
.
load
(
fakeUris
,
fakeGroupId
);
// Issue another load annotations request while the
// previous annotation load is still running.
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
searchClients
[
0
].
cancel
);
});
it
(
'does not load annotations if URIs list is empty'
,
()
=>
{
fakeUris
=
[];
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
notCalled
(
fakeAnnotationMapper
.
loadAnnotations
);
});
it
(
'calls annotationFetchStarted when it starts searching for annotations'
,
()
=>
{
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
fakeStore
.
annotationFetchStarted
);
});
it
(
'calls annotationFetchFinished when all annotations have been found'
,
()
=>
{
const
svc
=
service
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
fakeStore
.
annotationFetchFinished
);
});
it
(
'logs an error to the console if the search client runs into an error'
,
()
=>
{
const
svc
=
service
();
const
error
=
new
Error
(
'search for annotations failed'
);
svc
.
load
(
fakeUris
,
fakeGroupId
);
searchClients
[
0
].
emit
(
'error'
,
error
);
assert
.
calledWith
(
console
.
error
,
error
);
});
});
describe
(
'reply'
,
()
=>
{
let
svc
;
beforeEach
(()
=>
{
svc
=
service
();
});
const
filledAnnotation
=
()
=>
{
const
annot
=
fixtures
.
defaultAnnotation
();
annot
.
group
=
'mix3boop'
;
...
...
@@ -539,12 +280,6 @@ describe('annotationService', () => {
});
describe
(
'save'
,
()
=>
{
let
svc
;
beforeEach
(()
=>
{
svc
=
service
();
});
it
(
'calls the `create` API service for new annotations'
,
()
=>
{
fakeMetadata
.
isNew
.
returns
(
true
);
// Using the new-annotation fixture has no bearing on which API method
...
...
src/sidebar/services/test/load-annotations-test.js
0 → 100644
View file @
056a32bb
import
EventEmitter
from
'tiny-emitter'
;
import
loadAnnotationsService
,
{
$imports
}
from
'../load-annotations'
;
let
searchClients
;
let
longRunningSearchClient
=
false
;
class
FakeSearchClient
extends
EventEmitter
{
constructor
(
searchFn
,
opts
)
{
super
();
assert
.
ok
(
searchFn
);
searchClients
.
push
(
this
);
this
.
cancel
=
sinon
.
stub
();
this
.
incremental
=
!!
opts
.
incremental
;
this
.
get
=
sinon
.
spy
(
query
=>
{
assert
.
ok
(
query
.
uri
);
for
(
let
i
=
0
;
i
<
query
.
uri
.
length
;
i
++
)
{
const
uri
=
query
.
uri
[
i
];
this
.
emit
(
'results'
,
[{
id
:
uri
+
'123'
,
group
:
'__world__'
}]);
this
.
emit
(
'results'
,
[{
id
:
uri
+
'456'
,
group
:
'private-group'
}]);
}
if
(
!
longRunningSearchClient
)
{
this
.
emit
(
'end'
);
}
});
}
}
describe
(
'loadAnnotationsService'
,
()
=>
{
let
fakeAnnotationMapper
;
let
fakeApi
;
let
fakeStore
;
let
fakeStreamer
;
let
fakeStreamFilter
;
const
fakeGroupId
=
'group-id'
;
let
fakeUris
;
beforeEach
(()
=>
{
sinon
.
stub
(
console
,
'error'
);
searchClients
=
[];
longRunningSearchClient
=
false
;
fakeAnnotationMapper
=
{
loadAnnotations
:
sinon
.
stub
(),
unloadAnnotations
:
sinon
.
stub
(),
};
fakeApi
=
{
search
:
sinon
.
stub
(),
};
fakeStore
=
{
annotationFetchFinished
:
sinon
.
stub
(),
annotationFetchStarted
:
sinon
.
stub
(),
frames
:
sinon
.
stub
(),
savedAnnotations
:
sinon
.
stub
(),
updateFrameAnnotationFetchStatus
:
sinon
.
stub
(),
};
fakeStreamer
=
{
setConfig
:
sinon
.
stub
(),
connect
:
sinon
.
stub
(),
reconnect
:
sinon
.
stub
(),
};
fakeStreamFilter
=
{
resetFilter
:
sinon
.
stub
().
returns
({
addClause
:
sinon
.
stub
(),
}),
getFilter
:
sinon
.
stub
().
returns
({}),
};
fakeUris
=
[
'http://example.com'
];
$imports
.
$mock
({
'../search-client'
:
FakeSearchClient
,
});
});
afterEach
(()
=>
{
console
.
error
.
restore
();
$imports
.
$restore
();
});
function
createService
()
{
fakeStore
.
frames
.
returns
(
fakeUris
.
map
(
uri
=>
{
return
{
uri
:
uri
};
})
);
return
loadAnnotationsService
(
fakeAnnotationMapper
,
fakeApi
,
fakeStore
,
fakeStreamer
,
fakeStreamFilter
);
}
describe
(
'load'
,
()
=>
{
it
(
'unloads any existing annotations'
,
()
=>
{
// When new clients connect, all existing annotations should be unloaded
// before reloading annotations for each currently-connected client.
fakeStore
.
savedAnnotations
.
returns
([
{
id
:
fakeUris
[
0
]
+
'123'
},
{
id
:
fakeUris
[
0
]
+
'456'
},
]);
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
unloadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'123'
}),
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'456'
}),
]);
});
it
(
'loads all annotations for a URI'
,
()
=>
{
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fakeUris
[
0
]
+
'456'
}),
]);
});
it
(
'loads all annotations for a frame with multiple URIs'
,
()
=>
{
const
uri
=
'http://example.com/test.pdf'
;
const
fingerprint
=
'urn:x-pdf:fingerprint'
;
fakeUris
=
[
uri
,
fingerprint
];
const
svc
=
createService
();
// Override the default frames set by the service call above.
fakeStore
.
frames
.
returns
([
{
uri
:
uri
,
metadata
:
{
documentFingerprint
:
'fingerprint'
,
link
:
[
{
href
:
fingerprint
,
},
{
href
:
uri
,
},
],
},
},
]);
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fingerprint
+
'123'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
+
'456'
}),
]);
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
fingerprint
+
'456'
}),
]);
});
it
(
'loads all annotations for all URIs'
,
()
=>
{
fakeUris
=
[
'http://example.com'
,
'http://foobar.com'
];
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
[
fakeUris
[
0
]
+
'123'
,
fakeUris
[
0
]
+
'456'
,
fakeUris
[
1
]
+
'123'
,
fakeUris
[
1
]
+
'456'
,
].
forEach
(
uri
=>
{
assert
.
calledWith
(
fakeAnnotationMapper
.
loadAnnotations
,
[
sinon
.
match
({
id
:
uri
}),
]);
});
});
it
(
'updates annotation fetch status for all frames'
,
()
=>
{
fakeUris
=
[
'http://example.com'
,
'http://foobar.com'
];
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
fakeStore
.
updateFrameAnnotationFetchStatus
,
fakeUris
[
0
],
true
);
assert
.
calledWith
(
fakeStore
.
updateFrameAnnotationFetchStatus
,
fakeUris
[
1
],
true
);
});
it
(
'fetches annotations for the specified group'
,
()
=>
{
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledWith
(
searchClients
[
0
].
get
,
{
uri
:
fakeUris
,
group
:
fakeGroupId
,
});
});
it
(
'loads annotations in batches'
,
()
=>
{
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
ok
(
searchClients
[
0
].
incremental
);
});
it
(
"cancels previously search client if it's still running"
,
()
=>
{
const
svc
=
createService
();
// Issue a long running load annotations request.
longRunningSearchClient
=
true
;
svc
.
load
(
fakeUris
,
fakeGroupId
);
// Issue another load annotations request while the
// previous annotation load is still running.
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
searchClients
[
0
].
cancel
);
});
it
(
'does not load annotations if URIs list is empty'
,
()
=>
{
fakeUris
=
[];
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
notCalled
(
fakeAnnotationMapper
.
loadAnnotations
);
});
it
(
'calls annotationFetchStarted when it starts searching for annotations'
,
()
=>
{
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
fakeStore
.
annotationFetchStarted
);
});
it
(
'calls annotationFetchFinished when all annotations have been found'
,
()
=>
{
const
svc
=
createService
();
svc
.
load
(
fakeUris
,
fakeGroupId
);
assert
.
calledOnce
(
fakeStore
.
annotationFetchFinished
);
});
it
(
'logs an error to the console if the search client runs into an error'
,
()
=>
{
const
svc
=
createService
();
const
error
=
new
Error
(
'search for annotations failed'
);
svc
.
load
(
fakeUris
,
fakeGroupId
);
searchClients
[
0
].
emit
(
'error'
,
error
);
assert
.
calledWith
(
console
.
error
,
error
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment