Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 65acaad

Browse files
authored
fix: hide the overlay on hoverify unsubscription (#100)
1 parent 1936b96 commit 65acaad

File tree

3 files changed

+128
-14
lines changed

3 files changed

+128
-14
lines changed

karma.conf.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export default (config: Config): void => {
4545
mochaReporter: {
4646
showDiff: true,
4747
},
48+
client: {
49+
mocha: {
50+
timeout: 6000,
51+
},
52+
},
4853
coverageIstanbulReporter: {
4954
reports: ['json'],
5055
fixWebpackSourcePaths: true,

src/hoverifier.test.ts

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Range } from '@sourcegraph/extension-api-types'
22
import { isEqual } from 'lodash'
33
import { EMPTY, NEVER, Observable, of, Subject, Subscription } from 'rxjs'
4-
import { distinctUntilChanged, filter, map } from 'rxjs/operators'
4+
import { delay, distinctUntilChanged, filter, first, map } from 'rxjs/operators'
55
import { TestScheduler } from 'rxjs/testing'
6-
76
import { ErrorLike } from './errors'
87
import { propertyIsDefined } from './helpers'
98
import {
@@ -20,6 +19,7 @@ import { CodeViewProps, DOM } from './testutils/dom'
2019
import { createHoverAttachment, createStubActionsProvider, createStubHoverProvider } from './testutils/fixtures'
2120
import { dispatchMouseEventAtPositionImpure } from './testutils/mouse'
2221
import { HoverAttachment, LOADING } from './types'
22+
const { assert } = chai
2323

2424
describe('Hoverifier', () => {
2525
const dom = new DOM()
@@ -604,4 +604,95 @@ describe('Hoverifier', () => {
604604
})
605605
}
606606
})
607+
608+
describe('unhoverify', () => {
609+
it('hides the hover overlay when the code view is unhoverified', async () => {
610+
for (const codeView of testcases) {
611+
const hoverifier = createHoverifier({
612+
closeButtonClicks: NEVER,
613+
hoverOverlayElements: of(null),
614+
hoverOverlayRerenders: EMPTY,
615+
getHover: createStubHoverProvider(),
616+
getActions: () => of(null),
617+
})
618+
const positionJumps = new Subject<PositionJump>()
619+
const positionEvents = of(codeView.codeView).pipe(findPositionsFromEvents(codeView))
620+
621+
const codeViewSubscription = hoverifier.hoverify({
622+
dom: codeView,
623+
positionEvents,
624+
positionJumps,
625+
resolveContext: () => codeView.revSpec,
626+
})
627+
628+
dispatchMouseEventAtPositionImpure('mouseover', codeView, {
629+
line: 24,
630+
character: 6,
631+
})
632+
633+
await hoverifier.hoverStateUpdates
634+
.pipe(
635+
filter(state => !!state.hoverOverlayProps),
636+
first()
637+
)
638+
.toPromise()
639+
640+
codeViewSubscription.unsubscribe()
641+
642+
assert.strictEqual(hoverifier.hoverState.hoverOverlayProps, undefined)
643+
await of(null)
644+
.pipe(delay(200))
645+
.toPromise()
646+
assert.strictEqual(hoverifier.hoverState.hoverOverlayProps, undefined)
647+
}
648+
})
649+
it('does not hide the hover overlay when a different code view is unhoverified', async () => {
650+
for (const codeViewProps of testcases) {
651+
const hoverifier = createHoverifier({
652+
closeButtonClicks: NEVER,
653+
hoverOverlayElements: of(null),
654+
hoverOverlayRerenders: EMPTY,
655+
getHover: createStubHoverProvider(),
656+
getActions: () => of(null),
657+
})
658+
const positionJumps = new Subject<PositionJump>()
659+
const positionEvents = of(codeViewProps.codeView).pipe(findPositionsFromEvents(codeViewProps))
660+
661+
const codeViewSubscription = hoverifier.hoverify({
662+
dom: codeViewProps,
663+
positionEvents: NEVER,
664+
positionJumps: NEVER,
665+
resolveContext: () => {
666+
throw new Error('not called')
667+
},
668+
})
669+
hoverifier.hoverify({
670+
dom: codeViewProps,
671+
positionEvents,
672+
positionJumps,
673+
resolveContext: () => codeViewProps.revSpec,
674+
})
675+
676+
dispatchMouseEventAtPositionImpure('mouseover', codeViewProps, {
677+
line: 24,
678+
character: 6,
679+
})
680+
681+
await hoverifier.hoverStateUpdates
682+
.pipe(
683+
filter(state => !!state.hoverOverlayProps),
684+
first()
685+
)
686+
.toPromise()
687+
688+
codeViewSubscription.unsubscribe()
689+
690+
assert.isDefined(hoverifier.hoverState.hoverOverlayProps)
691+
await of(null)
692+
.pipe(delay(200))
693+
.toPromise()
694+
assert.isDefined(hoverifier.hoverState.hoverOverlayProps)
695+
}
696+
})
697+
})
607698
})

