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

WFTableMergeTest.lua

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

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

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

	-- WF.core.reactive
local KSPromises = require 'KSPromises'

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

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local KSTestUtils = require 'KSTestUtils'
local WFTableMerge = require 'WFTableMerge'


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

local WFTableMergeTest = {}

-- TODO [scouten 2015-07-31]: Replace with WFRxTestCase?

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

-- @mark applyOverlayToTable

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

local function testApplyOverlay( testName )

	return function( args )

		local expectedOverlay = args.overlay and KSTableUtils.deepCopy( args.overlay )

		WFTableMergeTest[ 'testApplyOverlay_' .. testName ] =
			function( test )

				-- This is a bit hacky, but it lets us use WFAsyncTestCases's nice
				-- reporting of table results.

				WFAsyncTestCase( test )

					:waitFor( function()

						local base = KSTableUtils.deepCopy( args.base )

						local result, baseHasChanged = WFTableMerge.applyOverlay( args.base, args.overlay )

						local baseHasChangedExpected = not KSTableUtils.deepEquals( base, result )

						return KSPromises.resolvedPromiseP( 'data', result, baseHasChanged == baseHasChangedExpected )

					end )
					:continueWhenValueMatches( 'data', args.expected, true )

					:waitFor( function()

						return KSPromises.resolvedPromiseP( 'data', args.overlay )

					end )
					:continueWhenValueMatches( 'data', expectedOverlay )

			end

	end

end

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

testApplyOverlay "emptySource" {

	base = nil,

	overlay = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testApplyOverlay "emptyOverlay" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	overlay = nil,

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testApplyOverlay "overlayNil" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	overlay = {
		blah = 45,
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = 45,
	}

}

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

testApplyOverlay "nestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	overlay = {
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testApplyOverlay "nestedTableReplacingScalar" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = 95,
	},

	overlay = {
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testApplyOverlay "mergeNestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
		},
	},

	overlay = {
		blah = {
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testApplyOverlay "deletedNestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	overlay = {
		blah = {
			_delete = true,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testApplyOverlay "deleteScalar" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = "zip",
	},

	overlay = {
		blah = {
			_delete = true,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testApplyOverlay "deleteNothing" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	overlay = {
		blah = {
			_delete = true,
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testApplyOverlay "replaceExisting" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		nested = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	overlay = {
		nested = {
			_replace = true,
			a = 1,
			b = 2,
			[ 42 ] = 'c',
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		nested = {
			a = 1,
			b = 2,
			[ 42 ] = 'c',
		},
	},

}

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

testApplyOverlay "replaceNothing" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	overlay = {
		nested = {
			_replace = true,
			a = 1,
			b = 2,
			[ 42 ] = 'c',
		},
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		nested = {
			a = 1,
			b = 2,
			[ 42 ] = 'c',
		},
	},

}

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

function WFTableMergeTest.testApplyOverlay_errorDeleteAtTopLevel()

	KSTestUtils.assertFails( function()

		WFTableMerge.applyOverlay( {}, { _delete = true } )

	end )

end

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

function WFTableMergeTest.testApplyOverlay_errorSourceTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.applyOverlay( t, {} )

	end )

end

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

function WFTableMergeTest.testApplyOverlay_errorOverlayTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.applyOverlay( {}, t )

	end )

end

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

function WFTableMergeTest.testApplyOverlay_errorInfiniteRecursionInOverlay()

	KSTestUtils.assertFails( function()

		local overlay = {}
		overlay.loop = overlay

		WFTableMerge.applyOverlay( { blah = 42 }, overlay )

	end )

end

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

-- @mark createOverlay

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

local function testCreateOverlay( testName )

	return function( args )

		local originalBase = KSTableUtils.deepCopy( args.base )
		local originalModified = KSTableUtils.deepCopy( args.modified )
		local resultOverlay

		WFTableMergeTest[ 'testCreateOverlay_' .. testName ] =
			function( test )

				-- This is a bit hacky, but it lets us use WFAsyncTestCases's nice
				-- reporting of table results.

				WFAsyncTestCase( test )

					:waitFor( function()

						resultOverlay = WFTableMerge.createOverlay( args.base,
																	args.modified,
																	args.maxDepth )

						return KSPromises.resolvedPromiseP( 'data', resultOverlay )

					end )
					:continueWhenValueMatches( 'data', args.expected )

					:waitFor( function()

						return KSPromises.resolvedPromiseP( 'data', args.base )

					end )
					:continueWhenValueMatches( 'data', originalBase )

					:waitFor( function()

						return KSPromises.resolvedPromiseP( 'data', args.modified )

					end )
					:continueWhenValueMatches( 'data', originalModified )

					-- Now verify that we can get back to modified using applyOverlay.

					:waitFor( function()

						local reconstructedModified
							= WFTableMerge.applyOverlay( args.base, resultOverlay )

						return KSPromises.resolvedPromiseP( 'data', reconstructedModified )

					end )
					:continueWhenValueMatches( 'data', args.modified )

			end

	end

end

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

testCreateOverlay "noop" {

	base = {
		foo = 42,
		bar = 95,
	},

	modified = {
		foo = 42,
		bar = 95,
	},

	expected = {},

}

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

testCreateOverlay "simpleAddition" {

	base = {
		foo = 42,
	},

	modified = {
		foo = 42,
		bar = 95,
	},

	expected = {
		bar = 95,
	},

}

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

testCreateOverlay "simpleDeletion" {

	base = {
		foo = 42,
		bar = 95,
	},

	modified = {
		foo = 42,
	},

	expected = {
		bar = { _delete = true },
	},

}

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

testCreateOverlay "simpleReplacement" {

	base = {
		foo = 42,
		bar = 95,
	},

	modified = {
		foo = 42,
		bar = 92,
	},

	expected = {
		bar = 92,
	},

}

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

testCreateOverlay "emptyBase" {

	base = {},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	expected = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

}

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

testCreateOverlay "emptyModified" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	modified = {},

	expected = {
		_id = { _delete = true },
		_slot = { _delete = true },
		test = { _delete = true },
	},

}

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

testCreateOverlay "nestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testCreateOverlay "nestedTableReplacingScalar" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = 95,
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testCreateOverlay "mergeNestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	expected = {
		blah = {
			b = 2,
			[ "foo" ] = 42,
		},
	},

}

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

