Digital PCT265 story PCT-135: Rewire to allow the page transformation behavior to list all changes performed to the model tree.

This commit is contained in:
Hedden, Kyle Matthew 2024-05-20 21:36:26 -04:00
parent f74bea64c2
commit d377ec84b7
6 changed files with 1275 additions and 22 deletions

View File

@ -175,6 +175,7 @@
583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335622BF6509C001D90D7 /* UAD_page_model.json */; }; 583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335622BF6509C001D90D7 /* UAD_page_model.json */; };
583335652BF6A5C3001D90D7 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583335642BF6A5C3001D90D7 /* TestUtils.swift */; }; 583335652BF6A5C3001D90D7 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583335642BF6A5C3001D90D7 /* TestUtils.swift */; };
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */; }; 583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */; };
5833356D2BFBF51C001D90D7 /* UAD_page_model_3.json in Resources */ = {isa = PBXBuildFile; fileRef = 5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */; };
5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; }; 5846ABF62B4762A600FA6C76 /* PollingBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */; };
58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; }; 58A9DD7D2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A9DD7C2AC2103300F5E0B0 /* ReplaceableMoleculeBehaviorModel.swift */; };
58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */; }; 58E7561D2BE04C320088BB5D /* MoleculeComparisonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E7561C2BE04C320088BB5D /* MoleculeComparisonProtocol.swift */; };
@ -797,6 +798,7 @@
583335622BF6509C001D90D7 /* UAD_page_model.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = UAD_page_model.json; sourceTree = "<group>"; }; 583335622BF6509C001D90D7 /* UAD_page_model.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = UAD_page_model.json; sourceTree = "<group>"; };
583335642BF6A5C3001D90D7 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; }; 583335642BF6A5C3001D90D7 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UAD_page_model_2.json; sourceTree = "<group>"; }; 583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UAD_page_model_2.json; sourceTree = "<group>"; };
5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = UAD_page_model_3.json; sourceTree = "<group>"; };
5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = "<group>"; }; 5846ABF52B4762A600FA6C76 /* PollingBehaviorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PollingBehaviorModel.swift; sourceTree = "<group>"; };
5878F0A42BD7E68800ADE23D /* mvmcoreui.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui.xcconfig; sourceTree = "<group>"; }; 5878F0A42BD7E68800ADE23D /* mvmcoreui.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui.xcconfig; sourceTree = "<group>"; };
5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = "<group>"; }; 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcoreui_dev.xcconfig; sourceTree = "<group>"; };
@ -1558,6 +1560,7 @@
children = ( children = (
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */, 583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */,
583335622BF6509C001D90D7 /* UAD_page_model.json */, 583335622BF6509C001D90D7 /* UAD_page_model.json */,
5833356C2BFBF51C001D90D7 /* UAD_page_model_3.json */,
); );
path = Modelling; path = Modelling;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2740,6 +2743,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */, 583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */,
5833356D2BFBF51C001D90D7 /* UAD_page_model_3.json in Resources */,
583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */, 583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -256,18 +256,14 @@ import MVMCore
var behaviorUpdatedModels = [MoleculeModelProtocol]() var behaviorUpdatedModels = [MoleculeModelProtocol]()
if var newTemplateModel = newPageModel as? TemplateModelProtocol { if var newTemplateModel = newPageModel as? TemplateModelProtocol {
executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in executeBehaviors { (behavior: PageMoleculeTransformationBehavior) in
if let updatedMolecules = behavior.onPageNew(rootMolecules: newTemplateModel.rootMolecules, delegateObjectIVar) { var changes = [any MoleculeModelProtocol]()
if let updatedMolecules = behavior.onPageNew(rootMolecules: newTemplateModel.rootMolecules, delegateObjectIVar, changes: &changes) {
updatedMolecules.forEach { molecule in updatedMolecules.forEach { molecule in
// Replace again in case there is a template level child.
if let replaced = try? newTemplateModel.replaceChildMolecule(with: molecule) { if let replaced = try? newTemplateModel.replaceChildMolecule(with: molecule) {
// Only recognize the molecules that actually changed. // Only recognize the molecules that actually changed.
if let replaced = replaced as? ParentMoleculeModelProtocol, let molecule = molecule as? ParentMoleculeModelProtocol { debugLog("Behavior updated \(changes) in template model.")
let diffs: [MoleculeModelProtocol] = replaced.findAllTheirsNotEqual(against: molecule) behaviorUpdatedModels.append(contentsOf: changes)
debugLog("Behavior updated \(diffs) in template model.")
behaviorUpdatedModels.append(contentsOf: diffs)
} else if !replaced.isEqual(to: molecule) {
debugLog("Behavior updated \(molecule) in template model.")
behaviorUpdatedModels.append(molecule) // Need to specifically trace molecule updates here as replacements are modifying the original tree. (We don't have a deep copy.)
}
} else { } else {
debugLog("Failed to replace \(molecule) in the template model.") debugLog("Failed to replace \(molecule) in the template model.")
} }

View File

@ -31,6 +31,7 @@ public extension PageBehaviorProtocol {
public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol { public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol {
func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) -> [MoleculeModelProtocol]? func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) -> [MoleculeModelProtocol]?
func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]?
func willSetupMolecule(with model: MoleculeModelProtocol, updating view: MoleculeViewProtocol?) func willSetupMolecule(with model: MoleculeModelProtocol, updating view: MoleculeViewProtocol?)
func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol)
func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar)
@ -41,7 +42,11 @@ public protocol PageMoleculeTransformationBehavior: PageBehaviorProtocol {
public extension PageMoleculeTransformationBehavior { public extension PageMoleculeTransformationBehavior {
// All optional. // All optional.
func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) -> [MoleculeModelProtocol]? { return nil } func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) -> [MoleculeModelProtocol]? {
var changes = [any MoleculeModelProtocol]()
return onPageNew(rootMolecules: rootMolecules, delegateObject, changes: &changes)
}
func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]? { return nil }
func willSetupMolecule(with model: MoleculeModelProtocol, updating view: MoleculeViewProtocol?) {} func willSetupMolecule(with model: MoleculeModelProtocol, updating view: MoleculeViewProtocol?) {}
func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) {} func didSetupMolecule(view: MoleculeViewProtocol, withModel: MoleculeModelProtocol) {}
func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) {} func willSetupNavigationBar(with model: NavigationItemModelProtocol, updating view: UINavigationBar) {}

View File

@ -44,11 +44,10 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
Self.debugLog("Initializing for \((model as! ReplaceableMoleculeBehaviorModel).moleculeIds)") Self.debugLog("Initializing for \((model as! ReplaceableMoleculeBehaviorModel).moleculeIds)")
} }
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) -> [MoleculeModelProtocol]? { public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?, changes: inout [MoleculeModelProtocol]) -> [MoleculeModelProtocol]? {
self.delegateObject = delegateObject self.delegateObject = delegateObject
modulesToListenFor = moleculeIds
let moleculeModels = moleculeIds.compactMap { moleculeId in let moleculeModels = moleculeIds.compactMap { moleculeId in
do { do {
return try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId) return try delegateObject?.moleculeDelegate?.getModuleWithName(moleculeId)
} catch { } catch {
@ -61,23 +60,23 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
} }
} }
return findAndReplace(moleculeModels, in: rootMolecules) return findAndReplace(moleculeModels, in: rootMolecules, changes: &changes)
} }
fileprivate func findAndReplace(_ moleculeModels: [any MoleculeModelProtocol], in rootMolecules: [any MoleculeModelProtocol]) -> [any MoleculeModelProtocol]? { fileprivate func findAndReplace(_ moleculeModels: [any MoleculeModelProtocol], in rootMolecules: [any MoleculeModelProtocol], changes: inout [MoleculeModelProtocol]) -> [any MoleculeModelProtocol]? {
debugLog("attempting to replace \(moleculeModels.map { $0.id }) in \(rootMolecules)") debugLog("attempting to replace \(moleculeModels.map { $0.id }) in \(rootMolecules)")
var hasReplacement = false var changeList = [any MoleculeModelProtocol]()
let updatedRootMolecules = rootMolecules.map { rootMolecule in let updatedRootMolecules = rootMolecules.map { rootMolecule in
// Top level check to return a new root molecule. // Top level check to return a new root molecule.
if let updatedMolecule = moleculeModels.first(where: { rootMolecule.id == $0.id }) { if let updatedMolecule = moleculeModels.first(where: { rootMolecule.id == $0.id }) {
guard !updatedMolecule.isEqual(to: rootMolecule) else { guard !updatedMolecule.isEqual(to: rootMolecule) else {
debugLog("molecule \(updatedMolecule) is the same as \(rootMolecule). skipping...") debugLog("top molecule \(updatedMolecule) is the same as \(rootMolecule). skipping...")
return rootMolecule return rootMolecule
} }
debugLog("replacing \(rootMolecule) with \(updatedMolecule)") debugLog("top replacing \(rootMolecule) with \(updatedMolecule)")
logUpdated(molecule: updatedMolecule) logUpdated(molecule: updatedMolecule)
hasReplacement = true changeList.append(updatedMolecule)
return updatedMolecule return updatedMolecule
} }
@ -89,12 +88,12 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
if let replacedMolecule = try parentMolecule.replaceChildMolecule(with: newMolecule) { if let replacedMolecule = try parentMolecule.replaceChildMolecule(with: newMolecule) {
guard !replacedMolecule.isEqual(to: newMolecule) else { guard !replacedMolecule.isEqual(to: newMolecule) else {
// Note: Slight risk here of replacing the something in the original tree and misreporting that is it not replaced based on equality. // Note: Slight risk here of replacing the something in the original tree and misreporting that is it not replaced based on equality.
debugLog("molecule \(newMolecule) is the same as \(replacedMolecule). skipping...") debugLog("deep molecule \(newMolecule) is the same as \(replacedMolecule). skipping...")
return return
} }
debugLog("replacing \(replacedMolecule) with \(newMolecule)") debugLog("deep replacing \(replacedMolecule) with \(newMolecule)")
logUpdated(molecule: newMolecule) logUpdated(molecule: newMolecule)
hasReplacement = true changeList.append(newMolecule)
} }
} catch { } catch {
let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))! let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: String(describing: type(of: self)))!
@ -106,6 +105,8 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior, Co
} }
return parentMolecule return parentMolecule
} }
let hasReplacement = !changeList.isEmpty
changes.append(contentsOf: changeList)
debugLog("replacing \(hasReplacement ? updatedRootMolecules.count : 0) molecules") debugLog("replacing \(hasReplacement ? updatedRootMolecules.count : 0) molecules")
return hasReplacement ? updatedRootMolecules : nil return hasReplacement ? updatedRootMolecules : nil
} }