src/hoverifier.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ export interface EventOptions<C extends object> {
167167
resolveContext: ContextResolver<C>
168168
adjustPosition?: PositionAdjuster<C>
169169
dom: DOMFunctions
170+
codeViewId: symbol
170171
}
171172

172173
/**
173174
* @template C Extra context for the hovered token.
174175
*/
175-
export interface HoverifyOptions<C extends object> extends EventOptions<C> {
176+
export interface HoverifyOptions<C extends object>
177+
extends Pick<EventOptions<C>, Exclude<keyof EventOptions<C>, 'codeViewId'>> {
176178
positionEvents: Subscribable<PositionEvent>
177179

178180
/**
@@ -242,6 +244,11 @@ interface InternalHoverifierState<C extends object, D, A> {
242244
* Actions to display as buttons or links in the hover.
243245
*/
244246
actionsOrError?: typeof LOADING | A[] | null | ErrorLike
247+
248+
/**
249+
* A value that identifies the code view that triggered the current hover overlay.
250+
*/
251+
codeViewId?: symbol
245252
}
246253

247254
/**
@@ -559,6 +566,7 @@ export function createHoverifier<C extends object, D, A>({
559566
target: HTMLElement
560567
adjustPosition?: PositionAdjuster<C>
561568
codeView: HTMLElement
569+
codeViewId: symbol
562570
hoverOrError?: typeof LOADING | (HoverAttachment & D) | ErrorLike | null
563571
position?: HoveredToken & C
564572
part?: DiffPart
@@ -633,7 +641,7 @@ export function createHoverifier<C extends object, D, A>({
633641
return adjustingPosition.pipe(map(position => ({ position, hoverOrError, ...rest })))
634642
})
635643
)
636-
.subscribe(({ hoverOrError, position, codeView, dom, part }) => {
644+
.subscribe(({ hoverOrError, position, codeView, codeViewId, dom, part }) => {
637645
// Update the highlighted token if the hover result is successful. If the hover result specifies a
638646
// range, use that; otherwise use the hover position (which will be expanded into a full token in
639647
// getTokenAtPosition).
@@ -657,6 +665,7 @@ export function createHoverifier<C extends object, D, A>({
657665
}
658666

659667
container.update({
668+
codeViewId,
660669
hoverOrError,
661670
highlightedRange,
662671
// Reset the hover position, it's gonna be repositioned after the hover was rendered
@@ -741,20 +750,24 @@ export function createHoverifier<C extends object, D, A>({
741750
})
742751
)
743752

753+
const resetHover = () => {
754+
container.update({
755+
hoverOverlayIsFixed: false,
756+
hoverOverlayPosition: undefined,
757+
hoverOrError: undefined,
758+
hoveredToken: undefined,
759+
actionsOrError: undefined,
760+
})
761+
}
762+
744763
// When the close button is clicked, unpin, hide and reset the hover
745764
subscription.add(
746765
merge(
747766
closeButtonClicks,
748767
fromEvent<KeyboardEvent>(window, 'keydown').pipe(filter(event => event.key === Key.Escape))
749768
).subscribe(event => {
750769
event.preventDefault()
751-
container.update({
752-
hoverOverlayIsFixed: false,
753-
hoverOverlayPosition: undefined,
754-
hoverOrError: undefined,
755-
hoveredToken: undefined,
756-
actionsOrError: undefined,
757-
})
770+
resetHover()
758771
})
759772
)
760773

@@ -792,19 +805,24 @@ export function createHoverifier<C extends object, D, A>({
792805
distinctUntilChanged((a, b) => isEqual(a, b))
793806
),
794807
hoverify({ positionEvents, positionJumps = EMPTY, ...eventOptions }: HoverifyOptions<C>): Subscription {
808+
const codeViewId = Symbol('CodeView')
795809
const subscription = new Subscription()
796-
const eventWithOptions = map((event: PositionEvent) => ({ ...event, ...eventOptions }))
797810
// Broadcast all events from this code view
798811
subscription.add(
799812
from(positionEvents)
800-
.pipe(eventWithOptions)
813+
.pipe(map(event => ({ ...event, ...eventOptions, codeViewId })))
801814
.subscribe(allPositionsFromEvents)
802815
)
803816
subscription.add(
804817
from(positionJumps)
805-
.pipe(map(jump => ({ ...jump, ...eventOptions })))
818+
.pipe(map(jump => ({ ...jump, ...eventOptions, codeViewId })))
806819
.subscribe(allPositionJumps)
807820
)
821+
subscription.add(() => {
822+
if (container.values.codeViewId === codeViewId) {
823+
resetHover()
824+
}
825+
})
808826
return subscription
809827
},
810828
unsubscribe(): void {

0 commit comments

Comments
 (0)