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

WFCatFormattedMetadataTest.lua

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

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

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
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'

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

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

	-- WF.managed_catalog
local WFCatFormattedMetadata = require 'WFCatFormattedMetadata'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'

local formatField = WFCatFormattedMetadata.formatField
local getOverlay = WFCatFormattedMetadata.getOverlay
local ts = tostring

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

local kUserId = string.rep( 'abcd', 8 )
local kXmpModifyDate = "2010-09-08T07:06:54"
local kExifDateTimeOriginal = "2011-11-11T11:11:11"
local kExifDateDigitized = "2012-12-12T12:12:12"
local kFakeToday = "2014-01-13T20:45:44"
local kWidth = 3000
local kCroppedWidth = 2250
local kHeight = 2000
local kCroppedHeight = 1500
local kTiffArtist = 'Pablano Pickalso'
local kTiffMake = 'Imaginary Photocorp Corporation'
local kTiffModel = 'Imaginary Camera Model'
local kAuxSerialNumber = '12233344455555666666'
local kAuxLens = 'Binford 6100 Series f/1.2'
local kExifExposureProgram = 'My Exposure Pgm'
local kExifMeteringMode = 'My Metering Mode'
local kExifFlashFired = true
local kExifFocalLength = { 50, 1 }
local kExifFocalLengthIn35mmFilm = { 38, 1 }
local kExifFocalPlaneXResolution = 125
local kExifFocalPlaneYResolution = 135
local kExifFocalPlaneResolutionUnit = 'in'
local kExifGPSImgDirection = 120
local kExifShutterSpeed = 6
local kExifFNumber = { 14, 10 }
local kExifISOSpeedRatings = 800
local kExifApertureValue = 8
local kExifMaxApertureValue = .97085
local kExifExposureTime = { 1, 64 }
local kExposureBias = { 1, 3 }
local kExifBrightnessValue = { 6, 2 }
local kExifSubjectDistance = 21
local kExifUserComment = 'My user Comment'
local kAuxApproximateFocusDistance = 31
local kXmpCreatorTool = 'Adobe Unit Test'
local kDngVersion = 'DNG version 1.2.3.4'
local kDngBackwardVersion = 'DNG version 0.1.2.3'
local kDngLightroomCompatibility = '7.6.5'
local kDngOriginalRawFilename = "_CR_0001.CR2"
local kSeattleLatitude = '47.6495051'
local kSeattleLongitude = '-122.3494251'

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

local fakeWFCatalogClass = Rx.Class 'WFCatalog' {}

local mcInfo = fakeWFCatalogClass()

rawset( mcInfo, 'ozUserId', function() return kUserId end )
rawset( mcInfo, 'changeDate', kFakeToday )

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

local options1 = {

}

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

local doc1 = {

	develop = {
		croppedWidth = kCroppedWidth,
		croppedHeight = kCroppedHeight,
		userOrientation = 2,
	},
	
	location = {
		altitude = 1000,
		latitude = kSeattleLatitude,
		longitude = kSeattleLongitude,
		imgDirection = kExifGPSImgDirection,
	},

	importSource = {
		originalWidth = kWidth,
		originalHeight = kHeight,
	},

	ratings = {
		[ kUserId ] = {
			date = kFakeToday,
			rating = 1,
		},
	},

	reviews = {
		[ kUserId ] = {
			date = kFakeToday,
			flag = 'pick',
		},
	},

	xmp = {

		aux = {
			ApproximateFocusDistance = kAuxApproximateFocusDistance,
			Lens = kAuxLens,
			SerialNumber = kAuxSerialNumber,
			EnhanceDetailsAlreadyApplied = true
		},

		dm = {
			good = false,
		},

		dng = {
			BitsPerSample = 8,
			DNGVersion = kDngVersion,
			DNGBackwardVersion = kDngBackwardVersion,
			LightroomCompatibility = kDngLightroomCompatibility,
			HasEmbeddedOriginalRawFile = false,
			HasFastLoadData = true,
			HasMosaicData = true,
			HasTransparency = false,
			LossyCompression = false,
			FloatingPoint = false,
			OriginalRawFileName = kDngOriginalRawFilename,
			PreviewWidth = 1500,
			PreviewLength = 1000,
			ImageWidth = 3001,
			ImageLength = 2001,
			OriginalImageWidth = 3002,
			OriginalImageLength = 2002,
		},

		exif = {
			ApertureValue = kExifApertureValue,
			BrightnessValue = kExifBrightnessValue,
			DateTimeDigitized = kExifDateDigitized,
			DateTimeOriginal = kExifDateTimeOriginal,
			ExposureBiasValue = kExposureBias,
			ExposureProgram = kExifExposureProgram,
			ExposureTime = kExifExposureTime,
			FNumber = kExifFNumber,
			FlashFired = kExifFlashFired,
			FocalLength = kExifFocalLength,
			FocalLengthIn35mmFilm = kExifFocalLengthIn35mmFilm,
			FocalPlaneResolutionUnit = kExifFocalPlaneResolutionUnit,
			FocalPlaneXResolution = kExifFocalPlaneXResolution,
			FocalPlaneYResolution = kExifFocalPlaneYResolution,
			ISOSpeedRatings = kExifISOSpeedRatings,
			MaxApertureValue = kExifMaxApertureValue,
			MeteringMode = kExifMeteringMode,
			ShutterSpeedValue = kExifShutterSpeed,
			SubjectDistance = kExifSubjectDistance,
			UserComment = kExifUserComment,
		},

		tiff = {
			Artist = kTiffArtist,
			Make = kTiffMake,
			Model = kTiffModel,
		},

		xmp = {
			Label = "blue",
			ModifyDate = kXmpModifyDate,
			CreatorTool = kXmpCreatorTool,
		}
	},
}

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

-- IPTC

local kCreator = 'Damien Assante'
local kTitle = 'This is the title'
local kHeadline = 'Extra, Extra! Read all about it.'
local kCaptionWriter = 'Pat Anderson'
local kCategory = 'Breaking News'
local kCity = 'Seattle'
local kState = 'Washington'
local kCountry = 'United States'
local kTransmissionReference = 'Developer'
local kInstructions = 'Ikea'
local kCredit = 'Where credit is due'
local kSource = 'Open'
local kLocation = 'Pale Blue Dot'
local kCountryCode = 'US'
local kUsageTerms = 'None'
local kWebStatement = 'http://www.google.com'
local kDateCreated = "2005-09-08T07:06:54"

-- Metadata
local kMetadataDate = "2005-09-08T07:06:55"
local kKeywords = {}
kKeywords[ 'Soup' ] = true
kKeywords[ 'Hot' ] = true
kKeywords[ 'Lava-hot' ] = true
kKeywords[ 'Ouch' ] = true
kKeywords[ 'Ice' ] = true
kKeywords[ 'please!' ] = true
local kCaption = "This soup is too hot!"
local kCopyright = "Charlie Chowder"
			
-- File
local kFileName = 'hotsoup.kdc'
local kFilePath = '/this/is/where/it/lives/hotsoup.kdc'
local kFileSize = 200
local kBaseName = 'hotsoup'
local kFileNameExtension = 'KDC'

-- Video
local kFrameRate = { 60, 1 }
local kDuration = { 240, 60 }
local kTrackNumber = 5
local kTapeName = 'The Tape Name'
local kSpeakerPlacement = {
	Left = -30,
	Right = 30,
	Center = 0, 
	LeftSurround = -110,
	RightSurround = 110,
}
local kShotName = 'Man Eats Soup That Is Too Hot'
local kShotLocation = 'Souper Restaurant'
local kShotDay = "2005-09-08T07:06:56"
local kShotDate = "2005-09-08T07:06:57"
local kReleaseDate = "2006-09-08T07:06:01"
local kProjectName = 'Soup: Yes We Can!'
local kLogComment = 'We should have used a creamier soup.'
local kInstrument = 'Tuba'
local kGood = true
local kGenre = 'Soup-based Comedy'
local kEngineer = 'Charlie Chowder'
local kDm_Scene = 'Restaurant Mishap'
local kDm_Artist = 'Gilbert Gumbo'
local kDirectorPhotography = 'Brandon Bisque'
local kDirector = 'Caitlyn Consomme'
local kComposer = 'Grace Gazpacho'
local kComment = 'I laughed, I cried, I had a craving for soup.'
local kClient = 'Jeff Andrew'	
local kCameraMove = 2
local kCameraAngle = 4
local kCameraModel = 'Sony FDRAX33 4K Handycam'
local kAltTapeName = 'The Alternate Tape Name'
local kAlbum = 'One Spoon Too Few'
local kVideoAlphaMode = 'pre-multiplied'
local kAudioSampleType = '32Float'
local kAudioSampleRate = 41100
local kAudioChannelType = '7.1'
local kClips = {}
kClips[ 1 ] = {} 
kClips[ 1 ][ 'start' ] = { 1000, 60 }
kClips[ 1 ][ 'end' ] = { 2000, 60 }
kClips[ 2 ] = {} 
kClips[ 2 ][ 'start' ] = { 957, 60 }
kClips[ 2 ][ 'end' ] = { 15420, 60 }
local kStorageWidth = 1920
local kStorageHeight = 1080
local kDisplayAspectRatio = { 16, 9 }

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

