1+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
12/* eslint-disable @typescript-eslint/unbound-method */
2- import { setupDefaultJobEnvironment } from '../../__mocks__/context'
3+ import { MockJobContext , setupDefaultJobEnvironment } from '../../__mocks__/context'
34import { setupMockShowStyleCompound } from '../../__mocks__/presetCollections'
45import { findInstancesToSync , PartInstanceToSync , SyncChangesToPartInstancesWorker } from '../syncChangesToPartInstance'
56import { mock } from 'jest-mock-extended'
@@ -9,6 +10,17 @@ import type { PlayoutRundownModel } from '../../playout/model/PlayoutRundownMode
910import type { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel'
1011import type { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
1112import { protectString } from '@sofie-automation/corelib/dist/protectedString'
13+ import { PlayoutModelImpl } from '../../playout/model/implementation/PlayoutModelImpl'
14+ import { PlaylistTimingType , ShowStyleBlueprintManifest } from '@sofie-automation/blueprints-integration'
15+ import { RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
16+ import { DBRundownPlaylist , SelectedPartInstance } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
17+ import { PlayoutRundownModelImpl } from '../../playout/model/implementation/PlayoutRundownModelImpl'
18+ import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
19+ import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
20+ import { PlayoutSegmentModelImpl } from '../../playout/model/implementation/PlayoutSegmentModelImpl'
21+ import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
22+ import { ProcessedShowStyleCompound } from '../../jobs'
23+ import { PartialDeep , ReadonlyDeep } from 'type-fest'
1224
1325jest . mock ( '../../playout/adlibTesting' )
1426import { validateAdlibTestingPartInstanceProperties } from '../../playout/adlibTesting'
@@ -67,14 +79,16 @@ describe('SyncChangesToPartInstancesWorker', () => {
6779 } )
6880
6981 describe ( 'syncChangesToPartInstance' , ( ) => {
70- function createMockPlayoutModel ( ) : PlayoutModel {
82+ function createMockPlayoutModel ( partialModel ?: Partial < Pick < PlayoutModel , 'nextPartInstance' > > ) {
7183 return mock < PlayoutModel > (
7284 {
7385 currentPartInstance : null ,
74- nextPartInstance : null ,
86+ nextPartInstance : partialModel ?. nextPartInstance ?? null ,
7587 previousPartInstance : null ,
7688
7789 clearAllNotifications : jest . fn ( ) ,
90+ // setPartInstanceAsNext: jest.fn(),
91+ // removeUntakenPartInstances: jest.fn(),
7892 } ,
7993 mockOptions
8094 )
@@ -117,6 +131,10 @@ describe('SyncChangesToPartInstancesWorker', () => {
117131 )
118132 }
119133
134+ beforeEach ( ( ) => {
135+ jest . clearAllMocks ( )
136+ } )
137+
120138 test ( 'successful with empty blueprint method' , async ( ) => {
121139 const context = setupDefaultJobEnvironment ( )
122140 const showStyleCompound = await setupMockShowStyleCompound ( context )
@@ -158,5 +176,278 @@ describe('SyncChangesToPartInstancesWorker', () => {
158176 expect ( syncIngestUpdateToPartInstanceFn ) . toHaveBeenCalledTimes ( 1 )
159177 expect ( validateAdlibTestingPartInstanceProperties ) . toHaveBeenCalledTimes ( 1 )
160178 } )
179+
180+ test ( 'removePartInstance for next calls recreateNextPartInstance' , async ( ) => {
181+ const context = setupDefaultJobEnvironment ( )
182+ const showStyleCompound = await setupMockShowStyleCompound ( context )
183+
184+ type TsyncIngestUpdateToPartInstanceFn = jest . MockedFunction <
185+ Required < ShowStyleBlueprintManifest > [ 'syncIngestUpdateToPartInstance' ]
186+ >
187+ const syncIngestUpdateToPartInstanceFn : TsyncIngestUpdateToPartInstanceFn = jest . fn ( )
188+ context . updateShowStyleBlueprint ( {
189+ syncIngestUpdateToPartInstance : syncIngestUpdateToPartInstanceFn ,
190+ } )
191+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
192+
193+ const partInstance = createMockPartInstance ( 'mockPartInstanceId' )
194+ const part = createMockPart ( 'mockPartId' )
195+
196+ const playoutModel = createMockPlayoutModel ( { nextPartInstance : partInstance } )
197+ const ingestModel = createMockIngestModelReadonly ( )
198+ const rundownModel = createMockPlayoutRundownModel ( )
199+
200+ const worker = new SyncChangesToPartInstancesWorker (
201+ context ,
202+ playoutModel ,
203+ ingestModel ,
204+ showStyleCompound ,
205+ blueprint
206+ )
207+ worker . recreateNextPartInstance = jest . fn ( )
208+
209+ const instanceToSync : PartInstanceToSync = {
210+ playoutRundownModel : rundownModel ,
211+ existingPartInstance : partInstance ,
212+ previousPartInstance : null ,
213+ playStatus : 'next' ,
214+ newPart : part ,
215+ proposedPieceInstances : Promise . resolve ( [ ] ) ,
216+ }
217+
218+ syncIngestUpdateToPartInstanceFn . mockImplementationOnce ( ( context ) => {
219+ // Remove the partInstance
220+ context . removePartInstance ( )
221+ } )
222+
223+ await worker . syncChangesToPartInstance ( instanceToSync )
224+
225+ expect ( partInstance . snapshotMakeCopy ) . toHaveBeenCalledTimes ( 1 )
226+ expect ( partInstance . snapshotRestore ) . toHaveBeenCalledTimes ( 0 )
227+ expect ( syncIngestUpdateToPartInstanceFn ) . toHaveBeenCalledTimes ( 1 )
228+ expect ( validateAdlibTestingPartInstanceProperties ) . toHaveBeenCalledTimes ( 0 )
229+ expect ( worker . recreateNextPartInstance ) . toHaveBeenCalledTimes ( 1 )
230+ } )
231+ } )
232+
233+ describe ( 'recreateNextPartInstance' , ( ) => {
234+ async function createSimplePlayoutModel (
235+ context : MockJobContext ,
236+ showStyleCompound : ReadonlyDeep < ProcessedShowStyleCompound >
237+ ) {
238+ const playlistId = protectString < RundownPlaylistId > ( 'mockPlaylistId' )
239+ const playlistLock = await context . lockPlaylist ( playlistId )
240+
241+ const rundown : DBRundown = {
242+ _id : protectString ( 'mockRundownId' ) ,
243+ externalId : 'mockExternalId' ,
244+ playlistId : playlistId ,
245+ showStyleBaseId : showStyleCompound . _id ,
246+ showStyleVariantId : showStyleCompound . showStyleVariantId ,
247+ name : 'mockName' ,
248+ organizationId : null ,
249+ studioId : context . studioId ,
250+ source : {
251+ type : 'http' ,
252+ } ,
253+ created : 0 ,
254+ modified : 0 ,
255+ importVersions : {
256+ blueprint : '' ,
257+ core : '' ,
258+ showStyleBase : '' ,
259+ showStyleVariant : '' ,
260+ studio : '' ,
261+ } ,
262+ timing : { type : PlaylistTimingType . None } ,
263+ }
264+
265+ const segment : DBSegment = {
266+ _id : protectString ( 'mockSegmentId' ) ,
267+ rundownId : rundown . _id ,
268+ name : 'mockSegmentName' ,
269+ externalId : 'mockSegmentExternalId' ,
270+ _rank : 0 ,
271+ }
272+
273+ const part0 : DBPart = {
274+ _id : protectString ( 'mockPartId0' ) ,
275+ segmentId : segment . _id ,
276+ rundownId : rundown . _id ,
277+ title : 'mockPartTitle0' ,
278+ _rank : 0 ,
279+ expectedDuration : 0 ,
280+ expectedDurationWithTransition : 0 ,
281+ externalId : 'mockPartExternalId0' ,
282+ }
283+
284+ const nextPartInstance : DBPartInstance = {
285+ _id : protectString ( 'mockPartInstanceId' ) ,
286+ part : part0 ,
287+ segmentId : segment . _id ,
288+ rundownId : rundown . _id ,
289+ takeCount : 0 ,
290+ rehearsal : false ,
291+ playlistActivationId : protectString ( 'mockPlaylistActivationId' ) ,
292+ segmentPlayoutId : protectString ( 'mockSegmentPlayoutId' ) ,
293+ }
294+
295+ const playlist : DBRundownPlaylist = {
296+ _id : playlistId ,
297+ externalId : 'mockExternalId' ,
298+ activationId : protectString ( 'mockActivationId' ) ,
299+ currentPartInfo : null ,
300+ nextPartInfo : {
301+ rundownId : nextPartInstance . rundownId ,
302+ partInstanceId : nextPartInstance . _id ,
303+ manuallySelected : false ,
304+ consumesQueuedSegmentId : false ,
305+ } ,
306+ previousPartInfo : null ,
307+ studioId : context . studioId ,
308+ name : 'mockName' ,
309+ created : 0 ,
310+ modified : 0 ,
311+ timing : { type : PlaylistTimingType . None } ,
312+ rundownIdsInOrder : [ ] ,
313+ }
314+
315+ const segmentModel = new PlayoutSegmentModelImpl ( segment , [ part0 ] )
316+ const rundownModel = new PlayoutRundownModelImpl ( rundown , [ segmentModel ] , [ ] )
317+ const playoutModel = new PlayoutModelImpl (
318+ context ,
319+ playlistLock ,
320+ playlistId ,
321+ [ ] ,
322+ playlist ,
323+ [ nextPartInstance ] ,
324+ new Map ( ) ,
325+ [ rundownModel ] ,
326+ undefined
327+ )
328+
329+ return { playlistId, playoutModel, part0, nextPartInstance }
330+ }
331+
332+ function createMockIngestModelReadonly ( ) : IngestModelReadonly {
333+ return mock < IngestModelReadonly > (
334+ {
335+ findPart : jest . fn ( ( ) => undefined ) ,
336+ getGlobalPieces : jest . fn ( ( ) => [ ] ) ,
337+ } ,
338+ mockOptions
339+ )
340+ }
341+
342+ test ( 'clear auto chosen partInstance' , async ( ) => {
343+ const context = setupDefaultJobEnvironment ( )
344+ const showStyleCompound = await setupMockShowStyleCompound ( context )
345+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
346+
347+ const { playoutModel } = await createSimplePlayoutModel ( context , showStyleCompound )
348+
349+ const ingestModel = createMockIngestModelReadonly ( )
350+
351+ const worker = new SyncChangesToPartInstancesWorker (
352+ context ,
353+ playoutModel ,
354+ ingestModel ,
355+ showStyleCompound ,
356+ blueprint
357+ )
358+
359+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
360+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
361+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
362+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
363+ consumesQueuedSegmentId : false ,
364+ manuallySelected : false ,
365+ } satisfies SelectedPartInstance )
366+
367+ await worker . recreateNextPartInstance ( undefined )
368+
369+ expect ( playoutModel . nextPartInstance ) . toBeFalsy ( )
370+ } )
371+
372+ test ( 'clear manually chosen partInstance' , async ( ) => {
373+ const context = setupDefaultJobEnvironment ( )
374+ const showStyleCompound = await setupMockShowStyleCompound ( context )
375+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
376+
377+ const { playoutModel } = await createSimplePlayoutModel ( context , showStyleCompound )
378+
379+ const ingestModel = createMockIngestModelReadonly ( )
380+
381+ const worker = new SyncChangesToPartInstancesWorker (
382+ context ,
383+ playoutModel ,
384+ ingestModel ,
385+ showStyleCompound ,
386+ blueprint
387+ )
388+
389+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
390+ // Force the next part to be manually selected, and verify
391+ playoutModel . setPartInstanceAsNext ( playoutModel . nextPartInstance , true , false )
392+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
393+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
394+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
395+ consumesQueuedSegmentId : false ,
396+ manuallySelected : true ,
397+ } satisfies SelectedPartInstance )
398+
399+ await worker . recreateNextPartInstance ( undefined )
400+
401+ expect ( playoutModel . nextPartInstance ) . toBeFalsy ( )
402+ } )
403+
404+ test ( 'clear manually chosen partInstance with replacement part' , async ( ) => {
405+ const context = setupDefaultJobEnvironment ( )
406+ const showStyleCompound = await setupMockShowStyleCompound ( context )
407+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
408+
409+ const { playoutModel, part0 } = await createSimplePlayoutModel ( context , showStyleCompound )
410+
411+ const ingestModel = createMockIngestModelReadonly ( )
412+
413+ const worker = new SyncChangesToPartInstancesWorker (
414+ context ,
415+ playoutModel ,
416+ ingestModel ,
417+ showStyleCompound ,
418+ blueprint
419+ )
420+
421+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
422+ const partInstanceIdBefore = playoutModel . nextPartInstance ! . partInstance . _id
423+
424+ // Force the next part to be manually selected, and verify
425+ playoutModel . setPartInstanceAsNext ( playoutModel . nextPartInstance , true , false )
426+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
427+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
428+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
429+ consumesQueuedSegmentId : false ,
430+ manuallySelected : true ,
431+ } satisfies SelectedPartInstance )
432+
433+ await worker . recreateNextPartInstance ( part0 )
434+
435+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
436+ // Must have been regenerated
437+ expect ( playoutModel . nextPartInstance ! . partInstance . _id ) . not . toEqual ( partInstanceIdBefore )
438+ expect ( playoutModel . nextPartInstance ! . partInstance ) . toMatchObject ( {
439+ part : {
440+ _id : part0 . _id ,
441+ } ,
442+ } satisfies PartialDeep < DBPartInstance > )
443+
444+ // Make sure the part is still manually selected
445+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
446+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
447+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
448+ consumesQueuedSegmentId : false ,
449+ manuallySelected : true ,
450+ } satisfies SelectedPartInstance )
451+ } )
161452 } )
162453} )
0 commit comments