11/* eslint-disable @typescript-eslint/no-non-null-assertion */
22/* eslint-disable @typescript-eslint/unbound-method */
3- import { setupDefaultJobEnvironment } from '../../__mocks__/context'
3+ import { MockJobContext , setupDefaultJobEnvironment } from '../../__mocks__/context'
44import { setupMockShowStyleCompound } from '../../__mocks__/presetCollections'
55import { findInstancesToSync , PartInstanceToSync , SyncChangesToPartInstancesWorker } from '../syncChangesToPartInstance'
66import { mock } from 'jest-mock-extended'
@@ -10,6 +10,17 @@ import type { PlayoutRundownModel } from '../../playout/model/PlayoutRundownMode
1010import type { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel'
1111import type { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
1212import { 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'
1324
1425jest . mock ( '../../playout/adlibTesting' )
1526import { validateAdlibTestingPartInstanceProperties } from '../../playout/adlibTesting'
@@ -165,5 +176,280 @@ describe('SyncChangesToPartInstancesWorker', () => {
165176 expect ( syncIngestUpdateToPartInstanceFn ) . toHaveBeenCalledTimes ( 1 )
166177 expect ( validateAdlibTestingPartInstanceProperties ) . toHaveBeenCalledTimes ( 1 )
167178 } )
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+ // Mock the method, we can test it separately
208+ worker . recreateNextPartInstance = jest . fn ( )
209+
210+ const instanceToSync : PartInstanceToSync = {
211+ playoutRundownModel : rundownModel ,
212+ existingPartInstance : partInstance ,
213+ previousPartInstance : null ,
214+ playStatus : 'next' ,
215+ newPart : part ,
216+ proposedPieceInstances : Promise . resolve ( [ ] ) ,
217+ }
218+
219+ syncIngestUpdateToPartInstanceFn . mockImplementationOnce ( ( context ) => {
220+ // Remove the partInstance
221+ context . removePartInstance ( )
222+ } )
223+
224+ await worker . syncChangesToPartInstance ( instanceToSync )
225+
226+ expect ( partInstance . snapshotMakeCopy ) . toHaveBeenCalledTimes ( 1 )
227+ expect ( partInstance . snapshotRestore ) . toHaveBeenCalledTimes ( 0 )
228+ expect ( syncIngestUpdateToPartInstanceFn ) . toHaveBeenCalledTimes ( 1 )
229+ expect ( validateAdlibTestingPartInstanceProperties ) . toHaveBeenCalledTimes ( 0 )
230+ expect ( worker . recreateNextPartInstance ) . toHaveBeenCalledTimes ( 1 )
231+ expect ( worker . recreateNextPartInstance ) . toHaveBeenCalledWith ( part )
232+ } )
233+ } )
234+
235+ describe ( 'recreateNextPartInstance' , ( ) => {
236+ async function createSimplePlayoutModel (
237+ context : MockJobContext ,
238+ showStyleCompound : ReadonlyDeep < ProcessedShowStyleCompound >
239+ ) {
240+ const playlistId = protectString < RundownPlaylistId > ( 'mockPlaylistId' )
241+ const playlistLock = await context . lockPlaylist ( playlistId )
242+
243+ const rundown : DBRundown = {
244+ _id : protectString ( 'mockRundownId' ) ,
245+ externalId : 'mockExternalId' ,
246+ playlistId : playlistId ,
247+ showStyleBaseId : showStyleCompound . _id ,
248+ showStyleVariantId : showStyleCompound . showStyleVariantId ,
249+ name : 'mockName' ,
250+ organizationId : null ,
251+ studioId : context . studioId ,
252+ source : {
253+ type : 'http' ,
254+ } ,
255+ created : 0 ,
256+ modified : 0 ,
257+ importVersions : {
258+ blueprint : '' ,
259+ core : '' ,
260+ showStyleBase : '' ,
261+ showStyleVariant : '' ,
262+ studio : '' ,
263+ } ,
264+ timing : { type : PlaylistTimingType . None } ,
265+ }
266+
267+ const segment : DBSegment = {
268+ _id : protectString ( 'mockSegmentId' ) ,
269+ rundownId : rundown . _id ,
270+ name : 'mockSegmentName' ,
271+ externalId : 'mockSegmentExternalId' ,
272+ _rank : 0 ,
273+ }
274+
275+ const part0 : DBPart = {
276+ _id : protectString ( 'mockPartId0' ) ,
277+ segmentId : segment . _id ,
278+ rundownId : rundown . _id ,
279+ title : 'mockPartTitle0' ,
280+ _rank : 0 ,
281+ expectedDuration : 0 ,
282+ expectedDurationWithTransition : 0 ,
283+ externalId : 'mockPartExternalId0' ,
284+ }
285+
286+ const nextPartInstance : DBPartInstance = {
287+ _id : protectString ( 'mockPartInstanceId' ) ,
288+ part : part0 ,
289+ segmentId : segment . _id ,
290+ rundownId : rundown . _id ,
291+ takeCount : 0 ,
292+ rehearsal : false ,
293+ playlistActivationId : protectString ( 'mockPlaylistActivationId' ) ,
294+ segmentPlayoutId : protectString ( 'mockSegmentPlayoutId' ) ,
295+ }
296+
297+ const playlist : DBRundownPlaylist = {
298+ _id : playlistId ,
299+ externalId : 'mockExternalId' ,
300+ activationId : protectString ( 'mockActivationId' ) ,
301+ currentPartInfo : null ,
302+ nextPartInfo : {
303+ rundownId : nextPartInstance . rundownId ,
304+ partInstanceId : nextPartInstance . _id ,
305+ manuallySelected : false ,
306+ consumesQueuedSegmentId : false ,
307+ } ,
308+ previousPartInfo : null ,
309+ studioId : context . studioId ,
310+ name : 'mockName' ,
311+ created : 0 ,
312+ modified : 0 ,
313+ timing : { type : PlaylistTimingType . None } ,
314+ rundownIdsInOrder : [ ] ,
315+ }
316+
317+ const segmentModel = new PlayoutSegmentModelImpl ( segment , [ part0 ] )
318+ const rundownModel = new PlayoutRundownModelImpl ( rundown , [ segmentModel ] , [ ] )
319+ const playoutModel = new PlayoutModelImpl (
320+ context ,
321+ playlistLock ,
322+ playlistId ,
323+ [ ] ,
324+ playlist ,
325+ [ nextPartInstance ] ,
326+ new Map ( ) ,
327+ [ rundownModel ] ,
328+ undefined
329+ )
330+
331+ return { playlistId, playoutModel, part0, nextPartInstance }
332+ }
333+
334+ function createMockIngestModelReadonly ( ) : IngestModelReadonly {
335+ return mock < IngestModelReadonly > (
336+ {
337+ findPart : jest . fn ( ( ) => undefined ) ,
338+ getGlobalPieces : jest . fn ( ( ) => [ ] ) ,
339+ } ,
340+ mockOptions
341+ )
342+ }
343+
344+ test ( 'clear auto chosen partInstance' , async ( ) => {
345+ const context = setupDefaultJobEnvironment ( )
346+ const showStyleCompound = await setupMockShowStyleCompound ( context )
347+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
348+
349+ const { playoutModel } = await createSimplePlayoutModel ( context , showStyleCompound )
350+
351+ const ingestModel = createMockIngestModelReadonly ( )
352+
353+ const worker = new SyncChangesToPartInstancesWorker (
354+ context ,
355+ playoutModel ,
356+ ingestModel ,
357+ showStyleCompound ,
358+ blueprint
359+ )
360+
361+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
362+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
363+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
364+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
365+ consumesQueuedSegmentId : false ,
366+ manuallySelected : false ,
367+ } satisfies SelectedPartInstance )
368+
369+ await worker . recreateNextPartInstance ( undefined )
370+
371+ expect ( playoutModel . nextPartInstance ) . toBeFalsy ( )
372+ } )
373+
374+ test ( 'clear manually chosen partInstance' , async ( ) => {
375+ const context = setupDefaultJobEnvironment ( )
376+ const showStyleCompound = await setupMockShowStyleCompound ( context )
377+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
378+
379+ const { playoutModel } = await createSimplePlayoutModel ( context , showStyleCompound )
380+
381+ const ingestModel = createMockIngestModelReadonly ( )
382+
383+ const worker = new SyncChangesToPartInstancesWorker (
384+ context ,
385+ playoutModel ,
386+ ingestModel ,
387+ showStyleCompound ,
388+ blueprint
389+ )
390+
391+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
392+ // Force the next part to be manually selected, and verify
393+ playoutModel . setPartInstanceAsNext ( playoutModel . nextPartInstance , true , false )
394+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
395+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
396+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
397+ consumesQueuedSegmentId : false ,
398+ manuallySelected : true ,
399+ } satisfies SelectedPartInstance )
400+
401+ await worker . recreateNextPartInstance ( undefined )
402+
403+ expect ( playoutModel . nextPartInstance ) . toBeFalsy ( )
404+ } )
405+
406+ test ( 'clear manually chosen partInstance with replacement part' , async ( ) => {
407+ const context = setupDefaultJobEnvironment ( )
408+ const showStyleCompound = await setupMockShowStyleCompound ( context )
409+ const blueprint = await context . getShowStyleBlueprint ( showStyleCompound . _id )
410+
411+ const { playoutModel, part0 } = await createSimplePlayoutModel ( context , showStyleCompound )
412+
413+ const ingestModel = createMockIngestModelReadonly ( )
414+
415+ const worker = new SyncChangesToPartInstancesWorker (
416+ context ,
417+ playoutModel ,
418+ ingestModel ,
419+ showStyleCompound ,
420+ blueprint
421+ )
422+
423+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
424+ const partInstanceIdBefore = playoutModel . nextPartInstance ! . partInstance . _id
425+
426+ // Force the next part to be manually selected, and verify
427+ playoutModel . setPartInstanceAsNext ( playoutModel . nextPartInstance , true , false )
428+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
429+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
430+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
431+ consumesQueuedSegmentId : false ,
432+ manuallySelected : true ,
433+ } satisfies SelectedPartInstance )
434+
435+ await worker . recreateNextPartInstance ( part0 )
436+
437+ expect ( playoutModel . nextPartInstance ) . toBeTruthy ( )
438+ // Must have been regenerated
439+ expect ( playoutModel . nextPartInstance ! . partInstance . _id ) . not . toEqual ( partInstanceIdBefore )
440+ expect ( playoutModel . nextPartInstance ! . partInstance ) . toMatchObject ( {
441+ part : {
442+ _id : part0 . _id ,
443+ } ,
444+ } satisfies PartialDeep < DBPartInstance > )
445+
446+ // Make sure the part is still manually selected
447+ expect ( playoutModel . playlist . nextPartInfo ) . toEqual ( {
448+ partInstanceId : playoutModel . nextPartInstance ! . partInstance . _id ,
449+ rundownId : playoutModel . nextPartInstance ! . partInstance . rundownId ,
450+ consumesQueuedSegmentId : false ,
451+ manuallySelected : true ,
452+ } satisfies SelectedPartInstance )
453+ } )
168454 } )
169455} )
0 commit comments