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

Commit 364ed5e

Browse files
committed
fix: overlay remains visible on click when already moused over
If the overlay is visible due to a mouseover event and the user clicks a token to pin it, previously the overlay would briefly disappear as its data was reloaded. This could be worked around in the consumer by having replayable observables, but most consumers did not implement this correctly. Now, when a click event pins the overlay (and does not change its position from what the prior mouseover event yielded), it does not unsubscribe the previous data observables; it remains visible and subscribed.
1 parent 0a3c993 commit 364ed5e

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

src/hoverifier.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,102 @@ describe('Hoverifier', () => {
9898
}
9999
})
100100

101+
it('pins the overlay without it disappearing temporarily on mouseover then click', () => {
102+
for (const codeView of testcases) {
103+
const scheduler = new TestScheduler((a, b) => chai.assert.deepEqual(a, b))
104+
105+
const hover = {}
106+
const defURL = 'def url'
107+
const delayTime = 10
108+
109+
scheduler.run(({ cold, expectObservable }) => {
110+
const hoverifier = createHoverifier({
111+
closeButtonClicks: NEVER,
112+
goToDefinitionClicks: new Observable<MouseEvent>(),
113+
hoverOverlayElements: of(null),
114+
hoverOverlayRerenders: EMPTY,
115+
fetchHover: createStubHoverFetcher(hover, delayTime),
116+
fetchJumpURL: createStubJumpURLFetcher(defURL, delayTime),
117+
pushHistory: noop,
118+
getReferencesURL: () => null,
119+
})
120+
121+
const positionJumps = new Subject<{
122+
position: Position
123+
codeView: HTMLElement
124+
scrollElement: HTMLElement
125+
}>()
126+
127+
const positionEvents = of(codeView.codeView).pipe(findPositionsFromEvents(codeView))
128+
129+
const subscriptions = new Subscription()
130+
131+
subscriptions.add(hoverifier)
132+
subscriptions.add(
133+
hoverifier.hoverify({
134+
dom: codeView,
135+
positionEvents,
136+
positionJumps,
137+
resolveContext: () => codeView.revSpec,
138+
})
139+
)
140+
141+
const hoverAndDefinitionUpdates = hoverifier.hoverStateUpdates.pipe(
142+
map(hoverState => !!hoverState.hoverOverlayProps),
143+
distinctUntilChanged(isEqual)
144+
)
145+
146+
// If you need to debug this test, the following might help. Append this to the `outputDiagram`
147+
// string below:
148+
//
149+
// ` ${delayAfterMouseover - 1}ms c ${delayTime - 1}ms d`
150+
//
151+
// Also, add these properties to `outputValues`:
152+
//
153+
// c: true, // the most important instant, right after the click to pin (must be true, meaning it doesn't disappear)
154+
// d: true,
155+
//
156+
// There should be no emissions at "c" or "d", so this will cause the test to fail. But those are
157+
// the most likely instants where there would be an emission if pinning is causing a temporary
158+
// disappearance of the overlay.
159+
const delayAfterMouseover = 100
160+
const outputDiagram = `${MOUSEOVER_DELAY}ms a ${delayTime - 1}ms b`
161+
const outputValues: {
162+
[key: string]: boolean
163+
} = {
164+
a: false,
165+
b: true,
166+
}
167+
168+
// Mouseover https://sourcegraph.sgdev.org/github.com/gorilla/mux@cb4698366aa625048f3b815af6a0dea8aef9280a/-/blob/mux.go#L24:6
169+
cold('a').subscribe(() =>
170+
dispatchMouseEventAtPositionImpure('mouseover', codeView, {
171+
line: 24,
172+
character: 6,
173+
})
174+
)
175+
176+
// Click (to pin) https://sourcegraph.sgdev.org/github.com/gorilla/mux@cb4698366aa625048f3b815af6a0dea8aef9280a/-/blob/mux.go#L24:6
177+
cold(`${MOUSEOVER_DELAY + delayTime + delayAfterMouseover}ms c`).subscribe(() =>
178+
dispatchMouseEventAtPositionImpure('click', codeView, {
179+
line: 24,
180+
character: 6,
181+
})
182+
)
183+
184+
// Mouseover something else and ensure it remains pinned.
185+
cold(`${MOUSEOVER_DELAY + delayTime + delayAfterMouseover + 100}ms d`).subscribe(() =>
186+
dispatchMouseEventAtPositionImpure('mouseover', codeView, {
187+
line: 25,
188+
character: 3,
189+
})
190+
)
191+
192+
expectObservable(hoverAndDefinitionUpdates).toBe(outputDiagram, outputValues)
193+
})
194+
}
195+
})
196+
101197
it('emits loading and then state on click events', () => {
102198
for (const codeView of testcases) {
103199
const scheduler = new TestScheduler((a, b) => chai.assert.deepEqual(a, b))

src/hoverifier.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ export function createHoverifier<C extends object>({
521521
)
522522

523523
/** Emits new positions including context at which a tooltip needs to be shown from clicks, mouseovers and URL changes. */
524-
const resolvedPositions = merge(codeMouseOverTargets, jumpTargets, codeClickTargets).pipe(
524+
const resolvedPositionEvents = merge(codeMouseOverTargets, jumpTargets, codeClickTargets).pipe(
525525
map(({ position, resolveContext, eventType, ...rest }) => ({
526526
...rest,
527527
eventType,
@@ -530,6 +530,13 @@ export function createHoverifier<C extends object>({
530530
share()
531531
)
532532

533+
const resolvedPositions = resolvedPositionEvents.pipe(
534+
// Suppress emissions from other events that refer to the same position as the current one. This makes it
535+
// so the overlay doesn't temporarily disappear when, e.g., clicking to pin the overlay when it's already
536+
// visible due to a mouseover.
537+
distinctUntilChanged((a, b) => isEqual(a.position, b.position))
538+
)
539+
533540
/** Emits new referencesURL values. */
534541
subscription.add(
535542
resolvedPositions
@@ -713,11 +720,14 @@ export function createHoverifier<C extends object>({
713720
// if either the hover or the definition turn out non-empty, pin the tooltip.
714721
// If they both turn out empty, unpin it so we don't end up with an invisible tooltip.
715722
//
716-
// zip together a position and the hover and definition fetches it triggered
723+
// zip together the corresponding hover and definition fetches
717724
subscription.add(
718-
zip(resolvedPositions, hoverObservables, definitionObservables)
725+
combineLatest(
726+
zip(hoverObservables, definitionObservables),
727+
resolvedPositionEvents.pipe(map(({ eventType }) => eventType))
728+
)
719729
.pipe(
720-
switchMap(([{ eventType }, hoverObservable, definitionObservable]) => {
730+
switchMap(([[hoverObservable, definitionObservable], eventType]) => {
721731
// If the position was triggered by a mouseover, never pin
722732
if (eventType !== 'click' && eventType !== 'jump') {
723733
return [false]

0 commit comments

Comments
 (0)