--[[----------------------------------------------------------------------------

KSReactiveTest.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2010 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

local KSReactiveTest = { }

--------------------------------------------------------------------------------

local assert = assert

--------------------------------------------------------------------------------

-- FIXME [mhamburg 2011-05-04]: Test that relayE and relayB do not unnaturally
-- prolong the lifespan of their results when passed the value true. There is
-- some evidence that they do.

--------------------------------------------------------------------------------

-- Confirm that t is an array containing the provided sequence of values

local function testSequence( t, ... )

	local count = select( '#', ... )
	
	if #t ~= count then
		return false, "Table length should match sequence length: " .. debug.traceback()
	end
	
	for i = 1, count do
		if t[ i ] ~= select( i, ... ) then
			return false, "Elements mismatch"
		end
	end
	
	return true

end

--------------------------------------------------------------------------------

function KSReactiveTest.testSourceE( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node = KSReactive_sourceE()
	
	local t = { }
	
	node:ownedOnDataE( test, function( v ) t[ #t + 1 ] = v end )
	
	node:post( 1 )
	node:post( 2 )
	node:post( 3 )
	
	assert( testSequence( t, 1, 2, 3 ), "Expected to accumulate 1, 2, 3" )
		
	node = KSReactive_sourceE()
	
	t = { }
	
	node:mapE( function( x ) return x * x end ):filterE( function( x ) return 10 < x end ):ownedOnDataE( test, function( v ) t[ #t + 1 ] = v end )
	
	node:post( 1 )
	node:post( 2 )
	node:post( 3 )
	node:post( 4 )
	node:post( 5 )
	
	assert( testSequence( t, 16, 25 ), "Expected to accumulate 16, 25" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testVariableB( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_variableB = KSReactive.variableB

	local node = KSReactive_variableB( 0 )
	
	node:assertValueNow( 0, "Initial value should be zero" )
	
	local t = { }
	
	node:ownedOnDataE( test, function( v ) t[ #t + 1 ] = v end )
	
	node:set( 1 )
	node:set( 2 )
	node:set( 2 ) -- Duplicate should be ignored
	node:set( 3 )
	
	assert( testSequence( t, 1, 2, 3 ), "Expected to accumulate 1, 2, 3" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMergeE( test )

	local KSReactive = require 'KSReactive'
	local WFCloseSupport = require 'WFCloseSupport'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	local node2 = KSReactive_sourceE()
	
	local node3 = node1:closingMergeE( node2 )
	
	local t = { }
	
	node3:ownedOnDataE( test, function( v ) t[ #t + 1 ] = v end )
	
	node1:post( 1 )
	node1:post( 2 )
	node2:post( 3 )
	node1:post( 4 )
	
	assert( testSequence( t, 1, 2, 3, 4 ), "Expected to accumulate 1, 2, 3, 4" )
	
	node3:close()
	
	assert( WFCloseSupport.hasBeenOrWillBeClosed( node1 ), "Close chaining to node 1" )
	assert( WFCloseSupport.hasBeenOrWillBeClosed( node2 ), "Close chaining to node 2" )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testFilterB()

	local KSReactive = require 'KSReactive'

	local KSReactive_variableB = KSReactive.variableB

	local nodeB = KSReactive_variableB( 0 )
	
	local oddsOnlyB = nodeB:filterB( function( x ) return x % 2 == 1 end, "Foo" )
	
	local oddsOnlyB2 = nodeB:filterB( function( x ) return x % 2 == 1 end )
	
	local evensOnlyB = nodeB:filterB( function( x ) return x % 2 == 0 end, "Foo" )
	
	oddsOnlyB:assertValueNow( "Foo", "Should have filtered out initial 0." )
	
	oddsOnlyB2:assertValueNow( nil, "Should have filtered out initial 0 and returned nil." )
	
	evensOnlyB:assertValueNow( 0, "Should not have filtered out initial 0." )
	
	nodeB:set( 1 )
	
	oddsOnlyB:assertValueNow( 1, "We should have updated for the value 1." )
	oddsOnlyB2:assertValueNow( 1, "We should also have updated for the value 1." )
	evensOnlyB:assertValueNow( 0, "We should not have updated for the value 1." )

	nodeB:set( 2 )
	
	oddsOnlyB:assertValueNow( 1, "We should not have updated for the value 2." )
	oddsOnlyB2:assertValueNow( 1, "We should also not have updated for the value 2." )
	evensOnlyB:assertValueNow( 2, "We should have updated for the value 2." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testFilterBPassesErrors()

	local KSReactive = require 'KSReactive'

	local KSReactive_variableB = KSReactive.variableB

	local nodeB = KSReactive_variableB( 0 )
	
	nodeB:setError( "This is an error" )
	
	local oddsOnlyB = nodeB:filterB( function( x ) return x % 2 == 1 end, "Foo" )
	
	assert( oddsOnlyB:isErrorNow(), "filterB should have passed the error through" )
	
	nodeB:set( 0 )
	
	assert( oddsOnlyB:isErrorNow(), "filterB should not have updated for 0" )
	
	nodeB:set( 1 )
	
	oddsOnlyB:assertValueNow( 1, "filterB should have updated for 1" )
	
	nodeB:setError( "Back to error" )
	
	assert( oddsOnlyB:isErrorNow(), "filterB should be back to being an error" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testSkipErrorsB()

	local KSReactive = require 'KSReactive'

	local KSReactive_variableB = KSReactive.variableB

	local nodeB = KSReactive_variableB( 0 )
	
	nodeB:setError( "This is an error" )
	
	local skippedB = nodeB:skipErrorsB( "Started with error" )
	
	skippedB:assertValueNow( "Started with error", "Should have transformed initial error." )
	
	nodeB:set( 0 )
	
	skippedB:assertValueNow( 0, "Should have updated for 0." )
	
	nodeB:set( 1 )
	
	skippedB:assertValueNow( 1, "Should have updated for 1." )
	
	nodeB:setError( "Another error" )
	
	skippedB:assertValueNow( 1, "Should not have updated for error." )
	
	nodeB:set( 2 )
	
	skippedB:assertValueNow( 2, "Should not have updated for 2." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testAnyE( test )

	local KSReactive = require 'KSReactive'
	local WFCloseSupport = require 'WFCloseSupport'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	local node2 = KSReactive_sourceE()
	
	local node3 = KSReactive.closingAnyE( node1, node2 )
	
	local t = { }
	
	node3:ownedOnDataE( test, function( v ) t[ #t + 1 ] = v end )
	
	node1:post( 1 )
	node1:post( 2 )
	node2:post( 3 )
	node1:post( 4 )
	
	assert( testSequence( t, true, true, true, true ), "Expected to accumulate 1, 2, 3, 4" )
	
	node3:close()
	
	assert( WFCloseSupport.hasBeenOrWillBeClosed( node1 ), "Close chaining to node 1" )
	assert( WFCloseSupport.hasBeenOrWillBeClosed( node2 ), "Close chaining to node 2" )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testMergeEMultipath( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	local node2 = KSReactive_sourceE()
	
	local node3 = node1:mapE( function( x ) return x * x end )
	
	local nodeM1 = node3:mergeE( node1 ):mergeE( node2 )
	
	local t1 = { }
	
	nodeM1:ownedOnDataE( test, function( v ) t1[ #t1 + 1 ] = v end )
	
	local nodeM2 = node1:mergeE( node2 ):mergeE( node3 )
	
	local t2 = { }
	
	nodeM2:ownedOnDataE( test, function( v ) t2[ #t2 + 1 ] = v end )
	
	local isDone = false
	
	nodeM2:ownedOnDoneE( test, function() isDone = true end )
	
	node1:post( 1 )
	node1:post( 2 )
	node2:post( 3 )
	node1:post( 4 )
	
	assert( testSequence( t1, 1, 4, 3, 16 ), "Expected to accumulate 1, 4, 3, 16" )

	assert( testSequence( t2, 1, 2, 3, 4 ), "Expected to accumulate 1, 2, 3, 4" )
	
	node1:close()
	node2:close()
	
	assert( isDone, "We should have closed the merged stream." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testAnyEMultipath( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	local node2 = KSReactive_sourceE()
	
	local node3 = node1:mapE( function( x ) return x * x end )
	
	local nodeM1 = KSReactive.anyE( node3, node1, node2 )
	
	local t1 = { }
	
	nodeM1:ownedOnDataE( test, function( v ) t1[ #t1 + 1 ] = v end )
	
	local nodeM2 = KSReactive.anyE( node1, node2, node3 )
	
	local t2 = { }
	
	nodeM2:ownedOnDataE( test, function( v ) t2[ #t2 + 1 ] = v end )
	
	local isDone = false
	
	nodeM2:ownedOnDoneE( test, function() isDone = true end )
	
	node1:post( 1 )
	node1:post( 2 )
	node2:post( 3 )
	node1:post( 4 )
	
	assert( testSequence( t1, true, true, true, true ), "Expected to accumulate 1, 4, 3, 16" )

	assert( testSequence( t2, true, true, true, true ), "Expected to accumulate 1, 2, 3, 4" )
	
	node1:close()
	node2:close()
	
	assert( isDone, "We should have closed the merged stream." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testDebugHook()

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	local node2 = KSReactive_sourceE()
	
	local node3 = node1:mapE( function( x ) return x * x end )
	
	local node4 = KSReactive_sourceE()
	
	local nodeM1 = KSReactive.anyE( node3, node1, node2, node4:mapE( function( x ) return x + x end ) )
	
	local t = { }
	
	nodeM1:setDebugHook( function( str ) t[ #t + 1 ] = str end, "Unit Test Merge" )
	
	node1:post( 1 )
	node1:post( 2 )
	node2:post( 3 )
	node1:post( 4 )
	
	assert( #t == 4, "We should have four message" )
	
	node4:postError( 'foo' )
	
	assert( #t == 5, "We should have five entries" )
	
	node1:close()
	node2:close()
	node4:close()

	-- table.foreachi( t, debug.print )
	
	assert( #t == 6, "We should have six entries" )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testZeroE( test )

	local KSReactive = require 'KSReactive'
	
	local zeroNode = KSReactive.zeroE()
	
	local isDone = false
	
	zeroNode:ownedOnDoneE( test, function() isDone = true end )
	
	assert( isDone, "We should have triggered the done action for the new zero node." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testDoubleOnDoneE( test )

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local relayE = sourceE:relayE()
	
	local isDone1 = false
	
	relayE:ownedOnDoneE( test, function() isDone1 = true end )
	
	sourceE:post( 3 )
	
	assert( not isDone1, "Should not be done yet" )
	
	sourceE:close()
	
	assert( isDone1, "Should now be done" )
	
	local isDone2 = false
	
	relayE:ownedOnDoneE( test, function() isDone2 = true end )
	
	assert( isDone2, "Second done should also be set" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testDoneBehaviorOnDoneE( test )

	local KSReactive = require 'KSReactive'
	
	local varB = KSReactive.variableB()
	
	local relayB = varB:relayB()
	
	local isDone1 = false
	
	relayB:ownedOnDoneE( test, function() isDone1 = true end )
	
	varB:set( 3 )
	
	assert( not isDone1, "Should not be done yet" )
	
	varB:close()
	
	assert( isDone1, "Should now be done" )
	
	local isDone2 = false
	
	relayB:ownedOnDoneE( test, function() isDone2 = true end )
	
	assert( isDone2, "Second done should also be set" )

end

--------------------------------------------------------------------------------

-- Test that hooking up a node after another node has sent still gets the
-- event that was sent.

function KSReactiveTest.testIntracycleResend( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node = KSReactive_sourceE()
	
	local result = 0
	
	node:ownedOnDataE( test, function()
		node:mapE( function( y ) return y * y end )
				:ownedOnDataE( test, function( v )
					result = result + v
				end )
	end )
	
	node:post( 4 )
	
	assert( result == 16, "We should still receive the 4 and turn it into 16. Got " .. tostring( result ) )
	
	local result2 = nil
	
	node:ownedOnDataE( test, function( v ) result2 = v end )
	
	assert( result2 == nil, "We should not have propagated the previous value out of cycle." )
	
	node:post( 5 )
	
	assert( result2 == 5, "We should now have captured the 5." )
	
	assert( result == 66, "Expected 4 * 4 + 5 * 5 + 5 * 5 = 66, got " .. tostring( result ) )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testCloseCallsTargets()

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node1 = KSReactive_sourceE()
	
	local node2 = node1:mapE( function( x ) return x * x end )
	
	local node1Closed = false
	local node2Closed = false
	
	node1:addCloseAction( function() node1Closed = true end )
	node2:addCloseAction( function() node2Closed = true end )
	
	node1:close()
	
	assert( node1Closed, "Node 1 did not close" )
	assert( node2Closed, "Node 2 did not close" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testHoldB( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE
	
	local node = KSReactive_sourceE()
	
	local behavior = node:holdB( "foo" )
	
	local count = 0
	
	behavior:ownedOnDataE( test, function() count = count + 1 end )
	
	behavior:assertValueNow( "foo", "Initial value should be foo" )
	
	node:post( "baz" )
	
	behavior:assertValueNow( "baz", "Behavior should have updated" )
	
	node:post( 1, 2, 3 )
	
	assert( testSequence( { behavior:valueNow() }, 1, 2, 3 ), "Multi-value behaviors should work" )
	
	node:post( "fraggle" )
	node:post( "fraggle" )
	node:post( "frizz" )
	node:post( "fraggle" )
	
	assert( count == 5, "Behavior should have fired five times." )
	
	node:close()
	
	behavior:assertValueNow( "fraggle", "valueNow should survive a close" )
	
	assert( count == 5, "count should not be affected by close" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapE( test )

	local KSReactive = require 'KSReactive'
	
	local KSReactive_sourceE = KSReactive.sourceE

	local node = KSReactive_sourceE()
	
	local rawMapE1 = node:rawMapE( function( status, x )
		if status == 'error' then
			if x == 'foo' then
				return 'same'
			else
				return 'error', 'baz'
			end
		else
			return 'data', x + 1
		end
	end )
	
	local t1 = { }
	local isDone1 = false
	
	rawMapE1:ownedOnDataE( test, function( v ) t1[ #t1 + 1 ] = v end )
	rawMapE1:ownedOnErrorE( test, function( v ) t1[ #t1 + 1 ] = v end )
	rawMapE1:ownedOnDoneE( test, function() isDone1 = true end )
	
	local rawMapE2 = node:rawMapE( function( ... ) return 'data', ( ... ) end )
	
	local t2 = { }
	local isDone2 = false

	rawMapE2:ownedOnDataE( test, function( v ) t2[ #t2 + 1 ] = v end )
	rawMapE2:ownedOnErrorE( test, function( v ) t2[ #t2 + 1 ] = v end )
	rawMapE2:ownedOnDoneE( test, function() isDone2 = true end )
	
	node:post( 1 )
	node:post( 2 )
	node:postError( 'skronk' )
	node:post( 4 )
	node:postError( 'foo' )
	node:postError( 'baz' )
	node:post( 10 )
	node:close()
	
	assert( testSequence( t1, 2, 3, 'baz', 5, 'foo', 'baz', 11 ), "Bad results for sequence 1" )
	assert( isDone1, "First sequence should be done" )
		
	assert( testSequence( t2, 'data', 'data', 'error', 'data', 'error', 'error', 'data' ),
			"Bad results for sequence 2" )
	assert( isDone2, "Second sequence should be done" )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapSwitchB()

	local KSReactive = require 'KSReactive'
	
	local b = KSReactive.variableB( 0 )
	
	local bb
	
	local smb = b:mapSwitchB( function() bb = KSReactive.variableB( false ); return bb end )
	
	smb:assertValueNow( false, "value should come through as false" )
	
	bb:set( true )
	
	smb:assertValueNow( true, "should see change from underlying behavior" )
	
	b:set( 1 )
	
	smb:assertValueNow( false, "value should be back to false" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapSwitchE( test )

	local KSReactive = require 'KSReactive'
	
	local r = { }
	local re = { }
	local rdone = false
	
	local e = KSReactive.sourceE()
	
	local ee
	
	local sme = e:mapSwitchE( function() ee = KSReactive.sourceE(); return ee end )
	
	sme:ownedOnDataE( test, function( x ) r[ #r + 1 ] = x end )
	sme:ownedOnErrorE( test, function( x ) re[ #re + 1 ] = x end )
	sme:ownedOnDoneE( test, function() rdone = true end )
	
	assert( ee == nil, "We should not have an event stream yet" )
	
	e:post( true )
	
	assert( ee ~= nil, "We should now have an event stream" )
	
	local closed_ee = false
	
	ee:addCloseAction( function() closed_ee = true end )
	
	ee:post( 'foo' )
	
	assert( r[ #r ] == 'foo', "We should have received the event foo" )
	
	ee:post( 'baz' )
	
	assert( r[ #r ] == 'baz', "We should have received the event baz" )
	
	ee:postError( "ee_error" )
	
	assert( re[ #re ] == 'ee_error', "We should have received the error ee_error" )
	
	e:post( 3.14 )
	
	KSReactive.testing_flushAutoClose()
	
	assert( closed_ee, "We should have closed the original event stream" )
	
	ee:post( 'bar' )
	
	assert( r[ #r ] == 'bar', "We should have received the event bar" )
	
	e:post( { } )
	
	ee:close()
	
	assert( not rdone, "Closing the event stream shouldn't have closed the switch" )
	
	e:postError( 'e_error' )
	
	assert( re[ #re ] == 'e_error', "We should have received the error e_error" )
	
	e:post( 'a' )
	
	e:close()
	
	ee:post( 'after_close' )
	
	assert( r[ #r ] == 'after_close', "We should have received the event bar" )
	
	ee:postDone()
	
	KSReactive.testing_flushAutoClose()
	
	assert( rdone, "We should now have closed the mapSwitchE" )
	
	assert( #r == 4, "We should have received 4 values" )
	
	assert( #re == 2, "We should have received 2 error" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testZipMapSwitchB()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local b = KSReactive.variableB( 0 )
	
	local bb
	
	local smb = b:zipMapSwitchB( function() bb = KSReactive.variableB( false ); return bb end )
	
	KSTestUtils.assertEquals "initial state" ( 0, false )( smb:valueNow() )
	
	bb:set( true )
	
	KSTestUtils.assertEquals "first sub-behavior change" ( 0, true )( smb:valueNow() )
	
	b:set( 1 )
	
	KSTestUtils.assertEquals "first root behavior change" ( 1, false )( smb:valueNow() )
	
	b:set( 2 )
	
	KSTestUtils.assertEquals "second root behavior change" ( 2, false )( smb:valueNow() )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testCallB()

	local KSReactive = require 'KSReactive'
	
	local fnB = KSReactive.variableB( function( a, b ) return a + b end )

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local cB = KSReactive.callB( fnB, aB, bB )
	
	local dB = KSReactive.callB( function( a, b ) return a + b end, cB, bB )
	
	dB:assertValueNow( 0, "Expected initial value to be 0." )
	
	bB:set( 4 )
	
	dB:assertValueNow( 8, "Expected value after b = 4 to be 8." )
	
	aB:set( 5 )
	
	dB:assertValueNow( 13, "Expected value after a = 5 to be 13." )
	
	fnB:set( function( a, b ) return a - b end )
	
	dB:assertValueNow( 5, "Expected value after changing function to be 5." )
	
	fnB:set( function( a, b ) return a or b end )
	
	dB:assertValueNow( 9, "Expected value after changing function to or to be 9." )
	
	aB:set( nil )
	
	dB:assertValueNow( 8, "Setting a to nil should give us b + b = 8." )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testArgTupleB()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local zB = KSReactive.constantB( "foo" )
	
	local cB = KSReactive.argTupleB( aB, 7, zB, bB )
	
	KSTestUtils.assertEquals "initial" ( 0, 7, "foo", 0 )( cB:valueNow() )
	
	bB:set( 4 )
	
	KSTestUtils.assertEquals "after bB = 4" ( 0, 7, "foo", 4 )( cB:valueNow() )

	aB:set( 5 )
	
	KSTestUtils.assertEquals "after aB = 5" ( 5, 7, "foo", 4 )( cB:valueNow() )

	aB:set( nil )
	
	KSTestUtils.assertEquals "after aB = nil" ( nil, 7, "foo", 4 )( cB:valueNow() )

	bB:set( nil )
	
	KSTestUtils.assertEquals "after bB = nil" ( nil, 7, "foo", nil )( cB:valueNow() )
	
	bB:set()
	
	KSTestUtils.assertEquals "after bB = nothing" ( nil, 7, "foo" )( cB:valueNow() )
	
	aB:setError( "Test error propagation" )
	
	KSTestUtils.assertEquals "error propagation" ( true, "Test error propagation" )( cB:isErrorNow() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testArgTupleB_done()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local zB = KSReactive.constantB( "foo" )
	
	local cB = KSReactive.argTupleB( aB, 7, zB, bB )
	
	KSTestUtils.assertEquals "initial" ( false )( cB:isDone() )
	
	aB:close()
	
	KSTestUtils.assertEquals "aB closed" ( false )( cB:isDone() )
	
	bB:close()
	
	KSTestUtils.assertEquals "bB closed" ( true )( bB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testArgTupleB_zero()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local tB = KSReactive.argTupleB()
	
	KSTestUtils.assertEquals "empty" ( )( tB:valueNow() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testArgTupleB_const()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.constantB( 3 )
	
	local bB = KSReactive.constantB( 4 )
	
	local tB = KSReactive.argTupleB( aB, bB )
	
	KSTestUtils.assertEquals "empty" ( true )( tB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testArgTupleB_constError()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.constantB( 3 )
	
	local bB = KSReactive.variableB( 4 )
	
	bB:setError( "Test error propagation" )
	
	bB:setDone() -- done changing; note that close will trigger errors
	
	local tB = KSReactive.argTupleB( aB, bB )
	
	KSTestUtils.assertEquals "isDone" ( true )( tB:isDone() )
	
	KSTestUtils.assertEquals "isErrorNow" ( true, "Test error propagation" )( tB:isErrorNow() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testDataCallB()

	local KSReactive = require 'KSReactive'
	
	local fnB = KSReactive.variableB( function( a, b ) return a + b end )

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local cB = KSReactive.dataCallB( fnB, aB, bB )
	
	local dB = KSReactive.dataCallB( function( a, b ) return a + b end, cB, bB )
	
	dB:assertValueNow( 0, "Expected initial value to be 0." )
	
	bB:set( 4 )
	
	dB:assertValueNow( 8, "Expected value after b = 4 to be 8." )
	
	aB:set( 5 )
	
	dB:assertValueNow( 13, "Expected value after a = 5 to be 13." )
	
	fnB:set( function( a, b ) return a - b end )
	
	dB:assertValueNow( 5, "Expected value after changing function to be 5." )
	
	fnB:set( function( a, b ) return a or b end )
	
	dB:assertValueNow( 9, "Expected value after changing function to or to be 9." )
	
	aB:set( nil )
	
	dB:assertValueNow( 8, "Setting a to nil should give us b + b = 8." )
	
	aB:setError( "Test error propagation" )
	
	local dB_isError, dB_msg = dB:isErrorNow()
	
	assert( dB_isError, "Setting to an error should propagate as the error." )
	
	assert( dB_msg == "Test error propagation", "The error message should propagate." )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testDataCallB_autoSwitch()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.variableB( false )
	
	local bB = KSReactive.variableB( 4 )
	
	local function fn( v )
		return v and bB:relayB() or "foo"
	end
	
	local cB = KSReactive.dataCallB( fn, aB )
	
	cB:assertValueNow( "foo", "initial value should be foo" )
	
	bB:setError( "Hidden error" )
	
	cB:assertValueNow( "foo", "errors on other branch should be hidden" )
	
	aB:set( true )
	
	KSTestUtils.assertEquals "error revealed" ( true, "Hidden error" )( cB:isErrorNow() )
	
	bB:set( "hello" )
	
	cB:assertValueNow( "hello", "error should now be cleared to hello" )
	
	aB:set( false )
	
	cB:assertValueNow( "foo", "we should have switched away to foo" )
	
	aB:set( true )
	
	cB:assertValueNow( "hello", "we should have switched back to hello" )
	
	bB:close()
	
	KSTestUtils.assertEquals "bB closed" ( false )( cB:isDone() )
	
	aB:close()
	
	KSTestUtils.assertEquals "aB closed" ( true )( cB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testDataCallB_autoSwitch_close2()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.variableB( false )
	
	local bB = KSReactive.variableB( "hello" )
	
	local function fn( v )
		return v and bB or "foo"
	end
	
	local cB = KSReactive.dataCallB( fn, aB )
	
	cB:assertValueNow( "foo", "initial value should be foo" )
	
	aB:set( true )
	
	cB:assertValueNow( "hello", "we should have switched to hello" )
	
	aB:close()
	
	KSTestUtils.assertEquals "aB closed" ( false )( cB:isDone() )
	
	bB:set( "greetings" )
	
	cB:assertValueNow( "greetings", "we should have switched to greetings" )
	
	bB:close()
	
	KSTestUtils.assertEquals "bB closed" ( true )( cB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testWeakEventNodesTable( test )

	local KSReactive = require 'KSReactive'
	
	local WFCloseSupport = require 'WFCloseSupport'
	
	local et = KSReactive.makeWeakEventTable( "< initial >" )
	
	local b = et:behaviorB( 'foo' )
	local e = et:eventE( 'baz' )

	b:assertValueNow( "< initial >", "Initial value for behavior is wrong." )
	
	et:postToEvent( 'foo', "Hello" )
	
	b:assertValueNow( "Hello", "Value failed to propagate" )
	
	local errorProcessed = false
	
	e:ownedOnErrorE( test, function() errorProcessed = true end )
	
	et:postErrorToEvent( 'baz', "BOOM!" )
	
	assert( errorProcessed, "The error failed to propagate" )
	
	et:postToEvent( 'blah', 4 ) -- Check that we can post to non-existent keys
	
	local ek = et:eventKeys()
	
	assert( #ek == 2, "We should have two active event keys." )
	assert( ( ek[ 1 ] == 'foo' and ek[ 2 ] == 'baz' ) or ( ek[ 1 ] == 'baz' and ek[ 2 ] == 'foo' ),
			"The wrong keys seem to be active." )
	
	et:closeEvent( 'baz' )
	
	assert( WFCloseSupport.hasBeenOrWillBeClosed( e ), "Close did not propagate" )
	
	assert( et:eventExists( 'foo' ), "Foo should exist" )
	assert( not et:eventExists( 'baz' ), "Baz should no longer exist" )
	assert( not et:eventExists( 'blah' ), "Blah should never have existed" )
	
	local ek2 = et:eventKeys()

	assert( #ek2 == 1, "We should only have one active event key now." )
	assert( ek2[ 1 ] == 'foo', "'foo' should be the active event key." )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( et:eventExists( 'foo' ), "Foo should still exist after garbage collection" )
	
	local beffect = b:ownedOnDataB( test, function() end )
		-- Anchor it
		
	local fn = function() end
	fn( beffect ) -- silence the lint about beffect going unused
		
	local weak = setmetatable( { }, { __mode = 'kv' } )
	
	weak[ 1 ] = b
	
	b = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ], "beffect = ownedOnDataB should have kept b alive" )
	assert( et:eventExists( 'foo' ), "b being alive should have kept the event alive" )
	
	et:postToEvent( 'foo', "Still here!" )
	
	weak[ 1 ]:assertValueNow( "Still here!", "Event wiring should still work for foo" )
	
	et:close()

	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( not weak[ 1 ], "Closing the event table should have allowed b to be collected" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testLiftB()

	local KSReactive = require 'KSReactive'
	
	local fB = KSReactive.liftB( function( a, b ) return a + b end )

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local cB = fB( aB, bB )
	
	local dB = fB( cB, bB )
	
	dB:assertValueNow( 0, "Expected initial value to be 0." )
	
	bB:set( 4 )
	
	dB:assertValueNow( 8, "Expected value after b = 4 to be 8." )
	
	aB:set( 5 )
	
	dB:assertValueNow( 13, "Expected value after a = 5 to be 13." )
	
	aB:setError( "Test error propagation" )
	
	local dB_isError, dB_msg = dB:isErrorNow()
	
	assert( dB_isError, "Setting to an error should propagate as the error." )
	
	assert( dB_msg == "Test error propagation", "The error message should propagate." )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testLiftB_autoSwitch()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.variableB( false )
	
	local bB = KSReactive.variableB( 4 )
	
	local fnB = KSReactive.liftB( function( v )
		return v and bB:relayB() or "foo"
	end )
	
	local cB = fnB( aB )
	
	cB:assertValueNow( "foo", "initial value should be foo" )
	
	bB:setError( "Hidden error" )
	
	cB:assertValueNow( "foo", "error should remain hidden" )
	
	aB:set( true )
	
	KSTestUtils.assertEquals "error revealed" ( true, "Hidden error" )( cB:isErrorNow() )
	
	bB:set( "hello" )
	
	cB:assertValueNow( "hello", "error should have been cleared" )
	
	aB:set( false )
	
	cB:assertValueNow( "foo", "we should have switched away" )
	
	aB:set( true )
	
	cB:assertValueNow( "hello", "we should have switched back" )
	
	bB:close()
	
	KSTestUtils.assertEquals "bB closed" ( false )( cB:isDone() )
	
	aB:close()
	
	KSTestUtils.assertEquals "aB closed" ( true )( cB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testLiftB_autoSwitch_close2()

	local KSReactive = require 'KSReactive'
	
	local KSTestUtils = require 'KSTestUtils'
	
	local aB = KSReactive.variableB( false )
	
	local bB = KSReactive.variableB( "hello" )
	
	local fnB = KSReactive.liftB( function( v )
		return v and bB or "foo"
	end )
	
	local cB = fnB( aB )
	
	cB:assertValueNow( "foo", "initial value should be foo" )
	
	aB:set( true )
	
	cB:assertValueNow( "hello", "value should have switched to hello" )
	
	aB:close()
	
	KSTestUtils.assertEquals "aB closed" ( false )( cB:isDone() )
	
	bB:set( "greetings" )
	
	cB:assertValueNow( "greetings", "value should now be greetings" )
	
	bB:close()
	
	KSTestUtils.assertEquals "bB closed" ( true )( cB:isDone() )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testLift()

	local KSReactive = require 'KSReactive'
	
	local t = KSReactive.lift {
		fnB = function( a, b ) return a + b end
	}
	
	local fnB = t.fnB

	local aB = KSReactive.variableB( 0 )
	
	local bB = KSReactive.variableB( 0 )
	
	local cB = fnB( aB, bB )
	
	local dB = fnB( cB, bB )
	
	dB:assertValueNow( 0, "Expected initial value to be 0." )
	
	bB:set( 4 )
	
	dB:assertValueNow( 8, "Expected value after b = 4 to be 8." )
	
	aB:set( 5 )
	
	dB:assertValueNow( 13, "Expected value after a = 5 to be 13." )
	
	aB:setError( "Test error propagation" )
	
	local dB_isError, dB_msg = dB:isErrorNow()
	
	assert( dB_isError, "Setting to an error should propagate as the error." )
	
	assert( dB_msg == "Test error propagation", "The error message should propagate." )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testConstantE( test )

	local KSReactive = require 'KSReactive'
	
	local source = KSReactive.sourceE()
	
	local resultFoo = source:constantE( 'foo' )
	
	local fooGotValue = false
	local fooResult = nil
	
	local function checkFooGotValue( vtype, result )
		assert( fooGotValue == vtype, "Expected to get value of type " .. vtype )
		assert( fooResult == result, "Expected value " .. result )
		fooGotValue = false
		fooResult = nil
	end
	
	resultFoo:ownedOnDataE( test, function( ... )
		assert( select( '#', ... ) == 1, "Expected 1 value" )
		fooGotValue = 'data'
		fooResult = ( ... )
	end )
	
	resultFoo:ownedOnErrorE( test, function( message )
		fooGotValue = 'error'
		fooResult = message
	end )
	
	source:post( 1 )
	checkFooGotValue( 'data', 'foo' )
	
	source:post( 2 )
	checkFooGotValue( 'data', 'foo' )
	
	source:postError( 'baz' )
	checkFooGotValue( 'error', 'baz' )

	source:post( 3 )
	checkFooGotValue( 'data', 'foo' )
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testTakeE( test )

	local KSReactive = require 'KSReactive'
	
	local source = KSReactive.sourceE()
	
	local taker = source:takeE( 3 )
	
	local results = { }
	local didFinish = false
	
	taker:ownedOnDataE( test, function( x ) results[ #results + 1 ] = x end )
	taker:ownedOnDoneE( test, function() didFinish = true end )
	
	local didFinish10 = false
	
	source:takeE( 10 ):ownedOnDoneE( test, function() didFinish10 = true end )

	local didFinish11 = false
	
	source:takeE( 11 ):ownedOnDoneE( test, function() didFinish11 = true end )

	local didFinish12 = false
	
	source:takeE( 12 ):ownedOnDoneE( test, function() didFinish12 = true end )

	for i = 1, 10 do
		source:post( i )
	end
	
	assert( didFinish, "taker should have finished by now" )
	assert( #results == 3, "We should have three results" )
	
	for i = 1, #results do
		assert( results[ i ] == i, "Wrong results at position: " .. i )
	end
	
	assert( didFinish10, "We should have signaled done for the exact match on count" )
	
	assert( not didFinish11, "We should not have finished 11." )
	assert( not didFinish12, "We should not have finished 12." )
	
	source:close()
	
	assert( didFinish11, "But closing should mark us as done (11)" )
	assert( didFinish12, "But closing should mark us as done (12)" )
	
end
	
--------------------------------------------------------------------------------

function KSReactiveTest.testTableB( test )

	local KSReactive = require 'KSReactive'
	local KSTableUtils = require 'KSTableUtils'
	
	local B1 = KSReactive.variableB( "foo" )
	local B2 = KSReactive.variableB( 3 )
	local B3 = B2:mapB( function( x ) return x * x end )
	local B4 = KSReactive.constantB( "baz" )
	
	local TB = KSReactive.tableB {
		a = B1,
		b = B2,
		c = { d = B3, e = B4 }
	}
	
	local match = { a = "foo", b = 3, c = { d = 9, e = "baz" } }
	
	local case = 'initial'
	
	local success = true
	local failedCase = 'none'
	
	TB:ownedOnDataB( test, function( data )
		if not KSTableUtils.deepEquals( match, data ) then
			success = false
			failedCase = case
		end
	end )
	
	match = { a = "foo", b = 4, c = { d = 16, e = "baz" } }
	case = '3 to 4'
	
	B2:set( 4 )
	
	match = { a = "flok", b = 4, c = { d = 16, e = "baz" } }
	case = 'foo to flok'
	
	B1:set( "flok" )
	
	match = { a = { glom = 7 }, b = 4, c = { d = 16, e = "baz" } }
	case = 'flok to glok'
	
	B1:set( { glom = 7 } )
	
	assert( success, "Match failed for case: " .. failedCase )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testTableBRejectsReactiveObjects()

	local KSReactive = require 'KSReactive'
	local KSPromises = require 'KSPromises'
	
	local p = KSPromises.resolvedPromiseP( 'data', 3 )
	
	assert( not pcall( KSReactive.tableB, { foo = { baz = p } } ),
			"We should have rejected a table containing a promise" )

	local e = KSReactive.sourceE()
	
	assert( not pcall( KSReactive.tableB, { foo = { baz = e } } ),
			"We should have rejected a table containing an event stream" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapSwitchBIsConstantFriendly( test )

	local KSReactive = require 'KSReactive'
	
	local constantBehaviors = {
		KSReactive.constantB( 1 ),
		KSReactive.constantB( 2 )
	}

	local closed = 0

	local function mapFn( i )

		local b = constantBehaviors[ i % 4 ]
		
		if not b then
			b = KSReactive.variableB( 'foo' ):addCloseAction( function() closed = closed + 1 end )
		end
		
		return b

	end
	
	local varB = KSReactive.variableB( 0 )

	local finished = false
	
	varB:mapSwitchB( mapFn ):ownedOnDoneE( test, function() finished = true end )
	
	for i = 1, 17 do -- Designed to run through to a constant behavior
		varB:set( i )
	end
	
	assert( not finished, "Finished too soon" )
	
	varB:close()
	
	assert( finished, "Finished too late" )
	
	KSReactive.testing_flushAutoClose()
	
	assert( closed == 9, "We should have closed 9 behaviors, did close " .. tostring( closed ) )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRunUntilDone()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local weak = setmetatable( {}, { __mode = 'kv' } )
	
	weak[ 1 ] = sourceE
	
	sourceE:runUntilDone()
	
	sourceE = nil
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ] ~= nil, "source was prematurely collected" )
	
	weak[ 1 ]:post( 'foo' )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ] ~= nil, "source was prematurely collected (2)" )
	
	weak[ 1 ]:postError( 'baz' )
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ] ~= nil, "source was prematurely collected (2)" )
	
	weak[ 1 ]:close()
	
	collectgarbage()
	collectgarbage()
	collectgarbage()
	
	assert( weak[ 1 ] == nil, "source should have been collected" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testEndAfterErrorE( test )

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local outE = sourceE:endAfterErrorE()
	
	local data = nil
	local done = false
	local emsg = nil
	
	outE:ownedOnDataE( test, function( x )
		data = x
	end )
	
	outE:ownedOnDoneE( test, function()
		done = true
	end )
	
	outE:ownedOnErrorE( test, function( msg )
		emsg = msg
	end )
	
	sourceE:post( 'baz' )
	sourceE:post( 'foo' )
	sourceE:postError( "First error" )
	sourceE:postError( "Second error" )
	sourceE:post( 'bleen' )
	sourceE:postError( "Third error" )
	
	assert( data == 'foo', "Should have ended up at data = foo" )
	assert( emsg == "First error", "We should only have passed the first error" )
	assert( done, "We should be done by now." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testAccumulateE_counter( test )

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local outE = sourceE:accumulateE( function( accum ) return accum + 1 end, 0 )
	
	local data = nil
	local emsg = nil

	outE:ownedOnDataE( test, function( x )
		data = x
	end )
	
	outE:ownedOnErrorE( test, function( msg )
		emsg = msg
	end )
	
	sourceE:post( 'baz' )
	sourceE:post( 'foo' )
	sourceE:postError( "First error" )
	sourceE:postError( "Second error" )
	sourceE:post( 'bleen' )
	sourceE:postError( "Third error" )
	
	assert( data == 3, "Should have ended up at data = foo" )
	assert( emsg == "Third error", "We should only have passed the first error" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testAccumulateB_counter( test )

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local outB = sourceE:accumulateB( function( accum ) return accum + 1 end, 0 )
	
	local data = nil
	local emsg = nil

	outB:ownedOnDataB( test, function( x )
		data = x
	end )
	
	assert( data == 0, "We should start out at zero" )
	
	outB:ownedOnErrorE( test, function( msg )
		emsg = msg
	end )
	
	sourceE:post( 'baz' )
	sourceE:post( 'foo' )
	sourceE:postError( "First error" )
	sourceE:postError( "Second error" )
	sourceE:post( 'bleen' )
	sourceE:postError( "Third error" )
	
	assert( data == 3, "Should have ended up at data = foo" )
	assert( emsg == "Third error", "We should only have passed the first error" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapEDone()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local mappedE = sourceE:mapE( function( x )
		if 3 < x then
			return KSReactive.DONE
		else
			return x
		end
	end )
	
	local heldB = mappedE:holdB( nil )
	
	for i = 1, 10 do
		sourceE:post( i )
	end
	
	heldB:assertValueNow( 3, "Value should have stopped at three." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapESkip()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local mappedE = sourceE:mapE( function( x )
		if x % 2 == 1 then
			return KSReactive.SKIP
		else
			return x
		end
	end )
	
	local heldB = mappedE:holdB( nil )
	
	for i = 1, 7 do
		sourceE:post( i )
	end
	
	heldB:assertValueNow( 6, "Value should have stopped at six." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapESame()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local mappedE = sourceE:mapE( function( x )
		if x % 2 == 0 then
			return KSReactive.SAME
		else
			return x + x
		end
	end )
	
	local heldB = mappedE:holdB( nil )
	
	for i = 1, 10 do
		sourceE:post( i )
	end
	
	heldB:assertValueNow( 10, "Value should have stopped at six." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapEDoneAndSame()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local mappedE = sourceE:rawMapE( function( status, x )
		if 3 < x then
			return KSReactive.DONE
		else
			return KSReactive.SAME
		end
	end )
	
	local heldB = mappedE:holdB( nil )
	
	for i = 1, 10 do
		sourceE:post( i )
	end
	
	heldB:assertValueNow( 3, "Value should have stopped at three." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapESkipAndSame()

	local KSReactive = require 'KSReactive'
	
	local sourceE = KSReactive.sourceE()
	
	local mappedE = sourceE:rawMapE( function( status, x )
		if x % 2 == 1 then
			return KSReactive.SKIP
		else
			return KSReactive.SAME
		end
	end )
	
	local heldB = mappedE:holdB( nil )
	
	for i = 1, 7 do
		sourceE:post( i )
	end
	
	heldB:assertValueNow( 6, "Value should have stopped at six." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapBDone()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( 0 )
	
	local mappedB = variableB:mapB( function() return KSReactive.DONE end )
	
	mappedB:assertValueNow( nil, "Value should have never advanced." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapBSkip()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( 1 )
	
	local mappedB = variableB:mapB( function( x ) return KSReactive.SKIP end )
	
	mappedB:assertValueNow( nil, "Value should have started at empty" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapBSame()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( "Hello!" )
	
	local mappedB = variableB:mapB( function( x ) return KSReactive.SAME end )
	
	mappedB:assertValueNow( "Hello!", "Value should have started at Hello" )
	
	for i = 1, 10 do
		variableB:set( i )
		mappedB:assertValueNow( i, "Variable should have advanced." )
	end

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapBDone()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( 0 )
	
	local mappedB = variableB:rawMapB( function() return KSReactive.DONE end )
	
	mappedB:assertValueNow( nil, "Value should have never advanced." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapBSkip()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( 1 )
	
	local mappedB = variableB:rawMapB( function() return KSReactive.SKIP end )
	
	mappedB:assertValueNow( nil, "Value should have started at empty" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapBSame()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( "Hello!" )
	
	local mappedB = variableB:rawMapB( function() return KSReactive.SAME end )
	
	mappedB:assertValueNow( "Hello!", "Value should have started at Hello" )
	
	for i = 1, 10 do
		variableB:set( i )
		mappedB:assertValueNow( i, "Variable should have advanced." )
	end

end

--------------------------------------------------------------------------------

function KSReactiveTest.testMapBInitialError()

	local KSReactive = require 'KSReactive'
	
	local variableB = KSReactive.variableB( "Hello!" )
	
	local mappedB = variableB:mapB( function() assert( false, "Error!" ) end )
	
	assert( mappedB:isErrorNow(), "Mapped should be at an error status" )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testClosingObjectRelayB()

	local KSReactive = require 'KSReactive'
	local WFCloseSupport = require 'WFCloseSupport'
	local WFObjectProxy = require 'WFObjectProxy'
	local Rx = require 'Rx'
	
	local isClosed = WFCloseSupport.hasBeenOrWillBeClosed
	
	local Closeable = Rx.Class "Closeable" {
		_name = false,
		WFCloseSupport.addCloseSupportMethods,
		__init = function( self, name )
			self._name = name
		end,
		close = function( self )
			self._name = "CLOSED"
		end,
		name = function( self )
			return self._name
		end
	}
	
	local Proxy = WFObjectProxy( "CloseableProxy", Closeable )
	
	local a = Proxy( 'a' )
	local b = Proxy( 'b' )
	
	local variableB = KSReactive.variableB( a )
	
	local proxyB = variableB:closingObjectRelayB()
	
	local a2 = proxyB:valueNow()
	
	--assert( a2 ~= a, "made new proxy" )
		-- [scouten 2015-03-30]: Remove equality test. The tests
		-- on the following will determine if closure works independently
		-- and this is the only location in WF where we depend on being
		-- able to test equality for proxy *instances*. (We recently modified
		-- WFObjectProxy to compare equal if the underlying shared instance
		-- is the same, thus invalidating this assertion. IMHO it's not worth
		-- it to add a test for distinct proxy instances just to make this
		-- work.)
	
	a:close()
	
	assert( not isClosed( a2 ), "proxy close counting ok" )
	assert( a2:name() == 'a', "my proxy still works" )
	
	variableB:set( b )
	
	assert( a2._name == 'CLOSED', "old value got closed" )
	
	local b2 = proxyB:valueNow()

	assert( b2:name() == 'b', "second value working..." )
	b:close()
	assert( b2:name() == 'b', "second value working..." )
	
	variableB:set( nil )

	assert( b2._name == 'CLOSED', "second value closed..." )

end

--------------------------------------------------------------------------------

function KSReactiveTest.testRawMapBInitialError()
end

--------------------------------------------------------------------------------

function KSReactiveTest.testClosingKnownMapB()

	local KSReactive = require 'KSReactive'
	local WFUnknown = require 'WFUnknown'
	
	local srcB = KSReactive.variableB( 'A' )
	local n = 0
	local mappedB = srcB
	:closingKnownMapB( function( x, y )
		if WFUnknown.isUnknown( x ) or WFUnknown.isUnknown( y ) then
			return 'error'
		else
			n = n + 1
			return string.format( 'success %d %s', n, x )
		end
	end )
	
	assert( mappedB:valueNow() == 'success 1 A' )
	
	srcB:set( WFUnknown.value(), 'C' )
	
	assert( select( '#', mappedB:valueNow() ) == 1 )
	assert( WFUnknown.isUnknown( mappedB:valueNow() ) )

	srcB:set( 'B' )
	
	assert( mappedB:valueNow() == 'success 2 B' )
	
	srcB:set( 'D', WFUnknown.value() )

	assert( select( '#', mappedB:valueNow() ) == 1 )
	assert( WFUnknown.isUnknown( mappedB:valueNow() ) )

	mappedB:close()
	
end

--------------------------------------------------------------------------------

function KSReactiveTest.testClosingKnownMapSwitchB()

	local KSReactive = require 'KSReactive'
	local WFUnknown = require 'WFUnknown'
	
	local srcB = KSReactive.variableB( 'A' )
	local n = 0
	local mappedB = srcB
	:closingKnownMapSwitchB( function( x, y )
		if WFUnknown.isUnknown( x ) or WFUnknown.isUnknown( y ) then
			return KSReactive.constantErrorB( 'error' )
		else
			n = n + 1
			return KSReactive.constantB( string.format( 'success %d %s', n, x ) )
		end
	end )

	assert( mappedB:valueNow() == 'success 1 A' )
	
	srcB:set( WFUnknown.value(), 'C' )
	
	assert( select( '#', mappedB:valueNow() ) == 1 )
	assert( WFUnknown.isUnknown( mappedB:valueNow() ) )

	srcB:set( 'B' )
	
	assert( mappedB:valueNow() == 'success 2 B' )
	
	srcB:set( 'D', WFUnknown.value() )

	assert( select( '#', mappedB:valueNow() ) == 1 )
	assert( WFUnknown.isUnknown( mappedB:valueNow() ) )

	mappedB:close()
	
end

--------------------------------------------------------------------------------

if require 'WFCapabilities'.getCapability 'KSReactive_emptyTuplesFromMap' == 'allowed' then

function KSReactiveTest.testNoneNotSkipped()
	-- test that a bug we had for a long time that behaviors that
	-- transition to none would suppress the change
	local KSReactive = require 'KSReactive'
	
	local last
	local t = { addCloseTarget = function()end }
	local b = KSReactive.variableB()
	b:closingMapB( function( case )
		if case == 1 then
			return 'first'
		elseif case == 2 then
			return
		elseif case == 3 then
			return 'third'
		end
	end )
	:ownedOnDataB( t, function( ... )
		last = ...
	end )
	b:set( 1 )
	assert( last == 'first', "First wrong" )
	b:set( 2 )
	assert( last == nil, "Second wrong" )
	b:set( 3 )
	assert( last == 'third', "Third wrong" )
	
end

end

--------------------------------------------------------------------------------

return KSReactiveTest
