Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 88 additions & 70 deletions src/resolvers/__tests__/mixEffect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as ME from '../mixEffect'
import * as Enums from '../../enums'
import { MixEffect, ExtendedMixEffect } from '../../state'
import { MixEffect } from '../../state'
import * as Defaults from '../../defaults'
import { Commands, Enums as AtemEnums, AtemStateUtil, VideoState } from 'atem-connection'
import { Commands, Enums as AtemEnums, AtemStateUtil } from 'atem-connection'
import { jsonClone } from '../../util'
import { DiffAllObject, DiffMixEffect } from '../../diff'

const STATE1 = AtemStateUtil.Create()
const ME1: MixEffect = AtemStateUtil.getMixEffect(STATE1, 0) as unknown as MixEffect
const STATE2 = AtemStateUtil.Create()
const ME2: MixEffect = AtemStateUtil.getMixEffect(STATE2, 0) as unknown as MixEffect

const fullDiffObject = DiffAllObject().video?.mixEffects as DiffMixEffect

function getState() {
const state1 = AtemStateUtil.Create()
const mixEffect1: MixEffect = AtemStateUtil.getMixEffect(state1, 0) as unknown as MixEffect
const state2 = AtemStateUtil.Create()
const mixEffect2: MixEffect = AtemStateUtil.getMixEffect(state2, 0) as unknown as MixEffect

return [mixEffect1, mixEffect2]
}

test('Unit: mix effect: same state gives no commands', function () {
const [ME1, ME2] = getState()

// same state gives no commands:
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject)
expect(commands).toHaveLength(0)
})

test('Unit: mix effect: same input gives no commands', function () {
;(ME1 as ExtendedMixEffect).input = 1
;(ME1 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT
;(ME2 as ExtendedMixEffect).input = 1
;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT
const [ME1, ME2] = getState()

ME1.programInput = 1
ME1.transition = Enums.TransitionStyle.CUT
ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.CUT

const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject)
expect(commands).toHaveLength(0)

delete (ME1 as Partial<ExtendedMixEffect>).input
delete (ME1 as Partial<ExtendedMixEffect>).transition
delete (ME2 as Partial<ExtendedMixEffect>).input
delete (ME2 as Partial<ExtendedMixEffect>).transition
})