local doc2 = {
	
	_serverProvidedMetadata = {
		updated = kMetadataDate,
	},
	
	video = {
		audioChannelType = kAudioChannelType,
		audioSampleRate = kAudioSampleRate,
		audioSampleType = kAudioSampleType,
		videoAlphaMode = kVideoAlphaMode,
		displayAspectRatio = kDisplayAspectRatio,
		storageWidth = kStorageWidth,
		storageHeight = kStorageHeight,
		duration = kDuration,
		frameRate = kFrameRate,
		clips = kClips,
	},
	
	importSource = {
		fileName = kFileName,
		filePath = kFilePath,
		fileSize = kFileSize,
		originalWidth = kWidth,
		originalHeight = kHeight,
	},
	
	location = {
		latitude = -100,
		longitude = 20,
		isoCountryCode = 'should have been overridden',
		city = 'should have been overridden',
		state = 'should have been overridden',
		country = 'should have been overridden',
	},
	
	xmp = {

		aux = {
			ApproximateFocusDistance = 0,
			SerialNumber = 'doc2 serial number',
		},

		exif = {
			ISOSpeedRatings = 400,
			BrightnessValue = "2/3",
			ExposureBiasValue = { 1, 0 },
		},
		
		tiff = {
			Artist = 'Ben Wyatt',
			Make = 'JAZZ CORP',
			Model = 'Jazz Super Camera x99',
		},
		
		dc = {
			creator = kCreator,
			description = kCaption,
			title = kTitle,
			rights = kCopyright,
			subject = kKeywords,
		},

		dm = {
			album = kAlbum,
			altTapeName = kAltTapeName,
			cameraModel = kCameraModel,
			cameraAngle = kCameraAngle,
			cameraMove = kCameraMove,
			client = kClient,
			comment = kComment,
			composer = kComposer,
			director = kDirector,
			directorPhotography = kDirectorPhotography,
			artist = kDm_Artist,
			scene = kDm_Scene,
			engineer = kEngineer,
			genre = kGenre,
			good = kGood,
			instrument = kInstrument,
			logComment = kLogComment,
			projectName = kProjectName,
			releaseDate = kReleaseDate,
			shotDate = kShotDate,
			shotDay = kShotDay,
			shotLocation = kShotLocation,
			shotName = kShotName,
			speakerPlacement = kSpeakerPlacement,
			tapeName = kTapeName,
			trackNumber = kTrackNumber,
		},

		photoshop = {
			Headline = kHeadline,
			CaptionWriter = kCaptionWriter,
			Category = kCategory,
			City = kCity,
			State = kState,
			Country = kCountry,
			TransmissionReference = kTransmissionReference,
			Instructions = kInstructions,
			Credit = kCredit,
			Source = kSource,
			DateCreated = kDateCreated,
		},
		
		iptcCore = {
			Location = kLocation,
			CountryCode = kCountryCode,
		},
		
		xmpRights = {
			UsageTerms = kUsageTerms,
			WebStatement = kWebStatement
		}
		
	},
}

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

local doc3 = {

	reviews = {
		[ kUserId ] = {
			date = kFakeToday,
			flag = 'reject',
		},
	},

	importSource = {
		fileName = kBaseName,
	},

	xmp = {
	
		aux = {
			ApproximateFocusDistance = 4294967295, -- FFFFFFFF.H ( Infinity )
			Lens = "Doc3 Lens",
			SerialNumber = 'doc3 serial number',
		},

		xmp = {
			Label = "",
			ModifyDate = kXmpModifyDate,
		},

		dng = {

			FloatingPoint = true,

		},

		exif = {
			DateTimeOriginal = kXmpModifyDate,
			DateTimeDigitized = kXmpModifyDate,
			ExposureBiasValue = { 0, 12 },
			FlashFired = false,
			BrightnessValue = "string", --Invalid brightness value
		},
	},
}

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

local doc4 = {
	
	_serverProvidedMetadata = {
		updated = kMetadataDate,
	},
	
	video = {
		audioChannelType = kAudioChannelType,
		audioSampleRate = kAudioSampleRate,
		audioSampleType = kAudioSampleType,
		videoAlphaMode = kVideoAlphaMode,
		displayAspectRatio = kDisplayAspectRatio,
		storageWidth = kStorageWidth,
		storageHeight = kStorageHeight,
		duration = kDuration,
		frameRate = kFrameRate,
		clips = kClips,
	},
	
	xmp = {
		dc = {
			subject = kKeywords,
		}
	},
	
	importSource = {
		fileName = kFileName,
		filePath = kFilePath,
		fileSize = kFileSize,
		originalWidth = kWidth,
		originalHeight = kHeight,
	},
	
	location = {
		latitude = -100,
		longitude = 20,
		isoCountryCode = kCountryCode,
		city = kCity,
		state = kState,
		country = kCountry,
	},
	
}

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

local WFCatFormattedMetadataTest = {}

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

local function testDoc( rxTest, name, doc, formatterId, expectedValue, optOptions )

	local formatted = formatField( mcInfo, optOptions or options1, doc, formatterId )
	rxTest:expectEqual( expectedValue, formatted )

end

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

local function testDoc1( rxTest, ... )
	return testDoc( rxTest, 'doc1', doc1, ... )
end

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

local function testDoc2( rxTest, ... )
	return testDoc( rxTest, 'doc2', doc2, ... )
end

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

local function testDoc3( rxTest, ... )
	return testDoc( rxTest, 'doc3', doc3, ... )
end

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

local function testDoc4( rxTest, ... )
	return testDoc( rxTest, 'doc4', doc4, ... )
end

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

local function spack( ... )
	return { n = select( '#', ... ), ... }
end

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

function WFCatFormattedMetadataTest.rxTestAttributeFormatting( rxTest )

	testDoc1( rxTest, 'com.adobe.rating', spack( 'rating', 1 ) )
	testDoc2( rxTest, 'com.adobe.rating', { 'unused' } )

	testDoc1( rxTest, 'com.adobe.ratingAndLabel', spack( 'literal', "blue / 1" ) )
	testDoc2( rxTest, 'com.adobe.ratingAndLabel', spack( 'literal', "- / -" ) )

	testDoc1( rxTest, 'com.adobe.compositeAttributes', spack(
		'ZSTR',
		"$$$/AgLibrary/MetadataFormatters/Attributes/Display=^1  ^U+00B7  ^2  ^U+00B7  ^3^4",
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Pick/Flagged=Flagged" },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/Rating/Labeled=Rating ^1", 1 },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/QuotedLabel=^1", 'blue' },
		"" ) )

	testDoc2( rxTest, 'com.adobe.compositeAttributes', spack(
		'ZSTR',
		"$$$/AgLibrary/MetadataFormatters/Attributes/Display=^1  ^U+00B7  ^2  ^U+00B7  ^3^4",
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Pick/Unflagged=Unflagged" },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/Rating/None=No Rating" },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/Label/None=No Label" },
		"" ) )

	testDoc3( rxTest, 'com.adobe.compositeAttributes', spack(
		'ZSTR',
		"$$$/AgLibrary/MetadataFormatters/Attributes/Display=^1  ^U+00B7  ^2  ^U+00B7  ^3^4",
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Pick/Rejected=Rejected" },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/Rating/None=No Rating" },
		{ 'ZSTR', "$$$/AgLibrary/MetadataFormatters/Attributes/Label/None=No Label" },
		"" ) )

	testDoc1( rxTest, 'com.adobe.rating.string', spack( 'literal', '1' ) )
	testDoc2( rxTest, 'com.adobe.rating.string', { 'unused' } )

	testDoc1( rxTest, 'com.adobe.colorLabels', spack( 'literal', 'blue' ) )
	testDoc2( rxTest, 'com.adobe.colorLabels', { 'unused' } )

	testDoc1( rxTest, 'com.adobe.colorLabels.string', spack( 'literal', 'blue' ) )
	testDoc2( rxTest, 'com.adobe.colorLabels.string', { 'unused' } )

