Skip to content

Commit

Permalink
chore(core): add tests to getReleaseEditEvents observable
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobonamin committed Dec 9, 2024
1 parent 21db4b8 commit 3f749c7
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 96 deletions.
2 changes: 2 additions & 0 deletions packages/sanity/src/core/releases/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface ReleasesReducerState {
/**
* An array of release ids ordered chronologically to represent the state of documents at the
* given point in time.
*
* TODO: Are we using this for anything?
*/
releaseStack: string[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ export function buildReleaseEditEvents(
transactions: TransactionLogEventWithEffects[],
release: ReleaseDocument,
): (EditReleaseEvent | CreateReleaseEvent)[] {
// Be sure we have all the events by checking the first transaction id and the release._rev

if (release._rev !== transactions[0].id) {
// Confirm we have all the events by checking the first transaction id and the release._rev, the should match.
if (release._rev !== transactions[0]?.id) {
console.error('Some transactions are missing, cannot calculate the edit events')
return []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {TestScheduler} from 'rxjs/testing'
import {type SanityClient} from 'sanity'
import {beforeEach, describe, expect, it, vi} from 'vitest'

import {getReleaseActivityEvents, RELEASE_ACTIVITY_INITIAL_VALUE} from './getReleaseActivityEvents'
import {
addIdToEvent,
getReleaseActivityEvents,
RELEASE_ACTIVITY_INITIAL_VALUE,
} from './getReleaseActivityEvents'
import {type ReleaseEvent} from './types'

const mockObservableRequest = vi.fn()
Expand Down Expand Up @@ -34,18 +38,6 @@ const addSecondDocumentEvent: Omit<ReleaseEvent, 'id'> = {
author: 'user-2',
}

const addIdToEvent = (event: Omit<ReleaseEvent, 'id'>) => ({
...event,
id: `${event.timestamp}-${event.type}`,
})

const initialValue = {
events: [],
loading: true,
error: null,
nextCursor: '',
}

describe('getReleaseActivityEvents', () => {
let testScheduler: TestScheduler

Expand Down Expand Up @@ -73,7 +65,7 @@ describe('getReleaseActivityEvents', () => {
const {events$} = getReleaseActivityEvents({client: mockClient, releaseId: 'release123'})
testScheduler.run(({expectObservable}) => {
expectObservable(events$).toBe('(ab)', {
a: initialValue,
a: RELEASE_ACTIVITY_INITIAL_VALUE,
b: {
events: [addIdToEvent(addFirstDocumentEvent), addIdToEvent(creationEvent)],
nextCursor: 'cursor1',
Expand Down Expand Up @@ -113,7 +105,7 @@ describe('getReleaseActivityEvents', () => {
actions.subscribe((action) => action())

expectObservable(events$).toBe('(ab)-(cd)', {
a: initialValue,
a: RELEASE_ACTIVITY_INITIAL_VALUE,
b: {
events: [addIdToEvent(addFirstDocumentEvent), addIdToEvent(creationEvent)],
nextCursor: 'cursor1',
Expand Down Expand Up @@ -170,7 +162,7 @@ describe('getReleaseActivityEvents', () => {

actions.subscribe((action) => action())
expectObservable(events$).toBe('(ab)-(cd)', {
a: initialValue,
a: RELEASE_ACTIVITY_INITIAL_VALUE,
b: {
loading: false,
nextCursor: 'cursor2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function removeDupes(prev: ReleaseEvent[], next: ReleaseEvent[]): ReleaseEvent[]
return Array.from(noDupes.values())
}

function addId(event: Omit<ReleaseEvent, 'id'>): ReleaseEvent {
export function addIdToEvent(event: Omit<ReleaseEvent, 'id'>): ReleaseEvent {
return {...event, id: `${event.timestamp}-${event.type}`} as ReleaseEvent
}

Expand Down Expand Up @@ -68,7 +68,7 @@ export function getReleaseActivityEvents({client, releaseId}: InitialFetchEvents
.pipe(
map((response) => {
return {
events: response.events.map(addId),
events: response.events.map(addIdToEvent),
nextCursor: response.nextCursor,
loading: false,
error: null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import {type TransactionLogEventWithEffects} from '@sanity/types'
import {TestScheduler} from 'rxjs/testing'
import {type ReleaseDocument, type SanityClient} from 'sanity'
import {afterEach, beforeEach, describe, expect, it, type Mock, vi} from 'vitest'

import {getTransactionsLogs} from '../../../../store/translog/getTransactionLogs'
import {
EDITS_EVENTS_INITIAL_VALUE,
type getReleaseEditEvents as getReleaseEditEventsFunction,
} from './getReleaseEditEvents'

const mockClient = {
config: vi.fn().mockReturnValue({dataset: 'testDataset'}),
} as unknown as SanityClient

vi.mock('../../../../store/translog/getTransactionLogs', () => {
return {
getTransactionsLogs: vi.fn(),
}
})
const MOCKED_RELEASE = {
userId: '',
_createdAt: '2024-12-05T16:34:59Z',
_rev: 'mocked-rev',
name: 'rWBfpXZVj',
state: 'active',
_updatedAt: '2024-12-05T17:09:28Z',
metadata: {
releaseType: 'undecided',
description: '',
title: 'winter drop',
},
publishAt: null,
_id: '_.releases.rWBfpXZVj',
_type: 'system.release',
finalDocumentStates: null,
} as unknown as ReleaseDocument

const MOCKED_TRANSACTION_LOGS: TransactionLogEventWithEffects[] = [
{
id: 'mocked-rev',
timestamp: '2024-12-05T17:09:28.325641Z',
author: 'p8xDvUMxC',
documentIDs: ['_.releases.rWBfpXZVj'],
effects: {
'_.releases.rWBfpXZVj': {
apply: [
11,
3,
23,
0,
12,
22,
'7:09:28',
23,
19,
20,
15,
10,
5,
19,
1,
17,
'undecided',
'releaseType',
15,
],
revert: [
11,
3,
23,
0,
12,
22,
'6:35:11',
23,
19,
20,
15,
10,
5,
17,
'2024-12-20T16:35:00.000Z',
'intendedPublishAt',
17,
'scheduled',
'releaseType',
15,
],
},
},
},
]

const MOCKED_EVENT = {
type: 'releaseEditEvent',
author: 'p8xDvUMxC',
change: {releaseType: 'undecided', intendedPublishDate: undefined},
id: 'mocked-rev',
timestamp: '2024-12-05T17:09:28.325641Z',
releaseName: 'rWBfpXZVj',
}
const mockGetTransactionsLogs = getTransactionsLogs as Mock

const MOCKED_RELEASES_STATE = {
state: 'loaded' as const,
releaseStack: [],
releases: new Map([[MOCKED_RELEASE._id, MOCKED_RELEASE]]),
}

describe('getReleaseEditEvents()', () => {
let testScheduler: TestScheduler
let getReleaseEditEvents: typeof getReleaseEditEventsFunction
beforeEach(async () => {
// We need to reset the module and reassign it because it has an internal cache that we need to evict
vi.resetModules()
const testModule = await import('./getReleaseEditEvents')
getReleaseEditEvents = testModule.getReleaseEditEvents

testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected)
})
})
afterEach(() => {
vi.resetAllMocks()
})
it('should initialize with the default value if releaseId is not provided', () => {
testScheduler.run(({expectObservable, hot}) => {
const releasesState$ = hot('a', {a: MOCKED_RELEASES_STATE})
const {editEvents$} = getReleaseEditEvents({
client: mockClient,
releaseId: undefined,
releasesState$: releasesState$,
})
expectObservable(editEvents$).toBe('(a|)', {a: EDITS_EVENTS_INITIAL_VALUE})
})
})
it('should not get the events if release is undefined', () => {
testScheduler.run(({expectObservable, hot}) => {
const releasesState$ = hot('a', {a: MOCKED_RELEASES_STATE})

const {editEvents$} = getReleaseEditEvents({
client: mockClient,
releaseId: 'not-existing-release',
releasesState$,
})

expectObservable(editEvents$).toBe('(a)', {a: EDITS_EVENTS_INITIAL_VALUE})
})
})
it('should get and build the release edit events', () => {
testScheduler.run(({expectObservable, cold, hot}) => {
const releasesState$ = hot('a', {a: MOCKED_RELEASES_STATE})

const {editEvents$} = getReleaseEditEvents({
client: mockClient,
releaseId: MOCKED_RELEASE._id,
releasesState$,
})
const mockResponse$ = cold('-a|', {a: MOCKED_TRANSACTION_LOGS})
mockGetTransactionsLogs.mockReturnValueOnce(mockResponse$)
expectObservable(editEvents$).toBe('ab', {
a: {editEvents: [], loading: true},
b: {
editEvents: [MOCKED_EVENT],
loading: false,
},
})
})
expect(mockGetTransactionsLogs).toHaveBeenCalledWith(mockClient, MOCKED_RELEASE._id, {
effectFormat: 'mendoza',
fromTransaction: undefined,
limit: 100,
reverse: true,
tag: 'sanity.studio.release.history',
toTransaction: MOCKED_RELEASE._rev,
})
})
it('should not refetch the edit events if rev has not changed', () => {
testScheduler.run(({expectObservable, cold, hot}) => {
// Simulate the release states changing over time, but the _rev is the same
// 'a' at frame 0: initial state with _rev=rev1
// 'b' at frame 5: updated state with _rev=rev2
const releasesState$ = hot('a---b', {
a: MOCKED_RELEASES_STATE,
b: MOCKED_RELEASES_STATE,
})
const {editEvents$} = getReleaseEditEvents({
client: mockClient,
releaseId: MOCKED_RELEASE._id,
releasesState$: releasesState$,
})
const mockResponse$ = cold('-a|', {a: MOCKED_TRANSACTION_LOGS})
mockGetTransactionsLogs.mockReturnValueOnce(mockResponse$)
// Even though the state changes, the editEvents$ should not emit again
expectObservable(editEvents$).toBe('ab', {
a: {editEvents: [], loading: true},
b: {
editEvents: [MOCKED_EVENT],
loading: false,
},
})
})
expect(mockGetTransactionsLogs).toHaveBeenCalledWith(mockClient, MOCKED_RELEASE._id, {
effectFormat: 'mendoza',
fromTransaction: undefined,
limit: 100,
reverse: true,
tag: 'sanity.studio.release.history',
toTransaction: MOCKED_RELEASE._rev,
})
})
it('should refetch the edit events if release._rev changes', () => {
testScheduler.run(({expectObservable, cold, hot}) => {
// Define the initial and updated release state
const updatedReleaseState = {
...MOCKED_RELEASES_STATE,
releases: new Map([[MOCKED_RELEASE._id, {...MOCKED_RELEASE, _rev: 'changed-rev'}]]),
}
// Simulate the release states changing over time
// 'a' at frame 0: initial state with _rev=rev1
// 'b' at frame 5: updated state with _rev=rev2
const releasesState$ = hot('a---b', {
a: MOCKED_RELEASES_STATE,
b: updatedReleaseState,
})

const {editEvents$} = getReleaseEditEvents({
client: mockClient,
releaseId: MOCKED_RELEASE._id,
releasesState$: releasesState$,
})

const mockResponse$ = cold('-a|', {a: MOCKED_TRANSACTION_LOGS})
const newTransaction = {
id: 'changed-rev',
timestamp: '2024-12-05T17:10:28.325641Z',
author: 'p8xDvUMxC',
documentIDs: ['_.releases.rWBfpXZVj'],
effects: {},
}
// It only returns the new transactions, the rest are from the cache, so they will be persisted.
const mockResponse2$ = cold('-a|', {a: [newTransaction]})

mockGetTransactionsLogs.mockReturnValueOnce(mockResponse$).mockReturnValueOnce(mockResponse2$)

expectObservable(editEvents$).toBe('ab---c', {
a: {editEvents: [], loading: true},
b: {
editEvents: [MOCKED_EVENT],
loading: false,
},
c: {
editEvents: [MOCKED_EVENT],
loading: false,
},
})
})
expect(mockGetTransactionsLogs).toHaveBeenCalledWith(mockClient, MOCKED_RELEASE._id, {
effectFormat: 'mendoza',
fromTransaction: undefined,
limit: 100,
reverse: true,
tag: 'sanity.studio.release.history',
toTransaction: MOCKED_RELEASE._rev,
})
expect(mockGetTransactionsLogs).toHaveBeenCalledWith(mockClient, MOCKED_RELEASE._id, {
effectFormat: 'mendoza',
// Uses the previous release._rev as the fromTransaction
fromTransaction: MOCKED_RELEASE._rev,
limit: 100,
reverse: true,
tag: 'sanity.studio.release.history',
// Uses the new release._rev as the toTransaction
toTransaction: 'changed-rev',
})
})
})
Loading

0 comments on commit 3f749c7

Please sign in to comment.