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

WFRxTestCaseTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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.

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

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable
local Subject = Rx.Subject

	-- WF.core.os
local KSDate = require 'KSDate'

	-- WF.core.rxlua
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.utils
local WFCloseSupport = require 'WFCloseSupport'

--============================================================================--

local testSpy = {

	_nativeTest = false,
		-- "test" object provided by native test system

	_actualWaitTime = false,
		-- (number) actual value to :waitForTestToFinish()

	_expectedWaitTime = 2,
		-- (number) expected value to :waitForTestToFinish()

	_testShouldSucceed = false,
		-- (boolean) true if test should call :notifyTestDidSucceed()

	_runAfterTest = false,
		-- (function) additional verification to run after WFRxTestCase finished

}

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

function testSpy:__init( nativeTest )

	self._nativeTest = nativeTest

	self._expectedWaitTime = self._expectedWaitTime

end


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

function testSpy:testName()

	return self._nativeTest:testName()
	
end

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

local function testFinished( self, testDidSucceed )

	local nativeTest = self._nativeTest

	if self._runAfterTest then

		local success, message = pcall( self._runAfterTest )

		if not success then

			nativeTest:notifyTestDidFail( message )
			return

		end

	end

	if self._actualWaitTime ~= self._expectedWaitTime then

		local message = string.format( ":waitForTestToFinish called with %s, expected %s",
									   self._actualWaitTime,
									   self._expectedWaitTime )

		nativeTest:notifyTestDidFail( message )

		return

	end

	if testDidSucceed ~= self._testShouldSucceed then

		if testDidSucceed then
			nativeTest:notifyTestDidFail( "Test succeeded, should have failed" )
		else
			nativeTest:notifyTestDidFail( "Test failed, should have succeeded" )
		end

		return

	end

	nativeTest:notifyTestDidSucceed()

end

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

function testSpy:notifyTestDidSucceed()

	testFinished( self, true )

end

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

function testSpy:notifyTestDidFail( reason )

	testFinished( self, false )

end

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

function testSpy:runAfterTest( func )

	self._runAfterTest = func

end

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

function testSpy:log( message )

	self._nativeTest:log( message )

end

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

function testSpy:waitForTestToFinish( waitTime )

	self._actualWaitTime = waitTime

	self._nativeTest:waitForTestToFinish( waitTime + 2 )

end

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

function testSpy:expectWaitForTestToFinish( waitTime )

	self._expectedWaitTime = waitTime

end

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

function testSpy:doNotExpectWaitForTestToFinish()

	self._expectedWaitTime = false

end

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

local function runTest( self, testFn )
	
	local testShouldFail = not self._testShouldSucceed
	
	local rxTestCase = WFRxTestCase.wrapTest( testFn, "WFRxTestCaseTest", testShouldFail )

	self._nativeTest:waitForTestToFinish( ( self._expectedWaitTime or 2 ) + 1 )

	rxTestCase( self )

end

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

function testSpy:expectTestToFail( testFn )

	self._testShouldSucceed = false

	runTest( self, testFn )

end

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

function testSpy:expectTestToSucceed( testFn )

	self._testShouldSucceed = true

	runTest( self, testFn )

end

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

testSpy = Rx.Class( 'WFRxTestCaseTest.testSpy', testSpy )


--============================================================================--

local WFRxTestCaseTest = {}

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

function WFRxTestCaseTest.rxTestNativeTest( rxTest )

	rxTest:expectEqual( true, rxTest:nativeTest() ~= nil )
	
end

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

function WFRxTestCaseTest.rxTestEmpty( rxTest )

	local o = rxTest:observe( Observable.empty() )
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestOneValue( rxTest )

	local o = rxTest:observe( Observable.just( 42 ) )

	o:expectData( 42 )
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestOneValueWithTuple( rxTest )

	local o = rxTest:observe( Observable.just( 42, "blah", "abc" ) )

	o:expectData( 42, "blah", "abc" )
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestExpectDataWithinTime( rxTest )

	local o = rxTest:observe( Observable.just( 42, "blah", "abc" ) )

	o:expectDataWithinTime( 1, 42, "blah", "abc" )
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestExpectDataChaining( rxTest )

	rxTest:setTimeLimit( 5 )
	
	rxTest:observe( Observable.just( 42 ) )
		:expectData( 42 )
		:expectCompleted()
		
	rxTest:observe( 
			Observable.timer( 2, WFRxScheduler.sharedScheduler() ) 
		)
		:expectDataWithinTime( 4, 0 )
		:expectCompleted()
		
