-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(core): implement listener connection relay to avoid abrupt reconn…
…ects
- Loading branch information
Showing
3 changed files
with
260 additions
and
20 deletions.
There are no files selected for viewing
138 changes: 138 additions & 0 deletions
138
packages/sanity/src/core/store/_legacy/document/__tests__/createPairListener.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/* eslint-disable max-nested-callbacks */ | ||
import {describe, expect, it, jest} from '@jest/globals' | ||
import {type SanityClient} from '@sanity/client' | ||
import {EMPTY, from, lastValueFrom, NEVER, Observable, of, timer} from 'rxjs' | ||
import {concatMap, delay, map, takeUntil, tap, toArray} from 'rxjs/operators' | ||
|
||
import {createPairListener} from '../createPairListener' | ||
|
||
describe('createPairListener', () => { | ||
it('properly sets up and discards overlapping listeners', async () => { | ||
const unsubscribe = jest.fn() | ||
const listen = jest.fn(() => { | ||
return new Observable((subscriber) => { | ||
subscriber.next({type: 'welcome'}) | ||
return unsubscribe | ||
}).pipe( | ||
// todo: figure out why a delay is needed here | ||
delay(1), | ||
) | ||
}) | ||
|
||
const getDocuments = jest.fn((ids: string[]) => of(ids.map((id) => ({_id: id})))) | ||
|
||
const mockedClient = { | ||
observable: { | ||
listen, | ||
getDocuments, | ||
}, | ||
} as unknown as SanityClient | ||
|
||
const listener = createPairListener( | ||
mockedClient, | ||
{publishedId: 'foo', draftId: 'drafts.bar'}, | ||
{relay: {exchangeWaitMin: 50, exchangeWaitMax: 50, exchangeOverLapTime: 10}}, | ||
) | ||
const events = await lastValueFrom( | ||
listener.pipe( | ||
takeUntil( | ||
// We'll subscribe for a little more than 20ms, which means the first leg should be done and the second leg should be started | ||
timer(80).pipe( | ||
tap(() => { | ||
// at this moment unsubscribe should have been called on the first listener leg | ||
expect(unsubscribe).toHaveBeenCalledTimes(1) | ||
}), | ||
), | ||
), | ||
toArray(), | ||
), | ||
) | ||
|
||
expect(listen).toHaveBeenCalledTimes(2) | ||
expect(unsubscribe).toHaveBeenCalledTimes(2) | ||
expect(getDocuments).toHaveBeenCalledTimes(1) | ||
|
||
expect(events).toEqual([ | ||
{ | ||
type: 'snapshot', | ||
document: {_id: 'drafts.bar'}, | ||
documentId: 'drafts.bar', | ||
}, | ||
{ | ||
type: 'snapshot', | ||
document: {_id: 'foo'}, | ||
documentId: 'foo', | ||
}, | ||
]) | ||
}) | ||
|
||
it('dedupes any listener events sent in the overlapping period', async () => { | ||
const unsubscribe = jest.fn() | ||
|
||
let listenerNo = 0 | ||
const listen = jest.fn(() => { | ||
listenerNo++ | ||
return from([ | ||
{type: 'welcome'}, | ||
{type: 'mutation', transactionId: `one-${listenerNo}`}, | ||
{type: 'mutation', transactionId: 'dupe'}, | ||
]).pipe(concatMap((ev) => timer(1).pipe(map(() => ev)))) | ||
}) | ||
|
||
const mockedClient = { | ||
observable: { | ||
listen, | ||
getDocuments: (ids: string[]) => of(ids.map((id) => ({_id: id}))), | ||
}, | ||
} as unknown as SanityClient | ||
|
||
const listener = createPairListener( | ||
mockedClient, | ||
{publishedId: 'foo', draftId: 'drafts.bar'}, | ||
{relay: {exchangeWaitMin: 30, exchangeWaitMax: 30, exchangeOverLapTime: 2}}, | ||
) | ||
const events = await lastValueFrom(listener.pipe(takeUntil(timer(55)), toArray())) | ||
|
||
expect(listen).toHaveBeenCalledTimes(2) | ||
expect(unsubscribe).toHaveBeenCalledTimes(0) | ||
|
||
expect(events).toEqual([ | ||
{ | ||
document: {_id: 'drafts.bar'}, | ||
documentId: 'drafts.bar', | ||
type: 'snapshot', | ||
}, | ||
{ | ||
document: {_id: 'foo'}, | ||
documentId: 'foo', | ||
type: 'snapshot', | ||
}, | ||
{transactionId: 'one-1', type: 'mutation'}, | ||
{transactionId: 'dupe', type: 'mutation'}, | ||
{transactionId: 'one-2', type: 'mutation'}, | ||
]) | ||
}) | ||
|
||
it('avoids subscribing to the next listener before the first listener has received a welcome event, even if exchange interval has passed', async () => { | ||
const listen = jest.fn(() => NEVER) | ||
const getDocuments = jest.fn(() => EMPTY) | ||
|
||
const mockedClient = { | ||
observable: { | ||
listen, | ||
getDocuments, | ||
}, | ||
} as unknown as SanityClient | ||
|
||
const listener = createPairListener( | ||
mockedClient, | ||
{publishedId: 'foo', draftId: 'drafts.bar'}, | ||
{relay: {exchangeWaitMin: 10, exchangeWaitMax: 10, exchangeOverLapTime: 2}}, | ||
) | ||
|
||
const events = await lastValueFrom(listener.pipe(takeUntil(timer(30)), toArray())) | ||
expect(listen).toHaveBeenCalledTimes(1) | ||
expect(getDocuments).toHaveBeenCalledTimes(0) | ||
expect(events).toEqual([]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters