Digital PCT265 story PCT-135: Switch to shallow equals with deep compare on parent in order to pinpoint midmatched models. Unit testing setup.

This commit is contained in:
Hedden, Kyle Matthew 2024-05-17 21:24:58 -04:00
parent 52e8ce7621
commit f74bea64c2
39 changed files with 2261 additions and 125 deletions

View File

@ -170,6 +170,11 @@
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; }; 52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; };
5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */; }; 5822720B2B1FC55F00F75BAE /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */; };
5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; }; 5822720C2B1FC55F00F75BAE /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */; };
583335592BF64E77001D90D7 /* MVMCoreUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583335582BF64E77001D90D7 /* MVMCoreUITests.swift */; };
5833355A2BF64E77001D90D7 /* MVMCoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */; };
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 */; };
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */ = {isa = PBXBuildFile; fileRef = 583335662BF6DCD0001D90D7 /* UAD_page_model_2.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 */; };
@ -610,6 +615,16 @@
FD99130028E21E4900542CC3 /* RuleNotEqualsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */; }; FD99130028E21E4900542CC3 /* RuleNotEqualsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9912FF28E21E4900542CC3 /* RuleNotEqualsModel.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
5833355B2BF64E77001D90D7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D29DF0C321E404D4003B2FB9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = D29DF0CB21E404D4003B2FB9;
remoteInfo = MVMCoreUI;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
01004F2F22721C3800991ECC /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; }; 01004F2F22721C3800991ECC /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggleModel.swift; sourceTree = "<group>"; }; 0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggleModel.swift; sourceTree = "<group>"; };
@ -777,6 +792,11 @@
52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; }; 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; };
582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; }; 582272092B1FC55F00F75BAE /* AccessibilityHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; }; 5822720A2B1FC55F00F75BAE /* RotorHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RotorHandler.swift; sourceTree = "<group>"; };
583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MVMCoreUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
583335582BF64E77001D90D7 /* MVMCoreUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUITests.swift; 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>"; };
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UAD_page_model_2.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>"; };
@ -1223,6 +1243,14 @@
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
583335532BF64E77001D90D7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5833355A2BF64E77001D90D7 /* MVMCoreUI.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D29DF0C921E404D4003B2FB9 /* Frameworks */ = { D29DF0C921E404D4003B2FB9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1507,6 +1535,33 @@
path = Accessibility; path = Accessibility;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
583335572BF64E77001D90D7 /* MVMCoreUITests */ = {
isa = PBXGroup;
children = (
583335602BF65063001D90D7 /* JSON */,
583335582BF64E77001D90D7 /* MVMCoreUITests.swift */,
583335642BF6A5C3001D90D7 /* TestUtils.swift */,
);
path = MVMCoreUITests;
sourceTree = "<group>";
};
583335602BF65063001D90D7 /* JSON */ = {
isa = PBXGroup;
children = (
583335612BF6506C001D90D7 /* Modelling */,
);
path = JSON;
sourceTree = "<group>";
};
583335612BF6506C001D90D7 /* Modelling */ = {
isa = PBXGroup;
children = (
583335662BF6DCD0001D90D7 /* UAD_page_model_2.json */,
583335622BF6509C001D90D7 /* UAD_page_model.json */,
);
path = Modelling;
sourceTree = "<group>";
};
8DD1E36C243B3CD900D8F2DF /* ThreeColumn */ = { 8DD1E36C243B3CD900D8F2DF /* ThreeColumn */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -2016,6 +2071,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */, D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */,
583335572BF64E77001D90D7 /* MVMCoreUITests */,
D29DF0CD21E404D4003B2FB9 /* Products */, D29DF0CD21E404D4003B2FB9 /* Products */,
D29DF0E421E4F3C7003B2FB9 /* Frameworks */, D29DF0E421E4F3C7003B2FB9 /* Frameworks */,
); );
@ -2025,6 +2081,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */, D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */,
583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2601,6 +2658,24 @@
/* End PBXHeadersBuildPhase section */ /* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
583335552BF64E77001D90D7 /* MVMCoreUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5833355F2BF64E77001D90D7 /* Build configuration list for PBXNativeTarget "MVMCoreUITests" */;
buildPhases = (
583335522BF64E77001D90D7 /* Sources */,
583335532BF64E77001D90D7 /* Frameworks */,
583335542BF64E77001D90D7 /* Resources */,
);
buildRules = (
);
dependencies = (
5833355C2BF64E77001D90D7 /* PBXTargetDependency */,
);
name = MVMCoreUITests;
productName = MVMCoreUITests;
productReference = 583335562BF64E77001D90D7 /* MVMCoreUITests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */ = { D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = D29DF0D421E404D4003B2FB9 /* Build configuration list for PBXNativeTarget "MVMCoreUI" */; buildConfigurationList = D29DF0D421E404D4003B2FB9 /* Build configuration list for PBXNativeTarget "MVMCoreUI" */;
@ -2625,9 +2700,13 @@
D29DF0C321E404D4003B2FB9 /* Project object */ = { D29DF0C321E404D4003B2FB9 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1320; LastUpgradeCheck = 1320;
ORGANIZATIONNAME = "Verizon Wireless"; ORGANIZATIONNAME = "Verizon Wireless";
TargetAttributes = { TargetAttributes = {
583335552BF64E77001D90D7 = {
CreatedOnToolsVersion = 15.4;
};
D29DF0CB21E404D4003B2FB9 = { D29DF0CB21E404D4003B2FB9 = {
CreatedOnToolsVersion = 10.1; CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1010; LastSwiftMigration = 1010;
@ -2650,11 +2729,21 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */, D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */,
583335552BF64E77001D90D7 /* MVMCoreUITests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
583335542BF64E77001D90D7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
583335672BF6DCD0001D90D7 /* UAD_page_model_2.json in Resources */,
583335632BF6509C001D90D7 /* UAD_page_model.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D29DF0CA21E404D4003B2FB9 /* Resources */ = { D29DF0CA21E404D4003B2FB9 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -2673,6 +2762,15 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
583335522BF64E77001D90D7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
583335592BF64E77001D90D7 /* MVMCoreUITests.swift in Sources */,
583335652BF6A5C3001D90D7 /* TestUtils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D29DF0C821E404D4003B2FB9 /* Sources */ = { D29DF0C821E404D4003B2FB9 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -3246,6 +3344,14 @@
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
5833355C2BF64E77001D90D7 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D29DF0CB21E404D4003B2FB9 /* MVMCoreUI */;
targetProxy = 5833355B2BF64E77001D90D7 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
D29DF32821EE8736003B2FB9 /* Localizable.strings */ = { D29DF32821EE8736003B2FB9 /* Localizable.strings */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
@ -3260,6 +3366,49 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
5833355D2BF64E77001D90D7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
5833355E2BF64E77001D90D7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.5;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
D29DF0D221E404D4003B2FB9 /* Debug */ = { D29DF0D221E404D4003B2FB9 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */; baseConfigurationReference = 5878F0A52BD7E6BE00ADE23D /* mvmcoreui_dev.xcconfig */;
@ -3452,6 +3601,15 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
5833355F2BF64E77001D90D7 /* Build configuration list for PBXNativeTarget "MVMCoreUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5833355D2BF64E77001D90D7 /* Debug */,
5833355E2BF64E77001D90D7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D29DF0C621E404D4003B2FB9 /* Build configuration list for PBXProject "MVMCoreUI" */ = { D29DF0C621E404D4003B2FB9 /* Build configuration list for PBXProject "MVMCoreUI" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D29DF0CB21E404D4003B2FB9"
BuildableName = "MVMCoreUI.framework"
BlueprintName = "MVMCoreUI"
ReferencedContainer = "container:MVMCoreUI.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D29DF0CB21E404D4003B2FB9"
BuildableName = "MVMCoreUI.framework"
BlueprintName = "MVMCoreUI"
ReferencedContainer = "container:MVMCoreUI.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -79,4 +79,16 @@ public class ButtonGroupModel: ParentMoleculeModelProtocol {
try container.encodeIfPresent(childWidthValue, forKey: .childWidthValue) try container.encodeIfPresent(childWidthValue, forKey: .childWidthValue)
try container.encodeIfPresent(childWidthPercentage, forKey: .childWidthPercentage) try container.encodeIfPresent(childWidthPercentage, forKey: .childWidthPercentage)
} }
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return buttons.count == model.buttons.count
&& surface == model.surface
&& enabled == model.enabled
&& alignment == model.alignment
&& rowQuantityPhone == model.rowQuantityPhone
&& rowQuantityTablet == model.rowQuantityTablet
&& childWidthValue == model.childWidthValue
&& childWidthPercentage == model.childWidthPercentage
}
} }

View File

@ -192,7 +192,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
return title == model.title return title == model.title
&& enabled == model.enabled && enabled == model.enabled
&& inverted == model.inverted && inverted == model.inverted
&& action.isEqual(to: model.action)
&& accessibilityText == model.accessibilityText && accessibilityText == model.accessibilityText
&& accessibilityIdentifier == model.accessibilityIdentifier && accessibilityIdentifier == model.accessibilityIdentifier
&& accessibilityTraits == model.accessibilityTraits && accessibilityTraits == model.accessibilityTraits
@ -201,6 +200,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
&& size == model.size && size == model.size
&& groupName == model.groupName && groupName == model.groupName
&& width == model.width && width == model.width
&& action.isEqual(to: model.action)
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {

View File

@ -125,5 +125,19 @@ open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
try container.encode(width, forKey: .width) try container.encode(width, forKey: .width)
try container.encode(height, forKey: .height) try container.encode(height, forKey: .height)
try container.encode(enabled, forKey: .enabled) try container.encode(enabled, forKey: .enabled)
try container.encode(inverted, forKey: .inverted)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor
&& disabledColor == model.disabledColor
&& color == model.color
&& degrees == model.degrees
&& lineWidth == model.lineWidth
&& width == model.width
&& height == model.height
&& enabled == model.enabled
&& inverted == model.inverted
} }
} }

View File

@ -61,4 +61,20 @@
case shouldMaskRecordedView case shouldMaskRecordedView
case allowServerParameters case allowServerParameters
} }
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor
&& image == model.image
&& accessibilityText == model.accessibilityText
&& fallbackImage == model.fallbackImage
&& imageFormat == model.imageFormat
&& width == model.width
&& height == model.height
&& contentMode == model.contentMode
&& cornerRadius == model.cornerRadius
&& clipsImage == model.clipsImage
&& allowServerParameters == model.allowServerParameters
&& shouldMaskRecordedView == model.shouldMaskRecordedView
}
} }

View File

@ -53,8 +53,4 @@ open class LabelAttributeActionModel: LabelAttributeModel {
guard super.isEqual(to: model), let model = model as? Self else { return false } guard super.isEqual(to: model), let model = model as? Self else { return false }
return action.isEqual(to: model.action) return action.isEqual(to: model.action)
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
return super.isEqual(to: model)
}
} }

View File

@ -81,4 +81,10 @@
return location == model.location return location == model.location
&& length == model.length && length == model.length
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return location == model.location
&& length == model.length
}
} }

View File

@ -137,20 +137,20 @@ import VDS
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor return backgroundColor == model.backgroundColor
&& text == model.text && text == model.text
&& accessibilityText == model.accessibilityText
&& textColor == model.textColor && textColor == model.textColor
&& fontStyle == model.fontStyle && fontStyle == model.fontStyle
&& fontName == model.fontName && fontName == model.fontName
&& fontSize == model.fontSize && fontSize == model.fontSize
&& textAlignment == model.textAlignment && textAlignment == model.textAlignment
&& attributes.isEqual(to: model.attributes)
&& html == model.html && html == model.html
&& hero == model.hero && hero == model.hero
&& makeWholeViewClickable == model.makeWholeViewClickable && makeWholeViewClickable == model.makeWholeViewClickable
&& numberOfLines == model.numberOfLines && numberOfLines == model.numberOfLines
&& accessibilityText == model.accessibilityText
&& accessibilityTraits == model.accessibilityTraits && accessibilityTraits == model.accessibilityTraits
&& inverted == inverted && inverted == inverted
&& shouldMaskRecordedView == model.shouldMaskRecordedView && shouldMaskRecordedView == model.shouldMaskRecordedView
&& attributes.isEqual(to: model.attributes)
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
@ -162,7 +162,6 @@ import VDS
&& fontName == model.fontName && fontName == model.fontName
&& fontSize == model.fontSize && fontSize == model.fontSize
&& textAlignment == model.textAlignment && textAlignment == model.textAlignment
&& attributes.isVisuallyEquivalent(to: model.attributes)
&& html == model.html && html == model.html
&& hero == model.hero && hero == model.hero
&& makeWholeViewClickable == model.makeWholeViewClickable && makeWholeViewClickable == model.makeWholeViewClickable
@ -170,6 +169,7 @@ import VDS
&& accessibilityText == model.accessibilityText && accessibilityText == model.accessibilityText
&& accessibilityTraits == model.accessibilityTraits && accessibilityTraits == model.accessibilityTraits
&& inverted == inverted && inverted == inverted
&& attributes.isVisuallyEquivalent(to: model.attributes)
} }
} }

View File

@ -53,17 +53,6 @@ public class TitleLockupModel: ParentMoleculeModelProtocol {
&& alignment == model.alignment && alignment == model.alignment
&& inverted == model.inverted && inverted == model.inverted
&& backgroundColor == model.backgroundColor && backgroundColor == model.backgroundColor
&& children.isEqual(to: model.children)
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return textAlignment == model.textAlignment
&& subTitleColor == model.subTitleColor
&& alignment == model.alignment
&& inverted == model.inverted
&& backgroundColor == model.backgroundColor
&& children.isVisuallyEquivalent(to: model.children)
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -68,4 +68,9 @@
var container = encoder.container(keyedBy: CodingKeys.self) var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(line, forKey: .line) try container.encode(line, forKey: .line)
} }
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return line.matchExistence(with: model.line)
}
} }

View File

@ -118,7 +118,7 @@ open class TabsModel: MoleculeModelProtocol {
&& borderLine == model.borderLine && borderLine == model.borderLine
&& minWidth == model.minWidth && minWidth == model.minWidth
&& selectedIndex == model.selectedIndex && selectedIndex == model.selectedIndex
&& tabs.isEqual(to: model.tabs) && tabs == model.tabs
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {

View File

@ -91,15 +91,7 @@ public class TwoButtonViewModel: ParentMoleculeModelProtocol {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor return backgroundColor == model.backgroundColor
&& fillContainer == model.fillContainer && fillContainer == model.fillContainer
&& primaryButton.isEqual(to: model.primaryButton) && primaryButton.matchExistence(with: model.primaryButton)
&& secondaryButton.isEqual(to: model.secondaryButton) && secondaryButton.matchExistence(with: model.secondaryButton)
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor
&& fillContainer == model.fillContainer
&& primaryButton.isVisuallyEquivalent(to: model.primaryButton)
&& secondaryButton.isVisuallyEquivalent(to: model.secondaryButton)
} }
} }

View File

@ -80,6 +80,14 @@ class AccordionListItemModel: MoleculeListItemModel {
try container.encodeModelIfPresent(expandAction, forKey: .expandAction) try container.encodeModelIfPresent(expandAction, forKey: .expandAction)
try container.encodeModelIfPresent(collapseAction, forKey: .collapseAction) try container.encodeModelIfPresent(collapseAction, forKey: .collapseAction)
} }
override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return selected == model.selected
&& hideLineWhenExpanded == model.hideLineWhenExpanded
&& expandAction.matchExistence(with: model.expandAction)
&& collapseAction.matchExistence(with: model.collapseAction)
}
} }
extension AccordionListItemModel: PageBehaviorProtocolRequirer { extension AccordionListItemModel: PageBehaviorProtocolRequirer {

View File

@ -140,7 +140,7 @@ import MVMCore
&& accessibilityText == model.accessibilityText && accessibilityText == model.accessibilityText
&& accessibilityValue == model.accessibilityValue && accessibilityValue == model.accessibilityValue
&& accessibilityTraits == model.accessibilityTraits && accessibilityTraits == model.accessibilityTraits
&& line.isEqual(to: model.line) && line.matchExistence(with: model.line)
&& action.isEqual(to: model.action) && action.isEqual(to: model.action)
} }
@ -153,6 +153,6 @@ import MVMCore
&& accessibilityText == model.accessibilityText && accessibilityText == model.accessibilityText
&& accessibilityValue == model.accessibilityValue && accessibilityValue == model.accessibilityValue
&& accessibilityTraits == model.accessibilityTraits && accessibilityTraits == model.accessibilityTraits
&& line.isVisuallyEquivalent(to: model.line) && line.matchExistence(with: model.line)
} }
} }

View File

@ -82,6 +82,6 @@
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false } guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
return border == border return border == model.border
} }
} }

View File

@ -52,14 +52,4 @@ import MVMCore
try container.encode(moleculeName, forKey: .moleculeName) try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeModel(molecule, forKey: .molecule) try container.encodeModel(molecule, forKey: .molecule)
} }
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return molecule.isEqual(to: model)
}
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
return molecule.isVisuallyEquivalent(to: model.molecule)
}
} }

View File

@ -65,11 +65,4 @@
&& percent == model.percent && percent == model.percent
&& gone == model.gone && gone == model.gone
} }
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
return spacing == model.spacing
&& percent == model.percent
&& gone == model.gone
}
} }

View File

@ -20,7 +20,7 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol {
private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]? private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]?
public var children: [MoleculeModelProtocol] { public var children: [MoleculeModelProtocol] {
return molecules.flatMap { $0 } return [tabs] + molecules.flatMap { $0 }
} }
public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? { public func replaceChildMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> MoleculeModelProtocol? {
@ -94,16 +94,7 @@ public class TabsListItemModel: ListItemModel, ParentMoleculeModelProtocol {
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool { public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false } guard super.isEqual(to: model), let model = model as? Self else { return false }
return tabs.isEqual(to: model.tabs) return molecules.count == model.molecules.count
&& molecules.count == model.molecules.count
&& zip(molecules, model.molecules).allSatisfy({ $0.0.isEqual(to: $0.1) })
}
public override func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard super.isVisuallyEquivalent(to: model), let model = model as? Self else { return false }
return tabs.isVisuallyEquivalent(to: model.tabs)
&& molecules.count == model.molecules.count
&& zip(molecules, model.molecules).allSatisfy({ $0.0.isVisuallyEquivalent(to: $0.1) })
} }
} }

View File

@ -73,6 +73,25 @@ public class NavigationImageButtonModel: NavigationButtonModelProtocol, Molecule
try container.encodeIfPresent(imageRenderingMode, forKey: .imageRenderingMode) try container.encodeIfPresent(imageRenderingMode, forKey: .imageRenderingMode)
} }
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return true }
return backgroundColor == model.backgroundColor
&& accessibilityIdentifier == model.accessibilityIdentifier
&& image == model.image
&& accessibilityText == model.accessibilityText
&& imageRenderingMode == model.imageRenderingMode
&& action.isEqual(to: action)
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return true }
return backgroundColor == model.backgroundColor
&& accessibilityIdentifier == model.accessibilityIdentifier
&& image == model.image
&& accessibilityText == model.accessibilityText
&& imageRenderingMode == model.imageRenderingMode
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Method // MARK: - Method
//-------------------------------------------------- //--------------------------------------------------

View File

@ -64,8 +64,7 @@ open class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtoco
public func isEqual(to model: any ModelComparisonProtocol) -> Bool { public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return molecule.isEqual(to: model.molecule) return backgroundColor == model.backgroundColor
&& backgroundColor == model.backgroundColor
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {

View File

@ -110,6 +110,9 @@ public class EyebrowHeadlineBodyLinkModel: ParentMoleculeModelProtocol {
public func isEqual(to model: any ModelComparisonProtocol) -> Bool { public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor return backgroundColor == model.backgroundColor
&& children.isEqual(to: model.children) && eyebrow.matchExistence(with: model.eyebrow)
&& headline.matchExistence(with: model.headline)
&& body.matchExistence(with: model.body)
&& link.matchExistence(with: model.link)
} }
} }

View File

@ -96,18 +96,8 @@ open class HeadlineBodyModel: ParentMoleculeModelProtocol {
public func isEqual(to model: any ModelComparisonProtocol) -> Bool { public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return headline.isEqual(to: model.headline) return style == style
&& body.isEqual(to: model.body) && backgroundColor == model.backgroundColor
&& style == style
&& backgroundColor == backgroundColor
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return headline.isVisuallyEquivalent(to: model.headline)
&& body.isVisuallyEquivalent(to: model.body)
&& style == style
&& backgroundColor == backgroundColor
} }
} }

View File

@ -174,7 +174,7 @@ open class Carousel: View {
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)") MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
if let originalModel, carouselModel.isVisuallyEquivalent(to: originalModel) { if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel) {
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems. // Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...") MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate) FormValidator.setupValidation(for: carouselModel, delegate: delegateObject?.formHolderDelegate)

View File

@ -175,14 +175,12 @@ import UIKit
public func isEqual(to model: any ModelComparisonProtocol) -> Bool { public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return backgroundColor == backgroundColor return backgroundColor == backgroundColor
&& molecules.isEqual(to: model.molecules)
&& spacing == model.spacing && spacing == model.spacing
&& border == model.border && border == model.border
&& loop == model.loop && loop == model.loop
&& height == model.height && height == model.height
&& itemWidthPercent == model.itemWidthPercent && itemWidthPercent == model.itemWidthPercent
&& itemAlignment == model.itemAlignment && itemAlignment == model.itemAlignment
&& pagingMolecule.isEqual(to: model.pagingMolecule)
&& paging == model.paging && paging == model.paging
&& useHorizontalMargins == model.useHorizontalMargins && useHorizontalMargins == model.useHorizontalMargins
&& leftPadding == model.leftPadding && leftPadding == model.leftPadding
@ -193,6 +191,8 @@ import UIKit
&& groupName == model.groupName && groupName == model.groupName
&& enabled == model.enabled && enabled == model.enabled
&& readOnly == model.readOnly && readOnly == model.readOnly
&& molecules.count == model.molecules.count
&& pagingMolecule.matchExistence(with: model.pagingMolecule)
} }
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool { public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
@ -212,8 +212,6 @@ import UIKit
&& baseValue == model.baseValue && baseValue == model.baseValue
&& enabled == model.enabled && enabled == model.enabled
&& readOnly == model.readOnly && readOnly == model.readOnly
&& pagingMolecule.isVisuallyEquivalent(to: model.pagingMolecule)
&& molecules.isVisuallyEquivalent(to: model.molecules)
} }
} }

View File

@ -82,16 +82,8 @@
public func isEqual(to model: any ModelComparisonProtocol) -> Bool { public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor return backgroundColor == model.backgroundColor
&& molecules.isEqual(to: model.molecules)
&& axis == model.axis
&& spacing == model.spacing
}
public func isVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return backgroundColor == model.backgroundColor
&& molecules.isVisuallyEquivalent(to: model.molecules)
&& axis == model.axis && axis == model.axis
&& spacing == model.spacing && spacing == model.spacing
&& molecules.count == model.molecules.count
} }
} }

View File

@ -11,7 +11,7 @@ import Foundation
public protocol MoleculeModelComparisonProtocol: ModelComparisonProtocol { public protocol MoleculeModelComparisonProtocol: ModelComparisonProtocol {
/** /**
True if there are no visual differences between models. Shallow check if there are no visual differences between models.
By default if the models are equal then they are visually equivalent. However, if there are parts of models that can be upddated without a UI update, this could be subset of properties. By default if the models are equal then they are visually equivalent. However, if there are parts of models that can be upddated without a UI update, this could be subset of properties.
**/ **/
@ -25,6 +25,16 @@ extension MoleculeModelComparisonProtocol {
} }
} }
public extension MoleculeModelComparisonProtocol where Self: ParentModelProtocol {
func isDeeplyVisuallyEquivalent(to model: any MoleculeModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return !findFirst(in: model, failing: { mine, theirs in
guard let mine = mine as? MoleculeModelComparisonProtocol, let theirs = theirs as? MoleculeModelComparisonProtocol else { return false }
return mine.isVisuallyEquivalent(to: theirs)
}).matched
}
}
public extension Optional { public extension Optional {
/// Checks if the curent model is equal to another model. /// Checks if the curent model is equal to another model.

View File

@ -21,7 +21,8 @@ public extension MoleculeModelProtocol {
func isEqual(to model: ModelProtocol) -> Bool { func isEqual(to model: ModelProtocol) -> Bool {
guard let model = model as? Self else { return false } guard let model = model as? Self else { return false }
return id == model.id return moleculeName == model.moleculeName
&& backgroundColor == model.backgroundColor
} }
var debugDescription: String { var debugDescription: String {

View File

@ -67,16 +67,6 @@ public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelP
public extension ParentMoleculeModelProtocol { public extension ParentMoleculeModelProtocol {
func isEqual(to model: any ModelProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.children.isEqual(to: model.children)
}
func areVisuallyEquivalent(to model: any ModelProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.children.isVisuallyEquivalent(to: model.children)
}
func reduceDepthFirstTraverse<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result { func reduceDepthFirstTraverse<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result {
var result = initialResult var result = initialResult
if (options == .parentFirst) { if (options == .parentFirst) {
@ -122,53 +112,66 @@ public extension ParentMoleculeModelProtocol {
} }
} }
extension ParentModelProtocol { public extension ParentModelProtocol {
public typealias DeepCompareResult = (matched: Bool, myChild: ModelProtocol?, theirChild: ModelProtocol?) typealias DeepCompareResult = (matched: Bool, mine: ModelProtocol?, theirs: ModelProtocol?)
public typealias ModelPair = (mine: ModelProtocol, theirs: ModelProtocol) typealias ModelPair = (mine: ModelProtocol, theirs: ModelProtocol)
public func deepEquals(to model: any ModelProtocol) -> DeepCompareResult { func deepEquals(to model: any ModelProtocol) -> Bool {
guard let model = model as? ParentModelProtocol else { return (false, self, model) } guard let model = model as? ParentModelProtocol else { return false }
return findFirst(in: model) { $0.isEqual(to: $1) } return !findFirst(in: model, failing: { $0.isEqual(to: $1) }).matched
} }
func findFirst(in anotherParent: ParentModelProtocol, where test: (ModelProtocol, ModelProtocol)->Bool) -> DeepCompareResult { func findFirst(in anotherParent: ParentModelProtocol, failing test: (ModelProtocol, ModelProtocol)->Bool, options: TreeTraversalOptions = .childFirst) -> DeepCompareResult {
guard test(self, anotherParent) else { return (false, myChild: self, theirChild: anotherParent)} guard options != .parentFirst, test(self, anotherParent) else { return (true, mine: self, theirs: anotherParent)}
let myChildren = children let myChildren = children
let theirChildren = anotherParent.children let theirChildren = anotherParent.children
guard myChildren.count == theirChildren.count else { return (false, myChild: self, theirChild: self) } guard myChildren.count == theirChildren.count else { return (true, mine: self, theirs: anotherParent) }
for index in myChildren.indices { for index in myChildren.indices {
if let myChild = myChildren[index] as? ParentModelProtocol { if let myChild = myChildren[index] as? ParentModelProtocol {
if let theirChild = theirChildren[index] as? ParentModelProtocol { if let theirChild = theirChildren[index] as? ParentModelProtocol {
let result = myChild.findFirst(in: theirChild, where: test) let result = myChild.findFirst(in: theirChild, failing: test)
guard result.0 else { return result } guard !result.0 else { return result }
} else { } else {
return (false, myChild: myChild, theirChild: theirChildren[index]) return (true, mine: myChild, theirs: theirChildren[index])
} }
} else if !test(myChildren[index], theirChildren[index]) { } else if !test(myChildren[index], theirChildren[index]) {
return (false, myChild: myChildren[index], theirChild: theirChildren[index]) return (true, mine: myChildren[index], theirs: theirChildren[index])
} }
} }
return (true, nil, nil) guard options == .childFirst, test(self, anotherParent) else { return (true, mine: self, theirs: anotherParent)}
return (false, nil, nil)
}
func findAllTheirsNotEqual<T>(against anotherParent: ParentModelProtocol) -> [T] {
return deepCompare(against: anotherParent) { $0.isEqual(to: $1) }.compactMap { $0.theirs as? T }
}
func findAllNotEqual(against anotherParent: ParentModelProtocol) -> [ModelPair] {
return deepCompare(against: anotherParent) { $0.isEqual(to: $1) }
} }
func deepCompare(against anotherParent: ParentModelProtocol, where test: (ModelProtocol, ModelProtocol)->Bool) -> [ModelPair] { func deepCompare(against anotherParent: ParentModelProtocol, where test: (ModelProtocol, ModelProtocol)->Bool) -> [ModelPair] {
guard test(self, anotherParent) else { return [(self, anotherParent)]} var allDiffs = [ModelPair]()
let myChildren = children let myChildren = children
let theirChildren = anotherParent.children let theirChildren = anotherParent.children
guard myChildren.count == theirChildren.count else { return [(self, anotherParent)] } guard myChildren.count == theirChildren.count else { return [(self, anotherParent)] }
var allDiffs = [ModelPair]() if !test(self, anotherParent) {
allDiffs.append((self, anotherParent))
}
for index in myChildren.indices { for index in myChildren.indices {
if let myChild = myChildren[index] as? ParentModelProtocol, if let myChild = myChildren[index] as? ParentModelProtocol,
let theirChild = theirChildren[index] as? ParentModelProtocol { let theirChild = theirChildren[index] as? ParentModelProtocol {
let childDiffs = myChild.deepCompare(against: theirChild, where: test) as [ModelPair] let childDiffs = myChild.deepCompare(against: theirChild, where: test)
allDiffs.append(contentsOf: childDiffs) allDiffs.append(contentsOf: childDiffs)
} else if !test(myChildren[index], theirChildren[index]) { } else if !test(myChildren[index], theirChildren[index]) {
allDiffs.append((myChildren[index], theirChildren[index])) allDiffs.append((myChildren[index], theirChildren[index]))

View File

@ -7,7 +7,7 @@
// //
public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol { public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, MoleculeTreeTraversalProtocol, ParentModelProtocol, MoleculeModelComparisonProtocol {
var template: String { get } var template: String { get }
var rootMolecules: [MoleculeModelProtocol] { get } var rootMolecules: [MoleculeModelProtocol] { get }
/// Page rendering ID. Unique betwen JSON parses. /// Page rendering ID. Unique betwen JSON parses.

View File

@ -121,4 +121,18 @@ import Foundation
try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex) try container.encodeIfPresent(tabBarIndex, forKey: .tabBarIndex)
try container.encode(shouldMaskScreenWhileRecording, forKey: .shouldMaskScreenWhileRecording) try container.encode(shouldMaskScreenWhileRecording, forKey: .shouldMaskScreenWhileRecording)
} }
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return pageType == model.pageType
&& backgroundColor == model.backgroundColor
&& screenHeading == model.screenHeading
&& formRules?.count ?? 0 == model.formRules?.count ?? 0
&& navigationBar.matchExistence(with: model.navigationBar)
&& tabBarHidden == model.tabBarHidden
&& hideLeftPanel == model.hideLeftPanel
&& hideRightPanel == model.hideRightPanel
&& tabBarIndex == model.tabBarIndex
&& shouldMaskScreenWhileRecording == model.shouldMaskScreenWhileRecording
}
} }

View File

@ -100,4 +100,14 @@
try container.encodeIfPresent(footerlessSpacerColor, forKey: .footerlessSpacerColor) try container.encodeIfPresent(footerlessSpacerColor, forKey: .footerlessSpacerColor)
try container.encodeIfPresent(footerlessSpacerHeight, forKey: .footerlessSpacerHeight) try container.encodeIfPresent(footerlessSpacerHeight, forKey: .footerlessSpacerHeight)
} }
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return line.matchExistence(with: model.line)
&& scrollToRowIndex == model.scrollToRowIndex
&& singleCellSelection == model.singleCellSelection
&& footerlessSpacerColor == model.footerlessSpacerColor
&& footerlessSpacerHeight == model.footerlessSpacerHeight
&& molecules?.count ?? 0 == model.molecules?.count ?? 0
}
} }

View File

@ -270,6 +270,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
return IndexPath(row: $0, section: 0) return IndexPath(row: $0, section: 0)
} }
debugLog("Refreshing rows \(indexPaths.map { $0.row })")
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
// All rows should have been layed out already on the first newDataBuildScreen reload with the getMoleculeInfoList call. Therefore, we can be safe to assume the top level cell configuration will not be modified and only the child content will be updated allowing us to levearage this more efficient method. // All rows should have been layed out already on the first newDataBuildScreen reload with the getMoleculeInfoList call. Therefore, we can be safe to assume the top level cell configuration will not be modified and only the child content will be updated allowing us to levearage this more efficient method.
tableView.reconfigureRows(at: indexPaths) tableView.reconfigureRows(at: indexPaths)

View File

@ -77,4 +77,12 @@
try container.encodeIfPresent(anchorFooter, forKey: .anchorFooter) try container.encodeIfPresent(anchorFooter, forKey: .anchorFooter)
try container.encodeModelIfPresent(footer, forKey: .footer) try container.encodeModelIfPresent(footer, forKey: .footer)
} }
public override func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard super.isEqual(to: model), let model = model as? Self else { return false }
return anchorHeader == model.anchorHeader
&& anchorFooter == model.anchorFooter
&& header.matchExistence(with: model.header)
&& footer.matchExistence(with: model.footer)
}
} }

View File

@ -40,7 +40,7 @@ import MVMCore
public var needsUpdateUI = false public var needsUpdateUI = false
private var observingForResponses: NSObjectProtocol? private var observingForResponses: NSObjectProtocol?
private var initialLoadFinished = false private var initialLoadFinished = false
private var isFirstRender = true public var isFirstRender = true
public var previousScreenSize = CGSize.zero public var previousScreenSize = CGSize.zero
public var selectedField: UIView? public var selectedField: UIView?
@ -259,7 +259,12 @@ import MVMCore
if let updatedMolecules = behavior.onPageNew(rootMolecules: newTemplateModel.rootMolecules, delegateObjectIVar) { if let updatedMolecules = behavior.onPageNew(rootMolecules: newTemplateModel.rootMolecules, delegateObjectIVar) {
updatedMolecules.forEach { molecule in updatedMolecules.forEach { molecule in
if let replaced = try? newTemplateModel.replaceChildMolecule(with: molecule) { if let replaced = try? newTemplateModel.replaceChildMolecule(with: molecule) {
if !replaced.isEqual(to: 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 {
let diffs: [MoleculeModelProtocol] = replaced.findAllTheirsNotEqual(against: molecule)
debugLog("Behavior updated \(diffs) in template model.")
behaviorUpdatedModels.append(contentsOf: diffs)
} else if !replaced.isEqual(to: molecule) {
debugLog("Behavior updated \(molecule) in template model.") 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.) behaviorUpdatedModels.append(molecule) // Need to specifically trace molecule updates here as replacements are modifying the original tree. (We don't have a deep copy.)
} }
@ -289,7 +294,7 @@ import MVMCore
!new.isEqual(to: old) !new.isEqual(to: old)
} }
debugLog("Page molecule updates\n\(diffs.map {"\($0.mine) vs. \($0.theirs)"}.joined(separator: "\n"))") debugLog("Page molecule updates\n\(diffs.map {"\($0.mine) vs. \($0.theirs)"}.joined(separator: "\n"))")
pageUpdatedModels = diffs.compactMap { $0.mine as? MoleculeModelProtocol } pageUpdatedModels = diffs.compactMap { $0.theirs as? MoleculeModelProtocol }
} }
let allUpdatedMolecules = isFirstRender ? [] : behaviorUpdatedModels + pageUpdatedModels let allUpdatedMolecules = isFirstRender ? [] : behaviorUpdatedModels + pageUpdatedModels
@ -303,7 +308,7 @@ import MVMCore
debugLog("Performing full page render...") debugLog("Performing full page render...")
updateUI() updateUI()
} else { } else {
debugLog("Updating \(allUpdatedMolecules) molecules...") debugLog("Performing partial render of \(allUpdatedMolecules) molecules...")
updateUI(for: allUpdatedMolecules) updateUI(for: allUpdatedMolecules)
} }

View File

@ -0,0 +1,807 @@
{
"template": "list",
"analyticsData": {
"vzdl.user.customerBusiness": "joint_cust",
"vzdl.target.engagement.intent": "account management",
"vzdl.page.feedCardImpression": "L1|P1|greetingSection|Edit profile & settings^L1|P1|mobileAccountSection|BillsTile^L1|P2|mobileAccountSection|UsageTile^L1|P3|mobileAccountSection|OrdersTile^L1|P4|mobileAccountSection|PlansTile^L1|P5|mobileAccountSection|Services_perksTile^L1|P6|mobileAccountSection|Account_activityTile^L1|P1|fivegHomeAccountSection|BillsTile^L1|P2|fivegHomeAccountSection|OrdersTile^L1|P3|fivegHomeAccountSection|PlansTile^L1|P4|fivegHomeAccountSection|Account_activityTile^L1|P5|fivegHomeAccountSection|Services_perksTile^L1|P1|fiosAccountSection|BillsTile^L1|P2|fiosAccountSection|PlansTile^L1|P3|fiosAccountSection|SupportTile^L1|P4|fiosAccountSection|Account_activityTile^L1|P5|fiosAccountSection|Home_offersTile",
"vzdl.user.id": "a3b00d5af5c7b5d26d017023f12dfa791dba533a33a3ed264c8c98237ae5902f",
"vzdl.user.account": "c0d793b51b000cce8d6ec062a71a224d0faaf8e50c36ebd31240539b31aca001",
"vzdl.user.accountType": "postpaid",
"vzdl.events.uadcardserved": "1",
"vzdl.page.channelSession": "4154e060-85ae-4e8f-a371-fb00c618797a",
"vzdl.page.siteSection": "mva_atomic",
"vzdl.page.sourceChannel": "mva",
"vzdl.page.flow": "account overview",
"vzdl.env.businessUnit": "wireless",
"vzdl.page.displayChannel": "mva",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.id": "atomicAccountLanding"
},
"molecules": [
{
"useVerticalMargins": false,
"backgroundColor": "black",
"moleculeName": "tabsListItem",
"tabs": {
"borderLine": false,
"moleculeName": "tabs",
"tabs": [
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "Mobile"
}
},
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "5G Home"
}
},
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "Fios"
}
}
],
"style": "dark",
"selectedIndex": 1
},
"molecules": [
[
{
"id": "priorityTiles",
"line": {
"type": "none"
},
"backgroundColor": "black",
"moleculeName": "listItem",
"molecule": {
"moleculeName": "carousel",
"height": 160,
"molecules": [
{
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"topPadding": 16,
"useHorizontalMargins": false,
"cornerRadius": 8,
"useVerticalMargins": true,
"backgroundColor": "white",
"bottomPadding": 16
}
],
"itemWidthPercent": 100,
"accessibilityText": "carousel",
"spacing": 12,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20,
"inverted": true
},
"rightPadding": 16,
"leftPadding": 16
},
"gone": true
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Bills",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P1|mobileAccountSection|BillsTile",
"vzdl.page.linkName": "Mobile|Bills",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/bill/overview/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Usage",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P2|mobileAccountSection|UsageTile",
"vzdl.page.linkName": "Mobile|Usage",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"sourcePage": "plansAndDeviceLanding",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobileFirstSS",
"pageType": "dataUsageDetails"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Orders",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P3|mobileAccountSection|OrdersTile",
"vzdl.page.linkName": "Mobile|Orders",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/orders/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Plans",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P4|mobileAccountSection|PlansTile",
"vzdl.page.linkName": "Mobile|Plans",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/cpc/mvm?pmd=y&tabbar=true",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Services & perks",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P5|mobileAccountSection|Services_perksTile",
"vzdl.page.linkName": "Mobile|Services & perks",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/products/producthub/home",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"id": "notification_count",
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Account activity",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P6|mobileAccountSection|Account_activityTile",
"vzdl.page.linkName": "Mobile|Account activity",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/optg/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"moleculeName": "listItem",
"line": {
"moleculeName": "line",
"type": "none"
},
"molecule": {
"moleculeName": "stack",
"axis": "horizontal",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Devices",
"fontStyle": "BoldTitleMedium"
},
"horizontalAlignment": "leading"
},
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "link",
"action": {
"analyticsData": {
"vzdl.page.id": "atomicAccountLanding",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.displayChannel": "mva",
"vzdl.page.linkName": "Manage all devices",
"vzdl.page.sourceChannel": "mva"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/devices/landing/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
},
"title": "Manage all devices"
},
"horizontalAlignment": "trailing"
}
]
}
},
{
"moleculeName": "listItem",
"style": "none",
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"moleculeName": "carouselItem",
"molecule": {
"molecule": {
"moleculeName": "stack",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Add a line and get select phones on us",
"fontStyle": "RegularMicro"
}
},
{
"moleculeName": "stackItem",
"molecule": {
"fontStyle": "RegularMicro",
"numberOfLines": 1,
"moleculeName": "label",
"text": "\n\n\n\n\n"
}
},
{
"moleculeName": "stackItem",
"molecule": {
"size": "small",
"title": "Shop",
"moleculeName": "button",
"action": {
"extraParameters": {
"browserUrl": "sales/next/shop.html?flow=AAL&fromAcct=true&isShopFlow=true&entrypoint=account",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
},
"verticalAlignment": "trailing",
"horizontalAlignment": "center"
},
{
"moleculeName": "stackItem",
"molecule": {
"size": "small",
"title": "Bring my own",
"moleculeName": "link",
"action": {
"extraParameters": {
"browserUrl": "sales/digital/byod.html?flow=NSO&fromAcct=true&isShopFlow=true&entrypoint=account",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
},
"horizontalAlignment": "center"
}
]
},
"verticalAlignment": "fill",
"topPadding": 12,
"image": {
"image": "https://ss7.vzw.com/is/image/VerizonWireless/background-image-mobile?&scl=2",
"moleculeName": "image"
},
"moleculeName": "bgImageContainer",
"bottomPadding": 12
},
"cornerRadius": 8
}
],
"itemWidthPercent": 48,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
},
{
"line": {
"moleculeName": "line",
"type": "none"
},
"moleculeName": "listItem",
"style": "shortDivider",
"molecule": {
"moleculeName": "label",
"text": "Make the most of your plans",
"fontStyle": "BoldTitleMedium"
}
},
{
"id": "forYouTiles",
"style": "none",
"moleculeName": "listItem",
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
}
],
"itemWidthPercent": 45,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
},
{
"line": {
"moleculeName": "line",
"type": "none"
},
"moleculeName": "listItem",
"style": "shortDivider",
"molecule": {
"moleculeName": "stack",
"axis": "horizontal",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Discover more",
"fontStyle": "BoldTitleMedium"
},
"verticalAlignment": "center",
"horizontalAlignment": "leading"
},
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "link",
"action": {
"analyticsData": {
"vzdl.page.id": "atomicAccountLanding",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.displayChannel": "mva",
"vzdl.page.linkName": "Mobile|Shop all",
"vzdl.page.sourceChannel": "mva"
},
"extraParameters": {
"locale": "EN",
"browserUrl": "sales/digital/shoplanding.html?isShopFlow=true&entrypoint=tabbar",
"requestFrom": "Shop"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
},
"title": "Shop all"
},
"verticalAlignment": "center",
"horizontalAlignment": "trailing"
}
]
}
},
{
"id": "discoverTiles",
"style": "none",
"moleculeName": "listItem",
"bottomPadding": 32,
"useVerticalMargins": true,
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
}
],
"itemWidthPercent": 45,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
}
]
]
}
],
"backgroundColor": "black",
"hab": {
"configuration": "single",
"inverted": false
},
"cache": true,
"header": {
"useVerticalMargins": true,
"backgroundColor": "black",
"moleculeName": "stack",
"bottomPadding": 12,
"molecules": [
{
"moleculeName": "stackItem",
"id": "greetingSection",
"molecule": {
"fontStyle": "RegularTitleMedium",
"textColor": "#FFFFFF",
"moleculeName": "label",
"text": "Hi Lebowski."
}
},
{
"moleculeName": "stackItem",
"molecule": {
"enabledColor": "white",
"title": "Edit profile & settings",
"moleculeName": "link",
"inverted": true,
"action": {
"actionType": "openPage",
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P1|greetingSection|Edit profile & settings",
"vzdl.page.linkName": "Edit profile & settings",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"pageType": "oneVzIdSettingsLanding",
"presentationStyle": "push",
"requestURL": "https://mobile-exp-qa2.vzw.com/mobile/nsa/nos/gw/launchapp/l2/webview",
"tryToReplaceFirst": false,
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/acct/unifiedprofile/overview"
}
}
}
}
]
},
"behaviors": [
{
"moleculeIds": [
"priorityTiles",
"forYouTiles",
"discoverTiles",
"priorityTiles5G",
"forYouTiles5G",
"discoverTiles5G",
"notification_count",
"priorityTilesFIOS",
"phoneServiceFIOS",
"internetServiceFIOS",
"tvServiceFIOS",
"forYouTilesFIOS",
"discoverTilesFIOS",
"FiosBills",
"FiosSubBills",
"FiosPlans",
"FiosSupport",
"notification_count_fios",
"greetingSection",
"FiosHomeOffers",
"priorityTilesLTE",
"forYouTilesLTE",
"discoverTilesLTE"
],
"behaviorName": "replaceMoleculeBehavior"
},
{
"refreshOnShown": true,
"moduleIds": [
"priorityTiles",
"priorityTiles5G",
"priorityTilesLTE"
],
"refreshOnFirstLoad": true,
"behaviorName": "pollingBehavior",
"runWhileHidden": false,
"refreshInterval": 10,
"refreshAction": {
"background": true,
"extraParameters": {
"category": "AccountOverview",
"channel": "VZW-MFA",
"locale": "EN",
"alwaysUseFallbackResponse": false,
"platform": "IOS",
"isLTE": false,
"requestFrom": "UAD",
"pageContext": "Account_Overview",
"isFWA": true
},
"actionType": "openPage",
"requestURL": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/gw/udb/topCardsMVA",
"pageType": "topCardsMVA"
}
},
{
"refreshOnShown": true,
"moduleIds": [
"priorityTiles",
"priorityTiles5G",
"priorityTilesLTE"
],
"refreshOnFirstLoad": true,
"behaviorName": "pollingBehavior",
"runWhileHidden": false,
"refreshInterval": 10,
"refreshAction": {
"background": true,
"extraParameters": {
"category": "AccountOverview",
"channel": "VZW-MFA",
"locale": "EN",
"alwaysUseFallbackResponse": false,
"platform": "IOS",
"isLTE": false,
"requestFrom": "UAD",
"pageContext": "Account_Overview",
"isFWA": true
},
"actionType": "openPage",
"requestURL": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/gw/udb/bottomCardsMVA",
"pageType": "bottomCardsMVA"
}
}
],
"pageType": "atomicAccountLanding",
"loggedInMdn": "2815434851",
"presentationStyle": "root",
"footerlessSpacerColor": "white",
"tabBarIndex": 1,
"navigationBar": {
"style": "dark",
"moleculeName": "navigationBar",
"additionalLeftButtons": [
{
"accessibilityText": "Verizon logo button, tap anytime to scroll to top of page",
"moleculeName": "navigationImageButton",
"image": "nav_vz_mark",
"imageRenderingMode": "alwaysOriginal",
"action": {
"actionType": "noop"
}
}
],
"additionalRightButtons": [
{
"accessibilityText": "Stores",
"moleculeName": "navigationImageButton",
"image": "nav_stores_white",
"action": {
"analyticsData": {
"vzdl.page.linkName": "global tab nav:stores"
},
"title": "Stores",
"actionType": "openPage",
"pageType": "rtlStoreJourney"
}
}
]
},
"footerlessSpacerHeight": 0
}

