Skip to content

Commit

Permalink
Fix switchPipe()() bug, missing edge-cases & cleaner code (#73)
Browse files Browse the repository at this point in the history
* switchPipe()() abort line signal unit-tests
* toPipe as BotAction Abort Behavior unit-test
* switchPipe()() code standardized & cleaned
- abortlinesignal, matchessignal, and regular pipeValues returned are
  supported
- includes unique abortlinesignal/matchessignal behavior
- fixes issue #71 and implements the related pseudo-code, commented at the bottom
  • Loading branch information
mrWh1te authored Sep 7, 2020
1 parent 04d274f commit 0aa62a0
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 44 deletions.
48 changes: 32 additions & 16 deletions src/botmation/actions/assembly-lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,32 +153,48 @@ export const switchPipe =
injects.push(wrapValueInPipe(toPipe))

// run the assembled BotAction's with the same Pipe object
let atLeastOneCaseMatched = false
let hasAtLeastOneCaseMatch = false
const actionsResults = []

for(const action of actions) {
const resolvedActionResult = await action(page, ...injects)
let resolvedActionResult = await action(page, ...injects)

// resolvedActionResult can be of 3 things
// 1. MatchesSignal 2. AbortLineSignal 3. PipeValue
// switchPipe will return (if not aborted) an array of all the resolved results of each BotAction assembled in the switchPipe()() 2nd call
if (isMatchesSignal(resolvedActionResult) && hasAtLeastOneMatch(resolvedActionResult)) {
atLeastOneCaseMatched = true
hasAtLeastOneCaseMatch = true
actionsResults.push(resolvedActionResult)
} else if (isAbortLineSignal(resolvedActionResult)) {
// switchPipe has unique AbortLineSignal behavior where it takes at least one succesful case()() to reduce
// the necessary count to abort out
const processedAbortSignal = processAbortLineSignal(resolvedActionResult)

// takes abort(1) to abort out of switchPipe if at least one case has matched
if (atLeastOneCaseMatched) {
return processedAbortSignal
// infinity signal breaks function, and returns upward
if (resolvedActionResult.assembledLines === 0) {
return resolvedActionResult
}

// if no case matches, reduce abortLineSignal.assembledLines count by 1
// to prevent aborting without a case match ie abort(1)
if (!hasAtLeastOneCaseMatch) {
resolvedActionResult = processAbortLineSignal(resolvedActionResult)
}

// switchPipe abort behavior
if (!isAbortLineSignal(resolvedActionResult)) {
// special case of "0" where the assembledLines was processed from 1->0 which returns the pipeValue
// don't break the line, simply append abortLineSignal.pipeValue to array
actionsResults.push(resolvedActionResult)
} else if (resolvedActionResult.assembledLines === 1) {
actionsResults.push(resolvedActionResult.pipeValue)
return actionsResults
} else {
// otherwise it takes at least abort(2) to abort out of the case matching test & line of botaction's
if (isAbortLineSignal(processedAbortSignal)) {
return processAbortLineSignal(processedAbortSignal)
} else {
actionsResults.push(processedAbortSignal)
}
// assembledLines 2+ - breaks line and breaks returning array functionality
// hence returned a processed abort line signal
return processAbortLineSignal(resolvedActionResult)
}
} else {
// normal BotAction so add the result to the array to return later
actionsResults.push(resolvedActionResult)
}

}

return actionsResults
Expand Down
162 changes: 134 additions & 28 deletions src/tests/botmation/actions/assembly-lines.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AbortLineSignal } from 'botmation/types/abort-line-signal'
import { createEmptyPipe, wrapValueInPipe } from 'botmation/helpers/pipe'
import { createAbortLineSignal } from 'botmation/helpers/abort'
import { pipeCase } from 'botmation/actions/pipe'
import { createMatchesSignal } from 'botmation/helpers/matches'

/**
* @description Assembly-Lines BotAction's
Expand Down Expand Up @@ -884,45 +885,150 @@ describe('[Botmation] actions/assembly-lines', () => {
expect(mockToPipeAction).toHaveBeenNthCalledWith(2, {}, wrapValueInPipe(200))
})

it('switchPipe() supports AbortLineSignal with special behavior where the assembledLines required to abort out of the BotAction is dependent on a MatchesSignal having at least 1 match', async() => {
it('switchPipe() returns an array of results representing a 1:1 relationship with the assembled BotActions unless fully aborted out', async() => {
const mockAction1 = jest.fn(() => Promise.resolve('mercury'))
const mockAction2 = jest.fn(() => Promise.resolve('venus'))
const mockAction3 = jest.fn(() => Promise.resolve('earth'))
const mockAction4 = jest.fn(() => Promise.resolve('mars'))
const mockActionDoesntRun = jest.fn(() => Promise.resolve('no'))

const results1 = await switchPipe(42)(
mockAction1,
mockAction2,
pipeCase(42)(
mockAction3
),
mockAction4,
pipeCase(37)(
mockActionDoesntRun
),
abort()
)(mockPage)

expect(results1).toEqual([
'mercury',
'venus',
createMatchesSignal({'0': 42}, 'earth'),
'mars',
createMatchesSignal(), // no matches so no ran code so returned pipeValue
undefined // breaks line but doesnt break array return so get abort pipeValue
])
})

/**
* switchPipe abort behavior
*
* if an assembled botaction returns an infinite AbortLineSignal(0) then return that
* otherwise, if no case matches, process a returned AbortLineSignal by 1 assembledLines (subtract 1 from assembledLines)
* then for either no case matches or has matches, upon abortline signal do the following:
* 0 = dont break line, append abortLineSignal.pipeValue to return array
* 1 = break line, append abortLineSignal.pipeValue to return array then return array
* 2+ = dont return array, but return an AbortLineSignal( 1+ ) // reduce count by 1
*
* if toPipe BotAction returns an abort signal, process it and abort normally
*/
it('switchPipe() supports AbortLineSignal with unique behavior', async() => {
const mockActionReturnsFive = jest.fn(() => Promise.resolve(5))
const mockActionPassThrough = jest.fn((p, pO) => Promise.resolve(pO.value))

// toPipe is a BotAction that aborts
const toPipeBotActionAborts = await switchPipe(abort(1, 'an abort value'))()(mockPage)
expect(toPipeBotActionAborts).toEqual('an abort value')
// no case matches - AbortLineSignal(0) - break like and return infinity abortline signal
const passThroughInfinityAbortsignal = await switchPipe()(
abort(0),
mockActionReturnsFive
)(mockPage)
expect(passThroughInfinityAbortsignal).toEqual({
brand: 'Abort_Signal',
assembledLines: 0
})
expect(mockActionReturnsFive).not.toHaveBeenCalled()

// toPipe is Value with assembled BotAction that aborts(1) without a matching case
const toPipeValueAbortOne = await switchPipe(8000)(
mockActionReturnsFive,
abort(1, 'another abort value')
// no case matches - AbortLineSignal 1 = dont break line, append abortLineSignal.pipeValue to return array
const abortLineOneNoCaseMatches = await switchPipe()(
abort(1, 'abort-1-pipe-value'),
mockActionReturnsFive
)(mockPage)
expect(abortLineOneNoCaseMatches).toEqual(['abort-1-pipe-value', 5])
expect(mockActionReturnsFive).toHaveBeenNthCalledWith(1, {}, createEmptyPipe())

expect(toPipeValueAbortOne).toEqual([5, 'another abort value']) // no case matching, takes 2
expect(mockActionReturnsFive).toHaveBeenNthCalledWith(1, {}, wrapValueInPipe(8000))
// no case matches - AbortLineSignal 2 = break line, append aLS.pipeValue to array and return that array
const abortLineTwoNoCaseMatches = await switchPipe()(
mockActionReturnsFive,
abort(2, 'abort-2-pipe-value'),
mockActionReturnsFive
)(mockPage)
expect(abortLineTwoNoCaseMatches).toEqual([5, 'abort-2-pipe-value'])
expect(mockActionReturnsFive).toHaveBeenCalledTimes(2)
expect(mockActionReturnsFive).not.toHaveBeenCalledTimes(3)

// toPipe is value with assembled BotAction that aborts(2+) without matching case
const toPipeValueAbortMulti = await switchPipe(10000)(
mockActionPassThrough,
abort(7, 'aborted pipe value to check')
// no case matches - AbortLineSignal 3+ = break line, but dont return array, instead return processed AbortLineSignal (reduce count by 1)
const abortLineThreeNoCaseMatches = await switchPipe()(
mockActionReturnsFive,
abort(3, 'abort-3-pipe-value'),
mockActionReturnsFive
)(mockPage)
expect(abortLineThreeNoCaseMatches).toEqual(createAbortLineSignal(1, 'abort-3-pipe-value')) // 1 to break line return array, 2+ to break line and return abortlinesignal instead of array - hence minus 2
expect(mockActionReturnsFive).toHaveBeenCalledTimes(3)
expect(mockActionReturnsFive).not.toHaveBeenCalledTimes(4)

expect(toPipeValueAbortMulti).toEqual(createAbortLineSignal(5, 'aborted pipe value to check'))
//
// case matches - abortlinesignal(0) break line and return abortlinesignal infinity instead of array
const passThroughInfinityAbortSignalWithMatches = await switchPipe(10)(
pipeCase(10)(
mockActionReturnsFive
),
abort(0),
mockActionReturnsFive
)(mockPage)
expect(passThroughInfinityAbortSignalWithMatches).toEqual({
brand: 'Abort_Signal',
assembledLines: 0
})
expect(mockActionReturnsFive).toHaveBeenCalledTimes(4)
expect(mockActionReturnsFive).not.toHaveBeenCalledTimes(5)

// toPipe is value with assembled BotAction that aborts(1) BUT with matching case so aborts entirely
const mockActionDoesntRun = jest.fn(() => Promise.resolve())
const toPipeValueAbortOneWithMatchingCase = await switchPipe(300)(
abort(), // no matching case, so the following still runs
pipeCase(300)(
mockActionReturnsFive,
mockActionPassThrough
// case matches - abortlinesignal(1) = break line, append abortLineSignal.pipeValue to return array then return array
const abortLineOneWithCaseMatches = await switchPipe(10)(
pipeCase(10)(
mockActionReturnsFive
),
mockActionReturnsFive,
abort(1, 'the case was 300'),
mockActionDoesntRun
abort(1, 'a-pipe-1-value-O_O'),
mockActionReturnsFive
)(mockPage)
expect(abortLineOneWithCaseMatches).toEqual([
createMatchesSignal({'0': 10}, 5),
'a-pipe-1-value-O_O'
])
expect(mockActionReturnsFive).toHaveBeenCalledTimes(5)
expect(mockActionReturnsFive).not.toHaveBeenCalledTimes(6)

// case matches - abortlinesignal(2) = dont return array, but return an AbortLineSignal( 1+ ) // reduce count by 1
const abortLineTwoWithCaseMatches = await switchPipe(10)(
pipeCase(10)(
mockActionReturnsFive
),
abort(2, 'a-pipe-2-value-O_O'),
mockActionReturnsFive
)(mockPage)
expect(abortLineTwoWithCaseMatches).toEqual(createAbortLineSignal(1, 'a-pipe-2-value-O_O'))
expect(mockActionReturnsFive).toHaveBeenCalledTimes(6)
expect(mockActionReturnsFive).not.toHaveBeenCalledTimes(7)

expect(toPipeValueAbortOneWithMatchingCase).toEqual('the case was 300')
expect(mockActionDoesntRun).not.toHaveBeenCalled()
//
// toPipe botaction aborts
const mockActionNeverRuns = jest.fn(() => Promise.resolve())

const toPipeAbortsInfinity = await switchPipe(abort(0))(mockActionNeverRuns)(mockPage)
expect(toPipeAbortsInfinity).toEqual(createAbortLineSignal(0))
expect(mockActionNeverRuns).not.toHaveBeenCalled()

const toPipeAbortsOne = await switchPipe(abort(1, 'a-value'))(mockActionNeverRuns)(mockPage)
expect(toPipeAbortsOne).toEqual('a-value')
expect(mockActionNeverRuns).not.toHaveBeenCalled()

const toPipeAbortsTwo = await switchPipe(abort(2, 'a-2-value'))(mockActionNeverRuns)(mockPage)
expect(toPipeAbortsTwo).toEqual(createAbortLineSignal(1, 'a-2-value'))
expect(mockActionNeverRuns).not.toHaveBeenCalled()
})
})

0 comments on commit 0aa62a0

Please sign in to comment.