end

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

function WFRxTestCaseTest.testExpectDataWithinTime_happensTooLate( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe( Observable.timer( 1, WFRxScheduler.sharedScheduler() ) )

		o:expectDataWithinTime( .25, 0 )

	end )

end

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

function WFRxTestCaseTest.rxTestExpectDataWithinTime_noInitialValue( rxTest )

	local o = rxTest:observe(
		Observable.timer( .25, WFRxScheduler.sharedScheduler() )
			:map( function()
				return 42, "blah", "abc"
			end ) )

	o:expectDataWithinTime( 1, 42, "blah", "abc" )

end

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

function WFRxTestCaseTest.rxTestExpectDataWithinTime_ignoreInterveningValues( rxTest )

	local o = rxTest:observe( Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ) )

	o:expectDataWithinTime( 2, 3 )

end

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

function WFRxTestCaseTest.testExpectDataWithinTime_severalValuesAreWrong( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ) )

		o:expectDataWithinTime( 2, 100 ) -- won't count that high in 2 seconds

	end )

end

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

function WFRxTestCaseTest.testExpectDataWithinTime_never( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe( Observable.never() )

		o:expectDataWithinTime( 1, 0 )

	end )

end

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

function WFRxTestCaseTest.rxTestExpectDataWithinTime_valueAlreadyHappened( rxTest )

	local o = rxTest:observe( Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ) )

	o:expectData( 1 )
	o:expectData( 2 )

	o:expectDataWithinTime( 1, 2 )

end

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

function WFRxTestCaseTest.rxTestExpectErrorWithinTime( rxTest )

	local o = rxTest:observe( 
		Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() )
			:flatMap( function()
			
				return Observable.throw( 'bad' )
				
			end )
			
		)
		
	o:expectErrorWithinTime( 1, 'bad' )
	
end

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

function WFRxTestCaseTest.testExpectErrorWithinTime_timeout( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe( 
			Observable.timer( 6, WFRxScheduler.sharedScheduler() )
				:flatMap( function()
				
					return Observable.throw( 'bad' )
					
				end )
				
			)

		o:expectErrorWithinTime( 1, 'bad' )

	end )
	
end

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

function WFRxTestCaseTest.rxTestExpectCompletedWithinTime( rxTest )

	local o = rxTest:observe( 
		Observable.timer( .25, WFRxScheduler.sharedScheduler() )
			:flatMapLatest( function()
			
				return Observable.empty()
				
			end )
			
		)
		
	o:expectCompletedWithinTime( 1 )
	
end

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

function WFRxTestCaseTest.testExpectCompletedWithinTime_timeout( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe( 
			Observable.timer( 6, WFRxScheduler.sharedScheduler() )
				:flatMap( function()
				
					return Observable.empty()
					
				end )
				
			)
			
		o:expectCompletedWithinTime( 1 )

	end )
	
end

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

function WFRxTestCaseTest.rxTestOneValue_shortForm( rxTest )

	rxTest:expectSingleValue( Observable.just( 42 ), 42 )

end

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

function WFRxTestCaseTest.rxTestOneValueWithTuple_shortForm( rxTest )

	rxTest:expectSingleValue( Observable.just( 42, "blah", "abc" ),
							  42, "blah", "abc" )

end

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

function WFRxTestCaseTest.rxTestIgnore( rxTest )

	local o = rxTest:observe( Observable.just( 42, "blah", "abc" ) )

	o:expectData( 42, ":ignore:", "abc" )
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestValueSequence( rxTest )

	local o = rxTest:observe( Observable.range( 1, 4 ) )

	o:expectData( 1 )
	o:expectData( 2 )
	o:expectData( 3 )
	o:expectData( 4 )

	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestSubscribeImmediately( rxTest )

	local s = Rx.Subject()

	local o = rxTest:observe( s )

	s:onNext( 42 )
		-- Even though this happens before :expectData, we should see it.

	o:expectData( 42 )

	s:onNext( 44 )
	o:expectData( 44 )

	s:onCompleted()
	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestImmediateError( rxTest )

	local o = rxTest:observe( Observable.throw( "expected", "this", "error", 42 ) )

	o:expectError( "expected", "this", "error", 42 )