end

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

function WFCatFormattedMetadataTest.rxTestDateFallbackFormatting( rxTest )

	-- Some format ids that have extensive searching behavior,
	-- falling back to numerous different metadata fields in order
	-- to find something to display.

	local dateTimeFallbacks = {
		[ 'com.adobe.captureTime' ] = 'mediumTime',
		[ 'com.adobe.captureDate' ] = 'mediumDate',
		[ 'com.adobe.captureDateTime' ] = 'mediumDateTime',
	}

	for formatter, dateFormat in pairs( dateTimeFallbacks ) do

		local doc = {
			captureDate = 1,
			-- originalCaptureDate = nil,
				-- ?? Should this be consulted
				-- You might think so... but it should only
				-- be present if an asset's capture date has
				-- been corrected. In that case, we know it is
				-- *wrong*. Therefore we not consult it.
			xmp = {
				exif = {
					DateTimeOriginal = 2,
					DateTimeDigitized = 4,
				},
				photoshop = {
					DateCreated = 3,
				},
				xmp = {
					CreateDate = 5,
					ModifyDate = 6,
				},
			},
		}

		for i = 1, 6 do
			testDoc( rxTest, formatter .. tostring( i ), doc, formatter, spack( dateFormat, i ) )
			local queue = { doc }

			-- Search thru the document for the pseudo-date that matches
			-- the current value of i and remove it. That way, the
			-- next time we evaluate the formatter the result will
			-- still match i (with its new value)

			-- This lets us assert that com.adobe.captureDate and ilk
			-- evaluate in the order specified in our doc above.

			while queue[ 1 ] do
				local t = table.remove( queue, 1 )
				for k, v in pairs( t ) do
					if type( v ) == 'table' then
						queue[ #queue + 1 ] = v
					elseif v == i then
						t[ k ] = nil
					end
				end
			end
		end

		-- Ensure that once all the dates are gone from the document
		-- we don't get any more dates returned.

		testDoc( rxTest, formatter .. " none", doc, formatter, { 'unused' } )
	end

end

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

function WFCatFormattedMetadataTest.rxTestDateFormatting( rxTest )

	local doc2_dateCreated_old = doc2.xmp.photoshop.DateCreated
	doc2.xmp.photoshop.DateCreated = nil

	testDoc1( rxTest, 'com.adobe.dateTime', spack( 'mediumDateTime', kXmpModifyDate ) )
	testDoc2( rxTest, 'com.adobe.dateTime', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTime', spack( 'mediumDateTime', kXmpModifyDate ) )

	testDoc1( rxTest, 'com.adobe.dateTime.onlyIfUnique', spack( 'mediumDateTime', kXmpModifyDate ) )
	testDoc2( rxTest, 'com.adobe.dateTime.onlyIfUnique', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTime.onlyIfUnique', { 'unused' } )

	testDoc1( rxTest, 'com.adobe.dateTime.dateOnly', spack( 'mediumDate', kXmpModifyDate ) )
	testDoc2( rxTest, 'com.adobe.dateTime.dateOnly', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTime.dateOnly', spack( 'mediumDate', kXmpModifyDate ) )

	testDoc1( rxTest, 'com.adobe.dateTime.timeOnly', spack( 'mediumTime', kXmpModifyDate ) )
	testDoc2( rxTest, 'com.adobe.dateTime.timeOnly', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTime.timeOnly', spack( 'mediumTime', kXmpModifyDate ) )

	testDoc1( rxTest, 'com.adobe.captureDateTime', spack( 'mediumDateTime', kExifDateTimeOriginal ) )
	testDoc2( rxTest, 'com.adobe.captureDateTime', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.captureDateTime', spack( 'mediumDateTime', kXmpModifyDate ) )

	testDoc1( rxTest, 'com.adobe.dateTimeOriginal', spack( 'mediumDateTime', kExifDateTimeOriginal ) )
	testDoc2( rxTest, 'com.adobe.dateTimeOriginal', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTimeOriginal', spack( 'mediumDateTime', kXmpModifyDate ) )

	testDoc1( rxTest, 'com.adobe.dateTimeDigitized', spack( 'mediumDateTime', kExifDateDigitized ) )
	testDoc2( rxTest, 'com.adobe.dateTimeDigitized', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTimeDigitized', spack( 'mediumDateTime', kXmpModifyDate ) )

	local p =  { pruneRedundantFields = true }
	testDoc1( rxTest, 'com.adobe.dateTimeDigitized', spack( 'mediumDateTime', kExifDateDigitized ), p )
	testDoc2( rxTest, 'com.adobe.dateTimeDigitized', { 'unused' }, p )
	testDoc3( rxTest, 'com.adobe.dateTimeDigitized', { 'unused' }, p )

	testDoc1( rxTest, 'com.adobe.dateTimeOriginal.onlyIfUnique', spack( 'mediumDateTime', kExifDateTimeOriginal ) )
	testDoc2( rxTest, 'com.adobe.dateTimeOriginal.onlyIfUnique', { 'unused' } )
	testDoc3( rxTest, 'com.adobe.dateTimeOriginal.onlyIfUnique', { 'unused' } )

	doc2.xmp.photoshop.DateCreated = doc2_dateCreated_old

end

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

function WFCatFormattedMetadataTest.rxTestDimensionFormatting( rxTest )

	testDoc1( rxTest, 'com.adobe.combinedImageFileDimensions',
		spack( 'ZSTR', "$$$/AgLibrary/MetadataFormatters/combinedImageFileDimensions=^1 cropped to ^2",
			{ 'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", ts( kWidth ), ts( kHeight ) },
			{ 'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", ts( kCroppedWidth ), ts( kCroppedHeight ) } ) )
	testDoc2( rxTest, 'com.adobe.combinedImageFileDimensions',
		spack( 'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", ts( kWidth ), ts( kHeight ) ) )

	testDoc1( rxTest, 'com.adobe.megapixels', spack(
			'ZSTR',
			"$$$/AgLibrary/MetadataFormatters/MegapixelsTemplate=^1 MP",
			{ 'number', 6, 1 }
		) )

	testDoc1( rxTest, 'com.adobe.imageFileDimensions', spack(
			'ZSTR',
			"$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2",
			ts( kWidth ),
			ts( kHeight )
	) )

	testDoc1( rxTest, 'com.adobe.imageCroppedDimensions', spack(
			'ZSTR',
			"$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2",
			ts( kCroppedWidth ), ts( kCroppedHeight )
		) )

	testDoc2( rxTest, 'com.adobe.imageCroppedDimensions', spack(
			'ZSTR',
			"$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2",
			ts( kWidth ), ts( kHeight )
		) )

	local p =  { pruneRedundantFields = true }

	testDoc1( rxTest, 'com.adobe.imageCroppedDimensions', spack(
			'ZSTR',
			"$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2",
			ts( kCroppedWidth ), ts( kCroppedHeight )
		), p )

	testDoc2( rxTest, 'com.adobe.imageCroppedDimensions', { 'unused' }, p )

	testDoc(
		rxTest,
		'video',
		{ _serverProvidedMetadata = { subtype = 'video' } },
		'com.adobe.imageCroppedDimensions',
		{ 'unused' },
		p )

end

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

function WFCatFormattedMetadataTest.rxTestExifFormatting( rxTest )

	testDoc1( rxTest, 'com.adobe.make', spack( 'literal', kTiffMake ) )
	testDoc1( rxTest, 'com.adobe.model', spack( 'literal', kTiffModel ) )
	testDoc1( rxTest, 'com.adobe.combinedCameraName', spack(
		'ZSTR', "$$$/AgLibrary/Exif/CameraTempl/ModelSer=^1 #^2",
		"Imaginary Photocorp Imaginary Camera Model",
		kAuxSerialNumber
	) )
	testDoc2( rxTest, 'com.adobe.combinedCameraName', spack(
		'ZSTR', "$$$/AgLibrary/Exif/CameraTempl/ModelSer=^1 #^2",
		"Jazz Super Camera x99",
		"doc2 serial number"
	) )
	testDoc3( rxTest, 'com.adobe.combinedCameraName', spack(
		'literal', "doc3 serial number"
	) )
	testDoc( rxTest,
		'com.adobe.combinedCameraName + coverage',
		{ xmp = { tiff = { Make = kTiffMake, Model = kTiffModel, } } },
		'com.adobe.combinedCameraName',
		spack( 'literal', "Imaginary Photocorp Imaginary Camera Model" ) )
	testDoc1( rxTest, 'com.adobe.combinedCameraLens', spack(
		'ZSTR',
		"$$$/AgLibrary/Exif/CameraTempl/MakeOrModelLensTempl=^1 + ^2",
		"Imaginary Photocorp Imaginary Camera Model",
		kAuxLens
	) )
	testDoc3( rxTest, 'com.adobe.combinedCameraLens', spack(
		'literal',
		"Doc3 Lens"
	) )
	testDoc( rxTest,
		'com.adobe.combinedCameraLens + coverage',
		{ xmp = { tiff = { Make = kTiffMake, Model = kTiffModel, } } },
		'com.adobe.combinedCameraLens',
		spack( 'literal', "Imaginary Photocorp Imaginary Camera Model" ) )
	testDoc1( rxTest, 'com.adobe.combinedCameraLensSetting', spack(
		'ZSTR',
		"$$$/AgLibrary/Exif/CameraTempl/MakeOrModelLensTempl=^1 + ^2",
		"Imaginary Photocorp Imaginary Camera Model",
		{
			'ZSTR',
			"$$$/AgMetadataFormatters/exif/commonPhotoSettings/focalLenAndLens=^1 (^2)",
			{
				'ZSTR',
				"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
				{ 'number', 50, 0, true },
			},
			kAuxLens
		}
	) )
	
	local doc2_tiff_old = doc2.xmp.tiff
	doc2.xmp.tiff = nil
	
	testDoc2( rxTest, 'com.adobe.combinedCameraLensSetting', { 'unused' } )
	
	doc2.xmp.tiff = doc2_tiff_old
	
	testDoc3( rxTest, 'com.adobe.combinedCameraLensSetting', spack( 'literal', "Doc3 Lens" ) )
	testDoc( rxTest,
		'com.adobe.combinedCameraLensSetting + coverage',
		{ xmp = { tiff = { Make = kTiffMake, Model = kTiffModel, } } },
		'com.adobe.combinedCameraLensSetting',
		spack( 'literal', "Imaginary Photocorp Imaginary Camera Model" ) )
	testDoc1( rxTest, 'com.adobe.serialNumber', spack( 'literal', kAuxSerialNumber ) )
	testDoc1( rxTest, 'com.adobe.exposure', spack( 'ZSTR',
		"$$$/AgLibrary/Exif/ExposureShutterSpeedAtAperture=^1 at ^2",
		{
			'ZSTR',
			"$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
			{ 'formattedFraction', 1, 64, true, },
		},
		{
			'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 1.4, 1, },
		}
	) )
	testDoc( rxTest, 'com.adobe.exposure + coverage',
			{ xmp = { exif = { ShutterSpeedValue = kExifShutterSpeed, } } },
			'com.adobe.exposure', spack( 'ZSTR',
			"$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
			{ 'formattedFraction', 1, 64, true, }
	) )
	testDoc( rxTest, 'com.adobe.exposure + coverage',
			{ xmp = { exif = { FNumber = kExifFNumber, } } },
			'com.adobe.exposure', spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 1.4, 1, }
	) )
	
	testDoc1( rxTest, 'com.adobe.exposureTime',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'formattedFraction', 1, 64, true, } ) )
		
	testDoc1( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'formattedFraction', 1, 64, true, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = { 1, 0 }
	
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue', spack( "" ) )
		
	doc3.xmp.exif.ShutterSpeedValue = { 7, 4 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.3, 1, } ) )
	
	doc3.xmp.exif.ShutterSpeedValue = { 1, 0.6 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.3, 1, } ) )
	
	doc3.xmp.exif.ShutterSpeedValue = { 1, 0.7 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.4, 1, } ) )
	
	doc3.xmp.exif.ShutterSpeedValue = { 1, 1 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.5, 1, } ) )
	
	doc3.xmp.exif.ShutterSpeedValue = { 1, 1.5 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.6, 1, } ) )
	
	doc3.xmp.exif.ShutterSpeedValue = { 1, 2 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.7, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = { 1, 3 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.8, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = { 1, 5 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 0.9, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = { 1, 16 }
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 1, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = -5
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 32, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = -4
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 16, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = -3
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 8, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = -2
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 4, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = -1
		
	testDoc3( rxTest, 'com.adobe.shutterSpeedValue',
		spack( 'ZSTR', "$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
		{ 'number', 2, 1, } ) )
		
	doc3.xmp.exif.ShutterSpeedValue = nil
		
	testDoc1( rxTest, 'com.adobe.FNumber',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 1.4, 1, }
	 ) )
	
	doc2.xmp.exif.FNumber = { 1, 4 }
	
	testDoc2( rxTest, 'com.adobe.FNumber',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 0.25, 2, }
	 ) )
	
	doc2.xmp.exif.FNumber = nil
	
	testDoc1( rxTest, 'com.adobe.apertureValue',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 16, 0, }
	 ) )
	testDoc1( rxTest, 'com.adobe.maxApertureValue',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
			{ 'number', 1.4, 1, }
	 ) )
	testDoc1( rxTest, 'com.adobe.exposureBiasValue',
		spack( 'ZSTR',
			"$$$/AgMetadataFormatters/exif/ExposureBiasValue=^1 EV",
			{ 'formattedFraction', 1, 3, true }
	 ) )
	testDoc2( rxTest, 'com.adobe.exposureBiasValue',
		spack( 'ZSTR',
			"$$$/AgMetadataFormatters/exif/ExposureBiasValue=^1 EV",
			""
	 ) )
	testDoc3( rxTest, 'com.adobe.exposureBiasValue',
		spack( 'ZSTR',
			"$$$/AgMetadataFormatters/exif/ExposureBiasValue=^1 EV",
			0
	 ) )
	testDoc1( rxTest, 'com.adobe.focalLength',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
			{ 'number', 50, 0, true}
	 ) )
	testDoc1( rxTest, 'com.adobe.focalLength',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
			{ 'number', 50, 7, true }
	 ), { focalLengthPrecision = 7 } )
	testDoc1( rxTest, 'com.adobe.focalLength35mm',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
			{ 'number', 38, 0, true }
	 ) )
	testDoc( rxTest, 'com.adobe.focalLength + coverage',
		{ xmp = { exif = { FocalLength = 0 } } },
		'com.adobe.focalLength',
		spack( 'ZSTR',
			"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
			"0"
	 ) )
	testDoc1( rxTest, 'com.adobe.focalPlaneXResolution', spack( 'literal', '125 in' ) )
	testDoc1( rxTest, 'com.adobe.focalPlaneYResolution', spack( 'literal', '135 in' ) )
	testDoc1( rxTest, 'com.adobe.lens', spack( 'literal', kAuxLens ) )
	testDoc1( rxTest, 'com.adobe.brightnessValue', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/BrightnessValue=^1", { 'number', 3, 2 }
	) )
	testDoc2( rxTest, 'com.adobe.brightnessValue', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/BrightnessValue=^1", { 'number', 0.66, 2 }
	) )
	testDoc3( rxTest, 'com.adobe.brightnessValue', { 'unused' } )
	testDoc1( rxTest, 'com.adobe.subjectDistance', spack( 'distance', kExifSubjectDistance, nil, false ) )
	testDoc2( rxTest, 'com.adobe.approximateFocusDistance', spack( 
		'ZSTR', "$$$/AgMetadataFormatters/SubjectDistance/Unknown=Unknown" ) )
	testDoc3( rxTest, 'com.adobe.approximateFocusDistance', spack(
		'ZSTR', "$$$/AgMetadataFormatters/SubjectDistance/Infinity=^U+221E" ) )
	testDoc1( rxTest, 'com.adobe.approximateFocusDistance', spack( 'distance', kAuxApproximateFocusDistance, nil, false ) )

	testDoc1( rxTest, 'com.adobe.commonPhotoSettings', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/commonPhotoSettings/full=^1, ^2",
		{
			'ZSTR', "$$$/AgMetadataFormatters/exif/commonPhotoSettings/exposureISO=^1, ISO ^2",
			{
				'ZSTR', "$$$/AgLibrary/Exif/ExposureShutterSpeedAtAperture=^1 at ^2",
				{
					'ZSTR',
					"$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
					{ 'formattedFraction', 1, 64, true, },
				},
				{
					'ZSTR',
					"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
					{ 'number', 1.4, 1, },
				}
			},
			{ 'number', kExifISOSpeedRatings, 0 }
		},
		{
			'ZSTR',
			"$$$/AgMetadataFormatters/exif/commonPhotoSettings/focalLenAndLens=^1 (^2)",
			{
				'ZSTR',
				"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
				{ 'number', 50, 0, true },
			},
			kAuxLens
		}
		) )

	testDoc2( rxTest, 'com.adobe.commonPhotoSettings', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/ISOSpeedRating=ISO ^1",
		{ 'number', 400, 0 }
	) )

	testDoc3( rxTest, 'com.adobe.commonPhotoSettings', spack( 'literal', "Doc3 Lens" ) )

	testDoc1( rxTest, 'com.adobe.exposureAndISO', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/commonPhotoSettings/exposureISO=^1, ISO ^2",
		{
			'ZSTR', "$$$/AgLibrary/Exif/ExposureShutterSpeedAtAperture=^1 at ^2",
			{
				'ZSTR',
				"$$$/AgMetadataFormatter/NumSecondsTemplate=^1 sec",
				{ 'formattedFraction', 1, 64, true, },
			},
			{
				'ZSTR',
				"$$$/AgLibrary/Exif/FStopTemplate/ASCII=f / ^0",
				{ 'number', 1.4, 1, },
			}
		},
		{ 'number', kExifISOSpeedRatings, 0 }
	) )

	testDoc1( rxTest, 'com.adobe.lensSetting', spack(
		'ZSTR',
		"$$$/AgMetadataFormatters/exif/commonPhotoSettings/focalLenAndLens=^1 (^2)",
		{
			'ZSTR',
			"$$$/AgLibrary/Exif/FocalLengthTemplate=^1 mm",
			{ 'number', 50, 0, true },
		},
		kAuxLens
	) )

	testDoc1( rxTest, 'com.adobe.ISOSpeedRating', spack(
		'ZSTR', "$$$/AgMetadataFormatters/exif/ISOSpeedRating=ISO ^1", { 'number', kExifISOSpeedRatings, 0 }
	) )

	testDoc1( rxTest, 'com.adobe.flash', spack( 'literal', "yes" ) )
	testDoc3( rxTest, 'com.adobe.flash', spack('literal', "no" ) )

	testDoc1( rxTest, 'com.adobe.exposureProgram', spack( 'literal', kExifExposureProgram ) )
	testDoc1( rxTest, 'com.adobe.meteringMode', spack( 'literal', kExifMeteringMode ) )
	testDoc1( rxTest, 'com.adobe.userComment', spack( 'literal', kExifUserComment ) )
	testDoc1( rxTest, 'com.adobe.artist', spack( 'literal', kTiffArtist ) )
	testDoc1( rxTest, 'com.adobe.software', spack( 'literal', kXmpCreatorTool ) )
	testDoc1( rxTest, 'com.adobe.gps.yahooUrl', { 'unused' } )
	testDoc1( rxTest, 'com.adobe.gps.googleUrl', { 'unused' } )
	testDoc1( rxTest, 'com.adobe.GPS', spack(
		'ZSTR', "$$$/AgLibrary/Exif/GPS/Pair=^1 ^2",
		{
			'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitude=^1^D^2\'^3\" ^4",
			47, 38, 58,
			{ "ZSTR", "$$$/Metadata/Exif/GPS/NorthLatitudeAbbr=N" },
			'+', '',
			tostring( kSeattleLatitude ), '38.9703'
		},
		{
			'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitude=^1^D^2\'^3\" ^4",
			122, 20, 58,
			{ "ZSTR", "$$$/Metadata/Exif/GPS/WestLongitudeAbbr=W" }, 
			'-', '-',
			tostring( -kSeattleLongitude ), '20.9655'
		}
	) )
	testDoc2( rxTest, 'com.adobe.GPS', spack(
		'ZSTR', "$$$/AgLibrary/Exif/GPS/Pair=^1 ^2",
		{
			'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitude=^1^D^2\'^3\" ^4",
			100, 0, 0,
			{ "ZSTR", "$$$/Metadata/Exif/GPS/SouthLatitudeAbbr=S" },
			'-', '-',
			'100.0000000', '0.0000'
		},
		{
			'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitude=^1^D^2\'^3\" ^4",
			20, 0, 0,
			{ "ZSTR", "$$$/Metadata/Exif/GPS/EastlongitudeAbbr=E" }, 
			'+', '',
			'20.0000000', '0.0000'
		}
	) )
	testDoc1( rxTest, 'com.adobe.GPSAltitude', spack( 'distance', 1000, 1, true ) )
	testDoc1( rxTest, 'com.adobe.GPSImgDirection', spack(
		'ZSTR', "$$$/AgLibrary/Exif/Direction/DisplayName=^1",
		{ 'ZSTR', "$$$/AgLocation/CardinalDirection/SouthEast=South-East" }
	) )
	testDoc1( rxTest, 'com.adobe.dng.version', spack( 'literal', kDngVersion ) )
	testDoc1( rxTest, 'com.adobe.dng.backwardVersion', spack( 'literal', kDngBackwardVersion ) )
	testDoc2( rxTest, 'com.adobe.dng.backwardVersion', spack( 'ZSTR', "$$$/AgMetadataFormatters/SubjectDistance/Unknown=Unknown" ) )
	testDoc1( rxTest, 'com.adobe.dng.compatibility', spack(
		'ZSTR', "$$$/AgLibrary/DNG/CompatibilityTemplate=Lightroom ^1",
		kDngLightroomCompatibility
	) )
	testDoc2( rxTest, 'com.adobe.dng.compatibility', spack(
		'ZSTR', "$$$/AgMetadataFormatters/SubjectDistance/Unknown=Unknown" ) )
	testDoc1( rxTest, 'com.adobe.dng.hasMosaicData', spack( 'ZSTR', "$$$/AgLibrary/DNG/Yes=Yes" ) )
	testDoc1( rxTest, 'com.adobe.dng.lossyCompression', spack( 'ZSTR', "$$$/AgLibrary/DNG/No=No" ) )
	testDoc1( rxTest, 'com.adobe.dng.floatingPointType', spack( 'ZSTR', "$$$/AgLibrary/DNG/Integral=Integer" ) )
	testDoc3( rxTest, 'com.adobe.dng.floatingPointType', spack( 'ZSTR', "$$$/AgLibrary/DNG/FloatingPoint=Floating Point" ) )
	testDoc1( rxTest, 'com.adobe.dng.hasTransparency', spack( 'ZSTR', "$$$/AgLibrary/DNG/No=No" ) )
	testDoc1( rxTest, 'com.adobe.dng.hasFastLoadData', spack( 'ZSTR', "$$$/AgLibrary/DNG/Embedded=Embedded" ) )
	testDoc1( rxTest, 'com.adobe.dng.hasEmbeddedOriginalRawFile', spack( 'ZSTR', "$$$/AgLibrary/DNG/NoneEmbedded=None Embedded" ) )
	testDoc1( rxTest, 'com.adobe.dng.originalRawFileName', spack( 'literal', kDngOriginalRawFilename ) )
	testDoc1( rxTest, 'com.adobe.dng.bitsPerSample', spack( 'number', 8, nil ) )
	testDoc1( rxTest, 'com.adobe.dng.previewDimensions', spack(
		'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", "1500", "1000" ) )
	testDoc1( rxTest, 'com.adobe.dng.imageDimensions', spack(
		'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", "3001", "2001" ) )
	testDoc1( rxTest, 'com.adobe.dng.originalImageDimensions', spack(
		'ZSTR', "$$$/AgLibrary/Exif/DimensionsTemplate=^1 x ^2", "3002", "2002" ) )
		
	testDoc1( rxTest, 'com.adobe.EnhanceDetailsAlreadyApplied', spack( 'literal', "yes" ) )
	testDoc2( rxTest, 'com.adobe.EnhanceDetailsAlreadyApplied', spack( 'literal', "no" ) )

end

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

function WFCatFormattedMetadataTest.rxTestIptcFormatting( rxTest )

	testDoc2( rxTest, 'com.adobe.creator', spack( 'literal', kCreator ) )
	
	testDoc2( rxTest, 'com.adobe.headline', spack( 'literal', kHeadline ) )
	testDoc2( rxTest, 'com.adobe.descriptionWriter', spack( 'literal', kCaptionWriter ) )
	testDoc2( rxTest, 'com.adobe.category', spack( 'literal', kCategory ) )
	
	testDoc2( rxTest, 'com.adobe.dateCreated', spack( 'literal', kDateCreated ) )
	testDoc2( rxTest, 'com.adobe.location', spack( 'literal', kLocation ) )
	testDoc2( rxTest, 'com.adobe.city', spack( 'literal', kCity ) )
	testDoc2( rxTest, 'com.adobe.state', spack( 'literal', kState ) )
	testDoc2( rxTest, 'com.adobe.country', spack( 'literal', kCountry ) )
	testDoc2( rxTest, 'com.adobe.isoCountryCode', spack( 'literal', kCountryCode ) )
	
	testDoc2( rxTest, 'com.adobe.title', spack( 'literal', kTitle ) )
	testDoc2( rxTest, 'com.adobe.jobIdentifier', spack( 'literal', kTransmissionReference ) )
	testDoc2( rxTest, 'com.adobe.instructions', spack( 'literal', kInstructions ) )
	testDoc2( rxTest, 'com.adobe.provider', spack( 'literal', kCredit ) )
	testDoc2( rxTest, 'com.adobe.source', spack( 'literal', kSource ) )
	
	testDoc2( rxTest, 'com.adobe.rightsUsageTerms', spack( 'literal', kUsageTerms ) )
	testDoc2( rxTest, 'com.adobe.copyrightInfoURL', spack( 'literal', kWebStatement ) )
	
	-- test location read only fields ( payload.location ) if their user writable fields 
	-- ( xmp.photoshop.City, etc. ) aren't present 
	testDoc4( rxTest, 'com.adobe.city', spack( 'literal', kCity ) )
	testDoc4( rxTest, 'com.adobe.state', spack( 'literal', kState ) )
	testDoc4( rxTest, 'com.adobe.country', spack( 'literal', kCountry ) )
	testDoc4( rxTest, 'com.adobe.isoCountryCode', spack( 'literal', kCountryCode ) )
	
end

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

function WFCatFormattedMetadataTest.rxTestMetadataFormatting( rxTest )

	testDoc2( rxTest, 'com.adobe.caption', spack( 'literal', kCaption ) )
	testDoc3( rxTest, 'com.adobe.caption', spack( 'literal', "" ) )
	
	testDoc2( rxTest, 'com.adobe.copyright', spack( 'literal', kCopyright ) )
	testDoc3( rxTest, 'com.adobe.copyright', { "unused" } )
	
	local keywords = {}
	for k in pairs( kKeywords ) do
		table.insert( keywords, k )
	end
	table.sort( keywords )
	
	testDoc2( rxTest, 'com.adobe.keywords', spack( 'literal', 
		table.concat( keywords, ', ' ) ) )
	testDoc3( rxTest, 'com.adobe.keywords', spack( 'literal', "" ) )
	
	testDoc2( rxTest, 'com.adobe.metadataDate', spack( 'mediumDateTime', kMetadataDate ) )
	testDoc3( rxTest, 'com.adobe.metadataDate', { "unused" } )
	
end

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

function WFCatFormattedMetadataTest.rxTestFileFormatting( rxTest )

	testDoc2( rxTest, 'com.adobe.filenameExtension', spack( 'literal', kFileNameExtension ) )
	testDoc3( rxTest, 'com.adobe.filenameExtension', { "unused" } )
	
	testDoc2( rxTest, 'com.adobe.filename', spack( 'literal', kFileName ) )

	testDoc2( rxTest, 'com.adobe.fileFormat', spack(
		'ZSTR', "$$$/MetadataBrowser/Filetype/RAW=Raw" ) )

	testDoc3( rxTest, 'com.adobe.fileFormat', { "unused" } )

	testDoc2( rxTest, 'com.adobe.basename', spack( 'literal', kBaseName ) )
	
	testDoc2( rxTest, 'com.adobe.filesize', spack( 'bytes', kFileSize ) )
	
end

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

function WFCatFormattedMetadataTest.rxTestVideoFormatting( rxTest )

	testDoc2( rxTest, 'com.adobe.trimmed_duration.optional', spack( 'literal', "04:17.7" ) )
	testDoc3( rxTest, 'com.adobe.trimmed_duration.optional', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.duration.combined.optional', spack( 'literal', "00:04.0" ) )
	testDoc3( rxTest, 'com.adobe.duration.combined.optional', { 'unused' } )
	
	local frameRateCalculation = { 'number', kFrameRate[ 1 ] / kFrameRate[ 2 ], 3, false }
	
	testDoc2( rxTest, 'com.adobe.videoFrameRate', spack( 
		'ZSTR',
		"$$$/AgLibrary/MetadataFormatters/Video/VideoFrameRate/UnitFramesPerSecopnd=^1 fps",
		frameRateCalculation	
	) )
	testDoc3( rxTest, 'com.adobe.videoFrameRate', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.duration', spack( 'literal', "00:04.0" ) )
	testDoc3( rxTest, 'com.adobe.duration', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.videoFrameSize', spack( 
		'literal', string.format( "%d x %d", kStorageWidth, kStorageHeight ) ) )
	testDoc3( rxTest, 'com.adobe.trackNumber', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.trackNumber', spack( 'number', kTrackNumber ) )
	testDoc3( rxTest, 'com.adobe.trackNumber', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.tapeName', spack( 'literal', kTapeName ) )
	testDoc3( rxTest, 'com.adobe.tapeName', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.speakerPlacement', spack( 'literal', kSpeakerPlacement ) )
	testDoc3( rxTest, 'com.adobe.speakerPlacement', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.shotName', spack( 'literal', kShotName ) )
	testDoc3( rxTest, 'com.adobe.shotName', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.shotLocation', spack( 'literal', kShotLocation ) )
	testDoc3( rxTest, 'com.adobe.shotLocation', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.shotDay', spack( 'mediumDateTime', kShotDay ) )
	testDoc3( rxTest, 'com.adobe.shotDay', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.shotDate', spack( 'literal', kShotDate ) )
	testDoc3( rxTest, 'com.adobe.shotDate', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.releaseDate', spack( 'mediumDateTime', kReleaseDate ) )
	testDoc3( rxTest, 'com.adobe.releaseDate', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.projectName', spack( 'literal', kProjectName ) )
	testDoc3( rxTest, 'com.adobe.projectName', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.logComment', spack( 'literal', kLogComment ) )
	testDoc3( rxTest, 'com.adobe.logComment', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.instrument', spack( 'literal', kInstrument ) )
	testDoc3( rxTest, 'com.adobe.instrument', { 'unused' } )
	
	testDoc1( rxTest, 'com.adobe.good', spack( 'literal', 'False' ) )
	testDoc2( rxTest, 'com.adobe.good', spack( 'literal', 'True' ) )
	testDoc3( rxTest, 'com.adobe.good', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.genre', spack( 'literal', kGenre ) )
	testDoc3( rxTest, 'com.adobe.genre', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.engineer', spack( 'literal', kEngineer ) )
	testDoc3( rxTest, 'com.adobe.engineer', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.dm_scene', spack( 'literal', kDm_Scene ) )
	testDoc3( rxTest, 'com.adobe.dm_scene', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.dm_artist', spack( 'literal', kDm_Artist ) )
	testDoc3( rxTest, 'com.adobe.dm_artist', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.directorPhotography', spack( 'literal', kDirectorPhotography ) )
	testDoc3( rxTest, 'com.adobe.directorPhotography', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.director', spack( 'literal', kDirector ) )
	testDoc3( rxTest, 'com.adobe.director', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.description', spack( 'literal', kCaption ) )
	testDoc3( rxTest, 'com.adobe.description', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.composer', spack( 'literal', kComposer ) )
	testDoc3( rxTest, 'com.adobe.composer', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.comment', spack( 'literal', kComment ) )
	testDoc3( rxTest, 'com.adobe.comment', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.client', spack( 'literal', kClient ) )		
	testDoc3( rxTest, 'com.adobe.client', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.cameraMove', spack( 
		'ZSTR', "$$$/AgLibrary/MetadataFormatters/Video/cameraMove/Aerial=Aerial"
	) )
	testDoc3( rxTest, 'com.adobe.videoPixelAspectRatio', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.cameraAngle', spack( 
		'ZSTR', "$$$/AgLibrary/MetadataFormatters/Video/cameraAngle/High Angle=High Angle"
	) )
	testDoc3( rxTest, 'com.adobe.videoPixelAspectRatio', { 'unused' } )
		
	testDoc2( rxTest, 'com.adobe.cameraModel', spack( 'literal', kCameraModel ) )
	testDoc3( rxTest, 'com.adobe.cameraModel', { 'unused' } )
	
	testDoc2( rxTest, 'com.adobe.altTapeName', spack( 'literal', kAltTapeName ) )
	testDoc3( rxTest, 'com.adobe.altTapeName', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.album', spack( 'literal', kAlbum ) )
	testDoc3( rxTest, 'com.adobe.album', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.videoPixelAspectRatio',
		spack( 'number', 1, 1, true ) )
	testDoc3( rxTest, 'com.adobe.videoPixelAspectRatio', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.videoAlphaMode', spack( 'literal', kVideoAlphaMode ) )
	testDoc3( rxTest, 'com.adobe.videoAlphaMode', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.audioSampleType', spack( 'literal', kAudioSampleType ) )
	testDoc3( rxTest, 'com.adobe.audioSampleType', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.audioSampleRate', spack(
		'ZSTR', "$$$/AgLibrary/MetadataFormatters/Video/audioSampleRate/UnitHertz=^1 kHz",
		kAudioSampleRate / 1000
	) )
	testDoc3( rxTest, 'com.adobe.audioSampleRate', { 'unused' } ) 
	
	testDoc2( rxTest, 'com.adobe.audioChannelType', spack( 'literal', kAudioChannelType ) ) 
	testDoc3( rxTest, 'com.adobe.audioChannelType', { 'unused' } ) 
	
end

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

local function testOverlay( rxTest, formatterId, value, expect )
	rxTest:expectEqual( expect, getOverlay( formatterId, value, mcInfo ) )
end

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

function WFCatFormattedMetadataTest.rxTestAttributeOverlays( rxTest )

	testOverlay( rxTest, 'com.adobe.rating', 5, {
		ratings = { [ kUserId ] = { rating = 5, date = kFakeToday, } }
	} )
	testOverlay( rxTest, 'com.adobe.rating', 0, {
		ratings = { [ kUserId ] = { _delete = true } }
	} )
	testOverlay( rxTest, 'com.adobe.rating', "0", {
		ratings = { [ kUserId ] = { _delete = true } }
	} )
	testOverlay( rxTest, 'com.adobe.rating', "4", {
		ratings = { [ kUserId ] = { rating = 4, date = kFakeToday, } }
	} )

	testOverlay( rxTest, 'com.adobe.colorLabels', 'red', {
		xmp = { xmp = { Label = 'red' } }
	} )
	testOverlay( rxTest, 'com.adobe.colorLabels', nil, {
		xmp = { xmp = { Label = { _delete = true } } }
	} )
	
end

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

function WFCatFormattedMetadataTest.rxTestExifOverlays( rxTest )

	local kDeleted = { _delete = true }
	
	testOverlay( rxTest, 'com.adobe.userComment', 'New Comment', {
		xmp = { exif = { UserComment = 'New Comment' } }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '1NE', {
		location = { latitude = kDeleted, longitude = kDeleted }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '', {
		location = { latitude = kDeleted, longitude = kDeleted }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '70 2 48.75N, 90 55 46.875W', {
		location = { latitude = 70.046875, longitude = -90.9296875,
			sublocation = kDeleted, city = kDeleted, state = kDeleted, country = kDeleted, isoCountryCode = kDeleted, }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '90 55 46.875W, 70 2 48.75N', {
		location = { latitude = 70.046875, longitude = -90.9296875,
			sublocation = kDeleted, city = kDeleted, state = kDeleted, country = kDeleted, isoCountryCode = kDeleted }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '70.046875,-90.9296875', {
		location = { latitude = 70.046875, longitude = -90.9296875,
			sublocation = kDeleted, city = kDeleted, state = kDeleted, country = kDeleted, isoCountryCode = kDeleted }
	} )
	testOverlay( rxTest, 'com.adobe.GPS', '70.046875,-', {
		location = { latitude = kDeleted, longitude = kDeleted }
	} )
	testOverlay( rxTest, 'com.adobe.GPSAltitude', '10000', {
		location = { altitude = 3048 }
	} )

end

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

function WFCatFormattedMetadataTest.rxTestIptcOverlays( rxTest )

	testOverlay( rxTest, 'com.adobe.creator', kCreator, {
		xmp = { dc = { creator = kCreator, } }
	} )

	testOverlay( rxTest, 'com.adobe.headline', kHeadline, {
		xmp = { photoshop = { Headline = kHeadline, } }
	} )
	
	testOverlay( rxTest, 'com.adobe.city', kCity, {
		xmp = { photoshop = { City = kCity, } }
	} )
	
	testOverlay( rxTest, 'com.adobe.instructions', kInstructions, {
		xmp = { photoshop = { Instructions = kInstructions, } }
	} )
	
	testOverlay( rxTest, 'com.adobe.rightsUsageTerms', kUsageTerms, {
		xmp = { xmpRights = { UsageTerms = kUsageTerms } }
	} )

end

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

function WFCatFormattedMetadataTest.rxTestMetadataOverlays( rxTest )

	testOverlay( rxTest, 'com.adobe.caption', kCaption, {
		xmp = { dc = { description = kCaption } }
	} )

	testOverlay( rxTest, 'com.adobe.caption', nil, {
		xmp = { dc = { description = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.copyright', kCopyright, {
		xmp = { dc = { rights = kCopyright, } }
	} )

	testOverlay( rxTest, 'com.adobe.copyright', nil, {
		xmp = { dc = { rights = { _delete = true } } }
	} )

end

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

function WFCatFormattedMetadataTest.rxTestVideoOverlays( rxTest )

	testOverlay( rxTest, 'com.adobe.album', kAlbum, { 
		xmp = { dm = { album = kAlbum } }
	} )

	testOverlay( rxTest, 'com.adobe.album', nil, { 
		xmp = { dm = { album = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.altTapeName', kAltTapeName, { 
		xmp = { dm = { altTapeName = kAltTapeName } }
	} )

	testOverlay( rxTest, 'com.adobe.altTapeName', nil, { 
		xmp = { dm = { altTapeName = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraModel', kCameraModel, { 
		xmp = { dm = { cameraModel = kCameraModel } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraModel', nil, { 
		xmp = { dm = { cameraModel = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraAngle', kCameraAngle, { 
		xmp = { dm = { cameraAngle = kCameraAngle } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraAngle', nil, { 
		xmp = { dm = { cameraAngle = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraMove', kCameraMove, { 
		xmp = { dm = { cameraMove = kCameraMove } }
	} )

	testOverlay( rxTest, 'com.adobe.cameraMove', nil, { 
		xmp = { dm = { cameraMove = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.client', kClient, { 
		xmp = { dm = { client = kClient } }
	} )

	testOverlay( rxTest, 'com.adobe.client', nil, { 
		xmp = { dm = { client = { _delete = true } } }
	} )
	
	testOverlay( rxTest, 'com.adobe.comment', kComment, { 
		xmp = { dm = { comment = kComment } }
	} )

	testOverlay( rxTest, 'com.adobe.comment', nil, { 
		xmp = { dm = { comment = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.composer', kComposer, { 
		xmp = { dm = { composer = kComposer } }
	} )

	testOverlay( rxTest, 'com.adobe.composer', nil, { 
		xmp = { dm = { composer = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.description', kCaption, { 
		xmp = { dc = { description = kCaption } }
	} )

	testOverlay( rxTest, 'com.adobe.description', nil, { 
		xmp = { dc = { description = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.director', kDirector, { 
		xmp = { dm = { director = kDirector } }
	} )

	testOverlay( rxTest, 'com.adobe.director', nil, { 
		xmp = { dm = { director = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.directorPhotography', kDirectorPhotography, { 
		xmp = { dm = { directorPhotography = kDirectorPhotography } }
	} )

	testOverlay( rxTest, 'com.adobe.directorPhotography', nil, { 
		xmp = { dm = { directorPhotography = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.dm_artist', kDm_Artist, { 
		xmp = { dm = { artist = kDm_Artist } }
	} )

	testOverlay( rxTest, 'com.adobe.dm_artist', nil, { 
		xmp = { dm = { artist = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.dm_scene', kDm_Scene, { 
		xmp = { dm = { scene = kDm_Scene } }
	} )

	testOverlay( rxTest, 'com.adobe.dm_scene', nil, { 
		xmp = { dm = { scene = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.engineer', kEngineer, { 
		xmp = { dm = { engineer = kEngineer } }
	} )

	testOverlay( rxTest, 'com.adobe.engineer', nil, { 
		xmp = { dm = { engineer = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.genre', kGenre, { 
		xmp = { dm = { genre = kGenre } }
	} )

	testOverlay( rxTest, 'com.adobe.genre', nil, { 
		xmp = { dm = { genre = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.good', 'True', { 
		xmp = { dm = { good = true } }
	} )

	testOverlay( rxTest, 'com.adobe.good', 'False', { 
		xmp = { dm = { good = false } }
	} )

	testOverlay( rxTest, 'com.adobe.good', nil, { 
		xmp = { dm = { good = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.instrument', kInstrument, { 
		xmp = { dm = { instrument = kInstrument } }
	} )

	testOverlay( rxTest, 'com.adobe.instrument', nil, { 
		xmp = { dm = { instrument = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.logComment', kLogComment, { 
		xmp = { dm = { logComment = kLogComment } }
	} )

	testOverlay( rxTest, 'com.adobe.logComment', nil, { 
		xmp = { dm = { logComment = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.projectName', kProjectName, { 
		xmp = { dm = { projectName = kProjectName } }
	} )

	testOverlay( rxTest, 'com.adobe.projectName', nil, { 
		xmp = { dm = { projectName = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.releaseDate', kReleaseDate, { 
		xmp = { dm = { releaseDate = kReleaseDate } }
	} )

	testOverlay( rxTest, 'com.adobe.releaseDate', nil, { 
			xmp = { dm = { releaseDate = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.shotDate', kShotDate, { 
		xmp = { dm = { shotDate = kShotDate } }
	} )

	testOverlay( rxTest, 'com.adobe.shotDate', nil, { 
		xmp = { dm = { shotDate = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.shotDay', kShotDay, { 
		xmp = { dm = { shotDay = kShotDay } }
	} )

	testOverlay( rxTest, 'com.adobe.shotDay', nil, { 
		xmp = { dm = { shotDay = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.shotLocation', kShotLocation, { 
		xmp = { dm = { shotLocation = kShotLocation } }
	} )

	testOverlay( rxTest, 'com.adobe.shotLocation', nil, { 
		xmp = { dm = { shotLocation = { _delete = true } } }
	} )
	
	testOverlay( rxTest, 'com.adobe.shotName', kShotName, { 
		xmp = { dm = { shotName = kShotName } }
	} )

	testOverlay( rxTest, 'com.adobe.shotName', nil, { 
		xmp = { dm = { shotName = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.speakerPlacement', kSpeakerPlacement, { 
		xmp = { dm = { speakerPlacement = kSpeakerPlacement } }
	} )

	testOverlay( rxTest, 'com.adobe.speakerPlacement', nil, { 
		xmp = { dm = { speakerPlacement = { _delete = true } } }
	} )

	testOverlay( rxTest, 'com.adobe.tapeName', kTapeName, { 
		xmp = { dm = { tapeName = kTapeName } }
	} )

	testOverlay( rxTest, 'com.adobe.tapeName', nil, { 
		xmp = { dm = { tapeName = { _delete = true } } }
	} )
	
	testOverlay( rxTest, 'com.adobe.trackNumber', kTrackNumber, { 
		xmp = { dm = { trackNumber = kTrackNumber } }
	} )

	testOverlay( rxTest, 'com.adobe.trackNumber', nil, { 
		xmp = { dm = { trackNumber = { _delete = true } } }
	} )

end

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

function WFCatFormattedMetadataTest.rxTestSetAndDeleteMetadata( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local assetList = { assetId }
	local gpsFormatterId = "com.adobe.GPS"
	local formatterIdList = { gpsFormatterId }
	
	local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, nil )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc ) )
	
	local onGoingHarvest = WFCatFormattedMetadata.ongoingHarvest( wfCatalog, assetList, formatterIdList )
	local observer = rxTest:observe( onGoingHarvest )
	observer:expectData( ':ignore:' )
	
	local docObserver = rxTest:observe( wfCatalog:docStore():ongoingDocValue( assetId )
		:map ( function( docVal )
			return docVal.location
		end ) )
	
	-- Setting the GPS data
	WFCatFormattedMetadata.setMetadata( wfCatalog, assetList, gpsFormatterId, "1N 4W", nil )
	docObserver:expectDataWithinTime( 1, { latitude = 1, longitude = -4 } )
	
	-- Removing GPS data
	WFCatFormattedMetadata.setMetadata( wfCatalog, assetList, gpsFormatterId, "", nil )
	docObserver:expectData( {} )
end

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

function WFCatFormattedMetadataTest.rxTestSetAndDeleteMultipleMetadata( rxTest )
	
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local assetId2 = KSIdGenerator.uuidInLowercaseHex()
	local assetList = { assetId, assetId2 }
	local gpsFormatterId = "com.adobe.GPS"
	local formatterIdList = { gpsFormatterId }
	
	local assetDoc = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, nil )
	local assetDoc2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, nil )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc ) )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc2 ) )
	
	local onGoingHarvest = WFCatFormattedMetadata.ongoingHarvest( wfCatalog, assetList, formatterIdList )
	local observer = rxTest:observe( onGoingHarvest )
	observer:expectData( ':ignore:' )
	
	local docObserver = rxTest:observe( wfCatalog:docStore():ongoingDocValue( assetId )
		:pluck( 'location' ) )
	
	local docObserver2 = rxTest:observe( wfCatalog:docStore():ongoingDocValue( assetId2 )
		:pluck( 'location' ) )
	
	-- Setting the GPS data
	local consolidatedMetadataValues = { { syncSourceId=assetId, {formatterId=gpsFormatterId, value="1N 4W"} },
	{ syncSourceId=assetId2, {formatterId=gpsFormatterId, value="1S 4E" }}, }
	
	rxTest:waitFor( WFCatFormattedMetadata.setMultipleMetadata( wfCatalog, consolidatedMetadataValues ) )
	docObserver:expectDataWithinTime( 1, { latitude = 1, longitude = -4 } )
	docObserver2:expectDataWithinTime( 1, { latitude = -1, longitude = 4 } )
	
	-- Removing GPS data
	WFCatFormattedMetadata.setMetadata( wfCatalog, assetList, gpsFormatterId, "", nil )
	docObserver:expectData( {} )
	docObserver2:expectData( {} )

end

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

function WFCatFormattedMetadataTest.rxTestBasicOngoingHarvest( rxTest )

	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local assetId1 = KSIdGenerator.uuidInLowercaseHex()
	local assetId2 = KSIdGenerator.uuidInLowercaseHex()
	local assetList = { assetId1, assetId2 }
	local gpsLatLngFormatterId = "com.adobe.GPSLatLng"
	local gpsFormatterId = "com.adobe.GPS"
	local formatterIdList = { gpsLatLngFormatterId, gpsFormatterId }
	
	local assetDoc1 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId1, nil )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc1 ) )
	
	local assetDoc2 = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId2, nil )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc2 ) )
	
	local function makeOnGoingHarvest( interestingFormatters )
		return WFCatFormattedMetadata.ongoingHarvest( wfCatalog, assetList, formatterIdList )
			:map( function( values )
				local ret = {}
				for _, v in ipairs( values ) do
					local formatter = v.formatterId
					if interestingFormatters[ formatter ] then
						ret[ formatter ] = v.mixed and 'mixed' or v.value
					end
				end
				return ret
			end )
	end
		
	local observer = rxTest:observe( makeOnGoingHarvest( { [ gpsLatLngFormatterId ] = true } ) )
	
	observer:expectData( { [ gpsLatLngFormatterId ] = {} } )

	-- Set the GPS location for one asset
	WFCatFormattedMetadata.setMetadata( wfCatalog, { assetId1 }, gpsFormatterId, "1N 4W", nil )
	observer:expectData( { [ gpsLatLngFormatterId ] = 'mixed' } )
	
	-- Set the GPS location for the other asset
	WFCatFormattedMetadata.setMetadata( wfCatalog, { assetId2 }, gpsFormatterId, "1N 4W", nil )
	observer:expectData( { [ gpsLatLngFormatterId ] = { latitude = 1, longitude = -4 } } )
	
end

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

return WFRxTestCase.wrapSuite( 'WFCatFormattedMetadataTest', WFCatFormattedMetadataTest )