test('Unit: mix effect: program input', function () {
;(ME2 as VideoState.MixEffect).programInput = 1
test('Unit: mix effect: hot cut program', function () {
const [ME1, ME2] = getState()

ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.CUT
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.ProgramInputCommand>
expect(commands).toHaveLength(1)

Expand All @@ -45,11 +51,12 @@ test('Unit: mix effect: program input', function () {
expect(commands[0].properties).toEqual({
source: 1,
})
;(ME2 as VideoState.MixEffect).programInput = 0
})

test('Unit: mix effect: preview input', function () {
;(ME2 as VideoState.MixEffect).previewInput = 1
const [ME1, ME2] = getState()

ME2.previewInput = 1
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand>
expect(commands).toHaveLength(1)

Expand All @@ -58,31 +65,55 @@ test('Unit: mix effect: preview input', function () {
expect(commands[0].properties).toEqual({
source: 1,
})
;(ME2 as VideoState.MixEffect).previewInput = 0
})

test('Unit: mix effect: cut command', function () {
;(ME2 as ExtendedMixEffect).input = 1
;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT
test('Unit: mix effect: program + preview', function () {
const [ME1, ME2] = getState()

ME2.previewInput = 2
ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.CUT
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand>

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type assertion in this test casts the result to Array<Commands.PreviewInputCommand>, but the expectation checks for a ProgramInputCommand at index 0. Using a union type here (or avoiding the cast) will keep the test type-safe and prevent future refactors from being masked by an overly narrow cast.

Suggested change
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand>
const commands = ME.resolveMixEffectsState(
[ME1],
[ME2],
fullDiffObject
) as Array<Commands.ProgramInputCommand | Commands.PreviewInputCommand>

Copilot uses AI. Check for mistakes.
expect(commands).toHaveLength(2)

expect(commands[0].constructor.name).toEqual('PreviewInputCommand')
expect(commands[0].constructor.name).toEqual('ProgramInputCommand')
expect(commands[0].mixEffect).toEqual(0)
expect(commands[0].properties).toEqual({
source: 1,
})

expect(commands[1].constructor.name).toEqual('CutCommand')
expect(commands[1].constructor.name).toEqual('PreviewInputCommand')
expect(commands[1].mixEffect).toEqual(0)
expect(commands[1].properties).toEqual({
source: 2,
})
})

delete (ME2 as Partial<ExtendedMixEffect>).input
delete (ME2 as Partial<ExtendedMixEffect>).transition
test('Unit: mix effect: deprecated "input" field', function () {
const [ME1, ME2] = getState()

ME2.previewInput = 2
ME2.input = 1 // this is deprecated and should follow the same logic as using programInput
ME2.transition = Enums.TransitionStyle.CUT
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand>
expect(commands).toHaveLength(2)

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: this assertion narrows to Array<Commands.PreviewInputCommand> while the test asserts a ProgramInputCommand is present. Widen the asserted type (eg a union) so TypeScript can help catch incorrect assumptions about command ordering/types.

Copilot uses AI. Check for mistakes.

expect(commands[0].constructor.name).toEqual('ProgramInputCommand')
expect(commands[0].mixEffect).toEqual(0)
expect(commands[0].properties).toEqual({
source: 1,
})
expect(commands[1].constructor.name).toEqual('PreviewInputCommand')
expect(commands[1].mixEffect).toEqual(0)
expect(commands[1].properties).toEqual({
source: 2,
})
})

test('Unit: mix effect: dummy command', function () {
;(ME2 as ExtendedMixEffect).input = 1
;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.DUMMY
const [ME1, ME2] = getState()

ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.DUMMY
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand>
expect(commands).toHaveLength(1)

Expand All @@ -93,14 +124,13 @@ test('Unit: mix effect: dummy command', function () {
})

// Dummy implies that something else will perform the cut. (eg a macro)

delete (ME2 as Partial<ExtendedMixEffect>).input
delete (ME2 as Partial<ExtendedMixEffect>).transition
})

test('Unit: mix effect: auto command', function () {
;(ME2 as ExtendedMixEffect).input = 1
;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.MIX
const [ME1, ME2] = getState()

ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.MIX
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<
Commands.PreviewInputCommand | Commands.TransitionPositionCommand
>
Expand All @@ -120,12 +150,13 @@ test('Unit: mix effect: auto command', function () {

expect(commands[2].constructor.name).toEqual('AutoTransitionCommand')
expect(commands[2].mixEffect).toEqual(0)
;(ME2 as ExtendedMixEffect).input = 0
})

test('Unit: mix effect: auto command, new transition', function () {
;(ME2 as ExtendedMixEffect).input = 1
;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.WIPE
const [ME1, ME2] = getState()

ME2.programInput = 1
ME2.transition = Enums.TransitionStyle.WIPE
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<
Commands.PreviewInputCommand | Commands.TransitionPositionCommand
>
Expand All @@ -151,21 +182,22 @@ test('Unit: mix effect: auto command, new transition', function () {

expect(commands[3].constructor.name).toEqual('AutoTransitionCommand')
expect(commands[3].mixEffect).toEqual(0)
;(ME2 as ExtendedMixEffect).input = 0
})

test('Unit: mix effect: transition preview', function () {
const [ME1, ME2] = getState()

ME2.transitionPreview = true
const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewTransitionCommand>
expect(commands).toHaveLength(1)

expect(commands[0].constructor.name).toEqual('PreviewTransitionCommand')
expect(commands[0].mixEffect).toEqual(0)

ME2.transitionPreview = false
})

test('Unit: mix effect: transition position', function () {
const [ME1, ME2] = getState()

ME2.transitionPosition = {
...ME2.transitionPosition,
inTransition: true,
Expand All @@ -179,15 +211,11 @@ test('Unit: mix effect: transition position', function () {
expect(commands[0].properties).toEqual({
handlePosition: 500,
})

ME2.transitionPosition = {
...ME2.transitionPosition,
inTransition: false,
handlePosition: 0,
}
})

test('Unit: mix effect: from transition, to no transition', function () {
const [ME1, ME2] = getState()

ME1.transitionPosition = {
...ME1.transitionPosition,
inTransition: true,
Expand All @@ -201,15 +229,11 @@ test('Unit: mix effect: from transition, to no transition', function () {
expect(commands[0].properties).toEqual({
handlePosition: 10000,
})

ME1.transitionPosition = {
...ME1.transitionPosition,
inTransition: false,
handlePosition: 0,
}
})

test('Unit: mix effect: transition properties', function () {
const [ME1, ME2] = getState()

ME2.transitionProperties.nextSelection = [
AtemEnums.TransitionSelection.Background,
AtemEnums.TransitionSelection.Key1,
Expand All @@ -228,15 +252,11 @@ test('Unit: mix effect: transition properties', function () {
nextSelection: [AtemEnums.TransitionSelection.Background, AtemEnums.TransitionSelection.Key1],
nextStyle: 1,
})

ME2.transitionProperties = {
...ME2.transitionProperties,
selection: [AtemEnums.TransitionSelection.Background],
style: 0,
}
})

test('Unit: mix effect: transition settings: dip', function () {
const [ME1, ME2] = getState()

ME2.transitionSettings.dip = {
input: 1,
rate: 50,
Expand All @@ -255,11 +275,11 @@ test('Unit: mix effect: transition settings: dip', function () {
input: 1,
rate: 50,
})

delete ME2.transitionSettings.dip
})

test('Unit: mix effect: transition settings: DVE', function () {
const [ME1, ME2] = getState()

ME2.transitionSettings.DVE = {
rate: 50,
logoRate: 50,
Expand Down Expand Up @@ -301,11 +321,11 @@ test('Unit: mix effect: transition settings: DVE', function () {
reverse: true,
flipFlop: true,
})

delete ME2.transitionSettings.DVE
})

test('Unit: mix effect: transition settings: mix', function () {
const [ME1, ME2] = getState()

ME2.transitionSettings.mix = jsonClone(Defaults.Video.MixTransitionSettings)
ME2.transitionSettings.mix.rate = 50
const commands = ME.resolveTransitionSettingsState(
Expand All @@ -321,11 +341,11 @@ test('Unit: mix effect: transition settings: mix', function () {
expect(commands[0].properties).toEqual({
rate: 50,
})

delete ME2.transitionSettings.mix
})

test('Unit: mix effect: transition settings: stinger', function () {
const [ME1, ME2] = getState()

ME2.transitionSettings.stinger = {
source: 1,
preMultipliedKey: true,
Expand Down Expand Up @@ -362,11 +382,11 @@ test('Unit: mix effect: transition settings: stinger', function () {
triggerPoint: 25,
mixRate: 25,
})

delete ME2.transitionSettings.stinger
})

test('Unit: mix effect: transition settings: wipe', function () {
const [ME1, ME2] = getState()

ME2.transitionSettings.wipe = {
rate: 50,
pattern: AtemEnums.Pattern.HorizontalBarnDoor,
Expand Down Expand Up @@ -401,6 +421,4 @@ test('Unit: mix effect: transition settings: wipe', function () {
reverseDirection: true,
flipFlop: true,
})

delete ME2.transitionSettings.wipe
})
Loading
Loading