testCreateOverlay "deletedNestedTable" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
			[ "foo" ] = 42,
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	expected = {
		blah = {
			_delete = true,
		},
	},

}

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

function WFTableMergeTest.testCreateOverlay_errorBaseTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.createOverlay( t, {} )

	end )

end

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

function WFTableMergeTest.testCreateOverlay_errorModifiedTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.createOverlay( {}, t )

	end )

end

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

function WFTableMergeTest.testCreateOverlay_errorInvalidMaxDepth_negative()

	KSTestUtils.assertFails( function()

		local base = {}

		local modified = {}

		WFTableMerge.createOverlay( base, modified, 0 )

	end )

end

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

function WFTableMergeTest.testCreateOverlay_errorInvalidMaxDepth_tooLarge()

	KSTestUtils.assertFails( function()

		local base = {}

		local modified = {}

		WFTableMerge.createOverlay( base, modified, 101 )

	end )

end

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

function WFTableMergeTest.testCreateOverlay_errorExceedsMaxDepth()

	KSTestUtils.assertFails( function()

		local base = { a = { b = { 1, 2, 3 } } }

		local modified = { a = { 11, 22, 33 } }

		WFTableMerge.createOverlay( base, modified, 1 )

	end )

end

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

function WFTableMergeTest.testCreateOverlay_errorInfiniteRecursionInOverlay()

	KSTestUtils.assertFails( function()

		local base = {}
		base.loop = base

		local modified = {}
		modified.loop = modified

		WFTableMerge.createOverlay( base, modified )

	end )

end

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

-- @mark createDiffForOz

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

local function testCreateDiffForOz( testName )

	return function( args )

		WFTableMergeTest[ 'testCreateDiffForOz_' .. testName ] =
			function( test )

				WFAsyncTestCase( test )

					:waitFor( function()

						local resultDiff = WFTableMerge.createDiffForOz( args.base,
																	args.modified,
																	args.maxDepth )

						return KSPromises.resolvedPromiseP( 'data', resultDiff )

					end )
					:continueWhenValueMatches( 'data', args.expected )

			end

	end

end

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

testCreateDiffForOz "basic" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
			b = 2,
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = 1,
		},
	},

	expected = {
		blah = {
			b= { _delete = true, }
		},
	},

}

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

testCreateDiffForOz "noEmptyTable1" {
	
	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
		},
	},

	expected = {
		blah = { _delete = true, },
	},

}

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

