stop the disconnected-history alert from blocking scrolling
- Skip older-history loads silently while the connection is down: the trigger previously failed with "not connected", popped the global alert over the timeline and froze scrolling until dismissed. - Accept fast reply-swipe flicks whose end state is clearly leftward even without an intermediate horizontal lock, while scroll-owned touches still never activate the swipe. - Add a seeded-chat UI test mode (launch argument), accessibility identifiers and a LumaUITests target with timeline scroll and reply swipe tests; guard the fix in verify.sh.
This commit is contained in:
1 parent
2868a13abd
commit
5a7fc36722
10 files changed
+304
-12
No files matched your search
@@ -83,6 +83,7 @@
|
|||||||
46924A3275BFEFD2218AD748 /* SystemPhotoCameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5198FF1E47790E498CC8C6F3 /* SystemPhotoCameraView.swift */; };
|
46924A3275BFEFD2218AD748 /* SystemPhotoCameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5198FF1E47790E498CC8C6F3 /* SystemPhotoCameraView.swift */; };
|
||||||
46985C2741D425D6486849F3 /* PhotoAttachmentPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D2294F0FE44B2268A0620BB /* PhotoAttachmentPreview.swift */; };
|
46985C2741D425D6486849F3 /* PhotoAttachmentPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D2294F0FE44B2268A0620BB /* PhotoAttachmentPreview.swift */; };
|
||||||
481F1C57B07AF615A3629C7E /* ArchiveSyncRecoveryPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61380B78824FDB98A933C7E9 /* ArchiveSyncRecoveryPolicyTests.swift */; };
|
481F1C57B07AF615A3629C7E /* ArchiveSyncRecoveryPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61380B78824FDB98A933C7E9 /* ArchiveSyncRecoveryPolicyTests.swift */; };
|
||||||
|
48812531C1B7DB7752F06DEF /* TimelineUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B5359E7A89ABF58B78CE036 /* TimelineUITests.swift */; };
|
||||||
48BE02EB8AA84E9CD1354E1A /* MediaPlaybackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ADAEFB7CB8441CA1A50C44 /* MediaPlaybackCoordinator.swift */; };
|
48BE02EB8AA84E9CD1354E1A /* MediaPlaybackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ADAEFB7CB8441CA1A50C44 /* MediaPlaybackCoordinator.swift */; };
|
||||||
496D99C625D771D6B91D99E7 /* ArchiveSyncRecoveryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28187D29B1BB6E3ECB02F637 /* ArchiveSyncRecoveryPolicy.swift */; };
|
496D99C625D771D6B91D99E7 /* ArchiveSyncRecoveryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28187D29B1BB6E3ECB02F637 /* ArchiveSyncRecoveryPolicy.swift */; };
|
||||||
49DC659401787CF44A33B620 /* LocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C67A8A07F9DC9DFB7FB35DA /* LocationProvider.swift */; };
|
49DC659401787CF44A33B620 /* LocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C67A8A07F9DC9DFB7FB35DA /* LocationProvider.swift */; };
|
||||||
@@ -270,6 +271,13 @@
|
|||||||
remoteGlobalIDString = 0565F6C7DA26F0482928704E;
|
remoteGlobalIDString = 0565F6C7DA26F0482928704E;
|
||||||
remoteInfo = Luma;
|
remoteInfo = Luma;
|
||||||
};
|
};
|
||||||
|
5C6218BA484572FDB4727ECA /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = AACE5AD3943208FFB6B19277 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 0565F6C7DA26F0482928704E;
|
||||||
|
remoteInfo = Luma;
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
@@ -292,6 +300,7 @@
|
|||||||
03AC6BC7FC7CFFD69A20DA17 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
|
03AC6BC7FC7CFFD69A20DA17 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
|
||||||
05EEEE8A125347F7C5014A09 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
05EEEE8A125347F7C5014A09 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||||
096748EF4DC92889BCAB3260 /* MessageCorrectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCorrectionTests.swift; sourceTree = "<group>"; };
|
096748EF4DC92889BCAB3260 /* MessageCorrectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCorrectionTests.swift; sourceTree = "<group>"; };
|
||||||
|
0B5359E7A89ABF58B78CE036 /* TimelineUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineUITests.swift; sourceTree = "<group>"; };
|
||||||
0D15DA1B85B68AD2419670C8 /* ArchiveSyncWorkBudget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveSyncWorkBudget.swift; sourceTree = "<group>"; };
|
0D15DA1B85B68AD2419670C8 /* ArchiveSyncWorkBudget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveSyncWorkBudget.swift; sourceTree = "<group>"; };
|
||||||
0D93B7CC521421C23459A9A4 /* ArchiveMessageBatchPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveMessageBatchPolicyTests.swift; sourceTree = "<group>"; };
|
0D93B7CC521421C23459A9A4 /* ArchiveMessageBatchPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveMessageBatchPolicyTests.swift; sourceTree = "<group>"; };
|
||||||
0DE298382FC70ECF1513BD4A /* XMPPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMPPService.swift; sourceTree = "<group>"; };
|
0DE298382FC70ECF1513BD4A /* XMPPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMPPService.swift; sourceTree = "<group>"; };
|
||||||
@@ -371,6 +380,7 @@
|
|||||||
8D2294F0FE44B2268A0620BB /* PhotoAttachmentPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoAttachmentPreview.swift; sourceTree = "<group>"; };
|
8D2294F0FE44B2268A0620BB /* PhotoAttachmentPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoAttachmentPreview.swift; sourceTree = "<group>"; };
|
||||||
8E29E7B704EE71C5AC70008E /* MediaPreviewProcessorVideoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewProcessorVideoTests.swift; sourceTree = "<group>"; };
|
8E29E7B704EE71C5AC70008E /* MediaPreviewProcessorVideoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewProcessorVideoTests.swift; sourceTree = "<group>"; };
|
||||||
8F8DE3CE0E26DEA21A27CF67 /* LumaConnectionStatsModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LumaConnectionStatsModule.swift; sourceTree = "<group>"; };
|
8F8DE3CE0E26DEA21A27CF67 /* LumaConnectionStatsModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LumaConnectionStatsModule.swift; sourceTree = "<group>"; };
|
||||||
|
96720381802141EBA1DA9772 /* LumaUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = LumaUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
96829BDC257FD8D61083DA4D /* VideoNoteRecordingLifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteRecordingLifecycle.swift; sourceTree = "<group>"; };
|
96829BDC257FD8D61083DA4D /* VideoNoteRecordingLifecycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteRecordingLifecycle.swift; sourceTree = "<group>"; };
|
||||||
99BE34DC082D19007241AA62 /* NewGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupView.swift; sourceTree = "<group>"; };
|
99BE34DC082D19007241AA62 /* NewGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupView.swift; sourceTree = "<group>"; };
|
||||||
9A338A8EB30206647BDD922F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
9A338A8EB30206647BDD922F /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -561,6 +571,14 @@
|
|||||||
path = Watch;
|
path = Watch;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
307BDB7CACD11611A6CCDC4E /* UITests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0B5359E7A89ABF58B78CE036 /* TimelineUITests.swift */,
|
||||||
|
);
|
||||||
|
path = UITests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4D0D58FD94D94B4D409033AF /* Tests */ = {
|
4D0D58FD94D94B4D409033AF /* Tests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -648,6 +666,7 @@
|
|||||||
D1D74E43299CD30D89B80F74 /* Resources */,
|
D1D74E43299CD30D89B80F74 /* Resources */,
|
||||||
2A98AB59ED6FB8802C301CF4 /* Sources */,
|
2A98AB59ED6FB8802C301CF4 /* Sources */,
|
||||||
4D0D58FD94D94B4D409033AF /* Tests */,
|
4D0D58FD94D94B4D409033AF /* Tests */,
|
||||||
|
307BDB7CACD11611A6CCDC4E /* UITests */,
|
||||||
90CFB4B53B5F9EF91D718FC2 /* Products */,
|
90CFB4B53B5F9EF91D718FC2 /* Products */,
|
||||||
);
|
);
|
||||||
indentWidth = 4;
|
indentWidth = 4;
|
||||||
@@ -685,6 +704,7 @@
|
|||||||
DF8C531E464C9C4EDE2648C6 /* Luma.app */,
|
DF8C531E464C9C4EDE2648C6 /* Luma.app */,
|
||||||
EA54340DD785335237158BEF /* LumaMac.app */,
|
EA54340DD785335237158BEF /* LumaMac.app */,
|
||||||
6317FAA29F22A3EA8450208F /* LumaTests.xctest */,
|
6317FAA29F22A3EA8450208F /* LumaTests.xctest */,
|
||||||
|
96720381802141EBA1DA9772 /* LumaUITests.xctest */,
|
||||||
19D4E9404712E8643E31CB93 /* LumaWatch.app */,
|
19D4E9404712E8643E31CB93 /* LumaWatch.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
@@ -770,6 +790,24 @@
|
|||||||
productReference = EA54340DD785335237158BEF /* LumaMac.app */;
|
productReference = EA54340DD785335237158BEF /* LumaMac.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
A086B6D25E7969CB643F1C8A /* LumaUITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 06AF122279B2585E145DB210 /* Build configuration list for PBXNativeTarget "LumaUITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
DE1009798E1797BDAC72B608 /* Sources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
C675A3F8639BBF6792B09F39 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = LumaUITests;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = LumaUITests;
|
||||||
|
productReference = 96720381802141EBA1DA9772 /* LumaUITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
|
};
|
||||||
BAB78463D7DE07A8F7B3D27D /* LumaWatch */ = {
|
BAB78463D7DE07A8F7B3D27D /* LumaWatch */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 5D8E754B3FFEE24B370D350E /* Build configuration list for PBXNativeTarget "LumaWatch" */;
|
buildConfigurationList = 5D8E754B3FFEE24B370D350E /* Build configuration list for PBXNativeTarget "LumaWatch" */;
|
||||||
@@ -821,6 +859,9 @@
|
|||||||
74A5CD40283A16247F9F36B2 = {
|
74A5CD40283A16247F9F36B2 = {
|
||||||
DevelopmentTeam = K3AS449A74;
|
DevelopmentTeam = K3AS449A74;
|
||||||
};
|
};
|
||||||
|
A086B6D25E7969CB643F1C8A = {
|
||||||
|
TestTargetID = 0565F6C7DA26F0482928704E;
|
||||||
|
};
|
||||||
BAB78463D7DE07A8F7B3D27D = {
|
BAB78463D7DE07A8F7B3D27D = {
|
||||||
DevelopmentTeam = K3AS449A74;
|
DevelopmentTeam = K3AS449A74;
|
||||||
};
|
};
|
||||||
@@ -849,6 +890,7 @@
|
|||||||
74A5CD40283A16247F9F36B2 /* LumaMac */,
|
74A5CD40283A16247F9F36B2 /* LumaMac */,
|
||||||
BAB78463D7DE07A8F7B3D27D /* LumaWatch */,
|
BAB78463D7DE07A8F7B3D27D /* LumaWatch */,
|
||||||
D4CD85C144854C8DA57B3903 /* LumaTests */,
|
D4CD85C144854C8DA57B3903 /* LumaTests */,
|
||||||
|
A086B6D25E7969CB643F1C8A /* LumaUITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -1145,6 +1187,14 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
DE1009798E1797BDAC72B608 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
48812531C1B7DB7752F06DEF /* TimelineUITests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
@@ -1158,6 +1208,11 @@
|
|||||||
target = 0565F6C7DA26F0482928704E /* Luma */;
|
target = 0565F6C7DA26F0482928704E /* Luma */;
|
||||||
targetProxy = 277A193FE4C68A777257EF0D /* PBXContainerItemProxy */;
|
targetProxy = 277A193FE4C68A777257EF0D /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
C675A3F8639BBF6792B09F39 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 0565F6C7DA26F0482928704E /* Luma */;
|
||||||
|
targetProxy = 5C6218BA484572FDB4727ECA /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
@@ -1266,6 +1321,24 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
64B3F29554E68D692FBF07CD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = app.luma.chat.uitests;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
TEST_TARGET_NAME = Luma;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
6A07A459DB18752AD74A145A /* Debug */ = {
|
6A07A459DB18752AD74A145A /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -1428,6 +1501,24 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
E381B8E38C4B5D9CBB9E6416 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = app.luma.chat.uitests;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
TEST_TARGET_NAME = Luma;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
FEECD67107BB7AE74F3574C8 /* Debug */ = {
|
FEECD67107BB7AE74F3574C8 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -1464,6 +1555,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Debug;
|
defaultConfigurationName = Debug;
|
||||||
};
|
};
|
||||||
|
06AF122279B2585E145DB210 /* Build configuration list for PBXNativeTarget "LumaUITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
E381B8E38C4B5D9CBB9E6416 /* Debug */,
|
||||||
|
64B3F29554E68D692FBF07CD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Debug;
|
||||||
|
};
|
||||||
23A91CAAA509D787E736CA14 /* Build configuration list for PBXNativeTarget "LumaMac" */ = {
|
23A91CAAA509D787E736CA14 /* Build configuration list for PBXNativeTarget "LumaMac" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|||||||
@@ -64,6 +64,17 @@
|
|||||||
ReferencedContainer = "container:Luma.xcodeproj">
|
ReferencedContainer = "container:Luma.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "A086B6D25E7969CB643F1C8A"
|
||||||
|
BuildableName = "LumaUITests.xctest"
|
||||||
|
BlueprintName = "LumaUITests"
|
||||||
|
ReferencedContainer = "container:Luma.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
<CommandLineArguments>
|
<CommandLineArguments>
|
||||||
</CommandLineArguments>
|
</CommandLineArguments>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ required=(
|
|||||||
Tests/SCRAMSHA512Tests.swift
|
Tests/SCRAMSHA512Tests.swift
|
||||||
Tests/AppLockPolicyTests.swift
|
Tests/AppLockPolicyTests.swift
|
||||||
Tests/CallHistorySyncTests.swift
|
Tests/CallHistorySyncTests.swift
|
||||||
|
UITests/TimelineUITests.swift
|
||||||
Sources/Shared/XMPP/LumaScramSha512Mechanism.swift
|
Sources/Shared/XMPP/LumaScramSha512Mechanism.swift
|
||||||
Sources/Shared/XMPP/CallHistorySync.swift
|
Sources/Shared/XMPP/CallHistorySync.swift
|
||||||
Sources/Shared/Security/AppLockVault.swift
|
Sources/Shared/Security/AppLockVault.swift
|
||||||
@@ -389,6 +390,10 @@ grep -q 'let items = pickedMediaItems' Sources/Shared/UI/ChatView.swift || {
|
|||||||
echo "Gallery staging must run on picker dismissal only"
|
echo "Gallery staging must run on picker dismissal only"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
grep -q 'connectionStatus == .connected' Sources/Shared/Models/AppModel.swift || {
|
||||||
|
echo "Older-history loads must skip silently while disconnected"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
grep -q 'onDismissRequest' Sources/Shared/UI/SystemPhotoCameraView.swift || {
|
grep -q 'onDismissRequest' Sources/Shared/UI/SystemPhotoCameraView.swift || {
|
||||||
echo "The camera picker must request dismissal before background preparation"
|
echo "The camera picker must request dismissal before background preparation"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -301,6 +301,9 @@ final class AppModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bootstrap() async {
|
func bootstrap() async {
|
||||||
|
#if DEBUG
|
||||||
|
if applyUITestChatIfRequested() { return }
|
||||||
|
#endif
|
||||||
guard !RuntimeEnvironment.isRunningTests else { return }
|
guard !RuntimeEnvironment.isRunningTests else { return }
|
||||||
guard let saved = preferences.load() else { return }
|
guard let saved = preferences.load() else { return }
|
||||||
prepareStore(for: saved.normalizedJID)
|
prepareStore(for: saved.normalizedJID)
|
||||||
@@ -459,6 +462,55 @@ final class AppModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
/// UI-test mode: seeds an in-memory conversation with numbered messages
|
||||||
|
/// so XCUITests can exercise timeline scrolling and the reply swipe
|
||||||
|
/// without an XMPP server. Activated with the `-luma-ui-test-chat`
|
||||||
|
/// launch argument.
|
||||||
|
private func applyUITestChatIfRequested() -> Bool {
|
||||||
|
guard ProcessInfo.processInfo.arguments.contains("-luma-ui-test-chat") else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let account = AccountConfiguration(
|
||||||
|
jid: "uitest@example.org",
|
||||||
|
displayName: "UI Test",
|
||||||
|
resource: "uitest"
|
||||||
|
)
|
||||||
|
self.account = account
|
||||||
|
let conversation = Conversation(
|
||||||
|
jid: "peer@example.org",
|
||||||
|
displayName: "uitest-peer",
|
||||||
|
lastMessage: "Тестовое сообщение номер 60",
|
||||||
|
lastActivity: Date(),
|
||||||
|
kind: .direct
|
||||||
|
)
|
||||||
|
modelContext.insert(conversation)
|
||||||
|
conversations = [conversation]
|
||||||
|
let base = Date(timeIntervalSince1970: 1_700_000_000)
|
||||||
|
var seeded: [ChatMessage] = []
|
||||||
|
for index in 1...60 {
|
||||||
|
let outgoing = index.isMultiple(of: 2)
|
||||||
|
let message = ChatMessage(
|
||||||
|
id: "uitest-msg-\(index)",
|
||||||
|
conversationID: conversation.jid,
|
||||||
|
senderJID: outgoing ? account.normalizedJID : conversation.jid,
|
||||||
|
body: "Тестовое сообщение номер \(index)",
|
||||||
|
timestamp: base.addingTimeInterval(TimeInterval(index * 60)),
|
||||||
|
direction: outgoing ? .outgoing : .incoming,
|
||||||
|
delivery: .sent,
|
||||||
|
security: .plaintext,
|
||||||
|
kind: .text
|
||||||
|
)
|
||||||
|
modelContext.insert(message)
|
||||||
|
seeded.append(message)
|
||||||
|
}
|
||||||
|
messages = seeded
|
||||||
|
rebuildMessageIndex()
|
||||||
|
selectedConversationID = conversation.jid
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func setApplicationActive(_ active: Bool) {
|
func setApplicationActive(_ active: Bool) {
|
||||||
appIsActive = active
|
appIsActive = active
|
||||||
guard !RuntimeEnvironment.isRunningTests else { return }
|
guard !RuntimeEnvironment.isRunningTests else { return }
|
||||||
@@ -654,8 +706,13 @@ final class AppModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadOlderHistoryForSelectedConversation() {
|
func loadOlderHistoryForSelectedConversation() {
|
||||||
|
// While disconnected the request would only fail with
|
||||||
|
// "not connected", which used to pop the global alert right over
|
||||||
|
// the timeline and blocked scrolling. Skip silently instead: the
|
||||||
|
// trigger re-fires once the connection is back.
|
||||||
guard !isLoadingOlderHistory,
|
guard !isLoadingOlderHistory,
|
||||||
hasMoreOlderHistory,
|
hasMoreOlderHistory,
|
||||||
|
connectionStatus == .connected,
|
||||||
let conversation = selectedConversation else { return }
|
let conversation = selectedConversation else { return }
|
||||||
// let oldestServerID = selectedMessages
|
// let oldestServerID = selectedMessages
|
||||||
// .sorted { lhs, rhs in
|
// .sorted { lhs, rhs in
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ struct MessageReplySwipePolicy {
|
|||||||
static let followDominance: CGFloat = 1.05
|
static let followDominance: CGFloat = 1.05
|
||||||
|
|
||||||
/// Whether this translation can lock the gesture as a horizontal swipe.
|
/// Whether this translation can lock the gesture as a horizontal swipe.
|
||||||
/// Only right-to-left (swipe left, negative width) is accepted; the
|
/// Only right-to-left (swipe left, negative width) is accepted, and the
|
||||||
/// opposite direction is deliberately left to the scroll view.
|
/// lock is only available early: once the finger has travelled mostly
|
||||||
|
/// vertically the scroll view owns the touch and the swipe must never
|
||||||
|
/// activate for the rest of that gesture.
|
||||||
static func canLock(_ translation: CGSize) -> Bool {
|
static func canLock(_ translation: CGSize) -> Bool {
|
||||||
translation.width < 0
|
translation.width < 0
|
||||||
|
&& abs(translation.height) <= 24
|
||||||
&& isHorizontal(translation, dominance: lockDominance)
|
&& isHorizontal(translation, dominance: lockDominance)
|
||||||
&& abs(translation.width) >= activationDistance * 0.5
|
&& abs(translation.width) >= activationDistance * 0.5
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -628,6 +628,7 @@ struct ChatView: View {
|
|||||||
.onChange(of: messages) { _, newMessages in
|
.onChange(of: messages) { _, newMessages in
|
||||||
rebuildTimelineEntries(from: newMessages)
|
rebuildTimelineEntries(from: newMessages)
|
||||||
}
|
}
|
||||||
|
.accessibilityIdentifier("chat-timeline")
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.onPreferenceChange(TimelineBottomYPreferenceKey.self) {
|
.onPreferenceChange(TimelineBottomYPreferenceKey.self) {
|
||||||
bottomY in
|
bottomY in
|
||||||
@@ -1851,6 +1852,7 @@ struct ChatView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.accessibilityLabel("Отменить ответ")
|
.accessibilityLabel("Отменить ответ")
|
||||||
}
|
}
|
||||||
|
.accessibilityIdentifier("reply-banner")
|
||||||
// .padding(.horizontal, 12)
|
// .padding(.horizontal, 12)
|
||||||
// .padding(.vertical, 8)
|
// .padding(.vertical, 8)
|
||||||
// .background(
|
// .background(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct MessageBubble: View {
|
|||||||
@State private var replySwipeOffset: CGFloat = 0
|
@State private var replySwipeOffset: CGFloat = 0
|
||||||
@State private var replySwipeArmed = false
|
@State private var replySwipeArmed = false
|
||||||
@State private var replySwipeLocked = false
|
@State private var replySwipeLocked = false
|
||||||
|
@State private var scrollOwnsGesture = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -38,6 +39,7 @@ struct MessageBubble: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear)
|
.background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
.accessibilityIdentifier("bubble-\(message.clientID)")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
guard isSelectionMode else { return }
|
guard isSelectionMode else { return }
|
||||||
onToggleSelection()
|
onToggleSelection()
|
||||||
@@ -55,6 +57,7 @@ struct MessageBubble: View {
|
|||||||
replySwipeOffset = 0
|
replySwipeOffset = 0
|
||||||
replySwipeArmed = false
|
replySwipeArmed = false
|
||||||
replySwipeLocked = false
|
replySwipeLocked = false
|
||||||
|
scrollOwnsGesture = false
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
if !isSelectionMode {
|
if !isSelectionMode {
|
||||||
@@ -178,12 +181,20 @@ struct MessageBubble: View {
|
|||||||
private var replySwipeGesture: some Gesture {
|
private var replySwipeGesture: some Gesture {
|
||||||
DragGesture(minimumDistance: 20)
|
DragGesture(minimumDistance: 20)
|
||||||
.onChanged { value in
|
.onChanged { value in
|
||||||
// Once the gesture is clearly horizontal it locks in, so a
|
// Arbitration: once the touch is clearly vertical the scroll
|
||||||
// slightly diagonal finish can neither break the reply swipe
|
// view owns it for good, so a later horizontal arc can never
|
||||||
// nor let the scroll view steal it.
|
// move bubbles mid-scroll. A reply swipe locks in only while
|
||||||
if !replySwipeLocked, MessageReplySwipePolicy.canLock(value.translation) {
|
// the finger is still moving mostly horizontally.
|
||||||
replySwipeLocked = true
|
if !replySwipeLocked, !scrollOwnsGesture {
|
||||||
|
if MessageReplySwipePolicy.canLock(value.translation) {
|
||||||
|
replySwipeLocked = true
|
||||||
|
} else if abs(value.translation.height) > 16,
|
||||||
|
abs(value.translation.width) < abs(value.translation.height)
|
||||||
|
{
|
||||||
|
scrollOwnsGesture = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
guard replySwipeLocked, !scrollOwnsGesture else { return }
|
||||||
let offset = MessageReplySwipePolicy.offset(
|
let offset = MessageReplySwipePolicy.offset(
|
||||||
locked: replySwipeLocked,
|
locked: replySwipeLocked,
|
||||||
translation: value.translation
|
translation: value.translation
|
||||||
@@ -212,12 +223,19 @@ struct MessageBubble: View {
|
|||||||
replySwipeArmed = armed
|
replySwipeArmed = armed
|
||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let shouldReply = MessageReplySwipePolicy.shouldReply(
|
// The lock is a hint, not a requirement: a fast flick may
|
||||||
locked: replySwipeLocked,
|
// deliver no intermediate frame that passes canLock, yet its
|
||||||
translation: value.translation,
|
// end state is still a clear leftward swipe. scrollOwnsGesture
|
||||||
predictedEndTranslation: value.predictedEndTranslation
|
// alone protects the timeline from reply activation.
|
||||||
)
|
let shouldReply =
|
||||||
|
!scrollOwnsGesture
|
||||||
|
&& MessageReplySwipePolicy.shouldReply(
|
||||||
|
locked: replySwipeLocked,
|
||||||
|
translation: value.translation,
|
||||||
|
predictedEndTranslation: value.predictedEndTranslation
|
||||||
|
)
|
||||||
replySwipeLocked = false
|
replySwipeLocked = false
|
||||||
|
scrollOwnsGesture = false
|
||||||
if replySwipeOffset != 0 || replySwipeArmed {
|
if replySwipeOffset != 0 || replySwipeArmed {
|
||||||
withAnimation(.spring(response: 0.24, dampingFraction: 0.82)) {
|
withAnimation(.spring(response: 0.24, dampingFraction: 0.82)) {
|
||||||
replySwipeOffset = 0
|
replySwipeOffset = 0
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ final class MessageReplySwipeTests: XCTestCase {
|
|||||||
XCTAssertFalse(MessageReplySwipePolicy.canLock(CGSize(width: 40, height: 4)))
|
XCTAssertFalse(MessageReplySwipePolicy.canLock(CGSize(width: 40, height: 4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCanLockIsUnavailableOnceTheGestureTurnsVertical() {
|
||||||
|
// A scroll that arcs left after travelling down must never lock the
|
||||||
|
// swipe: the vertical travel already exceeds the lock budget.
|
||||||
|
XCTAssertFalse(MessageReplySwipePolicy.canLock(CGSize(width: -30, height: 40)))
|
||||||
|
XCTAssertFalse(MessageReplySwipePolicy.canLock(CGSize(width: -80, height: 60)))
|
||||||
|
}
|
||||||
|
|
||||||
func testLockedSwipeToleratesVerticalDrift() {
|
func testLockedSwipeToleratesVerticalDrift() {
|
||||||
// Once locked the finger may drift: a 1.17 ratio would be rejected
|
// Once locked the finger may drift: a 1.17 ratio would be rejected
|
||||||
// by the 1.6 lock dominance but must still count as a reply swipe.
|
// by the 1.6 lock dominance but must still count as a reply swipe.
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class TimelineUITests: XCTestCase {
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
continueAfterFailure = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func launchApp() -> XCUIApplication {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launchArguments = ["-luma-ui-test-chat"]
|
||||||
|
app.launch()
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bubble(_ id: String, in app: XCUIApplication) -> XCUIElement {
|
||||||
|
app.descendants(matching: .any).matching(identifier: "bubble-\(id)").firstMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openChat(_ app: XCUIApplication) {
|
||||||
|
let chatRow = app.staticTexts["uitest-peer"]
|
||||||
|
XCTAssertTrue(chatRow.waitForExistence(timeout: 15))
|
||||||
|
chatRow.tap()
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.descendants(matching: .any).matching(identifier: "chat-timeline").firstMatch
|
||||||
|
.waitForExistence(timeout: 15)
|
||||||
|
)
|
||||||
|
XCTAssertTrue(bubble("uitest-msg-60", in: app).waitForExistence(timeout: 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTimelineVerticalScrollWorks() throws {
|
||||||
|
let app = launchApp()
|
||||||
|
openChat(app)
|
||||||
|
|
||||||
|
for _ in 0..<7 {
|
||||||
|
app.swipeDown()
|
||||||
|
}
|
||||||
|
XCTAssertTrue(bubble("uitest-msg-1", in: app).waitForExistence(timeout: 10))
|
||||||
|
|
||||||
|
for _ in 0..<9 {
|
||||||
|
app.swipeUp()
|
||||||
|
}
|
||||||
|
XCTAssertTrue(bubble("uitest-msg-60", in: app).waitForExistence(timeout: 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReplySwipeShowsPlateAndScrollStillWorks() throws {
|
||||||
|
let app = launchApp()
|
||||||
|
openChat(app)
|
||||||
|
|
||||||
|
// Message 60 can sit partially behind the composer, so drag on a
|
||||||
|
// bubble that is reliably visible and hittable in the bottom area.
|
||||||
|
let target = bubble("uitest-msg-55", in: app)
|
||||||
|
XCTAssertTrue(target.waitForExistence(timeout: 10))
|
||||||
|
XCTAssertTrue(target.isHittable, "The swipe target must be hittable")
|
||||||
|
// A controlled right-to-left drag: the stock swipeLeft() flicks too
|
||||||
|
// fast for the gesture's horizontal lock to engage reliably.
|
||||||
|
let start = target.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5))
|
||||||
|
start.press(forDuration: 0.15, thenDragTo: start.withOffset(CGVector(dx: -200, dy: 0)))
|
||||||
|
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||||
|
attachment.name = "after-drag"
|
||||||
|
attachment.lifetime = .keepAlways
|
||||||
|
add(attachment)
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.descendants(matching: .any).matching(identifier: "reply-banner").firstMatch
|
||||||
|
.waitForExistence(timeout: 5),
|
||||||
|
"A left swipe must open the reply plate"
|
||||||
|
)
|
||||||
|
|
||||||
|
app.buttons["Отменить ответ"].tap()
|
||||||
|
for _ in 0..<7 {
|
||||||
|
app.swipeDown()
|
||||||
|
}
|
||||||
|
XCTAssertTrue(bubble("uitest-msg-1", in: app).waitForExistence(timeout: 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+14
@@ -188,6 +188,19 @@ targets:
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER: app.luma.chat.tests
|
PRODUCT_BUNDLE_IDENTIFIER: app.luma.chat.tests
|
||||||
GENERATE_INFOPLIST_FILE: true
|
GENERATE_INFOPLIST_FILE: true
|
||||||
|
|
||||||
|
LumaUITests:
|
||||||
|
type: bundle.ui-testing
|
||||||
|
platform: iOS
|
||||||
|
deploymentTarget: "17.0"
|
||||||
|
sources:
|
||||||
|
- path: UITests
|
||||||
|
dependencies:
|
||||||
|
- target: Luma
|
||||||
|
settings:
|
||||||
|
base:
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER: app.luma.chat.uitests
|
||||||
|
GENERATE_INFOPLIST_FILE: true
|
||||||
|
|
||||||
schemes:
|
schemes:
|
||||||
Luma:
|
Luma:
|
||||||
build:
|
build:
|
||||||
@@ -200,6 +213,7 @@ schemes:
|
|||||||
config: Debug
|
config: Debug
|
||||||
targets:
|
targets:
|
||||||
- LumaTests
|
- LumaTests
|
||||||
|
- LumaUITests
|
||||||
archive:
|
archive:
|
||||||
config: Release
|
config: Release
|
||||||
LumaMac:
|
LumaMac:
|
||||||
|
|||||||
Reference in new issue
Block a user