File diff suppressed because it is too large Load Diff

View File

@ -189,6 +189,7 @@ final class MVMCoreUITests: XCTestCase {
let listTemplateModel1 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model")) let listTemplateModel1 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model"))
let listTemplateModel2 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model")) let listTemplateModel2 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model"))
let listTemplateModel3 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model_2")) let listTemplateModel3 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model_2"))
let listTemplateModel4 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model_3"))
let results = listTemplateModel1.findFirst(in: listTemplateModel2, failing: { $0.isEqual(to: $1) }) let results = listTemplateModel1.findFirst(in: listTemplateModel2, failing: { $0.isEqual(to: $1) })
XCTAssertFalse(results.matched) XCTAssertFalse(results.matched)
@ -199,6 +200,9 @@ final class MVMCoreUITests: XCTestCase {
XCTAssertTrue(results2.matched) XCTAssertTrue(results2.matched)
XCTAssertFalse(listTemplateModel1.deepEquals(to: listTemplateModel3)) XCTAssertFalse(listTemplateModel1.deepEquals(to: listTemplateModel3))
XCTAssertTrue(listTemplateModel1.isDeeplyVisuallyEquivalent(to: listTemplateModel3)) XCTAssertTrue(listTemplateModel1.isDeeplyVisuallyEquivalent(to: listTemplateModel3))
let results3: [MoleculeModelProtocol] = listTemplateModel1.findAllTheirsNotEqual(against: listTemplateModel4)
XCTAssertTrue(results3.count == 2)
} }
func testPageEqualityPerformance() throws { func testPageEqualityPerformance() throws {