testCreateDiffForOz "cascadeDeleteToParent" {
	
	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = { 
				b = 1,
			},
			c = {
				d = 2,
			}
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = {
			},
			c = {
			},
		},
	},

	expected = {
		blah = { _delete = true, },
	},

}

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

testCreateDiffForOz "cascadeDeleteToParent2" {
	
	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = { 
				b = 1,
			},
			c = {
				d = { e = 2 },
			}
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = {
			},
			c = {
				d = { }
			},
		},
	},

	expected = {
		blah = { _delete = true, },
	},

}

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

testCreateDiffForOz "noCascadeDeleteToParent" {
	
	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = { 
				b = 1,
			},
			c = {
				d = 2,
			}
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			a = {
			},
			c = {
				d = 2,
			},
		},
	},

	expected = {
		blah = {
			a = { _delete = true, },
		}
	},

}

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

testCreateDiffForOz "noPartialArrayOverwrite" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
			'b',
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
			'c',
		},
	},

	expected = {
		blah = {
			'a',
			'c',
		},
	},

}

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

testCreateDiffForOz "noPartialArrayAddition" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
			'b'
		},
	},

	expected = {
		blah = {
			'a',
			'b',
		},
	},

}

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

testCreateDiffForOz "noPartialArrayEqual" {

	base = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
			'b'
		},
	},

	modified = {
		_id = 'testDocId',
		_slot = 'main',
		test = 42,
		blah = {
			'a',
			'b'
		},
	},

	expected = {
	},

}
--------------------------------------------------------------------------------

-- @mark threeWayMerge

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

local function testThreeWayMerge( testName )

	return function( args )

		WFTableMergeTest[ 'testThreeWayMerge_' .. testName ] =
			function( test )

				-- This is a bit hacky, but it lets us use WFAsyncTestCases's nice
				-- reporting of table results.

				WFAsyncTestCase( test )

					:waitFor( function()

						local result, conflicts = WFTableMerge.threeWayMerge( args.base,
																			  args.a,
																			  args.b )

						result._conflict = conflicts

						return KSPromises.resolvedPromiseP( 'data', result )

					end )
					:continueWhenValueMatches( 'data', args.expected )

			end

	end

end

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

testThreeWayMerge "allIdentical" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "My Nifty Photo",
	},

	b = {
		favorite = true,
		title = "My Nifty Photo",
	},

	expected = {
		favorite = true,
		title = "My Nifty Photo",
	},

}

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

testThreeWayMerge "scalarConflictAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "Local New Title",
	},

	b = {
		favorite = true,
		title = "Server's New Title",
	},

	expected = {
		favorite = true,
		title = "Local New Title",
		_conflict = {
			title = "Server's New Title",
		},
	},

}

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

testThreeWayMerge "aEvolvedAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "Local New Title",
	},

	b = {
		favorite = true,
		title = "My Nifty Photo",
	},

	expected = {
		favorite = true,
		title = "Local New Title",
	},

}

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

testThreeWayMerge "bEvolvedAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "My Nifty Photo",
	},

	b = {
		favorite = true,
		title = "Server's New Title",
	},

	expected = {
		favorite = true,
		title = "Server's New Title",
	},

}

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

testThreeWayMerge "aEvolvedTrueToString" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = "2013-07-02T16:06:50+02:00",
		title = "My Nifty Photo",
	},

	b = {
		favorite = true,
		title = "My Nifty Photo",
	},

	expected = {
		favorite = "2013-07-02T16:06:50+02:00",
		title = "My Nifty Photo",
	},

}

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

testThreeWayMerge "bEvolvedAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "My Nifty Photo",
	},

	b = {
		favorite = "2013-07-02T16:07:10+02:00",
		title = "My Nifty Photo",
	},

	expected = {
		favorite = "2013-07-02T16:07:10+02:00",
		title = "My Nifty Photo",
	},

}

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

testThreeWayMerge "coevolvedAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "My New Title",
	},

	b = {
		favorite = true,
		title = "My New Title",
	},

	expected = {
		favorite = true,
		title = "My New Title",
	},

}

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

testThreeWayMerge "coevolvedFromEmpty" {

	base = {
		favorite = true,
	},

	a = {
		favorite = true,
		title = "My New Title",
	},

	b = {
		favorite = true,
		title = "My New Title",
	},

	expected = {
		favorite = true,
		title = "My New Title",
	},

}

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