View File

@ -0,0 +1,807 @@
{
"template": "list",
"analyticsData": {
"vzdl.user.customerBusiness": "joint_cust",
"vzdl.target.engagement.intent": "account management",
"vzdl.page.feedCardImpression": "L1|P1|greetingSection|Edit profile & settings^L1|P1|mobileAccountSection|BillsTile^L1|P2|mobileAccountSection|UsageTile^L1|P3|mobileAccountSection|OrdersTile^L1|P4|mobileAccountSection|PlansTile^L1|P5|mobileAccountSection|Services_perksTile^L1|P6|mobileAccountSection|Account_activityTile^L1|P1|fivegHomeAccountSection|BillsTile^L1|P2|fivegHomeAccountSection|OrdersTile^L1|P3|fivegHomeAccountSection|PlansTile^L1|P4|fivegHomeAccountSection|Account_activityTile^L1|P5|fivegHomeAccountSection|Services_perksTile^L1|P1|fiosAccountSection|BillsTile^L1|P2|fiosAccountSection|PlansTile^L1|P3|fiosAccountSection|SupportTile^L1|P4|fiosAccountSection|Account_activityTile^L1|P5|fiosAccountSection|Home_offersTile",
"vzdl.user.id": "a3b00d5af5c7b5d26d017023f12dfa791dba533a33a3ed264c8c98237ae5902f",
"vzdl.user.account": "c0d793b51b000cce8d6ec062a71a224d0faaf8e50c36ebd31240539b31aca001",
"vzdl.user.accountType": "postpaid",
"vzdl.events.uadcardserved": "1",
"vzdl.page.channelSession": "4154e060-85ae-4e8f-a371-fb00c618797a",
"vzdl.page.siteSection": "mva_atomic",
"vzdl.page.sourceChannel": "mva",
"vzdl.page.flow": "account overview",
"vzdl.env.businessUnit": "wireless",
"vzdl.page.displayChannel": "mva",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.id": "atomicAccountLanding"
},
"molecules": [
{
"useVerticalMargins": false,
"backgroundColor": "black",
"moleculeName": "tabsListItem",
"tabs": {
"borderLine": false,
"moleculeName": "tabs",
"tabs": [
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "Mobile"
}
},
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "5G Home"
}
},
{
"label": {
"fontStyle": "BoldTitleSmall",
"textColor": "white",
"moleculeName": "label",
"text": "Fios"
}
}
],
"style": "dark",
"selectedIndex": 1
},
"molecules": [
[
{
"id": "priorityTiles",
"line": {
"type": "none"
},
"backgroundColor": "black",
"moleculeName": "listItem",
"molecule": {
"moleculeName": "carousel",
"height": 160,
"molecules": [
{
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"topPadding": 16,
"useHorizontalMargins": false,
"cornerRadius": 8,
"useVerticalMargins": true,
"backgroundColor": "white",
"bottomPadding": 16
}
],
"itemWidthPercent": 100,
"accessibilityText": "carousel",
"spacing": 12,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20,
"inverted": true
},
"rightPadding": 16,
"leftPadding": 16
},
"gone": true
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Bills",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P1|mobileAccountSection|BillsTile",
"vzdl.page.linkName": "Mobile|Bills",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/bill/overview/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Usage",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P2|mobileAccountSection|UsageTile",
"vzdl.page.linkName": "Mobile|Usage",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "2",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"sourcePage": "plansAndDeviceLanding",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobileFirstSS",
"pageType": "dataUsageDetails"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Orders",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P3|mobileAccountSection|OrdersTile",
"vzdl.page.linkName": "Mobile|Orders",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/orders/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Plans",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P4|mobileAccountSection|PlansTile",
"vzdl.page.linkName": "Mobile|Plans",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/cpc/mvm?pmd=y&tabbar=true",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Services & perks",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P5|mobileAccountSection|Services_perksTile",
"vzdl.page.linkName": "Mobile|Services & perks",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/products/producthub/home",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"id": "notification_count",
"headlineBody": {
"moleculeName": "headlineBody",
"headline": {
"moleculeName": "label",
"text": "Account activity",
"textColor": "#000000"
}
},
"backgroundColor": "#FFFFFF",
"moleculeName": "list1CFWBdy",
"action": {
"actions": [
{
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P6|mobileAccountSection|Account_activityTile",
"vzdl.page.linkName": "Mobile|Account activity",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/optg/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
],
"actionType": "actions"
}
},
{
"moleculeName": "listItem",
"line": {
"moleculeName": "line",
"type": "none"
},
"molecule": {
"moleculeName": "stack",
"axis": "horizontal",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Devices",
"fontStyle": "BoldTitleMedium"
},
"horizontalAlignment": "leading"
},
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "link",
"action": {
"analyticsData": {
"vzdl.page.id": "atomicAccountLanding",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.displayChannel": "mva",
"vzdl.page.linkName": "Manage all devices",
"vzdl.page.sourceChannel": "mva"
},
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/devices/landing/",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
},
"title": "Manage all devices"
},
"horizontalAlignment": "trailing"
}
]
}
},
{
"moleculeName": "listItem",
"style": "none",
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"moleculeName": "carouselItem",
"molecule": {
"molecule": {
"moleculeName": "stack",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Add a line and get select phones on us",
"fontStyle": "RegularMicro"
}
},
{
"moleculeName": "stackItem",
"molecule": {
"fontStyle": "RegularMicro",
"numberOfLines": 1,
"moleculeName": "label",
"text": "\n\n\n\n\n"
}
},
{
"moleculeName": "stackItem",
"molecule": {
"size": "small",
"title": "Shop",
"moleculeName": "button",
"action": {
"extraParameters": {
"browserUrl": "sales/next/shop.html?flow=AAL&fromAcct=true&isShopFlow=true&entrypoint=account",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
},
"verticalAlignment": "trailing",
"horizontalAlignment": "center"
},
{
"moleculeName": "stackItem",
"molecule": {
"size": "small",
"title": "Bring my own",
"moleculeName": "link",
"action": {
"extraParameters": {
"browserUrl": "sales/digital/byod.html?flow=NSO&fromAcct=true&isShopFlow=true&entrypoint=account",
"requestFrom": "UAD"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
}
},
"horizontalAlignment": "center"
}
]
},
"verticalAlignment": "fill",
"topPadding": 12,
"image": {
"image": "https://ss7.vzw.com/is/image/VerizonWireless/background-image-mobile?&scl=2",
"moleculeName": "image"
},
"moleculeName": "bgImageContainer",
"bottomPadding": 12
},
"cornerRadius": 8
}
],
"itemWidthPercent": 48,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
},
{
"line": {
"moleculeName": "line",
"type": "none"
},
"moleculeName": "listItem",
"style": "shortDivider",
"molecule": {
"moleculeName": "label",
"text": "Make the most of your plans",
"fontStyle": "BoldTitleMedium"
}
},
{
"id": "forYouTiles",
"style": "none",
"moleculeName": "listItem",
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
}
],
"itemWidthPercent": 45,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
},
{
"line": {
"moleculeName": "line",
"type": "none"
},
"moleculeName": "listItem",
"style": "shortDivider",
"molecule": {
"moleculeName": "stack",
"axis": "horizontal",
"molecules": [
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "label",
"text": "Discover more",
"fontStyle": "BoldTitleMedium"
},
"verticalAlignment": "center",
"horizontalAlignment": "leading"
},
{
"moleculeName": "stackItem",
"molecule": {
"moleculeName": "link",
"action": {
"analyticsData": {
"vzdl.page.id": "atomicAccountLanding",
"vzdl.page.name": "atomicAccountLanding",
"vzdl.page.displayChannel": "mva",
"vzdl.page.linkName": "Mobile|Shop all",
"vzdl.page.sourceChannel": "mva"
},
"extraParameters": {
"locale": "EN",
"browserUrl": "sales/digital/shoplanding.html?isShopFlow=true&entrypoint=tabbar",
"requestFrom": "Shop"
},
"actionType": "openPage",
"appContext": "mobile/nsa/nos/gw/launchapp/l2",
"pageType": "webview"
},
"title": "Shop all"
},
"verticalAlignment": "center",
"horizontalAlignment": "trailing"
}
]
}
},
{
"id": "discoverTiles",
"style": "none",
"moleculeName": "listItem",
"bottomPadding": 32,
"useVerticalMargins": true,
"molecule": {
"moleculeName": "carousel",
"height": 230,
"molecules": [
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
},
{
"useHorizontalMargins": false,
"useVerticalMargins": false,
"backgroundColor": "coolGray1",
"moleculeName": "carouselItem",
"molecule": {
"contentMode": "scaleToFill",
"moleculeName": "image",
"image": "single_card_skeleton_loader",
"imageFormat": "gif"
},
"cornerRadius": 8
}
],
"itemWidthPercent": 45,
"accessibilityText": "carousel",
"spacing": 16,
"pagingMolecule": {
"moleculeName": "barsCarouselIndicator",
"position": -20
}
}
}
]
]
}
],
"backgroundColor": "black",
"hab": {
"configuration": "single",
"inverted": false
},
"cache": true,
"header": {
"useVerticalMargins": true,
"backgroundColor": "black",
"moleculeName": "stack",
"bottomPadding": 12,
"molecules": [
{
"moleculeName": "stackItem",
"id": "greetingSection",
"molecule": {
"fontStyle": "RegularTitleMedium",
"textColor": "#FFFFFF",
"moleculeName": "label",
"text": "Hi Lebowski."
}
},
{
"moleculeName": "stackItem",
"molecule": {
"enabledColor": "white",
"title": "Edit profile & settings",
"moleculeName": "link",
"inverted": true,
"action": {
"actionType": "openPage",
"analyticsData": {
"vzdl.page.sourceChannel": "mva",
"vzdl.page.feedCardClicked": "L1|P1|greetingSection|Edit profile & settings",
"vzdl.page.linkName": "Edit profile & settings",
"vzdl.page.displayChannel": "mva",
"vzdl.page.id": "atomicAccountLanding",
"vzdl.events.uadcardclicked": "1",
"vzdl.page.name": "atomicAccountLanding"
},
"pageType": "oneVzIdSettingsLanding",
"presentationStyle": "push",
"requestURL": "https://mobile-exp-qa2.vzw.com/mobile/nsa/nos/gw/launchapp/l2/webview",
"tryToReplaceFirst": false,
"extraParameters": {
"browserUrl": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/ui/acct/unifiedprofile/overview"
}
}
}
}
]
},
"behaviors": [
{
"moleculeIds": [
"priorityTiles",
"forYouTiles",
"discoverTiles",
"priorityTiles5G",
"forYouTiles5G",
"discoverTiles5G",
"notification_count",
"priorityTilesFIOS",
"phoneServiceFIOS",
"internetServiceFIOS",
"tvServiceFIOS",
"forYouTilesFIOS",
"discoverTilesFIOS",
"FiosBills",
"FiosSubBills",
"FiosPlans",
"FiosSupport",
"notification_count_fios",
"greetingSection",
"FiosHomeOffers",
"priorityTilesLTE",
"forYouTilesLTE",
"discoverTilesLTE"
],
"behaviorName": "replaceMoleculeBehavior"
},
{
"refreshOnShown": true,
"moduleIds": [
"priorityTiles",
"priorityTiles5G",
"priorityTilesLTE"
],
"refreshOnFirstLoad": true,
"behaviorName": "pollingBehavior",
"runWhileHidden": false,
"refreshInterval": 10,
"refreshAction": {
"background": true,
"extraParameters": {
"category": "AccountOverview",
"channel": "VZW-MFA",
"locale": "EN",
"alwaysUseFallbackResponse": false,
"platform": "IOS",
"isLTE": false,
"requestFrom": "UAD",
"pageContext": "Account_Overview",
"isFWA": true
},
"actionType": "openPage",
"requestURL": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/gw/udb/topCardsMVA",
"pageType": "topCardsMVA"
}
},
{
"refreshOnShown": true,
"moduleIds": [
"priorityTiles",
"priorityTiles5G",
"priorityTilesLTE"
],
"refreshOnFirstLoad": true,
"behaviorName": "pollingBehavior",
"runWhileHidden": false,
"refreshInterval": 10,
"refreshAction": {
"background": true,
"extraParameters": {
"category": "AccountOverview",
"channel": "VZW-MFA",
"locale": "EN",
"alwaysUseFallbackResponse": false,
"platform": "IOS",
"isLTE": false,
"requestFrom": "UAD",
"pageContext": "Account_Overview",
"isFWA": true
},
"actionType": "openPage",
"requestURL": "https://vzwqa2.verizonwireless.com/digital/nsa/secure/gw/udb/bottomCardsMVA",
"pageType": "bottomCardsMVA"
}
}
],
"pageType": "atomicAccountLanding",
"loggedInMdn": "2815434851",
"presentationStyle": "root",
"footerlessSpacerColor": "white",
"tabBarIndex": 1,
"navigationBar": {
"style": "dark",
"moleculeName": "navigationBar",
"additionalLeftButtons": [
{
"accessibilityText": "Verizon logo button, tap anytime to scroll to top of page",
"moleculeName": "navigationImageButton",
"image": "nav_vz_mark",
"imageRenderingMode": "alwaysOriginal",
"action": {
"actionType": "noop"
}
}
],
"additionalRightButtons": [
{
"accessibilityText": "Stores",
"moleculeName": "navigationImageButton",
"image": "nav_stores_white",
"action": {
"analyticsData": {
"vzdl.page.linkName": "global tab nav:stores"
},
"title": "Stores",
"actionType": "openPage",
"pageType": "rtlStoreJourney"
}
}
]
},
"footerlessSpacerHeight": 0
}

