import { FIXED_TRASH_ID } from '../constants' ; import { ASPIRATE_OFFSET_FROM_BOTTOM_MM , blowoutHelper , DEFAULT_PIPETTE , delayCommand , delayWithOffset , DEST_LABWARE , dropTipHelper , getErrorResult , getFlowRateAndOffsetParamsTransferLike , getRobotInitialStateNoTipsRemain , getRobotStateWithTipStandard , getSuccessResult , makeAirGapHelper , makeAspirateHelper , makeContext , makeDispenseHelper , makeDispenseAirGapHelper , makeTouchTipHelper , pickUpTipHelper , SOURCE_LABWARE , } from '../fixtures' ; import { distribute } from '../commandCreators/compound/distribute' ; import type { Command } from '@opentrons/shared-data/protocol/types/schemaV5Addendum' ; import type { DistributeArgs , InvariantContext , RobotState } from '../types' ; import { SOURCE_WELL_BLOWOUT_DESTINATION , DEST_WELL_BLOWOUT_DESTINATION , } from '../utils/misc' ; const airGapHelper = makeAirGapHelper ( { offsetFromBottomMm : 11.54 } ) ; const dispenseAirGapHelper = makeDispenseAirGapHelper ( { offsetFromBottomMm : 11.54 , } ) ; const aspirateHelper = makeAspirateHelper ( ) ; const dispenseHelper = makeDispenseHelper ( ) ; const touchTipHelper = makeTouchTipHelper ( ) ; const BLOWOUT_OFFSET_ANY = expect . any ( Number ) ; let mixinArgs ; let invariantContext ; let robotStateWithTip ; let robotInitialStateNoTipsRemain ; let blowoutSingleToTrash ; let blowoutSingleToSourceA1 ; let blowoutSingleToDestA4 ; let blowoutSingleToDestA3 ; beforeEach ( ( ) => { mixinArgs = { ... getFlowRateAndOffsetParamsTransferLike ( ) , commandCreatorFnName : 'distribute' , name : 'distribute test' , description : 'test blah blah' , pipette : DEFAULT_PIPETTE , sourceLabware : SOURCE_LABWARE , destLabware : DEST_LABWARE , preWetTip : false , touchTipAfterAspirate : false , disposalVolume : 60 , blowoutLocation : FIXED_TRASH_ID , mixBeforeAspirate : null , aspirateDelay : null , dispenseDelay : null , aspirateAirGapVolume : null , touchTipAfterDispense : false , } ; blowoutSingleToTrash = blowoutHelper ( FIXED_TRASH_ID , { offsetFromBottomMm : BLOWOUT_OFFSET_ANY , } ) ; blowoutSingleToSourceA1 = blowoutHelper ( SOURCE_LABWARE , { offsetFromBottomMm : BLOWOUT_OFFSET_ANY , } ) ; blowoutSingleToDestA4 = blowoutHelper ( DEST_LABWARE , { offsetFromBottomMm : BLOWOUT_OFFSET_ANY , well : 'A4' , } ) ; blowoutSingleToDestA3 = blowoutHelper ( DEST_LABWARE , { offsetFromBottomMm : BLOWOUT_OFFSET_ANY , well : 'A3' , } ) ; invariantContext = makeContext ( ) ; robotStateWithTip = getRobotStateWithTipStandard ( invariantContext ) ; robotInitialStateNoTipsRemain = getRobotInitialStateNoTipsRemain ( invariantContext ) ; } ) ; describe ( 'distribute: minimal example' , ( ) => { it ( 'single channel; 60uL from A1 -> A2, A3; no tip pickup' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' ] , changeTip : 'never' , volume : 60 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 180 ) , dispenseHelper ( 'A2' , 60 ) , dispenseHelper ( 'A3' , 60 ) , blowoutSingleToTrash , ] ) ; } ) ; } ) ; describe ( 'tip handling for multiple distribute chunks' , ( ) => { it ( 'changeTip: "once"' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'once' , volume : 90 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A2' , 90 ) , dispenseHelper ( 'A3' , 90 ) , blowoutSingleToTrash , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A4' , 90 ) , dispenseHelper ( 'A5' , 90 ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'changeTip: "always"' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'always' , volume : 90 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A2' , 90 ) , dispenseHelper ( 'A3' , 90 ) , blowoutSingleToTrash , dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'B1' ) , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A4' , 90 ) , dispenseHelper ( 'A5' , 90 ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'changeTip: "never" with carried-over tip' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 90 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A2' , 90 ) , dispenseHelper ( 'A3' , 90 ) , blowoutSingleToTrash , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A4' , 90 ) , dispenseHelper ( 'A5' , 90 ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'changeTip: "never" should fail with no initial tip' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'always' , volume : 150 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotInitialStateNoTipsRemain ) ; const res = getErrorResult ( result ) ; expect ( res . errors ) . toHaveLength ( 1 ) ; expect ( res . errors [ 0 ] ) . toMatchObject ( { type : 'INSUFFICIENT_TIPS' , } ) ; } ) ; } ) ; describe ( 'advanced settings: volume, mix, pre-wet tip, tip touch, tip position' , ( ) => { let mixCommandsWithDelay ; beforeEach ( ( ) => { mixCommandsWithDelay = [ aspirateHelper ( 'A1' , 35 ) , delayCommand ( 11 ) , dispenseHelper ( 'A1' , 35 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , delayCommand ( 12 ) , ] ; } ) ; it ( 'should mix before aspirate, then aspirate disposal volume' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' , 'A6' ] , changeTip : 'never' , volume : 120 , mixBeforeAspirate : { times : 2 , volume : 50 } , disposalVolume : 12 , blowoutLocation : SOURCE_LABWARE , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; const aspirateVol = 120 * 2 + 12 ; const mixCommands = [ aspirateHelper ( 'A1' , 50 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , aspirateHelper ( 'A1' , 50 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , ] ; expect ( res . commands ) . toEqual ( [ ... mixCommands , aspirateHelper ( 'A1' , aspirateVol ) , dispenseHelper ( 'A2' , 120 ) , dispenseHelper ( 'A3' , 120 ) , blowoutSingleToSourceA1 , ... mixCommands , aspirateHelper ( 'A1' , aspirateVol ) , dispenseHelper ( 'A4' , 120 ) , dispenseHelper ( 'A5' , 120 ) , blowoutSingleToSourceA1 , ... mixCommands , aspirateHelper ( 'A1' , 120 + 12 ) , dispenseHelper ( 'A6' , 120 ) , blowoutSingleToSourceA1 , ] ) ; } ) ; it . todo ( 'should pre-wet tip' ) ; it ( 'should delay after aspirate' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , aspirateDelay : { seconds : 12 , mmFromBottom : 14 } , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 300 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , dispenseHelper ( 'A2' , 100 ) , dispenseHelper ( 'A3' , 100 ) , dispenseHelper ( 'A4' , 100 ) , aspirateHelper ( 'A1' , 100 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , dispenseHelper ( 'A5' , 100 ) , ] ) ; } ) ; it ( 'should delay after air gap aspirate and regular aspirate' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , aspirateDelay : { seconds : 12 , mmFromBottom : 14 } , aspirateAirGapVolume : 5 , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 200 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , airGapHelper ( 'A1' , 5 ) , delayCommand ( 12 ) , dispenseAirGapHelper ( 'A2' , 5 ) , dispenseHelper ( 'A2' , 100 ) , dispenseHelper ( 'A3' , 100 ) , aspirateHelper ( 'A1' , 200 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , airGapHelper ( 'A1' , 5 ) , delayCommand ( 12 ) , dispenseAirGapHelper ( 'A4' , 5 ) , dispenseHelper ( 'A4' , 100 ) , dispenseHelper ( 'A5' , 100 ) , ] ) ; } ) ; it ( 'should air gap after aspirate and break into two chunks' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , aspirateAirGapVolume : 5 , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 200 ) , airGapHelper ( 'A1' , 5 ) , dispenseAirGapHelper ( 'A2' , 5 ) , dispenseHelper ( 'A2' , 100 ) , dispenseHelper ( 'A3' , 100 ) , aspirateHelper ( 'A1' , 200 ) , airGapHelper ( 'A1' , 5 ) , dispenseAirGapHelper ( 'A4' , 5 ) , dispenseHelper ( 'A4' , 100 ) , dispenseHelper ( 'A5' , 100 ) , ] ) ; } ) ; it ( 'should delay after air gap dispense and regular dispense' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , dispenseDelay : { seconds : 12 , mmFromBottom : 14 } , aspirateAirGapVolume : 5 , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 200 ) , airGapHelper ( 'A1' , 5 ) , dispenseAirGapHelper ( 'A2' , 5 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , aspirateHelper ( 'A1' , 200 ) , airGapHelper ( 'A1' , 5 ) , dispenseAirGapHelper ( 'A4' , 5 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , dispenseHelper ( 'A5' , 100 ) , ... delayWithOffset ( 'A5' , DEST_LABWARE ) , ] ) ; } ) ; it ( 'should delay after mix aspirate and regular aspirate' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , aspirateDelay : { seconds : 12 , mmFromBottom : 14 } , mixBeforeAspirate : { times : 2 , volume : 50 } , disposalVolume : 0 , } as DistributeArgs ; mixCommandsWithDelay = [ aspirateHelper ( 'A1' , 50 ) , delayCommand ( 12 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , aspirateHelper ( 'A1' , 50 ) , delayCommand ( 12 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , ] ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 300 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , dispenseHelper ( 'A2' , 100 ) , dispenseHelper ( 'A3' , 100 ) , dispenseHelper ( 'A4' , 100 ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 100 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE ) , dispenseHelper ( 'A5' , 100 ) , ] ) ; } ) ; it ( 'should touch tip after aspirate' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 90 , touchTipAfterAspirate : true , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 240 ) , touchTipHelper ( 'A1' ) , dispenseHelper ( 'A2' , 90 ) , dispenseHelper ( 'A3' , 90 ) , blowoutSingleToTrash , aspirateHelper ( 'A1' , 240 ) , touchTipHelper ( 'A1' ) , dispenseHelper ( 'A4' , 90 ) , dispenseHelper ( 'A5' , 90 ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'should touch tip after dispense' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 90 , touchTipAfterDispense : true , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A2' , 90 ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 90 ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , aspirateHelper ( 'A1' , 240 ) , dispenseHelper ( 'A4' , 90 ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A5' , 90 ) , touchTipHelper ( 'A5' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'should mix before aspirate w/ disposal vol' , ( ) => { const volume = 130 ; const disposalVolume = 20 ; const aspirateVol = volume * 2 + disposalVolume ; const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume , mixBeforeAspirate : { volume : 250 , times : 2 , } , disposalVolume , blowoutLocation : SOURCE_LABWARE , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; const mixCommands = [ aspirateHelper ( 'A1' , 250 ) , dispenseHelper ( 'A1' , 250 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , aspirateHelper ( 'A1' , 250 ) , dispenseHelper ( 'A1' , 250 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , ] ; expect ( res . commands ) . toEqual ( [ ... mixCommands , aspirateHelper ( 'A1' , aspirateVol ) , dispenseHelper ( 'A2' , volume ) , dispenseHelper ( 'A3' , volume ) , blowoutSingleToSourceA1 , ... mixCommands , aspirateHelper ( 'A1' , aspirateVol ) , dispenseHelper ( 'A4' , volume ) , dispenseHelper ( 'A5' , volume ) , blowoutSingleToSourceA1 , ] ) ; } ) ; it ( 'should delay after dispense' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , dispenseDelay : { seconds : 12 , mmFromBottom : 14 } , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 300 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , aspirateHelper ( 'A1' , 100 ) , dispenseHelper ( 'A5' , 100 ) , ... delayWithOffset ( 'A5' , DEST_LABWARE ) , ] ) ; } ) ; it ( 'should delay after mix dispense AND regular dispense' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' , 'A5' ] , changeTip : 'never' , volume : 100 , dispenseDelay : { seconds : 12 , mmFromBottom : 14 } , mixBeforeAspirate : { times : 2 , volume : 50 } , disposalVolume : 0 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; const mixCommandsWithDelay = [ aspirateHelper ( 'A1' , 50 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , delayCommand ( 12 ) , aspirateHelper ( 'A1' , 50 ) , dispenseHelper ( 'A1' , 50 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , delayCommand ( 12 ) , ] ; expect ( res . commands ) . toEqual ( [ ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 300 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 100 ) , dispenseHelper ( 'A5' , 100 ) , ... delayWithOffset ( 'A5' , DEST_LABWARE ) , ] ) ; } ) ; describe ( 'all advanced settings enabled' , ( ) => { let allArgs ; beforeEach ( ( ) => { allArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'B1' , 'B2' ] , changeTip : 'never' , volume : 45 , preWetTip : true , mixBeforeAspirate : { volume : 35 , times : 1 , } , aspirateDelay : { seconds : 11 , mmFromBottom : 15 } , touchTipAfterAspirate : true , touchTipAfterAspirateOffsetMmFromBottom : 14.5 , aspirateAirGapVolume : 31 , dispenseDelay : { seconds : 12 , mmFromBottom : 14 } , touchTipAfterDispense : true , blowoutFlowRateUlSec : 2.3 , blowoutOffsetFromTopMm : 3.3 , dispenseAirGapVolume : 3 , } ; } ) ; it ( 'should create commands in the expected order with expected params' , ( ) => { const args = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'B1' , 'B2' ] , changeTip : 'never' , volume : 45 , preWetTip : true , mixBeforeAspirate : { volume : 35 , times : 1 , } , aspirateDelay : { seconds : 11 , mmFromBottom : 15 } , touchTipAfterAspirate : true , touchTipAfterAspirateOffsetMmFromBottom : 14.5 , aspirateAirGapVolume : 31 , dispenseDelay : { seconds : 12 , mmFromBottom : 14 } , touchTipAfterDispense : true , blowoutFlowRateUlSec : 2.3 , blowoutOffsetFromTopMm : 3.3 , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ aspirateHelper ( 'A1' , 35 ) , delayCommand ( 11 ) , dispenseHelper ( 'A1' , 35 , { labware : SOURCE_LABWARE , offsetFromBottomMm : ASPIRATE_OFFSET_FROM_BOTTOM_MM , } ) , delayCommand ( 12 ) , aspirateHelper ( 'A1' , 150 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'B1' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'B1' , 45 ) , ... delayWithOffset ( 'B1' , DEST_LABWARE ) , touchTipHelper ( 'B1' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'B2' , 45 ) , ... delayWithOffset ( 'B2' , DEST_LABWARE ) , touchTipHelper ( 'B2' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in trash, reuse tip)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'never' , blowoutLocation : 'trashId' , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in trash, change tip each aspirate)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'always' , blowoutLocation : 'trashId' , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , airGapHelper ( 'A3' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'B1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in trash, change tip once)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'once' , blowoutLocation : 'trashId' , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToTrash , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in source, reuse tip)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'never' , blowoutLocation : SOURCE_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , airGapHelper ( 'A1' , 3 ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in source, change tip each aspirate)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'always' , blowoutLocation : SOURCE_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , airGapHelper ( 'A1' , 3 ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'B1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , airGapHelper ( 'A1' , 3 ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in source, change tip once)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'once' , blowoutLocation : SOURCE_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToSourceA1 , airGapHelper ( 'A1' , 3 ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in dest, reuse tip)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'never' , blowoutLocation : DEST_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA3 , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA4 , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in dest, change tip each aspirate)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'always' , blowoutLocation : DEST_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA3 , airGapHelper ( 'A3' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'B1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA4 , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; it ( 'should create commands in the expected order with expected params (blowout in dest, change tip once)' , ( ) => { const args = { ... allArgs , volume : 100 , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' , 'A4' ] , changeTip : 'once' , blowoutLocation : DEST_WELL_BLOWOUT_DESTINATION , } as DistributeArgs ; const result = distribute ( args , invariantContext , robotStateWithTip ) ; const res = getSuccessResult ( result ) ; expect ( res . commands ) . toEqual ( [ dropTipHelper ( 'A1' ) , pickUpTipHelper ( 'A1' ) , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 260 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A2' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A2' , 100 ) , ... delayWithOffset ( 'A2' , DEST_LABWARE ) , touchTipHelper ( 'A2' , { labware : DEST_LABWARE } ) , dispenseHelper ( 'A3' , 100 ) , ... delayWithOffset ( 'A3' , DEST_LABWARE ) , touchTipHelper ( 'A3' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA3 , ... mixCommandsWithDelay , aspirateHelper ( 'A1' , 160 ) , ... delayWithOffset ( 'A1' , SOURCE_LABWARE , 11 , 15 ) , touchTipHelper ( 'A1' , { offsetFromBottomMm : 14.5 } ) , airGapHelper ( 'A1' , 31 ) , delayCommand ( 11 ) , dispenseAirGapHelper ( 'A4' , 31 ) , delayCommand ( 12 ) , dispenseHelper ( 'A4' , 100 ) , ... delayWithOffset ( 'A4' , DEST_LABWARE ) , touchTipHelper ( 'A4' , { labware : DEST_LABWARE } ) , blowoutSingleToDestA4 , airGapHelper ( 'A4' , 3 , { labware : DEST_LABWARE } ) , delayCommand ( 11 ) , dropTipHelper ( 'A1' ) , ] ) ; } ) ; } ) ; } ) ; describe ( 'invalid input + state errors' , ( ) => { it ( 'invalid pipette ID should throw error' , ( ) => { const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' ] , changeTip : 'never' , volume : 100 , pipette : 'no-such-pipette-id-here' , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getErrorResult ( result ) ; expect ( res . errors ) . toHaveLength ( 1 ) ; expect ( res . errors [ 0 ] ) . toMatchObject ( { type : 'PIPETTE_DOES_NOT_EXIST' , } ) ; } ) ; } ) ; describe ( 'distribute volume exceeds pipette max volume' , ( ) => { it ( `no disposal volume` , ( ) => { const changeTip = 'once' ; const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' ] , changeTip , volume : 350 , disposalVolume : null , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getErrorResult ( result ) ; expect ( res . errors ) . toHaveLength ( 1 ) ; expect ( res . errors [ 0 ] . type ) . toEqual ( 'PIPETTE_VOLUME_EXCEEDED' ) ; } ) ; it ( `with disposal volume` , ( ) => { const changeTip = 'once' ; const distributeArgs = { ... mixinArgs , sourceWell : 'A1' , destWells : [ 'A2' , 'A3' ] , changeTip , volume : 250 , disposalVolume : 100 , } as DistributeArgs ; const result = distribute ( distributeArgs , invariantContext , robotStateWithTip ) ; const res = getErrorResult ( result ) ; expect ( res . errors ) . toHaveLength ( 1 ) ; expect ( res . errors [ 0 ] . type ) . toEqual ( 'PIPETTE_VOLUME_EXCEEDED' ) ; } ) ; } ) ;