testThreeWayMerge "conflictABecameTable" {

	base = {
		beer = 30,
		blah = 42,
	},

	a = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	b = {
		beer = 30,
		blah = 45,
	},

	expected = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
		_conflict = {
			blah = 45,
		},
	},

}

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

testThreeWayMerge "aNoLongerTable" {

	base = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	a = {
		beer = 30,
		blah = 45,
	},

	b = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	expected = {
		beer = 30,
		blah = 45,
	},

}

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

testThreeWayMerge "conflictBBecameTable" {

	base = {
		beer = 30,
		blah = 42,
	},

	a = {
		beer = 30,
		blah = 45,
	},

	b = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	expected = {
		beer = 30,
		blah = 45,
		_conflict = {
			blah = {
				_replace = true,
				woo = "hoo",
			},
		},
	},

}

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

testThreeWayMerge "bNoLongerTable" {

	base = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	a = {
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	b = {
		beer = 30,
		blah = 45,
	},

	expected = {
		beer = 30,
		blah = 45,
	},

}

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

testThreeWayMerge "conflictBBecameNil" {

	base = {
		beer = 30,
		blah = 42,
	},

	a = {
		beer = 30,
		blah = 45,
	},

	b = {
		beer = 30,
	},

	expected = {
		beer = 30,
		blah = 45,
		_conflict = {
			blah = {
				_delete = true,
			},
		},
	},

}

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

testThreeWayMerge "nestedTableNoop" {

	base = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	b = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

}

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

testThreeWayMerge "nestedTableAEvolved" {

	base = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			a = 71,
		},
	},

	b = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			a = 71,
		},
	},

}

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

testThreeWayMerge "nestedTableBEvolved" {

	base = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	b = {
		blah = 42,
		blop = {
			a = 71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			a = 71,
		},
	},

}

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

testThreeWayMerge "nestedTableConflict" {

	base = {
		blah = 42,
		blop = {
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			a = 68,
		},
	},

	b = {
		blah = 42,
		blop = {
			a = 71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			a = 68,
		},
		_conflict = {
			blop = {
				a = 71,
			},
		},
	},

}

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

testThreeWayMerge "nestedTableDeleteNoConflict" {

	base = {
		_changeCounter = 3,
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 1
			},
		},
		bar = {
			baz = 42
		},
		foo = 42
	},

	a = {
		_changeCounter = 3,
		_hash = '4',
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 1,
			},
		},
		bar = {
			baz = 42
		},
		foo = 42
	},

	b = {
		_changeCounter = 3,
		_deleted = true,
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 2,
			},
		},
	},

	expected = {
		_changeCounter = 3,
		_deleted = true,
		_hash = '4',
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 2,
			},
		},
	},

}

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

testThreeWayMerge "nestedTableDeleteWithConflict" {

	base = {
		_changeCounter = 3,
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 1
			},
		},
		bar = {
			baz = 42
		},
		foo = 42
	},

	a = {
		_changeCounter = 3,
		_hash = '4',
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 1,
			},
		},
		bar = {
			baz = 44
		},
		foo = 42
	},

	b = {
		_changeCounter = 3,
		_deleted = true,
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 2,
			},
		},
	},

	expected = {
		_changeCounter = 3,
		_deleted = true,
		_hash = '4',
		_id = 'testDocId',
		_slot = 'main',
		_syncStatus = {
			test = {
				timestamp = 2,
			},
		},
		bar = {
			baz = 44
		},
		_conflict = {
			bar = { _delete = true },
		},
	},


}

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

testThreeWayMerge "atomic_allIdentical" {

	base = {
		_merge = 'atomic',
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		favorite = true,
		title = "My Nifty Photo",
	},

	b = {
		favorite = true,
		title = "My Nifty Photo",
	},

	expected = {
		favorite = true,
		title = "My Nifty Photo",
	},

}

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

testThreeWayMerge "atomic_scalarConflictAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		_merge = 'atomic',
		favorite = true,
		title = "Local New Title",
	},

	b = {
		favorite = true,
		title = "Server's New Title",
	},

	expected = {
		_merge = 'atomic',
		favorite = true,
		title = "Local New Title",
		_conflict = {
			favorite = true,
			title = "Server's New Title",
		},
	},

}

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