View File

@ -0,0 +1,212 @@
//
// MVMCoreUITests.swift
// MVMCoreUITests
//
// Created by Kyle Hedden on 5/16/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import XCTest
import MVMCoreUI
import MVMCore
enum TestError: Error {
case resoureNotFound
}
final class MVMCoreUITests: XCTestCase {
override class func setUp() {
super.setUp()
CoreUIModelMapping.registerObjects()
}
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
//func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
// Any test you write for XCTest can be annotated as throws and async.
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
//}
func testModelShallowEquality() throws {
let model1 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
)
])
let model2 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
)
])
let model3 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
)
])
XCTAssertTrue(model1.isVisuallyEquivalent(to: model2))
XCTAssertTrue(model1.isVisuallyEquivalent(to: model3))
XCTAssertTrue(LabelModel(text: "Hello World").isEqual(to: LabelModel(text: "Hello World")))
XCTAssertFalse(LabelModel(text: "Hello World").isEqual(to: LabelModel(text: "Hello Moon")))
}
func testFindFirstCompare() throws {
let model1 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
)
])
let model2 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
)
])
let result = model1.findFirst(in: model2) { model1, model2 in
model1.isEqual(to: model2)
}
XCTAssertTrue(result.matched)
XCTAssertEqual((result.theirs as? LabelModel)?.text, "Hello Moon")
}
func testDeepCompare() throws {
let model1 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
)
])
let model2 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
)
])
let attributedLabel = LabelModel(text: "Hello World")
attributedLabel.attributes = [LabelAttributeActionModel(0, 5, action: ActionNoopModel())]
let model3 = StackModel(molecules: [
MoleculeStackItemModel(with:
attributedLabel
)
])
let attributedLabel2 = LabelModel(text: "Hello World")
attributedLabel2.attributes = [LabelAttributeActionModel(0, 5, action: ActionBackModel())]
let model4 = StackModel(molecules: [
MoleculeStackItemModel(with:
attributedLabel2
)
])
let results = model1.findAllNotEqual(against: model2)
XCTAssertEqual(results.count, 1)
XCTAssertEqual((results.first?.theirs as? LabelModel)?.text, "Hello Moon")
XCTAssertFalse(model1.deepEquals(to: model2))
XCTAssertFalse(model1.isDeeplyVisuallyEquivalent(to: model2))
XCTAssertFalse(model1.deepEquals(to: model3))
let visualResults = model3.findFirst(in: model4, failing: { mine, theirs in
guard let mine = mine as? MoleculeModelComparisonProtocol, let theirs = theirs as? MoleculeModelComparisonProtocol else { return false }
return mine.isVisuallyEquivalent(to: theirs)
})
XCTAssertFalse(visualResults.matched)
XCTAssertTrue(model3.isDeeplyVisuallyEquivalent(to: model4))
}
func testDeepCompareReturnsAllResults() {
let model1 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
),
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
),
MoleculeStackItemModel(with:
StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Stars")
)
])
)
])
let model2 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Stars")
),
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
),
MoleculeStackItemModel(with:
StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
)
])
)
])
let model3 = StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Stars")
),
MoleculeStackItemModel(with:
StackModel(molecules: [
MoleculeStackItemModel(with:
LabelModel(text: "Hello Moon")
)
])
),
MoleculeStackItemModel(with:
LabelModel(text: "Hello World")
)
])
var results = model1.findAllNotEqual(against: model2)
XCTAssertEqual(results.count, 3)
results = model1.findAllNotEqual(against: model3)
XCTAssertEqual(results.count, 3)
}
func testPageEquality() throws {
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 listTemplateModel3 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model_2"))
let results = listTemplateModel1.findFirst(in: listTemplateModel2, failing: { $0.isEqual(to: $1) })
XCTAssertFalse(results.matched)
XCTAssertTrue(listTemplateModel1.deepEquals(to: listTemplateModel2))
XCTAssertTrue(listTemplateModel1.isDeeplyVisuallyEquivalent(to: listTemplateModel2))
let results2 = listTemplateModel1.findFirst(in: listTemplateModel3, failing: { $0.isEqual(to: $1) })
XCTAssertTrue(results2.matched)
XCTAssertFalse(listTemplateModel1.deepEquals(to: listTemplateModel3))
XCTAssertTrue(listTemplateModel1.isDeeplyVisuallyEquivalent(to: listTemplateModel3))
}
func testPageEqualityPerformance() throws {
let listTemplateModel1 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model"))
let listTemplateModel2 = try JSONDecoder().decode(ListPageTemplateModel.self, from: getFileData("UAD_page_model_2"))
measure {
XCTAssertFalse(listTemplateModel1.deepEquals(to: listTemplateModel2))
}
}
}

View File

@ -0,0 +1,19 @@
//
// TestUtils.swift
// MVMCoreUITests
//
// Created by Kyle Hedden on 5/16/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
import XCTest
extension XCTestCase {
func getFileData(_ fileName: String, type: String = "json") throws -> Data {
guard let resourcePath = Bundle(identifier: "com.vzw.MVMCoreUITests")?.path(forResource: fileName, ofType: type) else { throw TestError.resoureNotFound }
return try Data(contentsOf: URL(fileURLWithPath: resourcePath))
}
}