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
c9cdea1e
Commit
c9cdea1e
authored
Oct 05, 2020
by
Lyza Danger Gardner
Committed by
Lyza Gardner
Oct 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Decaffeinate BucketBar
Make current set of tests pass
parent
9c82dbd0
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
262 additions
and
314 deletions
+262
-314
index.js
src/annotator/index.js
+1
-5
bucket-bar.coffee
src/annotator/plugin/bucket-bar.coffee
+0
-256
bucket-bar.js
src/annotator/plugin/bucket-bar.js
+205
-0
bucket-bar-test.js
src/annotator/plugin/test/bucket-bar-test.js
+56
-53
No files found.
src/annotator/index.js
View file @
c9cdea1e
...
...
@@ -20,6 +20,7 @@ import iconSet from './icons';
registerIcons
(
iconSet
);
import
configFrom
from
'./config/index'
;
import
BucketBarPlugin
from
'./plugin/bucket-bar'
;
import
CrossFramePlugin
from
'./plugin/cross-frame'
;
import
DocumentPlugin
from
'./plugin/document'
;
import
Guest
from
'./guest'
;
...
...
@@ -27,11 +28,6 @@ import PDFPlugin from './plugin/pdf';
import
PdfSidebar
from
'./pdf-sidebar'
;
import
Sidebar
from
'./sidebar'
;
// Modules that are still written in CoffeeScript and need to be converted to
// JS.
// @ts-expect-error
import
BucketBarPlugin
from
'./plugin/bucket-bar'
;
const
pluginClasses
=
{
// UI plugins
BucketBar
:
BucketBarPlugin
,
...
...
src/annotator/plugin/bucket-bar.coffee
deleted
100644 → 0
View file @
9c82dbd0
$
=
require
(
'jquery'
)
{
default
:
Delegator
}
=
require
(
'../delegator'
)
scrollIntoView
=
require
(
'scroll-into-view'
)
{
findClosestOffscreenAnchor
,
constructPositionPoints
,
buildBuckets
}
=
require
(
'../util/buckets'
)
highlighter
=
require
(
'../highlighter'
)
BUCKET_SIZE
=
16
# Regular bucket size
BUCKET_NAV_SIZE
=
BUCKET_SIZE
+
6
# Bucket plus arrow (up/down)
BUCKET_TOP_THRESHOLD
=
115
+
BUCKET_NAV_SIZE
# Toolbar
# Scroll to the next closest anchor off screen in the given direction.
scrollToClosest
=
(
anchors
,
direction
)
->
closest
=
findClosestOffscreenAnchor
(
anchors
,
direction
)
scrollIntoView
(
closest
.
highlights
[
0
])
module
.
exports
=
class
BucketBar
extends
Delegator
# svg skeleton
html
:
"""
<div class="annotator-bucket-bar">
</div>
"""
# buckets of annotations that overlap
buckets
:
[]
# index for fast hit detection in the buckets
index
:
[]
# tab elements
tabs
:
null
constructor
:
(
element
,
options
,
annotator
)
->
defaultOptions
=
{
# gapSize parameter is used by the clustering algorithm
# If an annotation is farther then this gapSize from the next bucket
# then that annotation will not be merged into the bucket
gapSize
:
60
# Selectors for the scrollable elements on the page
scrollables
:
[
'body'
]
}
super
$
(
@
html
),
Object
.
assign
(
defaultOptions
,
options
)
if
@
options
.
container
?
$
(
@
options
.
container
).
append
@
element
else
$
(
element
).
append
@
element
@
annotator
=
annotator
@
highlighter
=
options
.
highlighter
?
highlighter
# Test seam.
$
(
window
).
on
'resize scroll'
,
@
update
for
scrollable
in
@
options
.
scrollables
?
[]
$
(
scrollable
).
on
'resize scroll'
,
@
update
destroy
:
->
$
(
window
).
off
'resize scroll'
,
@
update
for
scrollable
in
@
options
.
scrollables
?
[]
$
(
scrollable
).
off
'resize scroll'
,
@
update
_collate
:
(
a
,
b
)
->
for
i
in
[
0
..
a
.
length
-
1
]
if
a
[
i
]
<
b
[
i
]
return
-
1
if
a
[
i
]
>
b
[
i
]
return
1
return
0
# Update sometime soon
update
:
=>
return
if
@
_updatePending
?
@
_updatePending
=
requestAnimationFrame
=>
delete
@
_updatePending
@
_update
()
_update
:
->
{
above
,
below
,
points
}
=
constructPositionPoints
(
@
annotator
.
anchors
)
fakeBuckets
=
buildBuckets
(
points
)
# Accumulate the overlapping anchors into buckets.
# The algorithm goes like this:
# - Collate the points by sorting on position then delta (+1 or -1)
# - Reduce over the sorted points
# - For +1 points, add the anchor at this point to an array of
# "carried" anchors. If it already exists, increase the
# corresponding value in an array of counts which maintains the
# number of points that include this anchor.
# - For -1 points, decrement the value for the anchor at this point
# in the carried array of counts. If the count is now zero, remove the
# anchor from the carried array of anchors.
# - If this point is the first, last, sufficiently far from the previous,
# or there are no more carried anchors, add a bucket marker at this
# point.
# - Otherwise, if the last bucket was not isolated (the one before it
# has at least one anchor) then remove it and ensure that its
# anchors and the carried anchors are merged into the previous
# bucket.
{
@
buckets
,
@
index
}
=
points
.
reduce
({
buckets
,
index
,
carry
},
[
x
,
d
,
a
],
i
,
points
)
=>
if
d
>
0
# delta type is "top/start": Add annotation
if
(
j
=
carry
.
anchors
.
indexOf
a
)
<
0
# If anchor not in carry
carry
.
anchors
.
unshift
a
# push on to front of carry
carry
.
counts
.
unshift
1
# initialize counts for this ... bucket?
else
carry
.
counts
[
j
]
++
# increment counts for this ...bucket?
else
# delta type is "bottom/end": Remove annotation
j
=
carry
.
anchors
.
indexOf
a
# We're assuming this anchor is already in the carry-anchors...
if
--
carry
.
counts
[
j
]
is
0
# if carry.counts[j] === 1 — if the bucket with this anchor has exactly
# 1 thing in it
carry
.
anchors
.
splice
j
,
1
# Remove this anchor from `carry.anchors`
carry
.
counts
.
splice
j
,
1
# I'm guessing this..."closes" the last open anchor for now?
if
(
(
index
.
length
is
0
or
i
is
points
.
length
-
1
)
or
# Is this the first or last point?
carry
.
anchors
.
length
is
0
or
# A zero marker?
x
-
index
[
index
.
length
-
1
]
>
@
options
.
gapSize
# A large gap?
)
# Mark a new bucket.
buckets
.
push
carry
.
anchors
.
slice
()
index
.
push
x
else
# Merge the previous bucket, making sure its predecessor contains
# all the carried anchors and the anchors in the previous
# bucket.
if
buckets
[
buckets
.
length
-
2
]
?
.
length
last
=
buckets
[
buckets
.
length
-
2
]
toMerge
=
buckets
.
pop
()
index
.
pop
()
else
last
=
buckets
[
buckets
.
length
-
1
]
toMerge
=
[]
last
.
push
a0
for
a0
in
toMerge
when
a0
not
in
last
last
.
push
a0
for
a0
in
carry
.
anchors
when
a0
not
in
last
{
buckets
,
index
,
carry
}
,
buckets
:
[]
index
:
[]
carry
:
anchors
:
[]
counts
:
[]
latest
:
0
@
buckets
.
forEach
(
bucket
,
bi
)
=>
console
.
assert
(
@
index
[
bi
]
is
fakeBuckets
.
index
[
bi
],
'index positions are equal'
)
bucket
.
forEach
(
anchor
,
ai
)
=>
console
.
assert
(
anchor
is
fakeBuckets
.
buckets
[
bi
][
ai
],
'anchors are the same'
,
anchor
.
annotation
.
$tag
,
fakeBuckets
.
buckets
[
bi
][
ai
].
annotation
.
$tag
)
# Scroll up
@
buckets
.
unshift
[],
above
,
[]
@
index
.
unshift
0
,
BUCKET_TOP_THRESHOLD
-
1
,
BUCKET_TOP_THRESHOLD
# Scroll down
@
buckets
.
push
[],
below
,
[]
@
index
.
push
window
.
innerHeight
-
BUCKET_NAV_SIZE
,
window
.
innerHeight
-
BUCKET_NAV_SIZE
+
1
,
window
.
innerHeight
# Calculate the total count for each bucket (without replies) and the
# maximum count.
max
=
0
for
b
in
@
buckets
max
=
Math
.
max
max
,
b
.
length
# Update the data bindings
element
=
@
element
# Keep track of tabs to keep element creation to a minimum.
@
tabs
||=
$
([])
# Remove any extra tabs and update @tabs.
@
tabs
.
slice
(
@
buckets
.
length
).
remove
()
@
tabs
=
@
tabs
.
slice
(
0
,
@
buckets
.
length
)
# Create any new tabs if needed.
$
.
each
@
buckets
.
slice
(
@
tabs
.
length
),
=>
div
=
$
(
'<div/>'
).
appendTo
(
element
)
@
tabs
.
push
(
div
[
0
])
div
.
addClass
(
'annotator-bucket-indicator'
)
# Focus corresponding highlights bucket when mouse is hovered
# TODO: This should use event delegation on the container.
.
on
'mousemove'
,
(
event
)
=>
bucket
=
@
tabs
.
index
(
event
.
currentTarget
)
for
anchor
in
@
annotator
.
anchors
toggle
=
anchor
in
@
buckets
[
bucket
]
$
(
anchor
.
highlights
).
toggleClass
(
'hypothesis-highlight-focused'
,
toggle
)
# Gets rid of them after
.
on
'mouseout'
,
(
event
)
=>
bucket
=
@
tabs
.
index
(
event
.
currentTarget
)
for
anchor
in
@
buckets
[
bucket
]
$
(
anchor
.
highlights
).
removeClass
(
'hypothesis-highlight-focused'
)
# Does one of a few things when a tab is clicked depending on type
.
on
'click'
,
(
event
)
=>
bucket
=
@
tabs
.
index
(
event
.
currentTarget
)
event
.
stopPropagation
()
# If it's the upper tab, scroll to next anchor above
if
(
@
isUpper
bucket
)
scrollToClosest
(
@
buckets
[
bucket
],
'up'
)
# If it's the lower tab, scroll to next anchor below
else
if
(
@
isLower
bucket
)
scrollToClosest
(
@
buckets
[
bucket
],
'down'
)
else
annotations
=
(
anchor
.
annotation
for
anchor
in
@
buckets
[
bucket
])
@
annotator
.
selectAnnotations
annotations
,
(
event
.
ctrlKey
or
event
.
metaKey
),
this
.
_buildTabs
(
@
tabs
,
@
buckets
)
_buildTabs
:
->
@
tabs
.
each
(
d
,
el
)
=>
el
=
$
(
el
)
bucket
=
@
buckets
[
d
]
bucketLength
=
bucket
?
.
length
title
=
if
bucketLength
!=
1
"Show
#{
bucketLength
}
annotations"
else
if
bucketLength
>
0
'Show one annotation'
el
.
attr
(
'title'
,
title
)
el
.
toggleClass
(
'upper'
,
@
isUpper
(
d
))
el
.
toggleClass
(
'lower'
,
@
isLower
(
d
))
if
@
isUpper
(
d
)
or
@
isLower
(
d
)
bucketSize
=
BUCKET_NAV_SIZE
else
bucketSize
=
BUCKET_SIZE
el
.
css
({
top
:
(
@
index
[
d
]
+
@
index
[
d
+
1
])
/
2
marginTop
:
-
bucketSize
/
2
display
:
unless
bucketLength
then
'none'
else
''
})
if
bucket
el
.
html
(
"<div class='label'>
#{
bucketLength
}
</div>"
)
isUpper
:
(
i
)
->
i
==
1
isLower
:
(
i
)
->
i
==
@
index
.
length
-
2
# Export constants
BucketBar
.
BUCKET_SIZE
=
BUCKET_SIZE
BucketBar
.
BUCKET_NAV_SIZE
=
BUCKET_NAV_SIZE
BucketBar
.
BUCKET_TOP_THRESHOLD
=
BUCKET_TOP_THRESHOLD
src/annotator/plugin/bucket-bar.js
0 → 100644
View file @
c9cdea1e
import
$
from
'jquery'
;
import
Delegator
from
'../delegator'
;
import
scrollIntoView
from
'scroll-into-view'
;
import
{
findClosestOffscreenAnchor
,
constructPositionPoints
,
buildBuckets
,
}
from
'../util/buckets'
;
const
BUCKET_SIZE
=
16
;
// Regular bucket size
const
BUCKET_NAV_SIZE
=
BUCKET_SIZE
+
6
;
// Bucket plus arrow (up/down)
const
BUCKET_TOP_THRESHOLD
=
115
+
BUCKET_NAV_SIZE
;
// Toolbar
// Scroll to the next closest anchor off screen in the given direction.
function
scrollToClosest
(
anchors
,
direction
)
{
const
closest
=
findClosestOffscreenAnchor
(
anchors
,
direction
);
if
(
closest
&&
closest
.
highlights
?.
length
)
{
scrollIntoView
(
closest
.
highlights
[
0
]);
}
}
export
default
class
BucketBar
extends
Delegator
{
constructor
(
element
,
options
,
annotator
)
{
const
defaultOptions
=
{
// gapSize parameter is used by the clustering algorithm
// If an annotation is farther then this gapSize from the next bucket
// then that annotation will not be merged into the bucket
// TODO: This is not currently used; reassess
gapSize
:
60
,
html
:
'<div class="annotator-bucket-bar"></div>'
,
// Selectors for the scrollable elements on the page
scrollables
:
[
'body'
],
};
const
opts
=
{
...
defaultOptions
,
...
options
};
super
(
$
(
opts
.
html
),
opts
);
this
.
buckets
=
[];
this
.
index
=
[];
this
.
tabs
=
$
([]);
if
(
this
.
options
.
container
)
{
$
(
this
.
options
.
container
).
append
(
this
.
element
);
}
else
{
$
(
element
).
append
(
this
.
element
);
}
this
.
annotator
=
annotator
;
this
.
updateFunc
=
()
=>
this
.
update
();
$
(
window
).
on
(
'resize scroll'
,
this
.
updateFunc
);
this
.
options
.
scrollables
.
forEach
(
scrollable
=>
{
$
(
scrollable
).
on
(
'scroll'
,
this
.
updateFunc
);
});
}
destroy
()
{
$
(
window
).
off
(
'resize scroll'
,
this
.
updateFunc
);
this
.
options
.
scrollables
.
forEach
(
scrollable
=>
{
$
(
scrollable
).
off
(
'scroll'
,
this
.
updateFunc
);
});
}
update
()
{
if
(
this
.
_updatePending
)
{
return
;
}
this
.
_updatePending
=
true
;
requestAnimationFrame
(()
=>
{
const
updated
=
this
.
_update
();
this
.
_updatePending
=
false
;
return
updated
;
});
}
_update
()
{
const
{
above
,
below
,
points
}
=
constructPositionPoints
(
this
.
annotator
.
anchors
);
const
bucketInfo
=
buildBuckets
(
points
);
this
.
buckets
=
bucketInfo
.
buckets
;
this
.
index
=
bucketInfo
.
index
;
// Scroll up
this
.
buckets
.
unshift
([],
above
,
[]);
this
.
index
.
unshift
(
0
,
BUCKET_TOP_THRESHOLD
-
1
,
BUCKET_TOP_THRESHOLD
);
// Scroll down
this
.
buckets
.
push
([],
below
,
[]);
this
.
index
.
push
(
window
.
innerHeight
-
BUCKET_NAV_SIZE
,
window
.
innerHeight
-
BUCKET_NAV_SIZE
+
1
,
window
.
innerHeight
);
// Remove any extra tabs and update tabs.
this
.
tabs
.
slice
(
this
.
buckets
.
length
).
remove
();
this
.
tabs
=
this
.
tabs
.
slice
(
0
,
this
.
buckets
.
length
);
// Create any new tabs if needed.
$
.
each
(
this
.
buckets
.
slice
(
this
.
tabs
.
length
),
()
=>
{
const
div
=
$
(
'<div/>'
).
appendTo
(
this
.
element
);
this
.
tabs
.
push
(
div
[
0
]);
div
.
addClass
(
'annotator-bucket-indicator'
)
// Focus corresponding highlights bucket when mouse is hovered
// TODO: This should use event delegation on the container.
.
on
(
'mousemove'
,
event
=>
{
const
bucketIndex
=
this
.
tabs
.
index
(
event
.
currentTarget
);
for
(
let
anchor
of
this
.
annotator
.
anchors
)
{
const
toggle
=
this
.
buckets
[
bucketIndex
].
includes
(
anchor
);
$
(
anchor
.
highlights
).
toggleClass
(
'hypothesis-highlight-focused'
,
toggle
);
}
})
.
on
(
'mouseout'
,
event
=>
{
const
bucket
=
this
.
tabs
.
index
(
event
.
currentTarget
);
this
.
buckets
[
bucket
].
forEach
(
anchor
=>
$
(
anchor
.
highlights
).
removeClass
(
'hypothesis-highlight-focused'
)
);
})
.
on
(
'click'
,
event
=>
{
const
bucket
=
this
.
tabs
.
index
(
event
.
currentTarget
);
event
.
stopPropagation
();
// If it's the upper tab, scroll to next anchor above
if
(
this
.
isUpper
(
bucket
))
{
scrollToClosest
(
this
.
buckets
[
bucket
],
'up'
);
// If it's the lower tab, scroll to next anchor below
}
else
if
(
this
.
isLower
(
bucket
))
{
scrollToClosest
(
this
.
buckets
[
bucket
],
'down'
);
}
else
{
const
annotations
=
this
.
buckets
[
bucket
].
map
(
anchor
=>
anchor
.
annotation
);
this
.
annotator
.
selectAnnotations
(
annotations
,
event
.
ctrlKey
||
event
.
metaKey
);
}
});
});
this
.
_buildTabs
();
}
_buildTabs
()
{
this
.
tabs
.
each
((
index
,
el
)
=>
{
let
bucketSize
;
el
=
$
(
el
);
const
bucket
=
this
.
buckets
[
index
];
const
bucketLength
=
bucket
?.
length
;
const
title
=
(()
=>
{
if
(
bucketLength
!==
1
)
{
return
`Show
${
bucketLength
}
annotations`
;
}
else
if
(
bucketLength
>
0
)
{
return
'Show one annotation'
;
}
return
''
;
})();
el
.
attr
(
'title'
,
title
);
el
.
toggleClass
(
'upper'
,
this
.
isUpper
(
index
));
el
.
toggleClass
(
'lower'
,
this
.
isLower
(
index
));
if
(
this
.
isUpper
(
index
)
||
this
.
isLower
(
index
))
{
bucketSize
=
BUCKET_NAV_SIZE
;
}
else
{
bucketSize
=
BUCKET_SIZE
;
}
el
.
css
({
top
:
(
this
.
index
[
index
]
+
this
.
index
[
index
+
1
])
/
2
,
marginTop
:
-
bucketSize
/
2
,
display
:
!
bucketLength
?
'none'
:
''
,
});
if
(
bucket
)
{
el
.
html
(
`<div class='label'>
${
bucketLength
}
</div>`
);
}
});
}
isUpper
(
i
)
{
return
i
===
1
;
}
isLower
(
i
)
{
return
i
===
this
.
index
.
length
-
2
;
}
}
// Export constants
BucketBar
.
BUCKET_SIZE
=
BUCKET_SIZE
;
BucketBar
.
BUCKET_NAV_SIZE
=
BUCKET_NAV_SIZE
;
BucketBar
.
BUCKET_TOP_THRESHOLD
=
BUCKET_TOP_THRESHOLD
;
src/annotator/plugin/test/bucket-bar-test.js
View file @
c9cdea1e
import
$
from
'jquery'
;
import
BucketBar
from
'../bucket-bar'
;
import
{
$imports
}
from
'../bucket-bar'
;
// Return DOM elements for non-empty bucket indicators in a `BucketBar`.
const
nonEmptyBuckets
=
function
(
bucketBar
)
{
...
...
@@ -13,22 +14,34 @@ const nonEmptyBuckets = function (bucketBar) {
};
const
createMouseEvent
=
function
(
type
,
{
ctrlKey
,
metaKey
}
=
{})
{
// In a modern browser we could use `new MouseEvent` constructor and pass
// `ctrlKey` and `metaKey` via the init object.
const
event
=
new
Event
(
type
);
event
.
ctrlKey
=
Boolean
(
ctrlKey
);
event
.
metaKey
=
Boolean
(
metaKey
);
return
event
;
return
new
MouseEvent
(
type
,
{
ctrlKey
,
metaKey
});
};
describe
(
'BucketBar'
,
function
()
{
let
fakeAnnotator
=
null
;
describe
(
'BucketBar'
,
()
=>
{
let
fakeAnnotator
;
let
fakeBucketUtil
;
beforeEach
(()
=>
{
fakeAnnotator
=
{
anchors
:
[],
selectAnnotations
:
sinon
.
stub
(),
};
fakeBucketUtil
=
{
findClosestOffscreenAnchor
:
sinon
.
stub
(),
constructPositionPoints
:
sinon
.
stub
()
.
returns
({
above
:
[],
below
:
[],
points
:
[]
}),
buildBuckets
:
sinon
.
stub
().
returns
([]),
};
$imports
.
$mock
({
'../util/buckets'
:
fakeBucketUtil
,
});
});
afterEach
(()
=>
{
$imports
.
$restore
();
});
const
createBucketBar
=
function
(
options
)
{
...
...
@@ -46,43 +59,37 @@ describe('BucketBar', function () {
};
context
(
'when a bucket is clicked'
,
()
=>
{
let
bucketBar
=
null
;
let
fakeHighlighter
=
null
;
let
bucketBar
;
beforeEach
(()
=>
{
fakeHighlighter
=
{
getBoundingClientRect
()
{
return
{
left
:
0
,
top
:
200
,
right
:
200
,
bottom
:
250
};
},
};
bucketBar
=
createBucketBar
({
highlighter
:
fakeHighlighter
});
bucketBar
=
createBucketBar
();
// Create fake anchors and render buckets.
const
anchors
=
[
createAnchor
()];
fakeBucketUtil
.
buildBuckets
.
returns
({
index
:
[
250
],
buckets
:
[[
anchors
[
0
]]],
});
bucketBar
.
annotator
.
anchors
=
anchors
;
return
bucketBar
.
_update
();
bucketBar
.
_update
();
});
it
.
skip
(
'selects the annotations'
,
()
=>
{
it
(
'selects the annotations'
,
()
=>
{
// Click on the indicator for the non-empty bucket.
const
bucketEls
=
nonEmptyBuckets
(
bucketBar
);
assert
.
equal
(
bucketEls
.
length
,
1
);
bucketEls
[
0
].
dispatchEvent
(
createMouseEvent
(
'click'
));
const
anns
=
bucketBar
.
annotator
.
anchors
.
map
(
anchor
=>
anchor
.
annotation
);
return
assert
.
calledWith
(
bucketBar
.
annotator
.
selectAnnotations
,
anns
,
false
);
assert
.
calledWith
(
bucketBar
.
annotator
.
selectAnnotations
,
anns
,
false
);
});
return
[
[
{
ctrlKey
:
true
,
metaKey
:
false
},
{
ctrlKey
:
false
,
metaKey
:
true
},
].
forEach
(({
ctrlKey
,
metaKey
})
=>
it
.
skip
(
'toggles selection of the annotations if Ctrl or Alt is pressed'
,
()
=>
{
it
(
'toggles selection of the annotations if Ctrl or Alt is pressed'
,
()
=>
{
// Click on the indicator for the non-empty bucket.
const
bucketEls
=
nonEmptyBuckets
(
bucketBar
);
assert
.
equal
(
bucketEls
.
length
,
1
);
...
...
@@ -93,11 +100,7 @@ describe('BucketBar', function () {
const
anns
=
bucketBar
.
annotator
.
anchors
.
map
(
anchor
=>
anchor
.
annotation
);
return
assert
.
calledWith
(
bucketBar
.
annotator
.
selectAnnotations
,
anns
,
true
);
assert
.
calledWith
(
bucketBar
.
annotator
.
selectAnnotations
,
anns
,
true
);
})
);
});
...
...
@@ -107,7 +110,7 @@ describe('BucketBar', function () {
//
// Note: This could be tested using only the public APIs of the `BucketBar`
// class using the approach of the "when a bucket is clicked" tests above.
return
describe
(
'_buildTabs'
,
function
()
{
describe
(
'_buildTabs'
,
()
=>
{
const
setup
=
function
(
tabs
)
{
const
bucketBar
=
createBucketBar
();
bucketBar
.
tabs
=
tabs
;
...
...
@@ -120,92 +123,92 @@ describe('BucketBar', function () {
return
bucketBar
;
};
it
(
'creates a tab with a title'
,
function
()
{
it
(
'creates a tab with a title'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
attr
(
'title'
),
'Show one annotation'
);
assert
.
equal
(
tab
.
attr
(
'title'
),
'Show one annotation'
);
});
it
(
'creates a tab with a pluralized title'
,
function
()
{
it
(
'creates a tab with a pluralized title'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
buckets
[
0
].
push
(
'Another Annotation?'
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
attr
(
'title'
),
'Show 2 annotations'
);
assert
.
equal
(
tab
.
attr
(
'title'
),
'Show 2 annotations'
);
});
it
(
'sets the tab text to the number of annotations'
,
function
()
{
it
(
'sets the tab text to the number of annotations'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
buckets
[
0
].
push
(
'Another Annotation?'
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
text
(),
'2'
);
assert
.
equal
(
tab
.
text
(),
'2'
);
});
it
(
'sets the tab text to the number of annotations'
,
function
()
{
it
(
'sets the tab text to the number of annotations'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
buckets
[
0
].
push
(
'Another Annotation?'
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
text
(),
'2'
);
assert
.
equal
(
tab
.
text
(),
'2'
);
});
it
(
'adds the class "upper" if the annotation is at the top'
,
function
()
{
it
(
'adds the class "upper" if the annotation is at the top'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
sinon
.
stub
(
bucketBar
,
'isUpper'
).
returns
(
true
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
hasClass
(
'upper'
),
true
);
assert
.
equal
(
tab
.
hasClass
(
'upper'
),
true
);
});
it
(
'removes the class "upper" if the annotation is not at the top'
,
function
()
{
it
(
'removes the class "upper" if the annotation is not at the top'
,
()
=>
{
const
tab
=
$
(
'<div />'
).
addClass
(
'upper'
);
const
bucketBar
=
setup
(
tab
);
sinon
.
stub
(
bucketBar
,
'isUpper'
).
returns
(
false
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
hasClass
(
'upper'
),
false
);
assert
.
equal
(
tab
.
hasClass
(
'upper'
),
false
);
});
it
(
'adds the class "lower" if the annotation is at the top'
,
function
()
{
it
(
'adds the class "lower" if the annotation is at the top'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
sinon
.
stub
(
bucketBar
,
'isLower'
).
returns
(
true
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
hasClass
(
'lower'
),
true
);
assert
.
equal
(
tab
.
hasClass
(
'lower'
),
true
);
});
it
(
'removes the class "lower" if the annotation is not at the top'
,
function
()
{
it
(
'removes the class "lower" if the annotation is not at the top'
,
()
=>
{
const
tab
=
$
(
'<div />'
).
addClass
(
'lower'
);
const
bucketBar
=
setup
(
tab
);
sinon
.
stub
(
bucketBar
,
'isLower'
).
returns
(
false
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
hasClass
(
'lower'
),
false
);
assert
.
equal
(
tab
.
hasClass
(
'lower'
),
false
);
});
it
(
'reveals the tab if there are annotations in the bucket'
,
function
()
{
it
(
'reveals the tab if there are annotations in the bucket'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
css
(
'display'
),
''
);
assert
.
equal
(
tab
.
css
(
'display'
),
''
);
});
return
it
(
'hides the tab if there are no annotations in the bucket'
,
function
()
{
it
(
'hides the tab if there are no annotations in the bucket'
,
()
=>
{
const
tab
=
$
(
'<div />'
);
const
bucketBar
=
setup
(
tab
);
bucketBar
.
buckets
=
[];
bucketBar
.
_buildTabs
();
return
assert
.
equal
(
tab
.
css
(
'display'
),
'none'
);
assert
.
equal
(
tab
.
css
(
'display'
),
'none'
);
});
});
});
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