testThreeWayMerge "atomic_aEvolvedAtTopLevel" {

	base = {
		_merge = 'atomic',
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		_merge = 'atomic',
		favorite = true,
		title = "Local New Title",
	},

	b = {
		_merge = 'atomic',
		favorite = true,
		title = "My Nifty Photo",
	},

	expected = {
		_merge = 'atomic',
		favorite = true,
		title = "Local New Title",
	},

}

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

testThreeWayMerge "atomic_bEvolvedAtTopLevel" {

	base = {
		_merge = 'atomic',
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		_merge = 'atomic',
		favorite = true,
		title = "My Nifty Photo",
	},

	b = {
		_merge = 'atomic',
		favorite = true,
		title = "Server's New Title",
	},

	expected = {
		_merge = 'atomic',
		favorite = true,
		title = "Server's New Title",
	},

}

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

testThreeWayMerge "atomic_coevolvedAtTopLevel" {

	base = {
		favorite = true,
		title = "My Nifty Photo",
	},

	a = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

	b = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

	expected = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

}

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

testThreeWayMerge "atomic_coevolvedFromEmpty" {

	base = {
		favorite = true,
	},

	a = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

	b = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

	expected = {
		_merge = 'atomic',
		favorite = true,
		title = "My New Title",
	},

}

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

testThreeWayMerge "atomic_conflictABecameTable" {

	base = {
		_merge = 'atomic',
		beer = 30,
		blah = 42,
	},

	a = {
		_merge = 'atomic',
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	b = {
		_merge = 'atomic',
		beer = 30,
		blah = 45,
	},

	expected = {
		_merge = 'atomic',
		beer = 30,
		blah = {
			woo = "hoo",
		},
		_conflict = {
			_merge = 'atomic',
			beer = 30,
			blah = 45,
		},
	},

}

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

testThreeWayMerge "atomic_conflictBBecameTable" {

	base = {
		_merge = 'atomic',
		beer = 30,
		blah = 42,
	},

	a = {
		_merge = 'atomic',
		beer = 30,
		blah = 45,
	},

	b = {
		_merge = 'atomic',
		beer = 30,
		blah = {
			woo = "hoo",
		},
	},

	expected = {
		_merge = 'atomic',
		beer = 30,
		blah = 45,
		_conflict = {
			_merge = 'atomic',
			beer = 30,
			blah = {
				woo = "hoo",
			},
		},
	},

}

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

testThreeWayMerge "atomic_conflictBBecameNil" {

	base = {
		_merge = 'atomic',
		beer = 30,
		blah = 42,
	},

	a = {
		_merge = 'atomic',
		beer = 30,
		blah = 45,
	},

	b = {
		_merge = 'atomic',
		beer = 30,
	},

	expected = {
		_merge = 'atomic',
		beer = 30,
		blah = 45,
		_conflict = {
			_merge = 'atomic',
			beer = 30,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableNoop" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableAEvolved" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableAEvolvedFromTableToScalar" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = { blah = 59 },
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = { blah = 59 },
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableBEvolved" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableBEvolvedFromTableToScalar" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = { blah = 59 },
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = { blah = 59 },
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

}

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

testThreeWayMerge "atomic_nestedTableConflict" {

	base = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 59,
		},
	},

	a = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 68,
		},
	},

	b = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			_merge = 'atomic',
			a = 68,
		},
		_conflict = {
			blop = {
				_merge = 'atomic',
				a = 71,
			},
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_allIdentical" {

	base = {
		favorite = true,
		"My Nifty Photo",
	},

	a = {
		favorite = true,
		"My Nifty Photo",
	},

	b = {
		favorite = true,
		"My Nifty Photo",
	},

	expected = {
		favorite = true,
		"My Nifty Photo",
	},

}

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

