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
fd162d2e
Commit
fd162d2e
authored
Feb 12, 2020
by
Lyza Danger Gardner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add `save` method to `AnnotationsService`
Also add polyfill for `Promise.prototype.finally`
parent
f705f6be
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
199 additions
and
4 deletions
+199
-4
boot.js
src/boot/boot.js
+1
-0
es2018.js
src/shared/polyfills/es2018.js
+1
-0
index.js
src/shared/polyfills/index.js
+4
-0
index-test.js
src/shared/polyfills/test/index-test.js
+4
-0
annotations.js
src/sidebar/services/annotations.js
+58
-0
annotations-test.js
src/sidebar/services/test/annotations-test.js
+131
-4
No files found.
src/boot/boot.js
View file @
fd162d2e
...
...
@@ -72,6 +72,7 @@ function bootHypothesisClient(doc, config) {
'es2015'
,
'es2016'
,
'es2017'
,
'es2018'
,
'url'
,
]);
...
...
src/shared/polyfills/es2018.js
0 → 100644
View file @
fd162d2e
import
'core-js/es/promise/finally'
;
src/shared/polyfills/index.js
View file @
fd162d2e
...
...
@@ -57,6 +57,10 @@ const needsPolyfill = {
return
!
hasMethods
(
Object
,
'entries'
,
'values'
);
},
es2018
:
()
=>
{
return
!
hasMethods
(
Promise
.
prototype
,
'finally'
);
},
// Test for a fully-working URL constructor.
url
:
()
=>
{
try
{
...
...
src/shared/polyfills/test/index-test.js
View file @
fd162d2e
...
...
@@ -36,6 +36,10 @@ describe('shared/polyfills/index', () => {
set
:
'es2017'
,
providesMethod
:
[
Object
,
'entries'
],
},
{
set
:
'es2018'
,
providesMethod
:
[
Promise
.
prototype
,
'finally'
],
},
{
set
:
'string.prototype.normalize'
,
providesMethod
:
[
String
.
prototype
,
'normalize'
],
...
...
src/sidebar/services/annotations.js
View file @
fd162d2e
import
SearchClient
from
'../search-client'
;
import
{
isNew
}
from
'../util/annotation-metadata'
;
import
{
privatePermissions
,
sharedPermissions
}
from
'../util/permissions'
;
// @ngInject
export
default
function
annotationsService
(
...
...
@@ -59,7 +61,63 @@ export default function annotationsService(
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
};
}
/**
* Save new (or update existing) annotation. On success,
* the annotation's `Draft` will be removed and the annotation added
* to the store.
*/
async
function
save
(
annotation
)
{
let
saved
;
const
annotationWithChanges
=
applyDraftChanges
(
annotation
);
if
(
isNew
(
annotation
))
{
saved
=
api
.
annotation
.
create
({},
annotationWithChanges
);
}
else
{
saved
=
api
.
annotation
.
update
(
{
id
:
annotation
.
id
},
annotationWithChanges
);
}
const
savedAnnotation
=
await
saved
;
Object
.
keys
(
annotation
).
forEach
(
key
=>
{
if
(
key
[
0
]
===
'$'
)
{
savedAnnotation
[
key
]
=
annotation
[
key
];
}
});
// Clear out any pending changes (draft)
store
.
removeDraft
(
annotation
);
// Add (or, in effect, update) the annotation to the store's collection
store
.
addAnnotations
([
savedAnnotation
]);
return
savedAnnotation
;
}
return
{
load
,
save
,
};
}
src/sidebar/services/test/annotations-test.js
View file @
fd162d2e
import
EventEmitter
from
'tiny-emitter'
;
import
*
as
fixtures
from
'../../test/annotation-fixtures'
;
import
annotationsService
from
'../annotations'
;
import
{
$imports
}
from
'../annotations'
;
...
...
@@ -33,6 +35,7 @@ describe('annotationService', () => {
let
fakeStore
;
let
fakeApi
;
let
fakeAnnotationMapper
;
let
fakeIsNew
;
let
fakeStreamer
;
let
fakeStreamFilter
;
...
...
@@ -48,17 +51,25 @@ describe('annotationService', () => {
unloadAnnotations
:
sinon
.
stub
(),
};
fakeApi
=
{
annotation
:
{
create
:
sinon
.
stub
().
resolves
(
fixtures
.
defaultAnnotation
()),
update
:
sinon
.
stub
().
resolves
(
fixtures
.
defaultAnnotation
()),
},
search
:
sinon
.
stub
(),
};
fakeIsNew
=
sinon
.
stub
().
returns
(
true
);
fakeStore
=
{
getState
:
sinon
.
stub
(),
addAnnotations
:
sinon
.
stub
(),
annotationFetchFinished
:
sinon
.
stub
(),
annotationFetchStarted
:
sinon
.
stub
(),
frames
:
sinon
.
stub
(),
getDraft
:
sinon
.
stub
().
returns
(
null
),
getState
:
sinon
.
stub
(),
hasSelectedAnnotations
:
sinon
.
stub
(),
removeDraft
:
sinon
.
stub
(),
searchUris
:
sinon
.
stub
(),
savedAnnotations
:
sinon
.
stub
(),
hasSelectedAnnotations
:
sinon
.
stub
(),
updateFrameAnnotationFetchStatus
:
sinon
.
stub
(),
annotationFetchStarted
:
sinon
.
stub
(),
annotationFetchFinished
:
sinon
.
stub
(),
};
fakeStreamer
=
{
setConfig
:
sinon
.
stub
(),
...
...
@@ -76,6 +87,7 @@ describe('annotationService', () => {
$imports
.
$mock
({
'../search-client'
:
FakeSearchClient
,
'../util/annotation-metadata'
:
{
isNew
:
fakeIsNew
},
});
});
...
...
@@ -265,4 +277,119 @@ describe('annotationService', () => {
assert
.
calledWith
(
console
.
error
,
error
);
});
});
describe
(
'save'
,
()
=>
{
let
svc
;
beforeEach
(()
=>
{
svc
=
service
();
});
it
(
'calls the `create` API service for new annotations'
,
()
=>
{
fakeIsNew
.
returns
(
true
);
// Using the new-annotation fixture has no bearing on which API method
// will get called because `isNew` is mocked, but it has representative
// properties
const
annotation
=
fixtures
.
newAnnotation
();
return
svc
.
save
(
annotation
).
then
(()
=>
{
assert
.
calledOnce
(
fakeApi
.
annotation
.
create
);
assert
.
notCalled
(
fakeApi
.
annotation
.
update
);
});
});
it
(
'calls the `update` API service for pre-existing annotations'
,
()
=>
{
fakeIsNew
.
returns
(
false
);
const
annotation
=
fixtures
.
defaultAnnotation
();
return
svc
.
save
(
annotation
).
then
(()
=>
{
assert
.
calledOnce
(
fakeApi
.
annotation
.
update
);
assert
.
notCalled
(
fakeApi
.
annotation
.
create
);
});
});
it
(
'calls the relevant API service with an object that has any draft changes integrated'
,
()
=>
{
const
annotation
=
fixtures
.
defaultAnnotation
();
annotation
.
text
=
'not this'
;
annotation
.
tags
=
[
'nope'
];
fakeStore
.
getDraft
.
returns
({
tags
:
[
'one'
,
'two'
],
text
:
'my text'
,
isPrivate
:
true
,
annotation
:
fixtures
.
defaultAnnotation
(),
});
return
svc
.
save
(
fixtures
.
defaultAnnotation
()).
then
(()
=>
{
const
annotationWithChanges
=
fakeApi
.
annotation
.
create
.
getCall
(
0
)
.
args
[
1
];
assert
.
equal
(
annotationWithChanges
.
text
,
'my text'
);
assert
.
sameMembers
(
annotationWithChanges
.
tags
,
[
'one'
,
'two'
]);
// Permissions converted to "private"
assert
.
include
(
annotationWithChanges
.
permissions
.
read
,
fixtures
.
defaultAnnotation
().
user
);
assert
.
notInclude
(
annotationWithChanges
.
permissions
.
read
,
[
'group:__world__'
,
]);
});
});
context
(
'successful save'
,
()
=>
{
it
(
'copies over internal app-specific keys to the annotation object'
,
()
=>
{
fakeIsNew
.
returns
(
false
);
const
annotation
=
fixtures
.
defaultAnnotation
();
annotation
.
$tag
=
'mytag'
;
annotation
.
$foo
=
'bar'
;
// The fixture here has no `$`-prefixed props
fakeApi
.
annotation
.
update
.
resolves
(
fixtures
.
defaultAnnotation
());
return
svc
.
save
(
annotation
).
then
(()
=>
{
const
savedAnnotation
=
fakeStore
.
addAnnotations
.
getCall
(
0
)
.
args
[
0
][
0
];
assert
.
equal
(
savedAnnotation
.
$tag
,
'mytag'
);
assert
.
equal
(
savedAnnotation
.
$foo
,
'bar'
);
});
});
it
(
'removes the annotation draft'
,
()
=>
{
const
annotation
=
fixtures
.
defaultAnnotation
();
return
svc
.
save
(
annotation
).
then
(()
=>
{
assert
.
calledWith
(
fakeStore
.
removeDraft
,
annotation
);
});
});
it
(
'adds the updated annotation to the store'
,
()
=>
{
const
annotation
=
fixtures
.
defaultAnnotation
();
fakeIsNew
.
returns
(
false
);
fakeApi
.
annotation
.
update
.
resolves
(
annotation
);
return
svc
.
save
(
annotation
).
then
(()
=>
{
assert
.
calledWith
(
fakeStore
.
addAnnotations
,
[
annotation
]);
});
});
});
context
(
'error on save'
,
()
=>
{
it
(
'does not remove the annotation draft'
,
()
=>
{
fakeApi
.
annotation
.
update
.
rejects
();
fakeIsNew
.
returns
(
false
);
return
svc
.
save
(
fixtures
.
defaultAnnotation
()).
catch
(()
=>
{
assert
.
notCalled
(
fakeStore
.
removeDraft
);
});
});
it
(
'does not add the annotation to the store'
,
()
=>
{
fakeApi
.
annotation
.
update
.
rejects
();
fakeIsNew
.
returns
(
false
);
return
svc
.
save
(
fixtures
.
defaultAnnotation
()).
catch
(()
=>
{
assert
.
notCalled
(
fakeStore
.
addAnnotations
);
});
});
});
});
});
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