/* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import "FNode.h" #import "FSnapshotUtilities.h" #import "FCompoundWrite.h" #import "FEmptyNode.h" #import "FLeafNode.h" #import "FNamedNode.h" @interface FCompoundWriteTest : XCTestCase @end @implementation FCompoundWriteTest - (id) leafNode { static id node = nil; if (!node) { node = [FSnapshotUtilities nodeFrom:@"leaf-node"]; } return node; } - (id) priorityNode { static id node = nil; if (!node) { node = [FSnapshotUtilities nodeFrom:@"prio"]; } return node; } - (id) baseNode { static id node = nil; if (!node) { NSDictionary *base = @{@"child-1" : @"value-1", @"child-2" : @"value-2"}; node = [FSnapshotUtilities nodeFrom:base]; } return node; } - (void) assertAppliedCompoundWrite:(FCompoundWrite *)compoundWrite equalsNode:(id)node withPriority:(id)priority { id updatedNode = [compoundWrite applyToNode:node]; if (node.isEmpty) { XCTAssertEqualObjects([FEmptyNode emptyNode], updatedNode, @"Applied compound write should be empty. %@", updatedNode); } else { XCTAssertEqualObjects([node updatePriority:priority], updatedNode, @"Applied compound write should equal node with priority. %@", updatedNode); } } - (void) testEmptyMergeIsEmpty { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; XCTAssertTrue(compoundWrite.isEmpty, @"Empty write should be empty %@", compoundWrite); } - (void) testCompoundWriteWithPriorityUpdateIsNotEmpty { FCompoundWrite *compoundWrite = [[FCompoundWrite emptyWrite] addWrite:self.priorityNode atKey:@".priority"]; XCTAssertFalse(compoundWrite.isEmpty, @"Priority update should not be empty %@", compoundWrite); } - (void) testCompoundWriteWithUpdateIsNotEmpty { FCompoundWrite *compoundWrite = [[FCompoundWrite emptyWrite] addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"foo/bar"]]; XCTAssertFalse(compoundWrite.isEmpty, @"Update should not be empty %@", compoundWrite); } - (void) testCompoundWriteWithRootUpdateIsNotEmpty { FCompoundWrite *compoundWrite = [[FCompoundWrite emptyWrite] addWrite:self.leafNode atPath:[FPath empty]]; XCTAssertFalse(compoundWrite.isEmpty, @"Update at root should not be empty %@", compoundWrite); } - (void) testCompoundWriteWithEmptyRootUpdateIsNotEmpty { FCompoundWrite *compoundWrite = [[FCompoundWrite emptyWrite] addWrite:[FEmptyNode emptyNode] atPath:[FPath empty]]; XCTAssertFalse(compoundWrite.isEmpty, @"Empty root update should not be empty %@", compoundWrite); } - (void) testCompoundWriteWithRootPriorityUpdateAndChildMergeIsNotEmpty { FCompoundWrite *compoundWrite = [[FCompoundWrite emptyWrite] addWrite:self.priorityNode atKey:@".priority"]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@".priority"]]; XCTAssertFalse(compoundWrite.isEmpty, @"Compound write with root priority update and child merge should not be empty."); } - (void) testAppliesLeafOverwrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[FPath empty]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, self.leafNode, @"Should get leaf node once applied %@", updatedNode); } - (void) testAppliesChildrenOverwrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id childNode = [[FEmptyNode emptyNode] updateImmediateChild:@"child" withNewChild:self.leafNode]; compoundWrite = [compoundWrite addWrite:childNode atPath:[FPath empty]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, childNode, @"Child overwrite should work"); } - (void) testAddsChildNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id expectedNode = [[FEmptyNode emptyNode] updateImmediateChild:@"child" withNewChild:self.leafNode]; compoundWrite = [compoundWrite addWrite:self.leafNode atKey:@"child"]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Adding child node should work %@", updatedNode); } - (void) testAddsDeepChildNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; FPath *path = [[FPath alloc] initWith:@"deep/deep/node"]; id expectedNode = [[FEmptyNode emptyNode] updateChild:path withNewChild:self.leafNode]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:path]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Should add deep child node correctly"); } - (void) testOverwritesExistingChild { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; FPath *path = [[FPath alloc] initWith:@"child-1"]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:path]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [self.baseNode updateImmediateChild:[path getFront] withNewChild:self.leafNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Overwriting existing child should work."); } - (void) testUpdatesExistingChild { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; FPath *path = [[FPath alloc] initWith:@"child-1/foo"]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:path]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [self.baseNode updateChild:path withNewChild:self.leafNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Updating existing child should work"); } - (void) testDoesntUpdatePriorityOnEmptyNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atKey:@".priority"]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:[FEmptyNode emptyNode] withPriority:[FEmptyNode emptyNode]]; } - (void) testUpdatesPriorityOnNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atKey:@".priority"]; id node = [FSnapshotUtilities nodeFrom:@"value"]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:node withPriority:self.priorityNode]; } - (void) testUpdatesPriorityOfChild { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; FPath *path = [[FPath alloc] initWith:@"child-1/.priority"]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:path]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [self.baseNode updateChild:path withNewChild:self.priorityNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Updating priority of child should work."); } - (void) testDoesntUpdatePriorityOfNonExistentChild { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; FPath *path = [[FPath alloc] initWith:@"child-3/.priority"]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:path]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, self.baseNode, @"Should not update priority of nonexistent child"); } - (void) testDeepUpdateExistingUpdates { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update1 = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; id update2 = [FSnapshotUtilities nodeFrom:@"baz-value"]; id update3 = [FSnapshotUtilities nodeFrom:@"new-foo-value"]; compoundWrite = [compoundWrite addWrite:update1 atPath:[[FPath alloc] initWith:@"child-1"]]; compoundWrite = [compoundWrite addWrite:update2 atPath:[[FPath alloc] initWith:@"child-1/baz"]]; compoundWrite = [compoundWrite addWrite:update3 atPath:[[FPath alloc] initWith:@"child-1/foo"]]; NSDictionary *expectedChild1 = @{@"foo":@"new-foo-value", @"bar":@"bar-value", @"baz":@"baz-value"}; id expectedNode = [self.baseNode updateImmediateChild:@"child-1" withNewChild:[FSnapshotUtilities nodeFrom:expectedChild1]]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Deep update with existing updates should work."); } - (void) testShallowUpdateRemovesDeepUpdate { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update1 = [FSnapshotUtilities nodeFrom:@"new-foo-value"]; id update2 = [FSnapshotUtilities nodeFrom:@"baz-value"]; id update3 = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; compoundWrite = [compoundWrite addWrite:update1 atPath:[[FPath alloc] initWith:@"child-1/foo"]]; compoundWrite = [compoundWrite addWrite:update2 atPath:[[FPath alloc] initWith:@"child-1/baz"]]; compoundWrite = [compoundWrite addWrite:update3 atPath:[[FPath alloc] initWith:@"child-1"]]; NSDictionary *expectedChild1 = @{@"foo":@"foo-value", @"bar":@"bar-value"}; id expectedNode = [self.baseNode updateImmediateChild:@"child-1" withNewChild:[FSnapshotUtilities nodeFrom:expectedChild1]]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Shallow update should remove deep udpates."); } - (void) testChildPriorityDoesntUpdateEmptyNodePriorityOnChildMerge { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@"child-1/.priority"]]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@"child-1"]]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:[FEmptyNode emptyNode] withPriority:[FEmptyNode emptyNode]]; } - (void) testChildPriorityUpdatesPriorityOnChildMerge { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@"child-1/.priority"]]; id node = [FSnapshotUtilities nodeFrom:@"value"]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@"child-1"]]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:node withPriority:self.priorityNode]; } - (void) testChildPriorityUpdatesEmptyPriorityOnChildMerge { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:[FEmptyNode emptyNode] atPath:[[FPath alloc] initWith:@"child-1/.priority"]]; id node = [[FLeafNode alloc] initWithValue:@"foo" withPriority:self.priorityNode]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@"child-1"]]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:node withPriority:[FEmptyNode emptyNode]]; } - (void) testDeepPrioritySetWorksOnEmptyNodeWhenOtherSetIsAvailable { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@"foo/.priority"]]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"foo/child"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; id updatedPriority = [updatedNode getChild:[[FPath alloc] initWith:@"foo"]].getPriority; XCTAssertEqualObjects(updatedPriority, self.priorityNode, @"Should get priority"); } - (void) testChildMergeLooksIntoUpdateNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; compoundWrite = [compoundWrite addWrite:update atPath:[FPath empty]]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@"foo"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; id expectedNode = [FSnapshotUtilities nodeFrom:@"foo-value"]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Child merge should get updates."); } - (void) testChildMergeRemovesNodeOnDeeperPaths { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; compoundWrite = [compoundWrite addWrite:update atPath:[FPath empty]]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@"foo/not/existing"]]; id updatedNode = [compoundWrite applyToNode:self.leafNode]; id expectedNode = [FEmptyNode emptyNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Should not have node."); } - (void) testChildMergeWithEmptyPathIsSameMerge { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; compoundWrite = [compoundWrite addWrite:update atPath:[FPath empty]]; XCTAssertEqualObjects([compoundWrite childCompoundWriteAtPath:[FPath empty]], compoundWrite, @"Child merge with empty path should be the same merge."); } - (void) testRootUpdateRemovesRootPriority { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@".priority"]]; id update = [FSnapshotUtilities nodeFrom:@"foo"]; compoundWrite = [compoundWrite addWrite:update atPath:[FPath empty]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, update, @"Root update should remove root priority"); } - (void) testDeepUpdateRemovesPriorityThere { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@"foo/.priority"]]; id update = [FSnapshotUtilities nodeFrom:@"bar"]; compoundWrite = [compoundWrite addWrite:update atPath:[[FPath alloc] initWith:@"foo"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; id expectedNode = [FSnapshotUtilities nodeFrom:@{@"foo":@"bar"}]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Deep update should remove priority there"); } - (void) testAddingUpdatesAtPathWorks { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init]; [updateDictionary setObject:@"foo-value" forKey:@"foo"]; [updateDictionary setObject:@"bar-value" forKey:@"bar"]; FCompoundWrite *updates = [FCompoundWrite compoundWriteWithValueDictionary:updateDictionary]; compoundWrite = [compoundWrite addCompoundWrite:updates atPath:[[FPath alloc] initWith:@"child-1"]]; NSDictionary *expectedChild1 = @{@"foo":@"foo-value", @"bar":@"bar-value"}; id expectedNode = [self.baseNode updateImmediateChild:@"child-1" withNewChild:[FSnapshotUtilities nodeFrom:expectedChild1]]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Adding updates at a path should work."); } - (void) testAddingUpdatesAtRootWorks { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init]; [updateDictionary setObject:@"new-value-1" forKey:@"child-1"]; [updateDictionary setObject:[NSNull null] forKey:@"child-2"]; [updateDictionary setObject:@"value-3" forKey:@"child-3"]; FCompoundWrite *updates = [FCompoundWrite compoundWriteWithValueDictionary:updateDictionary]; compoundWrite = [compoundWrite addCompoundWrite:updates atPath:[FPath empty]]; NSDictionary *expected = @{@"child-1":@"new-value-1", @"child-3":@"value-3"}; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [FSnapshotUtilities nodeFrom:expected]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Adding updates at root should work."); } - (void) testChildMergeOfRootPriorityWorks { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@".priority"]]; compoundWrite = [compoundWrite childCompoundWriteAtPath:[[FPath alloc] initWith:@".priority"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, self.priorityNode, @"Child merge of root priority should work."); } - (void) testCompleteChildrenOnlyReturnsCompleteOverwrites { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"child-1"]]; NSArray *expectedChildren = @[[[FNamedNode alloc] initWithName:@"child-1" andNode:self.leafNode]]; NSArray *completeChildren = [compoundWrite completeChildren]; XCTAssertEqualObjects(completeChildren, expectedChildren, @"Complete children should only return on complete overwrites."); } - (void) testCompleteChildrenOnlyReturnsEmptyOverwrites { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:[FEmptyNode emptyNode] atPath:[[FPath alloc] initWith:@"child-1"]]; NSArray *expectedChildren = @[[[FNamedNode alloc] initWithName:@"child-1" andNode:[FEmptyNode emptyNode]]]; NSArray *completeChildren = [compoundWrite completeChildren]; XCTAssertEqualObjects(completeChildren, expectedChildren, @"Complete children should return list with empty on empty overwrites."); } - (void) testCompleteChildrenDoesntReturnDeepOverwrites { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"child-1/deep/path"]]; NSArray *expectedChildren = @[]; NSArray *completeChildren = [compoundWrite completeChildren]; XCTAssertEqualObjects(completeChildren, expectedChildren, @"Should not get complete children on deep overwrites."); } - (void) testCompleteChildrenReturnAllCompleteChildrenButNoIncomplete { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"child-1/deep/path"]]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"child-2"]]; compoundWrite = [compoundWrite addWrite:[FEmptyNode emptyNode] atPath:[[FPath alloc] initWith:@"child-3"]]; NSDictionary *expected = @{ @"child-2":self.leafNode, @"child-3":[FEmptyNode emptyNode] }; NSMutableDictionary *actual = [[NSMutableDictionary alloc] init]; for (FNamedNode *node in compoundWrite.completeChildren) { [actual setObject:node.node forKey:node.name]; } XCTAssertEqualObjects(actual, expected, @"Complete children should get returned, but not incomplete ones."); } - (void) testCompleteChildrenReturnAllChildrenForRootSet { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.baseNode atPath:[FPath empty]]; NSDictionary *expected = @{ @"child-1": [FSnapshotUtilities nodeFrom:@"value-1"], @"child-2": [FSnapshotUtilities nodeFrom:@"value-2"] }; NSMutableDictionary *actual = [[NSMutableDictionary alloc] init]; for (FNamedNode *node in compoundWrite.completeChildren) { [actual setObject:node.node forKey:node.name]; } XCTAssertEqualObjects(actual, expected, @"Complete children should return all children on root set."); } - (void) testEmptyMergeHasNoShadowingWrite { XCTAssertFalse([[FCompoundWrite emptyWrite] hasCompleteWriteAtPath:[FPath empty]], @"Empty merge has no shadowing write."); } - (void) testCompoundWriteWithEmptyRootHasShadowingWrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:[FEmptyNode emptyNode] atPath:[FPath empty]]; XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[FPath empty]], @"Empty write should have shadowing write at root."); XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[[FPath alloc] initWith:@"child"]], @"Empty write should have complete write at child."); } - (void) testCompoundWriteWithRootHasShadowingWrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[FPath empty]]; XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[FPath empty]], @"Root write should have shadowing write at root."); XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[[FPath alloc] initWith:@"child"]], @"Root write should have complete write at child."); } - (void) testCompoundWriteWithDeepUpdateHasShadowingWrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[[FPath alloc] initWith:@"deep/update"]]; XCTAssertFalse([compoundWrite hasCompleteWriteAtPath:[FPath empty]], @"Deep write should not have complete write at root."); XCTAssertFalse([compoundWrite hasCompleteWriteAtPath:[[FPath alloc] initWith:@"deep"]], @"Deep write should not have should have complete write at child."); XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[[FPath alloc] initWith:@"deep/update"]], @"Deep write should have complete write at deep child."); } - (void) testCompoundWriteWithPriorityUpdateHasShadowingWrite { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@".priority"]]; XCTAssertFalse([compoundWrite hasCompleteWriteAtPath:[FPath empty]], @"Write with priority at root should not have complete write at root."); XCTAssertTrue([compoundWrite hasCompleteWriteAtPath:[[FPath alloc] initWith:@".priority"]], @"Write with priority at root should have complete priority."); } - (void) testUpdatesCanBeRemoved { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; compoundWrite = [compoundWrite addWrite:update atPath:[[FPath alloc] initWith:@"child-1"]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@"child-1"]]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, self.baseNode, @"Updates should be removed."); } - (void) testDeepRemovesHasNoEffectOnOverlayingSet { // TODO I don't get this one. FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update1 = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; id update2 = [FSnapshotUtilities nodeFrom:@"baz-value"]; id update3 = [FSnapshotUtilities nodeFrom:@"new-foo-value"]; compoundWrite = [compoundWrite addWrite:update1 atPath:[[FPath alloc] initWith:@"child-1"]]; compoundWrite = [compoundWrite addWrite:update2 atPath:[[FPath alloc] initWith:@"child-1/baz"]]; compoundWrite = [compoundWrite addWrite:update3 atPath:[[FPath alloc] initWith:@"child-1/foo"]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@"child-1/foo"]]; NSDictionary *expected = @{ @"foo":@"new-foo-value", @"bar":@"bar-value", @"baz":@"baz-value" }; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [self.baseNode updateImmediateChild:@"child-1" withNewChild:[FSnapshotUtilities nodeFrom:expected]]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Deep removes should have no effect on overlaying set."); } - (void) testRemoveAtPathWithoutSetIsWithoutEffect { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update1 = [FSnapshotUtilities nodeFrom:@{@"foo":@"foo-value", @"bar":@"bar-value"}]; id update2 = [FSnapshotUtilities nodeFrom:@"baz-value"]; id update3 = [FSnapshotUtilities nodeFrom:@"new-foo-value"]; compoundWrite = [compoundWrite addWrite:update1 atPath:[[FPath alloc] initWith:@"child-1"]]; compoundWrite = [compoundWrite addWrite:update2 atPath:[[FPath alloc] initWith:@"child-1/baz"]]; compoundWrite = [compoundWrite addWrite:update3 atPath:[[FPath alloc] initWith:@"child-1/foo"]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@"child-2"]]; NSDictionary *expected = @{ @"foo":@"new-foo-value", @"bar":@"bar-value", @"baz":@"baz-value" }; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [self.baseNode updateImmediateChild:@"child-1" withNewChild:[FSnapshotUtilities nodeFrom:expected]]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Removing at path without a set should have no effect."); } - (void) testCanRemovePriority { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@".priority"]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@".priority"]]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:self.leafNode withPriority:[FEmptyNode emptyNode]]; } - (void) testRemovingOnlyAffectsRemovedPath { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; NSDictionary *updateDictionary = @{ @"child-1": @"new-value-1", @"child-2": [NSNull null], @"child-3": @"value-3" }; FCompoundWrite *updates = [FCompoundWrite compoundWriteWithValueDictionary:updateDictionary]; compoundWrite = [compoundWrite addCompoundWrite:updates atPath:[FPath empty]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@"child-2"]]; NSDictionary *expected = @{ @"child-1": @"new-value-1", @"child-2": @"value-2", @"child-3": @"value-3" }; id updatedNode = [compoundWrite applyToNode:self.baseNode]; id expectedNode = [FSnapshotUtilities nodeFrom:expected]; XCTAssertEqualObjects(updatedNode, expectedNode, @"Removing should only affected removed paths"); } - (void) testRemoveRemovesAllDeeperSets { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; id update2 = [FSnapshotUtilities nodeFrom:@"baz-value"]; id update3 = [FSnapshotUtilities nodeFrom:@"new-foo-value"]; compoundWrite = [compoundWrite addWrite:update2 atPath:[[FPath alloc] initWith:@"child-1/baz"]]; compoundWrite = [compoundWrite addWrite:update3 atPath:[[FPath alloc] initWith:@"child-1/foo"]]; compoundWrite = [compoundWrite removeWriteAtPath:[[FPath alloc] initWith:@"child-1"]]; id updatedNode = [compoundWrite applyToNode:self.baseNode]; XCTAssertEqualObjects(updatedNode, self.baseNode, @"Remove should remove deeper sets."); } - (void) testRemoveAtRootAlsoRemovesPriority { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:[[FLeafNode alloc] initWithValue:@"foo" withPriority:self.priorityNode] atPath:[FPath empty]]; compoundWrite = [compoundWrite removeWriteAtPath:[FPath empty]]; id node = [FSnapshotUtilities nodeFrom:@"value"]; [self assertAppliedCompoundWrite:compoundWrite equalsNode:node withPriority:[FEmptyNode emptyNode]]; } - (void) testUpdatingPriorityDoesntOverwriteLeafNode { // TODO I don't get this one. FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[FPath empty]]; compoundWrite = [compoundWrite addWrite:self.priorityNode atPath:[[FPath alloc] initWith:@"child/.priority"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, self.leafNode, @"Updating priority should not overwrite leaf node."); } - (void) testUpdatingEmptyChildNodeDoesntOverwriteLeafNode { FCompoundWrite *compoundWrite = [FCompoundWrite emptyWrite]; compoundWrite = [compoundWrite addWrite:self.leafNode atPath:[FPath empty]]; compoundWrite = [compoundWrite addWrite:[FEmptyNode emptyNode] atPath:[[FPath alloc] initWith:@"child"]]; id updatedNode = [compoundWrite applyToNode:[FEmptyNode emptyNode]]; XCTAssertEqualObjects(updatedNode, self.leafNode, @"Updating empty node should not overwrite leaf node."); } @end