testThreeWayMerge "atomicImpliedByArray_scalarConflictAtTopLevel" {

	base = {
		favorite = true,
		"My Nifty Photo",
	},

	a = {
		favorite = true,
		"Local New Title",
	},

	b = {
		favorite = true,
		"Server's New Title",
	},

	expected = {
		favorite = true,
		"Local New Title",
		_conflict = {
			favorite = true,
			"Server's New Title",
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_aEvolvedAtTopLevel" {

	base = {
		favorite = true,
		"My Nifty Photo",
	},

	a = {
		favorite = true,
		"Local New Title",
	},

	b = {
		favorite = true,
		"My Nifty Photo",
	},

	expected = {
		favorite = true,
		"Local New Title",
	},

}

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

testThreeWayMerge "atomicImpliedByArray_bEvolvedAtTopLevel" {

	base = {
		favorite = true,
		"My Nifty Photo",
	},

	a = {
		favorite = true,
		"My Nifty Photo",
	},

	b = {
		favorite = true,
		"Server's New Title",
	},

	expected = {
		favorite = true,
		"Server's New Title",
	},

}

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

testThreeWayMerge "atomicImpliedByArray_coevolvedAtTopLevel" {

	base = {
		favorite = true,
		"My Nifty Photo",
	},

	a = {
		favorite = true,
		"My New Title",
	},

	b = {
		favorite = true,
		"My New Title",
	},

	expected = {
		favorite = true,
		"My New Title",
	},

}

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

testThreeWayMerge "atomicImpliedByArray_coevolvedFromEmpty" {

	base = {
		favorite = true,
	},

	a = {
		favorite = true,
		"My New Title",
	},

	b = {
		favorite = true,
		"My New Title",
	},

	expected = {
		favorite = true,
		"My New Title",
	},

}

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

testThreeWayMerge "atomicImpliedByArray_conflictABecameTable" {

	base = {
		beer = 30,
		42,
	},

	a = {
		beer = 30,
		{
			woo = "hoo",
		},
	},

	b = {
		beer = 30,
		45,
	},

	expected = {
		beer = 30,
		{
			woo = "hoo",
		},
		_conflict = {
			beer = 30,
			45,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_conflictBBecameTable" {

	base = {
		beer = 30,
		42,
	},

	a = {
		beer = 30,
		45,
	},

	b = {
		beer = 30,
		{
			woo = "hoo",
		},
	},

	expected = {
		beer = 30,
		45,
		_conflict = {
			beer = 30,
			{
				woo = "hoo",
			},
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_conflictBBecameNil" {

	base = {
		beer = 30,
		42,
	},

	a = {
		beer = 30,
		45,
	},

	b = {
		beer = 30,
	},

	expected = {
		beer = 30,
		45,
		_conflict = {
			beer = 30,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableNoop" {

	base = {
		blah = 42,
		blop = {
			59,
		},
	},

	a = {
		blah = 42,
		blop = {
			59,
		},
	},

	b = {
		blah = 42,
		blop = {
			59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			59,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableAEvolved" {

	base = {
		blah = 42,
		blop = {
			59,
		},
	},

	a = {
		blah = 42,
		blop = {
			71,
		},
	},

	b = {
		blah = 42,
		blop = {
			59,
		},
	},

	expected = {
		blah = 42,
		blop = {
			71,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableAEvolvedFromTableToScalar" {

	base = {
		blah = 42,
		blop = {
			{ blah = 59 },
		},
	},

	a = {
		blah = 42,
		blop = {
			71,
		},
	},

	b = {
		blah = 42,
		blop = {
			{ blah = 59 },
		},
	},

	expected = {
		blah = 42,
		blop = {
			71,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableBEvolved" {

	base = {
		blah = 42,
		blop = {
			59,
		},
	},

	a = {
		blah = 42,
		blop = {
			59,
		},
	},

	b = {
		blah = 42,
		blop = {
			71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			71,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableBEvolvedFromTableToScalar" {

	base = {
		blah = 42,
		blop = {
			{ blah = 59 },
		},
	},

	a = {
		blah = 42,
		blop = {
			{ blah = 59 },
		},
	},

	b = {
		blah = 42,
		blop = {
			71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			71,
		},
	},

}

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

testThreeWayMerge "atomicImpliedByArray_nestedTableConflict" {

	base = {
		blah = 42,
		blop = {
			59,
		},
	},

	a = {
		blah = 42,
		blop = {
			68,
		},
	},

	b = {
		blah = 42,
		blop = {
			71,
		},
	},

	expected = {
		blah = 42,
		blop = {
			68,
		},
		_conflict = {
			blop = {
				71,
			},
		},
	},

}

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

function WFTableMergeTest.testThreeWayMerge_errorBaseTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.threeWayMerge( t, {}, {} )

	end )

end

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

function WFTableMergeTest.testThreeWayMerge_errorATableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.threeWayMerge( {}, t, {} )

	end )

end

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

function WFTableMergeTest.testThreeWayMerge_errorBTableHasMetatable()

	KSTestUtils.assertFails( function()

		local t = setmetatable( {}, {} )

		WFTableMerge.threeWayMerge( {}, {}, t )

	end )

end

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

return WFTableMergeTest