end

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

function WFRxTestCaseTest.rxTestExpectOnlyError( rxTest )

	rxTest:expectOnlyError( Observable.throw( "expected", "this", "error", 42 ),
							"expected", "this", "error", 42 )

end

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

function WFRxTestCaseTest.rxTestExpectOnlyCompleted( rxTest )

	rxTest:expectOnlyCompleted( Observable.empty() )

end

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

function WFRxTestCaseTest.testExpectOnlyCompleted_errorNotEmpty( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		rxTest:expectOnlyCompleted( Observable.just( 42 ) )

	end )

end

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

function WFRxTestCaseTest.rxTestErrorAfterSomeValues( rxTest )

	local src = Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() )
		:take( 4 )
		:map( function( i )

			if i == 4 then
				error "expected error"
			else
				return i
			end

		end )

	local o = rxTest:observe( src )

	o:expectData( 1 )
	o:expectData( 2 )
	o:expectData( 3 )
	o:expectError( "expected error" )

end

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

function WFRxTestCaseTest.rxTestAsyncSequence( rxTest )

	local src = Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ):take( 4 )

	local o = rxTest:observe( src )

	o:expectData( 1 )
	o:expectData( 2 )
	o:expectData( 3 )
	o:expectData( 4 )

	o:expectCompleted()

end

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

function WFRxTestCaseTest.rxTestOverlappingObservables( rxTest )

	local b = Rx.BehaviorSubject( 42 )

	local observeB = rxTest:observe( b )
	observeB:expectData( 42 )

	local function updateStuff()
		b:onNext( 54 )
		return Observable.just( "blah" )
	end

	rxTest:expectSingleValue( updateStuff(), 'blah' )

	observeB:expectData( 54 )

end

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

function WFRxTestCaseTest.testClosesAfterTest( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		spy:runAfterTest( function()
			assert( WFCloseSupport.hasBeenOrWillBeClosed( rxTest ),
					"Didn't close rxTest object" )
		end )

		o:expectData( 42 )
		o:expectCompleted()

		rxTest:expectEqual( false, WFCloseSupport.hasBeenOrWillBeClosed( rxTest ) )

	end )

end

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

function WFRxTestCaseTest.testClosesAfterFailure( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		spy:runAfterTest( function()
			assert( WFCloseSupport.hasBeenOrWillBeClosed( rxTest ),
					"Didn't close rxTest object" )
		end )

		rxTest:expectEqual( false, WFCloseSupport.hasBeenOrWillBeClosed( rxTest ) )

		o:expectData( 49 ) -- intentionally wrong, will cause test to fail
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testDisposesObservers( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		spy:runAfterTest( function()
			assert( o:isDisposed(), "Didn't dispose Observer" )
		end )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testDisposesObserversAfterFailure( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		spy:runAfterTest( function()
			assert( o:isDisposed(), "Didn't dispose Observer" )
		end )

		o:expectData( 49 ) -- intentionally wrong, will cause test to fail
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testNotifiesTestDidSucceed( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToSucceed( function( rxTest )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testNotifiesTestDidSucceedAsync( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToSucceed( function( rxTest )

		local src = Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ):take( 4 )

		local o = rxTest:observe( src )

		o:expectData( 1 )
		o:expectData( 2 )
		o:expectData( 3 )
		o:expectData( 4 )

		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testVerboseSuccess( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testNotifiesTestDidFail( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 43 ) -- see what happens if we look for wrong value
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testNotifiesTestDidFailAsync( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local src = Observable.concat(
			Observable.timer( .25, .25, WFRxScheduler.sharedScheduler() ):take( 3 ),
			Observable.throw( "expected failure" ) )

		local o = rxTest:observe( src )

		o:expectData( 1 )
		o:expectData( 2 )
		o:expectData( 3 )
		o:expectCompleted() -- wrong

	end )

end

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

function WFRxTestCaseTest.testFailsTestIfErrorThrown( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		error "Expected failure"

	end )

end

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

function WFRxTestCaseTest.testFailsTestIfErrorThrownLater( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		local o = rxTest:observe(
			Observable.timer( .1, WFRxScheduler.sharedScheduler() ):map( function() return 42 end ) )

		o:expectData( 42 )

		error "Expected failure"

	end )

end

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

function WFRxTestCaseTest.testVerboseFailure( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectTestToFail( function( rxTest )

		rxTest:makeVerbose()

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 43 ) -- see what happens if we look for wrong value
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.rxTestExpectEqual_scalarMatch( rxTest )

	rxTest:expectEqual( 42, 42 )

end

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

function WFRxTestCaseTest.testExpectEqual_scalarMismatch( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		rxTest:expectEqual( 42, 44 )

	end )

end

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

function WFRxTestCaseTest.rxTestExpectEqual_tableMatch( rxTest )

	rxTest:expectEqual( { a = 42 }, { a = 42 } )

end

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

function WFRxTestCaseTest.testExpectEqual_tableMismatch( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		rxTest:expectEqual( { a = 42 }, { x = 'y' } )

	end )

end

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

function WFRxTestCaseTest.rxTestExpectSpecificError_match( rxTest )

	rxTest:expectSpecificError( "this is gonna happen",
		function()
			error "this is gonna happen"
		end )

end

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

function WFRxTestCaseTest.testExpectSpecificError_mismatch( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		rxTest:expectSpecificError( "this is gonna happen",
			function()
				error "this isn't gonna happen"
			end )

	end )

end

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

function WFRxTestCaseTest.testExpectSpecificError_noError( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		rxTest:expectSpecificError( "this is gonna happen",
			function()
				return "nope, no errors here today"
			end )

	end )

end

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

function WFRxTestCaseTest.rxTestExpectError_match( rxTest )

	rxTest:expectError(
		function()
			error "expected error"
		end)

end

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

function WFRxTestCaseTest.testExpectError_noError( nativeTest )

	local spy = testSpy( nativeTest )

	spy:doNotExpectWaitForTestToFinish()

	spy:expectTestToFail( function( rxTest )

		rxTest:expectError(
			function()
				return "nope, no errors here today"
			end )

	end )

end

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

function WFRxTestCaseTest.testFinally( nativeTest )

	local spy = testSpy( nativeTest )

	local nCallsToFinallyFunc = 0

	spy:runAfterTest( function()
		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )
	end )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:finally( function()
			nCallsToFinallyFunc = nCallsToFinallyFunc + 1
		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_returnsObservable( nativeTest )

	local spy = testSpy( nativeTest )

	local nCallsToFinallyFunc = 0
	local observableFinished = false

	spy:runAfterTest( function()

		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )

		assert( observableFinished, "Didn't wait for Observable to finish" )

	end )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:finally( function()

			nCallsToFinallyFunc = nCallsToFinallyFunc + 1

			return Observable.timer( .25, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observableFinished = true
				end )

		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_observableTimeout_testNightly( nativeTest )

	local spy = testSpy( nativeTest )

	spy:expectWaitForTestToFinish( 60 )
	
	local nCallsToFinallyFunc = 0
	local observableFinished = false

	spy:runAfterTest( function()

		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )
		assert( not observableFinished, "Waited for Observable to finish" )

	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:setTimeLimit( 60 )

		rxTest:finally( function()

			nCallsToFinallyFunc = nCallsToFinallyFunc + 1

			return Observable.timer( 35, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observableFinished = true
				end )

		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_multipleObservableFunctions( nativeTest )

	local spy = testSpy( nativeTest )

	local observable1Finished = false
	local observable2Finished = false
	local observable3Finished = false
	
	spy:runAfterTest( function()

		assert( observable1Finished, "Didn't wait for Observable 1 to finish" )
		assert( observable2Finished, "Didn't wait for Observable 2 to finish" )
		assert( observable3Finished, "Didn't wait for Observable 3 to finish" )
		
	end )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:finally( function()

			return Observable.timer( .2, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable1Finished = true
				end )

		end )

		rxTest:finally( function()

			return Observable.timer( .4, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable2Finished = true
				end )

		end )
		
		rxTest:finally( function()

			return Observable.timer( .6, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable3Finished = true
				end )

		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_multipleObservablesOneFails( nativeTest )

	local spy = testSpy( nativeTest )

	local observable1Finished = false
	local observable2Finished = false
	local observable3Finished = false
	
	spy:runAfterTest( function()

		assert( observable1Finished, "Didn't wait for Observable 1 to finish" )
		assert( observable2Finished, "Didn't wait for Observable 2 to finish" )
		assert( observable3Finished, "Didn't wait for Observable 3 to finish" )

	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:finally( function()
		
			return Observable.timer( .2, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable1Finished = true
					error "expecting this error"
				end )

		end )

		rxTest:finally( function()

			return Observable.timer( .4, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable2Finished = true
				end )

		end )
		
		rxTest:finally( function()

			return Observable.timer( .6, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observable3Finished = true
				end )

		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_observablesFails( nativeTest )

	local spy = testSpy( nativeTest )

	local nCallsToFinallyFunc = 0
	local observableFinished = false

	spy:runAfterTest( function()

		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )
		assert( observableFinished, "Didn't wait for Observable to finish" )

	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:finally( function()

			nCallsToFinallyFunc = nCallsToFinallyFunc + 1

			return Observable.timer( .25, WFRxScheduler.sharedScheduler() )
				:tap( nil, nil, function()
					observableFinished = true
					error "expecting this error"
				end )

		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_afterTestFails( nativeTest )

	local spy = testSpy( nativeTest )

	local nCallsToFinallyFunc = 0

	spy:runAfterTest( function()
		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )
	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:finally( function()
			nCallsToFinallyFunc = nCallsToFinallyFunc + 1
		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 49 ) -- intentionally wrong to cause failure
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_multipleFunctions( nativeTest )

	local spy = testSpy( nativeTest )

	local finallyFuncCalls = {}

	spy:runAfterTest( function()
		assert( finallyFuncCalls[ 1 ] == 3, "third func not called first" )
		assert( finallyFuncCalls[ 2 ] == 2, "second func not called second" )
		assert( finallyFuncCalls[ 3 ] == 1, "first func not called last" )
	end )

	spy:expectTestToSucceed( function( rxTest )

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 1
		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 2
		end )

		o:expectData( 42 )
		o:expectCompleted()

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 3
		end )

	end )

end

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

function WFRxTestCaseTest.testFinally_finallyFnFails( nativeTest )

	local spy = testSpy( nativeTest )

	local nCallsToFinallyFunc = 0

	spy:runAfterTest( function()
		assert( nCallsToFinallyFunc > 0, "Didn't call finally func" )
		assert( nCallsToFinallyFunc < 2, "Called finally func more than once" )
	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:finally( function()
			nCallsToFinallyFunc = nCallsToFinallyFunc + 1
			error "expecting this error"
		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		o:expectData( 42 )
		o:expectCompleted()

	end )

end

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

function WFRxTestCaseTest.testFinally_multipleFinallyFnFailures( nativeTest )

	local spy = testSpy( nativeTest )

	local finallyFuncCalls = {}

	spy:runAfterTest( function()
		assert( finallyFuncCalls[ 1 ] == 3, "third func not called first" )
		assert( finallyFuncCalls[ 2 ] == 2, "second func not called second" )
		assert( finallyFuncCalls[ 3 ] == 1, "first func not called last" )
	end )

	spy:expectTestToFail( function( rxTest )

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 1
		end )

		local o = rxTest:observe( Observable.just( 42 ) )

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 2
			error "blah"
		end )

		o:expectData( 42 )
		o:expectCompleted()

		rxTest:finally( function()
			finallyFuncCalls[ #finallyFuncCalls + 1 ] = 3
			error "bork bork bork"
		end )

	end )

end

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

function WFRxTestCaseTest.rxTestGarbageCollection( rxTest )

	local s = Subject()

	local weakRefToS = setmetatable( { s = s }, { __mode = 'kv' } )

	rxTest:expectEqual( false, s:hasObservers() )

	repeat

		local os = rxTest:observe( s )

		s:onNext( "Hello, world" )
		os:expectData( "Hello, world" )

		rxTest:expectEqual( true, s:hasObservers() )

	until true -- let os fall out of scope

	s = nil

	collectgarbage()
	collectgarbage()
	collectgarbage()

	rxTest:expectEqual( true, weakRefToS.s == nil )

end

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

function WFRxTestCaseTest.rxTestSleep( rxTest )

	local start = KSDate.timeNowForAnimation()

	rxTest:sleep( 1 )

	local actualDelay = KSDate.timeNowForAnimation() - start

	if actualDelay < .9 then
		error( string.format( "Expected delay of at least 1 second, got %.2f seconds", actualDelay ) )
	end

end

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

function WFRxTestCaseTest.rxTestTestName( rxTest )

	rxTest:expectEqual( "rxTestTestName", rxTest:testName() )

end

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

function WFRxTestCaseTest.rxTestToString( rxTest )

	rxTest:expectEqual( "WFRxTestCaseRunner( \"rxTestToString\" )", tostring( rxTest ) )

end

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

function WFRxTestCaseTest.rxTestLog( rxTest )

	rxTest:log( "Does this appear in test log? It should." )
		-- Test logs are write-only from the perspective of Lua code, so
		-- there's no real way to verify that this works.

end

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

local count = 0
function WFRxTestCaseTest.rxTestRetry( rxTest )
	
	rxTest:setRetryLimit( 3 )
	
	count = count + 1
	rxTest:expectEqual( 2, count )
	
end

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

local anotherCount = 0

function WFRxTestCaseTest.rxTestRetryWithDelay( rxTest )

	rxTest:setTimeLimit( 3 )
	
	rxTest:setRetryLimit( 10, 0.1 )
	
	anotherCount = anotherCount + 1
	rxTest:expectEqual( 10, anotherCount )
	
end

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

local aNewCount = 0

function WFRxTestCaseTest.rxTestAutoRetry( rxTest )

	-- Verify automatic retry for standard tests.

	aNewCount = aNewCount + 1
	
	rxTest:expectEqual( 2, aNewCount )
	
end

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

local aNewCount2 = 0

function WFRxTestCaseTest.rxTestAutoRetry_testNightly( rxTest )

	-- Verify automatic retry for nightly tests.

	aNewCount2 = aNewCount2 + 1
	
	rxTest:expectEqual( 3, aNewCount2 )
	
end

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

function WFRxTestCaseTest.rxTestGetFirstValue( rxTest )

	local simpleObservable = Observable.just( 1 )
	
	local firstValue = rxTest:getFirstValue( simpleObservable )

	rxTest:expectEqual( 1, firstValue )

	local multipleDataEventObservable = Observable.from( { 1, 2, 3, 4, 5 } )
	
	firstValue = rxTest:getFirstValue( multipleDataEventObservable )

	rxTest:expectEqual( 1, firstValue )

	local emptyObservable = Observable.empty()
	
	firstValue = rxTest:getFirstValue( emptyObservable )

	rxTest:expectEqual( nil, firstValue )

end

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

function WFRxTestCaseTest.rxTestGetFirstValueUnpacked( rxTest )

	local simpleObservable = Observable.just( 1 )
	
	local firstValue = rxTest:getFirstValue( simpleObservable )

	rxTest:expectEqual( 1, firstValue )

	local multipleDataEventObservable = Rx.Observable.create( function( obs )
	
		obs:onNext( 1, 2)
		obs:onCompleted()
		end )
	
	local firstValue1, firstValue2 = rxTest:getFirstValue( multipleDataEventObservable )

	rxTest:expectEqual( 1, firstValue1 )
	
	rxTest:expectEqual( 2, firstValue2 )

end

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

function WFRxTestCaseTest.rxTestGetLastValue( rxTest )

	local simpleObservable = Observable.just( 1 )
	
	local firstValue = rxTest:getLastValue( simpleObservable )

	rxTest:expectEqual( 1, firstValue )

	local multipleDataEventObservable = Observable.from( { 1, 2, 3, 4, 5 } )
	
	firstValue = rxTest:getLastValue( multipleDataEventObservable )

	rxTest:expectEqual( 5, firstValue )

	local emptyObservable = Observable.empty()
	
	firstValue = rxTest:getLastValue( emptyObservable )

	rxTest:expectEqual( nil, firstValue )

end

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

return WFRxTestCase.wrapSuite( WFRxTestCaseTest )
