diff --git a/MasMagicPills.xcodeproj/project.pbxproj b/MasMagicPills.xcodeproj/project.pbxproj index fe946ae..b8e350f 100644 --- a/MasMagicPills.xcodeproj/project.pbxproj +++ b/MasMagicPills.xcodeproj/project.pbxproj @@ -369,6 +369,72 @@ F2C9B0B02A1E216800046F2A /* Publishers.MapToResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C9B0AB2A1E214700046F2A /* Publishers.MapToResultTests.swift */; }; F2C9B0B12A1E216900046F2A /* Publishers.MapToResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C9B0AB2A1E214700046F2A /* Publishers.MapToResultTests.swift */; }; F2C9B0B22A1E216A00046F2A /* Publishers.MapToResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C9B0AB2A1E214700046F2A /* Publishers.MapToResultTests.swift */; }; + F2E3A40A2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4092B6312D100E85E39 /* URLComponentsExtensions.swift */; }; + F2E3A40B2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4092B6312D100E85E39 /* URLComponentsExtensions.swift */; }; + F2E3A40C2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4092B6312D100E85E39 /* URLComponentsExtensions.swift */; }; + F2E3A40E2B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A40D2B63131500E85E39 /* URLComponentsExtensionsTests.swift */; }; + F2E3A40F2B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A40D2B63131500E85E39 /* URLComponentsExtensionsTests.swift */; }; + F2E3A4102B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A40D2B63131500E85E39 /* URLComponentsExtensionsTests.swift */; }; + F2E3A4192B63175900E85E39 /* StringExtensions+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */; }; + F2E3A41A2B63175900E85E39 /* StringExtensions+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */; }; + F2E3A41D2B63175B00E85E39 /* StringExtensions+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */; }; + F2E3A41E2B63175B00E85E39 /* StringExtensions+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */; }; + F2E3A4212B63175D00E85E39 /* StringExtensions+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */; }; + F2E3A4222B63175D00E85E39 /* StringExtensions+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */; }; + F2E3A4252B63175E00E85E39 /* StringExtensions+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */; }; + F2E3A4262B63175E00E85E39 /* StringExtensions+Regex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */; }; + F2E3A4282B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4272B6317B000E85E39 /* StringExtensions+RegexTests.swift */; }; + F2E3A4292B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4272B6317B000E85E39 /* StringExtensions+RegexTests.swift */; }; + F2E3A42A2B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4272B6317B000E85E39 /* StringExtensions+RegexTests.swift */; }; + F2E3A42C2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A42B2B6317C400E85E39 /* StringExtensions+RangesTests.swift */; }; + F2E3A42D2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A42B2B6317C400E85E39 /* StringExtensions+RangesTests.swift */; }; + F2E3A42E2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A42B2B6317C400E85E39 /* StringExtensions+RangesTests.swift */; }; + F2E3A4312B631D0E00E85E39 /* PublisherExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */; }; + F2E3A4322B631D0E00E85E39 /* PublisherExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */; }; + F2E3A4332B631D0E00E85E39 /* PublisherExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */; }; + F2E3A4342B631D0E00E85E39 /* PublisherExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */; }; + F2E3A4362B631DEB00E85E39 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */; }; + F2E3A4372B631DEB00E85E39 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */; }; + F2E3A4382B631DEB00E85E39 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */; }; + F2E3A4392B631DEB00E85E39 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */; }; + F2E3A43B2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */; }; + F2E3A43C2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */; }; + F2E3A43D2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */; }; + F2E3A43E2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */; }; + F2E3A4402B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */; }; + F2E3A4412B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */; }; + F2E3A4422B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */; }; + F2E3A4432B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */; }; + F2E3A4462B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */; }; + F2E3A4472B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */; }; + F2E3A4482B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */; }; + F2E3A4492B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */; }; + F2E3A44B2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */; }; + F2E3A44C2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */; }; + F2E3A44D2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */; }; + F2E3A44E2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */; }; + F2E3A4502B64047800E85E39 /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */; }; + F2E3A4512B64047800E85E39 /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */; }; + F2E3A4522B64047800E85E39 /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */; }; + F2E3A4532B64047800E85E39 /* ImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */; }; + F2E3A4562B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4552B6825EF00E85E39 /* PassthroughSubjectTests.swift */; }; + F2E3A4572B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4552B6825EF00E85E39 /* PassthroughSubjectTests.swift */; }; + F2E3A4582B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4552B6825EF00E85E39 /* PassthroughSubjectTests.swift */; }; + F2E3A45A2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4592B6825FC00E85E39 /* CurrentValueSubjectTests.swift */; }; + F2E3A45B2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4592B6825FC00E85E39 /* CurrentValueSubjectTests.swift */; }; + F2E3A45C2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4592B6825FC00E85E39 /* CurrentValueSubjectTests.swift */; }; + F2E3A45E2B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A45D2B684F8300E85E39 /* PublishersExtensionsTests.swift */; }; + F2E3A45F2B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A45D2B684F8300E85E39 /* PublishersExtensionsTests.swift */; }; + F2E3A4602B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A45D2B684F8300E85E39 /* PublishersExtensionsTests.swift */; }; + F2E3A4622B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4612B68517000E85E39 /* Publishers.WithLatestFromTests.swift */; }; + F2E3A4632B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4612B68517000E85E39 /* Publishers.WithLatestFromTests.swift */; }; + F2E3A4642B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4612B68517000E85E39 /* Publishers.WithLatestFromTests.swift */; }; + F2E3A4662B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4652B68F89F00E85E39 /* ColorExtensionsTests.swift */; }; + F2E3A4672B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4652B68F89F00E85E39 /* ColorExtensionsTests.swift */; }; + F2E3A4682B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A4652B68F89F00E85E39 /* ColorExtensionsTests.swift */; }; + F2E3A46B2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A46A2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift */; }; + F2E3A46C2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A46A2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift */; }; + F2E3A46D2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E3A46A2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift */; }; F2E47FF426B82EFE0055A0D6 /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E47FF326B82EFE0055A0D6 /* TextStyle.swift */; }; F2E47FF726B8315D0055A0D6 /* TextStyleModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E47FF626B8315D0055A0D6 /* TextStyleModifier.swift */; }; F2E4800D26B83F7F0055A0D6 /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E47FF326B82EFE0055A0D6 /* TextStyle.swift */; }; @@ -589,6 +655,25 @@ F2C78F28238D76DF008C4846 /* UITableViewHeaderFooterViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewHeaderFooterViewExtensions.swift; sourceTree = ""; }; F2C9B0A62A1C075500046F2A /* Publishers.MapToResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publishers.MapToResult.swift; sourceTree = ""; }; F2C9B0AB2A1E214700046F2A /* Publishers.MapToResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publishers.MapToResultTests.swift; sourceTree = ""; }; + F2E3A4092B6312D100E85E39 /* URLComponentsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsExtensions.swift; sourceTree = ""; }; + F2E3A40D2B63131500E85E39 /* URLComponentsExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponentsExtensionsTests.swift; sourceTree = ""; }; + F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StringExtensions+Regex.swift"; sourceTree = ""; }; + F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StringExtensions+Ranges.swift"; sourceTree = ""; }; + F2E3A4272B6317B000E85E39 /* StringExtensions+RegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StringExtensions+RegexTests.swift"; sourceTree = ""; }; + F2E3A42B2B6317C400E85E39 /* StringExtensions+RangesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StringExtensions+RangesTests.swift"; sourceTree = ""; }; + F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublisherExtensions.swift; sourceTree = ""; }; + F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = ""; }; + F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValueSubjectExtensions.swift; sourceTree = ""; }; + F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassthroughSubjectExtensions.swift; sourceTree = ""; }; + F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhaseChangeNotification.swift; sourceTree = ""; }; + F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publishers.WithLatestFrom.swift; sourceTree = ""; }; + F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageExtensions.swift; sourceTree = ""; }; + F2E3A4552B6825EF00E85E39 /* PassthroughSubjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassthroughSubjectTests.swift; sourceTree = ""; }; + F2E3A4592B6825FC00E85E39 /* CurrentValueSubjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValueSubjectTests.swift; sourceTree = ""; }; + F2E3A45D2B684F8300E85E39 /* PublishersExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishersExtensionsTests.swift; sourceTree = ""; }; + F2E3A4612B68517000E85E39 /* Publishers.WithLatestFromTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publishers.WithLatestFromTests.swift; sourceTree = ""; }; + F2E3A4652B68F89F00E85E39 /* ColorExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensionsTests.swift; sourceTree = ""; }; + F2E3A46A2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhaseChangeNotificationTests.swift; sourceTree = ""; }; F2E47FF326B82EFE0055A0D6 /* TextStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStyle.swift; sourceTree = ""; }; F2E47FF626B8315D0055A0D6 /* TextStyleModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStyleModifier.swift; sourceTree = ""; }; F2E4801526B842000055A0D6 /* TextStylesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStylesTests.swift; sourceTree = ""; }; @@ -906,6 +991,7 @@ isa = PBXGroup; children = ( F25E770C29916109001D974C /* FontExtensionsTests.swift */, + F2E3A4652B68F89F00E85E39 /* ColorExtensionsTests.swift */, ); path = Extensions; sourceTree = ""; @@ -913,6 +999,7 @@ F27FE191299FDBCD00D4A2ED /* Combine */ = { isa = PBXGroup; children = ( + F2E3A42F2B631CF900E85E39 /* Extensions */, F27FE196299FE92300D4A2ED /* Publishers */, ); path = Combine; @@ -925,6 +1012,7 @@ F27FE192299FDBE100D4A2ED /* Publishers.RemoveNils.swift */, F27FE1AA299FEFCE00D4A2ED /* Publishers.FilterFalses.swift */, F2C9B0A62A1C075500046F2A /* Publishers.MapToResult.swift */, + F2E3A44A2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift */, ); path = Publishers; sourceTree = ""; @@ -932,6 +1020,7 @@ F27FE198299FE93700D4A2ED /* Combine */ = { isa = PBXGroup; children = ( + F2E3A4542B6825D800E85E39 /* Extensions */, F27FE199299FE94300D4A2ED /* Publishers */, ); path = Combine; @@ -944,6 +1033,7 @@ F27FE1A5299FED9700D4A2ED /* Publishers.MapToVoidTests.swift */, F27FE1AF299FF08200D4A2ED /* Publishers.FilterFalsesTests.swift */, F2C9B0AB2A1E214700046F2A /* Publishers.MapToResultTests.swift */, + F2E3A4612B68517000E85E39 /* Publishers.WithLatestFromTests.swift */, ); path = Publishers; sourceTree = ""; @@ -1048,12 +1138,15 @@ F25CA6702A20E57C00B1F0B7 /* ResultExtensions.swift */, F200836E2266339800AC6EE1 /* SequenceExtensions.swift */, F200830A225F4BFC00AC6EE1 /* StringExtensions.swift */, + F2E3A4152B6316C500E85E39 /* StringExtensions+Ranges.swift */, + F2E3A4112B63163800E85E39 /* StringExtensions+Regex.swift */, F2C1A25F24E2E055005DC7A8 /* StringExtensions+Crypto.swift */, F2008324225F84A100AC6EE1 /* StringExtensions+Formating.swift */, F200831F225F83EF00AC6EE1 /* StringExtensions+Validators.swift */, F200831A225F831F00AC6EE1 /* StringExtensions+Values.swift */, F200835622662DC400AC6EE1 /* TimeIntervalExtensions.swift */, F6D6D4C9230D5CBD00D161F9 /* TimeZoneExtensions.swift */, + F2E3A4092B6312D100E85E39 /* URLComponentsExtensions.swift */, F200835F2266316C00AC6EE1 /* URLExtensions.swift */, ); path = Extensions; @@ -1095,17 +1188,57 @@ F2008332225F877700AC6EE1 /* StringExtensions+FormatingTests.swift */, F200832E225F876300AC6EE1 /* StringExtensions+ValidatorsTests.swift */, F2008336225F878600AC6EE1 /* StringExtensions+ValuesTests.swift */, + F2E3A4272B6317B000E85E39 /* StringExtensions+RegexTests.swift */, + F2E3A42B2B6317C400E85E39 /* StringExtensions+RangesTests.swift */, F200830F225F4C5200AC6EE1 /* StringExtensionsTests.swift */, F200835B22662DDD00AC6EE1 /* TimeIntervalExtensionsTests.swift */, F25A99AD2953379F00D19C14 /* TimeZoneExtensionsTests.swift */, F20083612266317600AC6EE1 /* URLExtensionsTests.swift */, + F2E3A40D2B63131500E85E39 /* URLComponentsExtensionsTests.swift */, ); path = Extensions; sourceTree = ""; }; + F2E3A42F2B631CF900E85E39 /* Extensions */ = { + isa = PBXGroup; + children = ( + F2E3A43A2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift */, + F2E3A43F2B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift */, + F2E3A4302B631D0E00E85E39 /* PublisherExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + F2E3A4442B63FC0C00E85E39 /* Utilities */ = { + isa = PBXGroup; + children = ( + F2E3A4452B63FC2100E85E39 /* PhaseChangeNotification.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + F2E3A4542B6825D800E85E39 /* Extensions */ = { + isa = PBXGroup; + children = ( + F2E3A4592B6825FC00E85E39 /* CurrentValueSubjectTests.swift */, + F2E3A4552B6825EF00E85E39 /* PassthroughSubjectTests.swift */, + F2E3A45D2B684F8300E85E39 /* PublishersExtensionsTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + F2E3A4692B68FB1A00E85E39 /* Utilities */ = { + isa = PBXGroup; + children = ( + F2E3A46A2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift */, + ); + path = Utilities; + sourceTree = ""; + }; F2E47FF026B82ED90055A0D6 /* SwiftUI */ = { isa = PBXGroup; children = ( + F2E3A4442B63FC0C00E85E39 /* Utilities */, F2E47FF526B8314D0055A0D6 /* Extensions */, F2E47FF226B82EF30055A0D6 /* Types */, ); @@ -1115,6 +1248,7 @@ F2E47FF126B82EE30055A0D6 /* SwiftUI */ = { isa = PBXGroup; children = ( + F2E3A4692B68FB1A00E85E39 /* Utilities */, F25E770B299160D9001D974C /* Extensions */, F2E4801426B841F00055A0D6 /* Types */, ); @@ -1134,6 +1268,8 @@ children = ( F2E47FF626B8315D0055A0D6 /* TextStyleModifier.swift */, F23EE9D6295485E20078D516 /* FontExtensions.swift */, + F2E3A4352B631DEB00E85E39 /* ColorExtensions.swift */, + F2E3A44F2B64047800E85E39 /* ImageExtensions.swift */, ); path = Extensions; sourceTree = ""; @@ -1519,6 +1655,7 @@ files = ( F27FE197299FE92C00D4A2ED /* Publishers.MapToVoid.swift in Sources */, F210E6AE231EC709004D9F0A /* XibRepresentable.swift in Sources */, + F2E3A4192B63175900E85E39 /* StringExtensions+Ranges.swift in Sources */, F2C9B0A72A1C075500046F2A /* Publishers.MapToResult.swift in Sources */, F2C1A28724E46041005DC7A8 /* CurrencyCodeType.swift in Sources */, F27FE1AB299FEFCE00D4A2ED /* Publishers.FilterFalses.swift in Sources */, @@ -1531,10 +1668,12 @@ F6F97EFA226F1E5000C3E953 /* UIImageExtensions.swift in Sources */, F26C3AF822537A4600189D28 /* MagicPills.swift in Sources */, F6E7C6872264902E00F0F11E /* UIFontExtensions.swift in Sources */, + F2E3A4312B631D0E00E85E39 /* PublisherExtensions.swift in Sources */, F249673E230EBCFA00C06E2E /* TableDataSource.swift in Sources */, F21A6779226F4E360026F6E9 /* CGImageExtensions+UIKit.swift in Sources */, F2C1A27324E41E3B005DC7A8 /* NSAttributedString+UIKit.swift in Sources */, 44A4817F28F80FDB000F6562 /* CalendarExtensions.swift in Sources */, + F2E3A4462B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */, F6B3838F22649D56001E4068 /* UILabelExtensions.swift in Sources */, F6F97EEE226DAF2C00C3E953 /* UIViewExtensions.swift in Sources */, F29FA9AF2B03C6340064A711 /* JWT.swift in Sources */, @@ -1545,6 +1684,7 @@ F25EED6528D0AB9400819C10 /* DateExtensions+Format.swift in Sources */, F20082F3225E518600AC6EE1 /* DecimalExtensions.swift in Sources */, F2496739230EB99400C06E2E /* ZoomAndSnapFlowLayout.swift in Sources */, + F2E3A43B2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */, F2C1A26024E2E055005DC7A8 /* StringExtensions+Crypto.swift in Sources */, F200830B225F4BFC00AC6EE1 /* StringExtensions.swift in Sources */, F22A356222807465004E8546 /* Fold.swift in Sources */, @@ -1554,6 +1694,7 @@ F2C78F1F238D67E1008C4846 /* UITableViewCellExtensions.swift in Sources */, 47269D43241FE79A008D58EB /* DayMomentType.swift in Sources */, F2A74DB9225765CF001F0DF2 /* ArrayExtensions.swift in Sources */, + F2E3A41A2B63175900E85E39 /* StringExtensions+Regex.swift in Sources */, F62CC32D2270683C00DC46F2 /* UITableViewExtensions.swift in Sources */, F22A38E7227C76FD0012C010 /* DictionaryExtensions.swift in Sources */, F20082D3225DFF4F00AC6EE1 /* OptionalType.swift in Sources */, @@ -1569,12 +1710,15 @@ F20082C7225DFC9D00AC6EE1 /* BoolExtensions.swift in Sources */, F2C78F16238D66CE008C4846 /* CellIdentificable.swift in Sources */, F23EE9D7295485E20078D516 /* FontExtensions.swift in Sources */, + F2E3A4402B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */, F297F0C924E55B0A00963BD8 /* CollectionExtensions.swift in Sources */, F6F3330D23042AEA00E93F0E /* CALayerExtensions.swift in Sources */, F6D6D4C2230C108F00D161F9 /* LocaleExtensions.swift in Sources */, F2C78F29238D76DF008C4846 /* UITableViewHeaderFooterViewExtensions.swift in Sources */, + F2E3A44B2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */, F20082E5225E1E0000AC6EE1 /* DateExtensions.swift in Sources */, F20083602266316C00AC6EE1 /* URLExtensions.swift in Sources */, + F2E3A4362B631DEB00E85E39 /* ColorExtensions.swift in Sources */, F20083402266115E00AC6EE1 /* BundleExtensions.swift in Sources */, F2C502AD2951E83E007AEF92 /* IndexedItemIdentifiable.swift in Sources */, F21A6777226F4E260026F6E9 /* CIImageExtensions+UIKit.swift in Sources */, @@ -1584,6 +1728,7 @@ F22A38D8227C63250012C010 /* OrderedSet.swift in Sources */, F68925CF22CDE8290008263B /* IntExtensions.swift in Sources */, F249672D230D5DA200C06E2E /* Difference.swift in Sources */, + F2E3A4502B64047800E85E39 /* ImageExtensions.swift in Sources */, F200835722662DC400AC6EE1 /* TimeIntervalExtensions.swift in Sources */, F2008325225F84A100AC6EE1 /* StringExtensions+Formating.swift in Sources */, F297F0D324E569DF00963BD8 /* KeyedValue.swift in Sources */, @@ -1610,6 +1755,7 @@ F20082FE225E556500AC6EE1 /* DecimalExtensionsTests.swift in Sources */, F27512AC252B3DCB0057D6F0 /* CollectionCellForTests.swift in Sources */, F68925D522CDE8A00008263B /* IntExtensionsTests.swift in Sources */, + F2E3A45A2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */, F22A3568228074D4004E8546 /* FoldTests.swift in Sources */, F20083452266117700AC6EE1 /* BundleExtensionsTests.swift in Sources */, F20082D9225DFFC300AC6EE1 /* OptionalTypeTests.swift in Sources */, @@ -1619,12 +1765,15 @@ F27FE1B4299FF09B00D4A2ED /* Publishers.FilterFalsesTests.swift in Sources */, F2C9B0B02A1E216800046F2A /* Publishers.MapToResultTests.swift in Sources */, F69A081E2264DF1200CB09F2 /* StringExtensionsUIKitTests.swift in Sources */, + F2E3A4282B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */, F27FE19C299FE96500D4A2ED /* Publishers.RemoveNilsTests.swift in Sources */, + F2E3A42C2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */, F210E6B2231EC7F0004D9F0A /* XibRepresentableTests.swift in Sources */, F62CC3482270B52700DC46F2 /* ToastTests.swift in Sources */, F2008371226633A500AC6EE1 /* SequenceExtensionsTests.swift in Sources */, F25CA67A2A20E68400B1F0B7 /* ResultExtensionsTests.swift in Sources */, F297F0CF24E55F0A00963BD8 /* CollectionExtensionsTests.swift in Sources */, + F2E3A4622B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */, F2496732230D5E1B00C06E2E /* DifferenceTests.swift in Sources */, F62CC32A22705F5700DC46F2 /* UIStackViewExtensionsTests.swift in Sources */, F2C1A28D24E464CD005DC7A8 /* CurrencyCodeTypeTests.swift in Sources */, @@ -1636,26 +1785,32 @@ F2496729230D506F00C06E2E /* SemVerTests.swift in Sources */, F23DC0AB2B0EA68B00B2DD3C /* JWTTests.swift in Sources */, F6F333152304370800E93F0E /* CALayerExtensionsTests.swift in Sources */, + F2E3A40A2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */, F20082E1225DFFD900AC6EE1 /* OptionalExtensionsTests.swift in Sources */, + F2E3A45E2B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */, F2008333225F877700AC6EE1 /* StringExtensions+FormatingTests.swift in Sources */, F2008337225F878600AC6EE1 /* StringExtensions+ValuesTests.swift in Sources */, F20083632266317900AC6EE1 /* URLExtensionsTests.swift in Sources */, 44A4818828F82219000F6562 /* CalendarExtensionsTests.swift in Sources */, F2A74DC022576610001F0DF2 /* ArrayExtensionsTests.swift in Sources */, + F2E3A46B2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */, F69A082022672BDC00CB09F2 /* UIFontExtensionsTests.swift in Sources */, F22A38DE227C63930012C010 /* OrderedSetTests.swift in Sources */, F22A38FE227C80800012C010 /* DefinesPrimaryKeyTests.swift in Sources */, F27512B5252B3DD80057D6F0 /* TableViewCellForTests.swift in Sources */, F6F97F04226F53E100C3E953 /* UIScrollViewExtensionsTest.swift in Sources */, F200835C22662DDD00AC6EE1 /* TimeIntervalExtensionsTests.swift in Sources */, + F2E3A4662B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */, F26C630526C422160065E04C /* CodableTestObject.swift in Sources */, F27FE1A7299FED9900D4A2ED /* Publishers.MapToVoidTests.swift in Sources */, F62CC34B2270B53C00DC46F2 /* SnackBarTests.swift in Sources */, F62CC34E2272015500DC46F2 /* UIViewControllerExtensionsTests.swift in Sources */, F2496746230EBE4200C06E2E /* LastExecutionStateTests.swift in Sources */, + F2E3A4562B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */, 44B30F24268B2502007C5B51 /* CodableExtensionsTests.swift in Sources */, F20082DD225DFFCF00AC6EE1 /* BoolExtensionsTests.swift in Sources */, F2C78F27238D6AF4008C4846 /* CellIdentificableTests.swift in Sources */, + F2E3A40E2B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */, F2E4801626B842000055A0D6 /* TextStylesTests.swift in Sources */, F22A38F0227C775F0012C010 /* DictionaryExtensionsTests.swift in Sources */, F200834A226612F800AC6EE1 /* FakeBundle.swift in Sources */, @@ -1676,13 +1831,16 @@ F2091DB12631AD23002F071E /* CodableExtensions.swift in Sources */, F2E4801126B83FB90055A0D6 /* TextStyleModifier.swift in Sources */, F2C1A27924E41E76005DC7A8 /* NSAttributedStringExtensions.swift in Sources */, + F2E3A4512B64047800E85E39 /* ImageExtensions.swift in Sources */, F20083662266317E00AC6EE1 /* URLExtensions.swift in Sources */, + F2E3A4472B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */, F6D6D4C3230C108F00D161F9 /* LocaleExtensions.swift in Sources */, F20082F4225E518600AC6EE1 /* DecimalExtensions.swift in Sources */, F200830C225F4BFC00AC6EE1 /* StringExtensions.swift in Sources */, F20082C3225DFC6E00AC6EE1 /* OptionalExtensions.swift in Sources */, F2A74DBA225765CF001F0DF2 /* ArrayExtensions.swift in Sources */, 44A4818028F80FDB000F6562 /* CalendarExtensions.swift in Sources */, + F2E3A43C2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */, F2E4801226B83FB90055A0D6 /* TextStyle.swift in Sources */, F20082D4225DFF4F00AC6EE1 /* OptionalType.swift in Sources */, F2008326225F84A100AC6EE1 /* StringExtensions+Formating.swift in Sources */, @@ -1697,6 +1855,7 @@ F20082E6225E1E0000AC6EE1 /* DateExtensions.swift in Sources */, F200831C225F831F00AC6EE1 /* StringExtensions+Values.swift in Sources */, F22A356322807465004E8546 /* Fold.swift in Sources */, + F2E3A41D2B63175B00E85E39 /* StringExtensions+Ranges.swift in Sources */, F2496725230D504200C06E2E /* SemVer.swift in Sources */, F2008321225F83EF00AC6EE1 /* StringExtensions+Validators.swift in Sources */, F27FE1AC299FEFCE00D4A2ED /* Publishers.FilterFalses.swift in Sources */, @@ -1704,12 +1863,17 @@ F297F0D424E569DF00963BD8 /* KeyedValue.swift in Sources */, F200835822662DC400AC6EE1 /* TimeIntervalExtensions.swift in Sources */, F25CA6722A20E57C00B1F0B7 /* ResultExtensions.swift in Sources */, + F2E3A44C2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */, + F2E3A4372B631DEB00E85E39 /* ColorExtensions.swift in Sources */, + F2E3A41E2B63175B00E85E39 /* StringExtensions+Regex.swift in Sources */, + F2E3A4322B631D0E00E85E39 /* PublisherExtensions.swift in Sources */, 47269D44241FE7CC008D58EB /* DayMomentType.swift in Sources */, F2C502AE2951E83E007AEF92 /* IndexedItemIdentifiable.swift in Sources */, F200832B225F862900AC6EE1 /* DataExtensions.swift in Sources */, F2496742230EBDEF00C06E2E /* LastExecutionState.swift in Sources */, F2008374226633AB00AC6EE1 /* SequenceExtensions.swift in Sources */, F6F3331A2306B03F00E93F0E /* CGFloatExtensions.swift in Sources */, + F2E3A4412B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */, F29FA9B02B03C6340064A711 /* JWT.swift in Sources */, F2C1A28824E46055005DC7A8 /* CurrencyCodeType.swift in Sources */, F25E770829915EEC001D974C /* IndexedItem.swift in Sources */, @@ -1727,15 +1891,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F2E3A45B2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */, F2008372226633A700AC6EE1 /* SequenceExtensionsTests.swift in Sources */, + F2E3A4292B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */, F20082EB225E1E7100AC6EE1 /* DateExtensionsTests.swift in Sources */, F20082FF225E556500AC6EE1 /* DecimalExtensionsTests.swift in Sources */, F20082DA225DFFC300AC6EE1 /* OptionalTypeTests.swift in Sources */, F22A38DF227C63930012C010 /* OrderedSetTests.swift in Sources */, F68925D622CDE8A10008263B /* IntExtensionsTests.swift in Sources */, + F2E3A45F2B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */, F297F0D924E572A300963BD8 /* KeyedValueTests.swift in Sources */, F297F0D024E55F0B00963BD8 /* CollectionExtensionsTests.swift in Sources */, F27FE1B5299FF09C00D4A2ED /* Publishers.FilterFalsesTests.swift in Sources */, + F2E3A42D2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */, F22A3569228074D4004E8546 /* FoldTests.swift in Sources */, F2008311225F4C5200AC6EE1 /* StringExtensionsTests.swift in Sources */, F25E77122991612F001D974C /* FontExtensionsTests.swift in Sources */, @@ -1745,15 +1913,19 @@ F20082E2225DFFD900AC6EE1 /* OptionalExtensionsTests.swift in Sources */, F22A38FF227C80800012C010 /* DefinesPrimaryKeyTests.swift in Sources */, F27FE1A8299FED9B00D4A2ED /* Publishers.MapToVoidTests.swift in Sources */, + F2E3A40B2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */, + F2E3A4572B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */, F2A74DC122576610001F0DF2 /* ArrayExtensionsTests.swift in Sources */, F2008334225F877700AC6EE1 /* StringExtensions+FormatingTests.swift in Sources */, F27FE19D299FE96600D4A2ED /* Publishers.RemoveNilsTests.swift in Sources */, 44A4818928F8221A000F6562 /* CalendarExtensionsTests.swift in Sources */, + F2E3A40F2B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */, F25A99AB2953378D00D19C14 /* LocaleExtensionTests.swift in Sources */, F249672A230D506F00C06E2E /* SemVerTests.swift in Sources */, F249674B230EBE9500C06E2E /* JSONTestHelper.swift in Sources */, F25A99AF2953379F00D19C14 /* TimeZoneExtensionsTests.swift in Sources */, F20083642266317A00AC6EE1 /* URLExtensionsTests.swift in Sources */, + F2E3A4632B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */, F20083462266117700AC6EE1 /* BundleExtensionsTests.swift in Sources */, F209D41E24E6C6BC002BF853 /* Int64ExtensionsTests.swift in Sources */, F25CA67B2A20E68400B1F0B7 /* ResultExtensionsTests.swift in Sources */, @@ -1765,10 +1937,12 @@ F6F3331F2306B0AA00E93F0E /* CGFloatExtensionsTests.swift in Sources */, 44B30F25268B2502007C5B51 /* CodableExtensionsTests.swift in Sources */, F22A38F1227C77610012C010 /* DictionaryExtensionsTests.swift in Sources */, + F2E3A46C2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */, F2C1A26624E2E0A1005DC7A8 /* StringExtensions+CryptoTests.swift in Sources */, F2496747230EBE4200C06E2E /* LastExecutionStateTests.swift in Sources */, F2E4801726B842000055A0D6 /* TextStylesTests.swift in Sources */, F2008338225F878600AC6EE1 /* StringExtensions+ValuesTests.swift in Sources */, + F2E3A4672B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */, F2008330225F876300AC6EE1 /* StringExtensions+ValidatorsTests.swift in Sources */, F22A357122807839004E8546 /* DispatchQueueExtensionsTests.swift in Sources */, ); @@ -1780,6 +1954,7 @@ files = ( F27FE1A3299FED8200D4A2ED /* Publishers.MapToVoid.swift in Sources */, F210E6AF231EC709004D9F0A /* XibRepresentable.swift in Sources */, + F2E3A4212B63175D00E85E39 /* StringExtensions+Ranges.swift in Sources */, F2C9B0A92A1C075500046F2A /* Publishers.MapToResult.swift in Sources */, F27FE1A0299FE96E00D4A2ED /* Publishers.RemoveNils.swift in Sources */, F27FE1AD299FEFCE00D4A2ED /* Publishers.FilterFalses.swift in Sources */, @@ -1792,10 +1967,12 @@ F22A355E22806EAC004E8546 /* DefinesPrimaryKey.swift in Sources */, F20083672266317F00AC6EE1 /* URLExtensions.swift in Sources */, F20082F5225E518600AC6EE1 /* DecimalExtensions.swift in Sources */, + F2E3A4332B631D0E00E85E39 /* PublisherExtensions.swift in Sources */, F21A677C226F4E580026F6E9 /* UIImageExtensions.swift in Sources */, F249673F230EBCFA00C06E2E /* TableDataSource.swift in Sources */, F6F97F02226F501E00C3E953 /* UIScrollViewExtensions.swift in Sources */, F2C1A27524E41E3F005DC7A8 /* NSAttributedString+UIKit.swift in Sources */, + F2E3A4482B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */, 44A4818128F80FDB000F6562 /* CalendarExtensions.swift in Sources */, F200830D225F4BFC00AC6EE1 /* StringExtensions.swift in Sources */, F29FA9B12B03C6340064A711 /* JWT.swift in Sources */, @@ -1806,6 +1983,7 @@ F6D6D4CC230D5CBD00D161F9 /* TimeZoneExtensions.swift in Sources */, F25EED6728D0AB9400819C10 /* DateExtensions+Format.swift in Sources */, F62CC32822704F0500DC46F2 /* UIStackViewExtensions.swift in Sources */, + F2E3A43D2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */, F249673C230EBB7A00C06E2E /* ZoomAndSnapFlowLayout.swift in Sources */, F2C1A26224E2E07A005DC7A8 /* StringExtensions+Crypto.swift in Sources */, F20082D5225DFF4F00AC6EE1 /* OptionalType.swift in Sources */, @@ -1815,6 +1993,7 @@ F2C78F20238D67E1008C4846 /* UITableViewCellExtensions.swift in Sources */, 47269D45241FE7CD008D58EB /* DayMomentType.swift in Sources */, F2008327225F84A100AC6EE1 /* StringExtensions+Formating.swift in Sources */, + F2E3A4222B63175D00E85E39 /* StringExtensions+Regex.swift in Sources */, F2C1A28124E45EFD005DC7A8 /* Int64Extensions.swift in Sources */, F20082C9225DFC9D00AC6EE1 /* BoolExtensions.swift in Sources */, F22A38E9227C76FD0012C010 /* DictionaryExtensions.swift in Sources */, @@ -1830,12 +2009,15 @@ F2008379226789AA00AC6EE1 /* UILabelExtensions.swift in Sources */, F2008322225F83EF00AC6EE1 /* StringExtensions+Validators.swift in Sources */, F21A677E226F4E600026F6E9 /* UIViewExtensions.swift in Sources */, + F2E3A4422B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */, F23EE9D9295485E20078D516 /* FontExtensions.swift in Sources */, F2C78F17238D66CE008C4846 /* CellIdentificable.swift in Sources */, F297F0CB24E55B1000963BD8 /* CollectionExtensions.swift in Sources */, F6F3330F23042AEA00E93F0E /* CALayerExtensions.swift in Sources */, + F2E3A44D2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */, F6D6D4C4230C108F00D161F9 /* LocaleExtensions.swift in Sources */, F2C78F2A238D76DF008C4846 /* UITableViewHeaderFooterViewExtensions.swift in Sources */, + F2E3A4382B631DEB00E85E39 /* ColorExtensions.swift in Sources */, F200835922662DC400AC6EE1 /* TimeIntervalExtensions.swift in Sources */, F21A677B226F4E540026F6E9 /* CGImageExtensions+UIKit.swift in Sources */, F2C502AF2951E83E007AEF92 /* IndexedItemIdentifiable.swift in Sources */, @@ -1845,6 +2027,7 @@ F2496743230EBDEF00C06E2E /* LastExecutionState.swift in Sources */, F22A38DA227C634E0012C010 /* OrderedSet.swift in Sources */, F68925D122CDE83B0008263B /* IntExtensions.swift in Sources */, + F2E3A4522B64047800E85E39 /* ImageExtensions.swift in Sources */, F249672F230D5DA200C06E2E /* Difference.swift in Sources */, F62CC33E2270862800DC46F2 /* UIViewControllerExtensions.swift in Sources */, F2008378226789A700AC6EE1 /* UIFontExtensions.swift in Sources */, @@ -1871,6 +2054,7 @@ F297F0DA24E572A300963BD8 /* KeyedValueTests.swift in Sources */, F2008300225E556500AC6EE1 /* DecimalExtensionsTests.swift in Sources */, F68925D722CDE8A20008263B /* IntExtensionsTests.swift in Sources */, + F2E3A45C2B6825FC00E85E39 /* CurrentValueSubjectTests.swift in Sources */, F22A356A228074D4004E8546 /* FoldTests.swift in Sources */, F20082DB225DFFC300AC6EE1 /* OptionalTypeTests.swift in Sources */, F2008312225F4C5200AC6EE1 /* StringExtensionsTests.swift in Sources */, @@ -1880,12 +2064,15 @@ F27FE1B6299FF09C00D4A2ED /* Publishers.FilterFalsesTests.swift in Sources */, F2C9B0B22A1E216A00046F2A /* Publishers.MapToResultTests.swift in Sources */, F21A677F226F4E6B0026F6E9 /* UICollectionViewExtensionsTests.swift in Sources */, + F2E3A42A2B6317B000E85E39 /* StringExtensions+RegexTests.swift in Sources */, F27FE19E299FE96700D4A2ED /* Publishers.RemoveNilsTests.swift in Sources */, + F2E3A42E2B6317C400E85E39 /* StringExtensions+RangesTests.swift in Sources */, F210E6B3231EC7F0004D9F0A /* XibRepresentableTests.swift in Sources */, F62CC3492270B52700DC46F2 /* ToastTests.swift in Sources */, F21A6781226F4E6B0026F6E9 /* UIImageExtensionsTests.swift in Sources */, F25CA67C2A20E68500B1F0B7 /* ResultExtensionsTests.swift in Sources */, F297F0D124E55F0C00963BD8 /* CollectionExtensionsTests.swift in Sources */, + F2E3A4642B68517000E85E39 /* Publishers.WithLatestFromTests.swift in Sources */, F2496734230D5E1B00C06E2E /* DifferenceTests.swift in Sources */, F62CC32B22705F5700DC46F2 /* UIStackViewExtensionsTests.swift in Sources */, F2C1A28F24E464CF005DC7A8 /* CurrencyCodeTypeTests.swift in Sources */, @@ -1897,26 +2084,32 @@ F22A357222807839004E8546 /* DispatchQueueExtensionsTests.swift in Sources */, F23DC0AD2B0EA68B00B2DD3C /* JWTTests.swift in Sources */, F249672B230D506F00C06E2E /* SemVerTests.swift in Sources */, + F2E3A40C2B6312D100E85E39 /* URLComponentsExtensions.swift in Sources */, F6F333172304370900E93F0E /* CALayerExtensionsTests.swift in Sources */, + F2E3A4602B684F8300E85E39 /* PublishersExtensionsTests.swift in Sources */, F2008335225F877700AC6EE1 /* StringExtensions+FormatingTests.swift in Sources */, F6F97F05226F53E100C3E953 /* UIScrollViewExtensionsTest.swift in Sources */, F20083652266317A00AC6EE1 /* URLExtensionsTests.swift in Sources */, 44A4818A28F8221D000F6562 /* CalendarExtensionsTests.swift in Sources */, F20083472266117700AC6EE1 /* BundleExtensionsTests.swift in Sources */, + F2E3A46D2B68FB2B00E85E39 /* PhaseChangeNotificationTests.swift in Sources */, F200834C226612F800AC6EE1 /* FakeBundle.swift in Sources */, F200837A226789B100AC6EE1 /* UIFontExtensionsTests.swift in Sources */, F22A38E0227C63940012C010 /* OrderedSetTests.swift in Sources */, F22A3900227C80800012C010 /* DefinesPrimaryKeyTests.swift in Sources */, F200837B226789B500AC6EE1 /* UILabelExtensionTests.swift in Sources */, F20082DF225DFFCF00AC6EE1 /* BoolExtensionsTests.swift in Sources */, + F2E3A4682B68F89F00E85E39 /* ColorExtensionsTests.swift in Sources */, F26C630726C422160065E04C /* CodableTestObject.swift in Sources */, F27FE1A9299FED9B00D4A2ED /* Publishers.MapToVoidTests.swift in Sources */, F62CC34C2270B53C00DC46F2 /* SnackBarTests.swift in Sources */, F62CC34F2272015500DC46F2 /* UIViewControllerExtensionsTests.swift in Sources */, F2496748230EBE4200C06E2E /* LastExecutionStateTests.swift in Sources */, + F2E3A4582B6825EF00E85E39 /* PassthroughSubjectTests.swift in Sources */, 44B30F26268B2503007C5B51 /* CodableExtensionsTests.swift in Sources */, F2008339225F878600AC6EE1 /* StringExtensions+ValuesTests.swift in Sources */, F2C78F26238D6AF2008C4846 /* CellIdentificableTests.swift in Sources */, + F2E3A4102B63131500E85E39 /* URLComponentsExtensionsTests.swift in Sources */, F2E4801826B842000055A0D6 /* TextStylesTests.swift in Sources */, F22A38F2227C77620012C010 /* DictionaryExtensionsTests.swift in Sources */, F21A6780226F4E6B0026F6E9 /* UIViewExtensionsTests.swift in Sources */, @@ -1932,6 +2125,7 @@ F26C3AFB22537A4600189D28 /* MagicPills.swift in Sources */, F258CE9E25F2A603007EF07E /* MagicError.swift in Sources */, 44A4818228F80FDB000F6562 /* CalendarExtensions.swift in Sources */, + F2E3A4492B63FC2100E85E39 /* PhaseChangeNotification.swift in Sources */, F20082F6225E518600AC6EE1 /* DecimalExtensions.swift in Sources */, F200830E225F4BFC00AC6EE1 /* StringExtensions.swift in Sources */, F2C1A28224E45EFE005DC7A8 /* Int64Extensions.swift in Sources */, @@ -1945,12 +2139,15 @@ F2E4801326B841370055A0D6 /* TextStyle.swift in Sources */, F2008376226633AD00AC6EE1 /* SequenceExtensions.swift in Sources */, F27FE1A1299FE96F00D4A2ED /* Publishers.RemoveNils.swift in Sources */, + F2E3A44E2B63FEF300E85E39 /* Publishers.WithLatestFrom.swift in Sources */, + F2E3A4432B63FBAE00E85E39 /* PassthroughSubjectExtensions.swift in Sources */, F22A38EA227C76FD0012C010 /* DictionaryExtensions.swift in Sources */, F2E4800E26B83FB50055A0D6 /* TextStyleModifier.swift in Sources */, F2496730230D5DA200C06E2E /* Difference.swift in Sources */, F200831E225F831F00AC6EE1 /* StringExtensions+Values.swift in Sources */, F200832D225F862900AC6EE1 /* DataExtensions.swift in Sources */, F2C9B0AA2A1C075500046F2A /* Publishers.MapToResult.swift in Sources */, + F2E3A4392B631DEB00E85E39 /* ColorExtensions.swift in Sources */, F20082CA225DFC9D00AC6EE1 /* BoolExtensions.swift in Sources */, F2496744230EBDEF00C06E2E /* LastExecutionState.swift in Sources */, F22A356522807465004E8546 /* Fold.swift in Sources */, @@ -1958,13 +2155,17 @@ F25CA6742A20E57C00B1F0B7 /* ResultExtensions.swift in Sources */, F27FE1AE299FEFCE00D4A2ED /* Publishers.FilterFalses.swift in Sources */, F25EED6828D0AB9400819C10 /* DateExtensions+Format.swift in Sources */, + F2E3A4342B631D0E00E85E39 /* PublisherExtensions.swift in Sources */, F22A355F22806EAD004E8546 /* DefinesPrimaryKey.swift in Sources */, F297F0CC24E55B1100963BD8 /* CollectionExtensions.swift in Sources */, + F2E3A4262B63175E00E85E39 /* StringExtensions+Regex.swift in Sources */, F2C1A27B24E41E77005DC7A8 /* NSAttributedStringExtensions.swift in Sources */, F2AC727C23460D4200661BE9 /* TimeZoneExtensions.swift in Sources */, + F2E3A4532B64047800E85E39 /* ImageExtensions.swift in Sources */, F20083682266317F00AC6EE1 /* URLExtensions.swift in Sources */, F20083432266115E00AC6EE1 /* BundleExtensions.swift in Sources */, F2496727230D504200C06E2E /* SemVer.swift in Sources */, + F2E3A43E2B63FB9600E85E39 /* CurrentValueSubjectExtensions.swift in Sources */, F200835A22662DC400AC6EE1 /* TimeIntervalExtensions.swift in Sources */, F23EE9DA295485E20078D516 /* FontExtensions.swift in Sources */, F68925D222CDE83C0008263B /* IntExtensions.swift in Sources */, @@ -1976,6 +2177,7 @@ F29FA9B22B03C6340064A711 /* JWT.swift in Sources */, F22A38DB227C634F0012C010 /* OrderedSet.swift in Sources */, F2AC727A23460D2E00661BE9 /* FormatterExtensions.swift in Sources */, + F2E3A4252B63175E00E85E39 /* StringExtensions+Ranges.swift in Sources */, F2C502B02951E83E007AEF92 /* IndexedItemIdentifiable.swift in Sources */, 47269D46241FE7CF008D58EB /* DayMomentType.swift in Sources */, ); diff --git a/Source/Combine/Extensions/CurrentValueSubjectExtensions.swift b/Source/Combine/Extensions/CurrentValueSubjectExtensions.swift new file mode 100644 index 0000000..73a0a01 --- /dev/null +++ b/Source/Combine/Extensions/CurrentValueSubjectExtensions.swift @@ -0,0 +1,11 @@ +import Combine +import Foundation + +public extension CurrentValueSubject where Output: Equatable { + /// Emit new value if distinct, ignore + func sendIfDistinct(_ input: Output) { + if input != value { + send(input) + } + } +} diff --git a/Source/Combine/Extensions/PassthroughSubjectExtensions.swift b/Source/Combine/Extensions/PassthroughSubjectExtensions.swift new file mode 100644 index 0000000..bfe165b --- /dev/null +++ b/Source/Combine/Extensions/PassthroughSubjectExtensions.swift @@ -0,0 +1,12 @@ +import Combine +import Foundation + +public extension PassthroughSubject where Output == String { + func sendType(_ type: T.Type, tag: String = "") { + send("\(type.self)-\(tag)") + } + + func filterType(_ type: T.Type, tag: String = "") -> Publishers.Filter> { + filter { $0 == "\(type.self)-\(tag)" } + } +} diff --git a/Source/Combine/Extensions/PublisherExtensions.swift b/Source/Combine/Extensions/PublisherExtensions.swift new file mode 100644 index 0000000..b2e1e42 --- /dev/null +++ b/Source/Combine/Extensions/PublisherExtensions.swift @@ -0,0 +1,15 @@ +import Combine +import Foundation + +public extension Publisher { + /** + Projects each element from a publisher into a new publisher and then transforms an publisher into an publisher producing values only from the most recent value. + - parameter transform: A transform function to apply to each element. + - returns: A publisher whose elements are the result of invoking the transform function on each value of source producing a publisher and that at any point in time produces the elements of the most recent inner sequence that has been received. + */ + func flatMapLatest(_ transform: @escaping (Output) -> AnyPublisher) -> AnyPublisher { + map(transform) + .switchToLatest() + .eraseToAnyPublisher() + } +} diff --git a/Source/Combine/Publishers/Publishers.WithLatestFrom.swift b/Source/Combine/Publishers/Publishers.WithLatestFrom.swift new file mode 100644 index 0000000..af83b6b --- /dev/null +++ b/Source/Combine/Publishers/Publishers.WithLatestFrom.swift @@ -0,0 +1,84 @@ +import Combine +import Foundation + +public extension Publisher { + /// Emit with the latest value from given publisher, if the given publisher is empty upstream value will loss. + func withLatestFrom(_ other: Other) -> Publishers.WithLatestFrom { + Publishers.WithLatestFrom(upstream: self, other: other) + } +} + +public extension Publishers { + struct WithLatestFrom: Publisher where Upstream.Failure == Other.Failure { + public typealias Output = (Upstream.Output, Other.Output) + public typealias Failure = Upstream.Failure + + public let upstream: Upstream + public let other: Other + + public init(upstream: Upstream, other: Other) { + self.upstream = upstream + self.other = other + } + + public func receive(subscriber: Downstream) + where Output == Downstream.Input, Downstream.Failure == Upstream.Failure { + let merged = mergedStream(upstream, other) + let result = resultStream(from: merged) + result.subscribe(subscriber) + } + } +} + +private extension Publishers.WithLatestFrom { + // MARK: - Types + enum MergedElement { + case upstream1(Upstream.Output) + case upstream2(Other.Output) + } + + typealias ScanResult = + (value1: Upstream.Output?, + value2: Other.Output?, shouldEmit: Bool) + + // MARK: - Pipelines + func mergedStream(_ upstream1: Upstream, _ upstream2: Other) + -> AnyPublisher { + let mergedElementUpstream1 = upstream1 + .map { MergedElement.upstream1($0) } + let mergedElementUpstream2 = upstream2 + .map { MergedElement.upstream2($0) } + return mergedElementUpstream1 + .merge(with: mergedElementUpstream2) + .eraseToAnyPublisher() + } + + func resultStream( + from mergedStream: AnyPublisher + ) -> AnyPublisher { + mergedStream + .scan(nil) { (prevResult: ScanResult?, mergedElement: MergedElement) -> ScanResult? in + var newValue1: Upstream.Output? + var newValue2: Other.Output? + let shouldEmit: Bool + + switch mergedElement { + case .upstream1(let value): + newValue1 = value + shouldEmit = prevResult?.value2 != nil + + case .upstream2(let value): + newValue2 = value + shouldEmit = false + } + + return ScanResult(value1: newValue1 ?? prevResult?.value1, + value2: newValue2 ?? prevResult?.value2, + shouldEmit: shouldEmit) + } + .compactMap { $0 } + .filter { $0.shouldEmit } + .map { Output($0.value1!, $0.value2!) } + .eraseToAnyPublisher() + } +} diff --git a/Source/Foundation/Extensions/BundleExtensions.swift b/Source/Foundation/Extensions/BundleExtensions.swift index cfe6fc4..da5dd9e 100644 --- a/Source/Foundation/Extensions/BundleExtensions.swift +++ b/Source/Foundation/Extensions/BundleExtensions.swift @@ -1,23 +1,42 @@ import Foundation public extension Bundle { - /// The release ("Major"."Minor"."Patch") or version number of the bundle (read-only, optional) - var versionNumber: String? { - object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + /// The release ("Major"."Minor"."Patch") or version number of the bundle + var versionNumber: String { + (object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? "0" + } + + @available(*, deprecated, renamed: "versionNumber") + var appVersion: String { + versionNumber } /// The version number of the bundle. (read-only, optional) - var buildNumber: String? { - object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String + var buildNumber: String { + (object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String) ?? "0" + } + + @available(*, deprecated, renamed: "buildNumber") + var appBuild: String { + buildNumber } /// Release number with version number or version number if are the same (read-only, optional) - var fullVersionNumber: String? { - guard let versionNumber = self.versionNumber, - let buildNumber = self.buildNumber else { - return nil - } + var fullVersionNumber: String { + versionNumber == buildNumber ? "v\(versionNumber)" : "v\(versionNumber)(\(buildNumber))" + } + + @available(*, deprecated, renamed: "fullVersionNumber") + var appFullVersion: String { + fullVersionNumber + } + + var isRunningFromTestFlight: Bool { + appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" + } - return versionNumber == buildNumber ? "v\(versionNumber)" : "v\(versionNumber)(\(buildNumber))" + @available(*, deprecated, renamed: "isRunningFromTestFlight") + var runningFromTestFlight: Bool { + isRunningFromTestFlight } } diff --git a/Source/Foundation/Extensions/DecimalExtensions.swift b/Source/Foundation/Extensions/DecimalExtensions.swift index b7f79db..29e6fa1 100644 --- a/Source/Foundation/Extensions/DecimalExtensions.swift +++ b/Source/Foundation/Extensions/DecimalExtensions.swift @@ -1,6 +1,32 @@ import Foundation public extension Decimal { + /// Return the given percent from current value (self) + /// - Returns: Decimal + func asPercentage(_ value: Decimal) -> Decimal { + self / (100 / value) + } + + /// Return the negated value + var negated: Decimal { + -self + } + + /// Return true if value is != 0 + var isNotZero: Bool { + !isZero + } + + /// Return double from decimal value. + var doubleValue: Double { + Double(truncating: self as NSNumber) + } + + @available(*, deprecated, renamed: "doubleValue") + var asDouble: Double { + doubleValue + } + /// Convert milliseconds(self) to seconds var millisecondsToSeconds: Decimal { self / 1_000 diff --git a/Source/Foundation/Extensions/IntExtensions.swift b/Source/Foundation/Extensions/IntExtensions.swift index ff7921e..ba2191a 100644 --- a/Source/Foundation/Extensions/IntExtensions.swift +++ b/Source/Foundation/Extensions/IntExtensions.swift @@ -5,8 +5,17 @@ public extension Int { "\(self)" } + var decimalValue: Decimal { + Decimal(self) + } + + @available(*, deprecated, renamed: "decimalValue") + var toDecimal: Decimal { + decimalValue + } + @available(*, deprecated, renamed: "stringValue") var toString: String { - "\(self)" + stringValue } } diff --git a/Source/Foundation/Extensions/StringExtensions+Crypto.swift b/Source/Foundation/Extensions/StringExtensions+Crypto.swift index 9be9ea4..67350ff 100644 --- a/Source/Foundation/Extensions/StringExtensions+Crypto.swift +++ b/Source/Foundation/Extensions/StringExtensions+Crypto.swift @@ -52,6 +52,40 @@ public extension String { return hash.map { String(format: "%02x", $0) }.joined().uppercased() } + /// Decode Base64 string if possible. Returns nil if fails. + var base64decoded: String? { + guard let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) else { + return nil + } + + return String(data: data, encoding: .utf8) + } + + /// Decode Base64 URL-Safe string if possible. Returns nil if fails. + var base64UrlDecoded: String? { + var base64 = self + .replacingOccurrences(of: "_", with: "/") + .replacingOccurrences(of: "-", with: "+") + + if base64.count % 4 != 0 { + base64.append(String(repeating: "=", count: 4 - base64.count % 4)) + } + return base64.base64decoded + } + + /// Encode into Base64 String + var base64encoded: String { + dataUTF8.base64EncodedString() + } + + /// Encode into Base64 URL-Safe string. + var base64UrlEncoded: String { + base64encoded + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "=", with: "") + } + func hmac(_ digest: HMACDigest) -> String { switch digest { case .sha256(let secret): diff --git a/Source/Foundation/Extensions/StringExtensions+Formating.swift b/Source/Foundation/Extensions/StringExtensions+Formating.swift index b07057f..d0bbb8e 100644 --- a/Source/Foundation/Extensions/StringExtensions+Formating.swift +++ b/Source/Foundation/Extensions/StringExtensions+Formating.swift @@ -15,4 +15,54 @@ public extension String { } return formatted } + + /// Return numbers from string + var onlyNumbers: String { + self.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression) + } + + func removing(prefix: String) -> String { + guard hasPrefix(prefix) else { return self } + return String(dropFirst(prefix.count)) + } + + func removing(suffix: String) -> String { + guard hasSuffix(suffix) else { return self } + return String(dropLast(suffix.count)) + } + + var addingTrailingSpaceIfNotEmpty: String { + isEmpty ? "" : "\(self) " + } + + var capitalizedWords: String { + self.split(separator: " ") + .map { $0.capitalized } + .joined(separator: " ") + } + + var capitalizedSentences: String { + self.components(separatedBy: ". ") + .map { String($0).capitalizedFirstLetter } + .joined(separator: ". ") + } + + var capitalizedFirstLetter: String { + if starts(withAnyOf: ["¡", "¿"]) { + return prefix(2).uppercased() + dropFirst(2) + } + return prefix(1).uppercased() + dropFirst(1) + } + + var lowercasedLeastTheFirstUnchanged: String { + prefix(1) + dropFirst().lowercased() + } + + var removingWhiteSpaces: String { + components(separatedBy: .whitespaces).joined() + } + + var trimmed: String { + trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + } } diff --git a/Source/Foundation/Extensions/StringExtensions+Ranges.swift b/Source/Foundation/Extensions/StringExtensions+Ranges.swift new file mode 100644 index 0000000..a49f600 --- /dev/null +++ b/Source/Foundation/Extensions/StringExtensions+Ranges.swift @@ -0,0 +1,58 @@ +import Foundation + +public extension String { + /// Look for where is the first text occurence in string + /// + /// - Parameters: + /// - text: Text to look for + /// - options: Options to compare + /// - range: Range of string where look for + /// - locale: Information for use in formatting data for presentation + /// - Returns: Range for the first text occurence in string (optional) + func firstRangeOcurrence(_ text: String, + options: String.CompareOptions = [], + range: Range? = nil, + locale: Locale? = nil) -> NSRange? { + guard let range = self.range(of: text, + options: options, + range: range ?? startIndex..? = nil, + locale: Locale? = nil) -> [NSRange] { + var start = range?.lowerBound ?? startIndex + let end = range?.upperBound ?? endIndex + var ranges: [NSRange] = [] + while start < end, let range = self.range(of: text, + options: options, + range: start.. NSRange? { + guard let range = self.range(of: string, options: [], range: startIndex.. Bool { + range(of: regex, options: .regularExpression) != nil + } + + /// Replaces the matches of the given regex pattern with an user-defined String. + func replacingRegexMatches(of pattern: String, with replacing: String) throws -> String { + let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) + let range = NSRange(location: 0, length: self.count) + return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacing) + } +} diff --git a/Source/Foundation/Extensions/StringExtensions+Validators.swift b/Source/Foundation/Extensions/StringExtensions+Validators.swift index 757c205..4b6c18f 100644 --- a/Source/Foundation/Extensions/StringExtensions+Validators.swift +++ b/Source/Foundation/Extensions/StringExtensions+Validators.swift @@ -29,13 +29,17 @@ public extension String { return phoneTest.evaluate(with: self.removingWhiteSpaces) } + var isValidHexColor: Bool { + self.satisfiesRegex("^#?[0-9a-fA-F]{3,6}$") + } + var removingSpanishCountryCode: String { if self.isValidSpanishPhone { let phoneWithoutWhiteSpaces = self.removingWhiteSpaces - let phoneWithoutPrefix = phoneWithoutWhiteSpaces.removePrefix("+34") - .removePrefix("34") - .removePrefix("034") - .removePrefix("0034") + let phoneWithoutPrefix = phoneWithoutWhiteSpaces.removing(prefix: "+34") + .removing(prefix: "34") + .removing(prefix: "034") + .removing(prefix: "0034") return phoneWithoutPrefix } return self diff --git a/Source/Foundation/Extensions/StringExtensions+Values.swift b/Source/Foundation/Extensions/StringExtensions+Values.swift index 2aaa85a..36c63b8 100644 --- a/Source/Foundation/Extensions/StringExtensions+Values.swift +++ b/Source/Foundation/Extensions/StringExtensions+Values.swift @@ -17,6 +17,18 @@ public extension String { URL(string: self) } + /// Return URL value only if an valid Url for Internet + var internetUrlValue: URL? { + if isValidInternetUrl { + return urlValue + } + return nil + } + + var dataUTF8: Data { + data(using: String.Encoding.utf8)! + } + /// Convert String to Date with Date Format (if don't specify it, will look for all Date Formats contemplated) /// /// - Parameters: @@ -32,4 +44,11 @@ public extension String { locale: locale, timeZone: timeZone) } + + func htmlValue(fontSize: Float, fontFamily: String? = nil) -> String { + if let fontFamily = fontFamily { + return "\(self)" + } + return "\(self)" + } } diff --git a/Source/Foundation/Extensions/StringExtensions.swift b/Source/Foundation/Extensions/StringExtensions.swift index 3a147bb..3b4e047 100644 --- a/Source/Foundation/Extensions/StringExtensions.swift +++ b/Source/Foundation/Extensions/StringExtensions.swift @@ -1,189 +1,12 @@ import Foundation public extension String { - /// Return numbers from string - var onlyNumbers: String { - self.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression) - } - - /// Return URL value only if an valid Url for Internet - var internetUrlValue: URL? { - if isValidInternetUrl { - return urlValue - } - return nil - } - - /// Remove a prefix from a String - func removePrefix(_ prefix: String) -> String { - guard self.hasPrefix(prefix) else { return self } - return String(self.dropFirst(prefix.count)) - } - - var addTrailingSpaceIfNotEmpty: String { - isEmpty ? "" : "\(self) " - } - - var capitalizeWords: String { - self.split(separator: " ") - .map { $0.capitalized } - .joined(separator: " ") - } - - var capitalizeSentences: String { - self.components(separatedBy: ". ") - .map { String($0).capitalizedFirstLetter } - .joined(separator: ". ") - } - - func starts(withAnyOf: [String]) -> Bool { - withAnyOf.contains { starts(with: $0) } - } - - var capitalizedFirstLetter: String { - if starts(withAnyOf: ["¡", "¿"]) { - return prefix(2).uppercased() + dropFirst(2) - } - return prefix(1).uppercased() + dropFirst(1) - } - - var capitalizedWords: String { - self.split(separator: " ") - .map { $0.capitalized } - .joined(separator: " ") - } - - var capitalizedSentences: String { - self.components(separatedBy: ". ") - .map { String($0).capitalizedFirstLetter } - .joined(separator: ". ") - } - - var lowercasedLeastTheFirstUnchanged: String { - prefix(1) + dropFirst().lowercased() - } - - var dataUTF8: Data { - data(using: String.Encoding.utf8)! - } - - var removingWhiteSpaces: String { - components(separatedBy: .whitespaces).joined() - } - - var trimmed: String { - trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - } - - /// Decode Base64 string if possible. Returns nil if fails. - var base64decoded: String? { - guard let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) else { - return nil - } - - return String(data: data, encoding: .utf8) - } - - /// Decode Base64 URL-Safe string if possible. Returns nil if fails. - var base64UrlDecoded: String? { - var base64 = self - .replacingOccurrences(of: "_", with: "/") - .replacingOccurrences(of: "-", with: "+") - - if base64.count % 4 != 0 { - base64.append(String(repeating: "=", count: 4 - base64.count % 4)) - } - return base64.base64decoded - } - - /// Encode into Base64 String - var base64encoded: String { - dataUTF8.base64EncodedString() - } - - /// Encode into Base64 URL-Safe string. - var base64UrlEncoded: String { - base64encoded - .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "+", with: "-") - .replacingOccurrences(of: "=", with: "") - } - func localized(bundle: Bundle = .main, tableName: String = "Common") -> String { NSLocalizedString(self, tableName: tableName, bundle: bundle, value: "**\(self)**", comment: "") } - func htmlValue(fontSize: Float, fontFamily: String? = nil) -> String { - if let fontFamily = fontFamily { - return "\(self)" - } - return "\(self)" - } - - func satisfiesRegex(_ regex: String) -> Bool { - range(of: regex, options: .regularExpression) != nil - } - - /// Replaces the matches of the given regex pattern with an user-defined String. - func replacingRegexMatches(of pattern: String, with replacing: String) throws -> String { - let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) - let range = NSRange(location: 0, length: self.count) - return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacing) - } - - /// Look for where is the first text occurence in string - /// - /// - Parameters: - /// - text: Text to look for - /// - options: Options to compare - /// - range: Range of string where look for - /// - locale: Information for use in formatting data for presentation - /// - Returns: Range for the first text occurence in string (optional) - func firstRangeOcurrence(_ text: String, - options: String.CompareOptions = [], - range: Range? = nil, - locale: Locale? = nil) -> NSRange? { - guard let range = self.range(of: text, - options: options, - range: range ?? startIndex..? = nil, - locale: Locale? = nil) -> [NSRange] { - var start = range?.lowerBound ?? startIndex - let end = range?.upperBound ?? endIndex - var ranges: [NSRange] = [] - while start < end, let range = self.range(of: text, - options: options, - range: start.. NSRange? { - guard let range = self.range(of: string, options: [], range: startIndex.. Bool { + withAnyOf.contains { starts(with: $0) } } /// Format the string by slicing in equal segments with given separator diff --git a/Source/Foundation/Extensions/URLComponentsExtensions.swift b/Source/Foundation/Extensions/URLComponentsExtensions.swift new file mode 100644 index 0000000..b74d743 --- /dev/null +++ b/Source/Foundation/Extensions/URLComponentsExtensions.swift @@ -0,0 +1,14 @@ +import Foundation + +public extension URLComponents { + /// Fetch string value of given queryItem key. Return nil if not found. + subscript(queryItem queryItem: String) -> String? { + guard let queryItem = queryItems?.first(where: { $0.name.lowercased() == queryItem.lowercased() }), + let itemValue = queryItem.value + else { + return nil + } + + return itemValue + } +} diff --git a/Source/Foundation/Extensions/URLExtensions.swift b/Source/Foundation/Extensions/URLExtensions.swift index 21ee5bc..3faa927 100644 --- a/Source/Foundation/Extensions/URLExtensions.swift +++ b/Source/Foundation/Extensions/URLExtensions.swift @@ -1,6 +1,12 @@ import Foundation public extension URL { + func appendingFragment(_ fragment: String?) -> URL { + var components = URLComponents(url: self, resolvingAgainstBaseURL: true) ?? URLComponents() + components.fragment = fragment + return components.url ?? self + } + func appendingItems(items: [URLQueryItem]) throws -> URL { guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { throw MagicError.badRequest @@ -17,9 +23,9 @@ public extension URL { return string.urlValue! } - var isMailto: Bool { + var isMailTo: Bool { guard scheme == "mailto" else { return false } - return absoluteString.replacingOccurrences(of: "mailto:", with: "").isValidEmail + return removingQuery.absoluteString.replacingOccurrences(of: "mailto:", with: "").isValidEmail } var isClickToCall: Bool { @@ -27,10 +33,62 @@ public extension URL { return absoluteString.replacingOccurrences(of: "tel:", with: "").isValidForPhoneDialer } + /// Is the value is a ClickToCall URL, returns the phone number + var clickToCallDestination: String? { + guard isClickToCall else { return nil } + guard let scheme = scheme else { return nil } + if absoluteString.hasPrefix("\(scheme):\\") { + return absoluteString.removing(prefix: "\(scheme):\\") + } + if absoluteString.hasPrefix("\(scheme):") { + return absoluteString.removing(prefix: "\(scheme):") + } + return nil + } + + /// Is the value is a MailTo URL, returns the email + var mailToDestination: String? { + guard isMailTo else { return nil } + guard let scheme = scheme else { return nil } + if absoluteString.hasPrefix("\(scheme):\\") { + return removingQuery.absoluteString.removing(prefix: "\(scheme):\\") + } + if absoluteString.hasPrefix("\(scheme):") { + return removingQuery.absoluteString.removing(prefix: "\(scheme):") + } + return nil + } + + /// Return the same url without query params + var removingQuery: URL { + if var components = URLComponents(string: absoluteString) { + components.query = nil + return components.url ?? self + } + + return self + } + /// Retrieve the resource of the url (or the absolute string if not available) var resourceSpecifier: String { (self as NSURL).resourceSpecifier ?? absoluteString } + + func checkIsReachableOnInternet() async -> Bool { + var request = URLRequest(url: self) + request.httpMethod = "HEAD" + + let session = URLSession(configuration: .ephemeral) + defer { session.invalidateAndCancel() } + + guard let (_, response) = try? await session.data(for: request), + let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + return false + } + + return true + } } extension URL: Comparable { @@ -38,3 +96,13 @@ extension URL: Comparable { lhs.absoluteString < rhs.absoluteString } } + +public extension [URL] { + func filterUnreachableUrls() async -> [URL] { + var result = [URL]() + for url in self where await url.checkIsReachableOnInternet() { + result.append(url) + } + return result + } +} diff --git a/Source/Foundation/Types/SemVer.swift b/Source/Foundation/Types/SemVer.swift index 65de3c5..518b8bd 100644 --- a/Source/Foundation/Types/SemVer.swift +++ b/Source/Foundation/Types/SemVer.swift @@ -11,6 +11,21 @@ public struct Semver: Comparable, CustomStringConvertible { } } + /// Initialices a Semver from a string version compatible if a value given. + /// - Parameter string: version literal + public init?(_ string: String?) { + guard let string = string else { + return nil + } + self.init(string) + } + + /// Initialices a Semver from a OperatingSystemVersion value. + /// - Parameter string: version literal + public init(_ version: OperatingSystemVersion) { + self.init("\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)") + } + /// Initialices a Semver from his components /// - Parameter major: major componente of the version /// - Parameter minor: minor componente of the version @@ -73,3 +88,9 @@ public func == (lhs: Semver, rhs: Semver) -> Bool { } return false } + +public extension OperatingSystemVersion { + var semver: Semver { + Semver(self) + } +} diff --git a/Source/SwiftUI/Extensions/ColorExtensions.swift b/Source/SwiftUI/Extensions/ColorExtensions.swift new file mode 100644 index 0000000..e6c6afe --- /dev/null +++ b/Source/SwiftUI/Extensions/ColorExtensions.swift @@ -0,0 +1,25 @@ +import Foundation +import SwiftUI + +public extension Color { + init(hex: String) throws { + var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + guard cString.isValidHexColor else { + throw MagicError.invalidInput + } + + if cString.hasPrefix("#") { + cString.remove(at: cString.startIndex) + } + + var rgbValue: UInt64 = 0 + Scanner(string: cString).scanHexInt64(&rgbValue) + + self = Color(.sRGB, + red: Double((rgbValue & 0xFF0000) >> 16) / 255.0, + green: Double((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: Double(rgbValue & 0x0000FF) / 255.0, + opacity: Double(1.0)) + } +} diff --git a/Source/SwiftUI/Extensions/ImageExtensions.swift b/Source/SwiftUI/Extensions/ImageExtensions.swift new file mode 100644 index 0000000..518832e --- /dev/null +++ b/Source/SwiftUI/Extensions/ImageExtensions.swift @@ -0,0 +1,5 @@ +import Foundation +import SwiftUI + +public extension Image { +} diff --git a/Source/SwiftUI/Utilities/PhaseChangeNotification.swift b/Source/SwiftUI/Utilities/PhaseChangeNotification.swift new file mode 100644 index 0000000..a7ab197 --- /dev/null +++ b/Source/SwiftUI/Utilities/PhaseChangeNotification.swift @@ -0,0 +1,28 @@ +import Foundation +import SwiftUI + +public extension Notification.Name { + static let phaseChangeNotification = Notification.Name("phaseChangeNotification") +} + +public extension View { + /// Wraps a listener to centralice all change of phases. + /// You need to add to the main .onChange() the post of the notification `phaseChangeNotification` into `NotificationCenter`. + /// ``` + /// .onChange(of: scenePhase) { phase in + /// NotificationCenter.default.post( + /// name: .phaseChangeNotification, + /// object: phase + /// ) + /// } + /// ``` + func onPhaseChange(_ action: @escaping (ScenePhase) -> Void) -> some View { + onReceive(NotificationCenter.default.publisher(for: .phaseChangeNotification)) { notification in + if let phase = notification.object as? ScenePhase { + action(phase) + } else { + fatalError("You need to pass the phase object with the notification.") + } + } + } +} diff --git a/Source/UIKit/Extensions/UIImageExtensions.swift b/Source/UIKit/Extensions/UIImageExtensions.swift index 0354f37..4de477e 100644 --- a/Source/UIKit/Extensions/UIImageExtensions.swift +++ b/Source/UIKit/Extensions/UIImageExtensions.swift @@ -9,6 +9,11 @@ public extension UIImage { /// - color: image fill color. /// - size: image size. convenience init(color: UIColor, size: CGSize) { + guard size.width > 0, size.height > 0 else { + self.init() + return + } + UIGraphicsBeginImageContextWithOptions(size, false, 1) defer { @@ -31,6 +36,10 @@ public extension UIImage { /// - Parameter color: color to fill /// - Returns: image colored with new color func colored(_ color: UIColor) -> UIImage? { + guard size.width > 0, size.height > 0 else { + return nil + } + UIGraphicsBeginImageContextWithOptions(size, false, scale) color.setFill() guard let context = UIGraphicsGetCurrentContext() else { @@ -60,6 +69,10 @@ public extension UIImage { /// - Parameter color: color for tint mask /// - Returns: image with tinted mask func tinted(_ color: UIColor) -> UIImage? { + guard size.width > 0, size.height > 0 else { + return nil + } + defer { UIGraphicsEndImageContext() } @@ -90,4 +103,34 @@ public extension UIImage { return context.makeImage()?.uiImage } + + func resized(ratio: CGFloat, isOpaque: Bool = true) -> UIImage { + let canvas = CGSize(width: size.width * ratio, height: size.height * ratio) + let format = imageRendererFormat + format.opaque = isOpaque + format.scale = UIScreen.main.scale + + return UIGraphicsImageRenderer(size: canvas, format: format).image { _ in + draw(in: CGRect(origin: .zero, size: canvas)) + } + } + + func resized(width: CGFloat, isOpaque: Bool = true) -> UIImage { + let canvas = CGSize(width: width, height: CGFloat(ceil(width / size.width * size.height))) + let format = imageRendererFormat + format.opaque = isOpaque + format.scale = UIScreen.main.scale + + return UIGraphicsImageRenderer(size: canvas, format: format).image { _ in + draw(in: CGRect(origin: .zero, size: canvas)) + } + } + + static func imageResized(data: Data, width: CGFloat, isOpaque: Bool = true) -> UIImage? { + UIImage(data: data)?.resized(width: width, isOpaque: isOpaque) + } + + static func imageResized(data: Data, percentage: CGFloat, isOpaque: Bool = true) -> UIImage? { + UIImage(data: data)?.resized(ratio: percentage, isOpaque: isOpaque) + } } diff --git a/Tests/Combine/Extensions/CurrentValueSubjectTests.swift b/Tests/Combine/Extensions/CurrentValueSubjectTests.swift new file mode 100644 index 0000000..5bc9a53 --- /dev/null +++ b/Tests/Combine/Extensions/CurrentValueSubjectTests.swift @@ -0,0 +1,25 @@ +import Combine +import MasMagicPills +import XCTest + +class CurrentValueSubjectTests: XCTestCase { + func test_send_if_distinct() { + var cancellables = Set() + let expectation = expectation(description: "wait for async process") + expectation.expectedFulfillmentCount = 2 + + let subject = CurrentValueSubject("Hi") + + subject + .sink { value in + print("received \(value)") + expectation.fulfill() + } + .store(in: &cancellables) + + subject.sendIfDistinct("Hi") + subject.sendIfDistinct("Bye") + + waitForExpectations(timeout: 10) + } +} diff --git a/Tests/Combine/Extensions/PassthroughSubjectTests.swift b/Tests/Combine/Extensions/PassthroughSubjectTests.swift new file mode 100644 index 0000000..09ce162 --- /dev/null +++ b/Tests/Combine/Extensions/PassthroughSubjectTests.swift @@ -0,0 +1,35 @@ +import Combine +import Foundation +import MasMagicPills +import XCTest + +class PassthroughSubjectTests: XCTestCase { + func test_send_type_and_filter_type() { + var cancellables = Set() + let expectation1 = expectation(description: "wait for async process") + let expectation2 = expectation(description: "wait for async process") + + let subject = PassthroughSubject() + + subject + .filterType(String.self) + .sink { value in + print("received \(value)") + expectation1.fulfill() + } + .store(in: &cancellables) + + subject + .filterType(Decimal.self) + .sink { value in + print("received \(value)") + expectation2.fulfill() + } + .store(in: &cancellables) + + subject.sendType(Decimal.self) + subject.sendType(String.self) + + waitForExpectations(timeout: 10) + } +} diff --git a/Tests/Combine/Extensions/PublishersExtensionsTests.swift b/Tests/Combine/Extensions/PublishersExtensionsTests.swift new file mode 100644 index 0000000..413702e --- /dev/null +++ b/Tests/Combine/Extensions/PublishersExtensionsTests.swift @@ -0,0 +1,36 @@ +import Combine +import Foundation +import MasMagicPills +import XCTest + +class PublishersExtensionsTests: XCTestCase { + func test_flat_map_latest() { + var cancellables = Set() + let expectation = expectation(description: "wait for async process") + expectation.expectedFulfillmentCount = 3 + + let subject1 = PassthroughSubject() + let subject2 = PassthroughSubject() + + subject1 + .flatMapLatest { value1 in + subject2 + .map { value2 in "\(value2) (\(value1)))" } + .eraseToAnyPublisher() + } + .sink { values in + print("received \(values)") + expectation.fulfill() + } + .store(in: &cancellables) + + subject1.send(4) + subject2.send("hi1") // 1 + subject1.send(5) + subject1.send(7) + subject2.send("hi2") // 2 + subject2.send("hi3") // 3 + + waitForExpectations(timeout: 3) + } +} diff --git a/Tests/Combine/Publishers/Publishers.RemoveNilsTests.swift b/Tests/Combine/Publishers/Publishers.RemoveNilsTests.swift index 9238748..6d70957 100644 --- a/Tests/Combine/Publishers/Publishers.RemoveNilsTests.swift +++ b/Tests/Combine/Publishers/Publishers.RemoveNilsTests.swift @@ -11,7 +11,7 @@ class PublishersRemoveNilsTests: XCTestCase { let expectedOutput = ["hola", "chau", "nil"] Publishers.Sequence(sequence: inputData) - .removeNils() + .filterNils() .collect() .sink { value in XCTAssertEqual(value, expectedOutput) diff --git a/Tests/Combine/Publishers/Publishers.WithLatestFromTests.swift b/Tests/Combine/Publishers/Publishers.WithLatestFromTests.swift new file mode 100644 index 0000000..4737c97 --- /dev/null +++ b/Tests/Combine/Publishers/Publishers.WithLatestFromTests.swift @@ -0,0 +1,32 @@ +import Combine +import MasMagicPills +import XCTest + +class PublishersWithLatestFromTests: XCTestCase { + func test_with_latest_from() { + var cancellables = Set() + let expectation = expectation(description: "wait for async process") + expectation.expectedFulfillmentCount = 4 + + let subject1 = PassthroughSubject() + let subject2 = CurrentValueSubject("hi0") + + subject1 + .withLatestFrom(subject2) + .sink { values in + print("received \(values)") + expectation.fulfill() + } + .store(in: &cancellables) + + subject1.send(4) + subject2.send("hi1") // 1 + subject1.send(5) + subject1.send(7) + subject2.send("hi2") // 2 + subject2.send("hi3") // 3 + subject1.send(9) + + waitForExpectations(timeout: 3) + } +} diff --git a/Tests/Foundation/Extensions/BundleExtensionsTests.swift b/Tests/Foundation/Extensions/BundleExtensionsTests.swift index a47c94d..3bdfcff 100644 --- a/Tests/Foundation/Extensions/BundleExtensionsTests.swift +++ b/Tests/Foundation/Extensions/BundleExtensionsTests.swift @@ -8,7 +8,7 @@ class BundleExtensionsTests: XCTestCase { let bundle = FakeBundle() bundle.versionNumberValue = expectedVersionNumber - XCTAssertEqual(bundle.versionNumber, expectedVersionNumber) + XCTAssertEqual(bundle.appVersion, expectedVersionNumber) } func test_build_number() { @@ -16,7 +16,7 @@ class BundleExtensionsTests: XCTestCase { let bundle = FakeBundle() bundle.buildNumberValue = expectedBuildNumber - XCTAssertEqual(bundle.buildNumber, expectedBuildNumber) + XCTAssertEqual(bundle.appBuild, expectedBuildNumber) } func test_full_version_number_when_value_are_different() { @@ -26,7 +26,7 @@ class BundleExtensionsTests: XCTestCase { bundle.versionNumberValue = expectedVersionNumber bundle.buildNumberValue = expectedBuildNumber - XCTAssertEqual("v\(expectedVersionNumber)(\(expectedBuildNumber))", bundle.fullVersionNumber) + XCTAssertEqual("v\(expectedVersionNumber)(\(expectedBuildNumber))", bundle.appFullVersion) } func test_full_version_number_when_value_are_equals() { @@ -36,7 +36,7 @@ class BundleExtensionsTests: XCTestCase { bundle.versionNumberValue = expectedVersionNumber bundle.buildNumberValue = expectedBuildNumber - XCTAssertEqual("v\(expectedVersionNumber)", bundle.fullVersionNumber) + XCTAssertEqual("v\(expectedVersionNumber)", bundle.appFullVersion) } func test_full_version_number_when_value_are_not_present() { @@ -44,6 +44,14 @@ class BundleExtensionsTests: XCTestCase { bundle.versionNumberValue = nil bundle.buildNumberValue = nil - XCTAssertNil(bundle.fullVersionNumber) + XCTAssertEqual(bundle.fullVersionNumber, "v0") + } + + func test_running_from_testflight() { + let bundle = FakeBundle() + XCTAssertFalse(bundle.runningFromTestFlight) + + bundle.testflight = true + XCTAssertTrue(bundle.runningFromTestFlight) } } diff --git a/Tests/Foundation/Extensions/DecimalExtensionsTests.swift b/Tests/Foundation/Extensions/DecimalExtensionsTests.swift index 349da93..e4d9709 100644 --- a/Tests/Foundation/Extensions/DecimalExtensionsTests.swift +++ b/Tests/Foundation/Extensions/DecimalExtensionsTests.swift @@ -3,6 +3,23 @@ import MasMagicPills import XCTest class DecimalExtensionsTests: XCTestCase { + func test_as_percentage() { + XCTAssertEqual((120 as Decimal).asPercentage(50), 60) + } + + func test_negated() { + XCTAssertEqual((120 as Decimal).negated, -120) + } + + func test_is_not_zero() { + XCTAssertTrue((12 as Decimal).isNotZero) + XCTAssertFalse((0 as Decimal).isNotZero) + } + + func test_double_value() { + XCTAssertEqual((12 as Decimal).asDouble, (12 as Double)) + } + func test_format_megabytes_in_spanish_format() { XCTAssertEqual((1_000 as Decimal).formattedMegabytes(.spanishSpain), "1 GB") XCTAssertEqual((900 as Decimal).formattedMegabytes(.spanishSpain), "900 MB") diff --git a/Tests/Foundation/Extensions/IntExtensionsTests.swift b/Tests/Foundation/Extensions/IntExtensionsTests.swift index 3a19f0f..6fc2a6b 100644 --- a/Tests/Foundation/Extensions/IntExtensionsTests.swift +++ b/Tests/Foundation/Extensions/IntExtensionsTests.swift @@ -4,8 +4,12 @@ import XCTest class IntExtensionsTests: XCTestCase { func test_int_to_string_conversion() { - XCTAssertEqual((24 as Int).stringValue, "24") - XCTAssertEqual((1_024 as Int).stringValue, "1024") - XCTAssertNotEqual((1_024 as Int).stringValue, "10") + XCTAssertEqual((24 as Int).toString, "24") + XCTAssertEqual((1_024 as Int).toString, "1024") + XCTAssertNotEqual((1_024 as Int).toString, "10") + } + + func test_int_to_decimal_convertion() { + XCTAssertEqual((24 as Int).toDecimal, 24) } } diff --git a/Tests/Foundation/Extensions/StringExtensions+CryptoTests.swift b/Tests/Foundation/Extensions/StringExtensions+CryptoTests.swift index 48cc88e..0e5d4fc 100644 --- a/Tests/Foundation/Extensions/StringExtensions+CryptoTests.swift +++ b/Tests/Foundation/Extensions/StringExtensions+CryptoTests.swift @@ -20,6 +20,13 @@ class StringExtensionsCryptoTests: XCTestCase { XCTAssertEqual("hi, this is more secure! ;)".sha512, "53CEA135E5859223C9DD6FC050D3E7FEBA51E43B00210B20E27E1BC5FD87E67971B6646044A7C2F188E89297BB0BCAB22F31517B66CBBF1C27A43D632289FE36") } + func test_base64_encode_and_decode() { + XCTAssertEqual("hola".base64encoded.base64decoded, "hola") + XCTAssertEqual("chau".base64encoded.base64decoded, "chau") + XCTAssertEqual("😘".base64encoded.base64decoded, "😘") + XCTAssertNil("bad base64 string".base64decoded) + } + func test_hmac_sha256_hash() { XCTAssertEqual("Prueba".hmac(.sha256(secret: "this-is-a-signing-key-for-HS256-jijiji")), "BopCTSSzXNmUx6009cioMF2PJD/F+Gp0EOC3ajN4jI0=") diff --git a/Tests/Foundation/Extensions/StringExtensions+FormatingTests.swift b/Tests/Foundation/Extensions/StringExtensions+FormatingTests.swift index 964cb5c..11075a0 100644 --- a/Tests/Foundation/Extensions/StringExtensions+FormatingTests.swift +++ b/Tests/Foundation/Extensions/StringExtensions+FormatingTests.swift @@ -24,4 +24,56 @@ class StringExtensionsFormatingTests: XCTestCase { XCTAssertEqual("123123123".formattedAsPhoneNumber, "123 123 123") XCTAssertEqual("".formattedAsPhoneNumber, "") } + + func test_only_numbers() { + XCTAssertEqual("asfw15as15352gd".onlyNumbers, "1515352") + } + + func test_removing_suffix() { + XCTAssertEqual("cuna".removing(suffix: "cu"), "cuna") + XCTAssertEqual("cuna".removing(suffix: "na"), "cu") + } + + func test_removing_prefix() { + XCTAssertEqual("cuna".removing(prefix: "cu"), "na") + XCTAssertEqual("cuna".removing(prefix: "sa"), "cuna") + } + + func test_addtrailingspaceifnotempty() { + XCTAssertEqual("".addingTrailingSpaceIfNotEmpty, "") + XCTAssertEqual("hola".addingTrailingSpaceIfNotEmpty, "hola ") + } + + func test_capitalizedWords() { + XCTAssertEqual("hola que tal?".capitalizedWords, "Hola Que Tal?") + XCTAssertEqual("hola y chau".capitalizedWords, "Hola Y Chau") + } + + func test_capitalizedSentences() { + XCTAssertEqual("hola. soy un test".capitalizedSentences, "Hola. Soy un test") + XCTAssertEqual("wawa. wewe we wi. wowo".capitalizedSentences, "Wawa. Wewe we wi. Wowo") + } + + func test_capitalized_first_letter() { + XCTAssertEqual("".capitalizedFirstLetter, "") + XCTAssertEqual("Hola".capitalizedFirstLetter, "Hola") + XCTAssertEqual("HOLA".capitalizedFirstLetter, "HOLA") + XCTAssertEqual("hOLA".capitalizedFirstLetter, "HOLA") + XCTAssertEqual("¿hola?".capitalizedFirstLetter, "¿Hola?") + XCTAssertEqual("¡chau!".capitalizedFirstLetter, "¡Chau!") + } + + func test_lowercased_all_least_the_first_unchanged() { + XCTAssertEqual("Hola".lowercasedLeastTheFirstUnchanged, "Hola") + XCTAssertEqual("HOLA".lowercasedLeastTheFirstUnchanged, "Hola") + XCTAssertEqual("hOLA".lowercasedLeastTheFirstUnchanged, "hola") + } + + func test_removing_whitespaces() { + XCTAssertEqual("hola que tal".removingWhiteSpaces, "holaquetal") + } + + func test_trimed() { + XCTAssertEqual(" hola ".trimmed, "hola") + } } diff --git a/Tests/Foundation/Extensions/StringExtensions+RangesTests.swift b/Tests/Foundation/Extensions/StringExtensions+RangesTests.swift new file mode 100644 index 0000000..7b64db2 --- /dev/null +++ b/Tests/Foundation/Extensions/StringExtensions+RangesTests.swift @@ -0,0 +1,29 @@ +import Foundation +import MasMagicPills +import XCTest + +class StringExtensionsRangesTests: XCTestCase { + func test_first_range_ocurrence() { + let text = "hola chau" + + XCTAssertEqual(text.firstRangeOcurrence("hola"), NSRange(location: 0, length: 4)) + XCTAssertEqual(text.firstRangeOcurrence("chau"), NSRange(location: 5, length: 4)) + XCTAssertNil(text.firstRangeOcurrence("adios")) + } + + func test_ocurrences_ranges() { + let ocurrences = "hola hola, que tal?".ocurrencesRanges("hola") + + XCTAssertEqual(ocurrences, [NSRange(location: 0, length: 4), NSRange(location: 5, length: 4)]) + } + + func test_bodyRange() { + XCTAssertEqual("hola".bodyRange, NSRange(location: 0, length: 4)) + XCTAssertEqual("ho".bodyRange, NSRange(location: 0, length: 2)) + } + + func test_nsRange_of_string() { + XCTAssertNil("hola".nsRange(of: "chau")) + XCTAssertEqual("hola".nsRange(of: "la"), NSRange(location: 2, length: 2)) + } +} diff --git a/Tests/Foundation/Extensions/StringExtensions+RegexTests.swift b/Tests/Foundation/Extensions/StringExtensions+RegexTests.swift new file mode 100644 index 0000000..5c68af4 --- /dev/null +++ b/Tests/Foundation/Extensions/StringExtensions+RegexTests.swift @@ -0,0 +1,17 @@ +import Foundation +import MasMagicPills +import XCTest + +class StringExtensionsRegexTests: XCTestCase { + func test_satisfies_regex() { + XCTAssertTrue("X1234567X".satisfiesRegex("[XYZ][0-9]{7}[0-9A-Z]")) + XCTAssertTrue("X1234567X".satisfiesRegex("^[xyzXYZ]{1}[0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$")) + XCTAssertTrue("C12452341".satisfiesRegex("^([ABCDEFGHJKLMNPQRSUVW]{1})([0-9A-J]{8})$")) + XCTAssertTrue("85731245-Z".satisfiesRegex("^([0-9]{8})([-/]{0,1})([a-zA-Z])$")) + } + + func test_replacing_regex_matches() { + XCTAssertEqual(try? "+851 124 124 124".replacingRegexMatches(of: "\\+[0-9]{1,3}\\ ", with: ""), "124 124 124") + XCTAssertEqual(try? "+124 918918918".replacingRegexMatches(of: "\\+[0-9]{1,3}\\ ", with: "☎: "), "☎: 918918918") + } +} diff --git a/Tests/Foundation/Extensions/StringExtensions+ValidatorsTests.swift b/Tests/Foundation/Extensions/StringExtensions+ValidatorsTests.swift index 32d3787..0a9871b 100644 --- a/Tests/Foundation/Extensions/StringExtensions+ValidatorsTests.swift +++ b/Tests/Foundation/Extensions/StringExtensions+ValidatorsTests.swift @@ -31,6 +31,17 @@ class StringExtensionsValidatorsTests: XCTestCase { XCTAssertTrue("+34 687 687 687".isValidSpanishPhone) } + func test_is_valid_color() { + XCTAssertTrue("#C0C0C0".isValidHexColor) + XCTAssertTrue("#00FF06".isValidHexColor) + XCTAssertTrue("#ffffff".isValidHexColor) + XCTAssertTrue("#ffffff".isValidHexColor) + XCTAssertTrue("#fff".isValidHexColor) + XCTAssertFalse("#ff".isValidHexColor) + XCTAssertFalse("#fffffff".isValidHexColor) + XCTAssertFalse("#lkaslfs".isValidHexColor) + } + func test_is_not_valid_spanish_phone() { XCTAssertFalse("587687687".isValidSpanishPhone) XCTAssertFalse("124".isValidSpanishPhone) diff --git a/Tests/Foundation/Extensions/StringExtensions+ValuesTests.swift b/Tests/Foundation/Extensions/StringExtensions+ValuesTests.swift index 7504393..eac985e 100644 --- a/Tests/Foundation/Extensions/StringExtensions+ValuesTests.swift +++ b/Tests/Foundation/Extensions/StringExtensions+ValuesTests.swift @@ -20,6 +20,11 @@ class StringExtensionsValuesTests: XCTestCase { XCTAssertNil("wawa".boolValue) } + func test_internet_url_value() { + XCTAssertNil("".internetUrlValue) + XCTAssertEqual("http://www.google.com".internetUrlValue, URL(string: "http://www.google.com")) + } + func test_url_value() { XCTAssertNil("".urlValue) XCTAssertEqual("http://www.google.com".urlValue, URL(string: "http://www.google.com")) @@ -36,4 +41,28 @@ class StringExtensionsValuesTests: XCTestCase { XCTAssertNil("".date()) XCTAssertNil("wawa".date()) } + + func test_utf8_convertion() { + XCTAssertEqual("hola".dataUTF8.stringUTF8, "hola") + } + + func test_html_value() { + let text1 = "hola" + let text2 = "chau" + let html1 = text1.htmlValue(fontSize: 12) + let html2 = text2.htmlValue(fontSize: 14, fontFamily: "Arial") + + let attrString1 = try? NSAttributedString(data: html1.dataUTF8, + options: [.documentType: NSAttributedString.DocumentType.html], + documentAttributes: nil) + let attrString2 = try? NSAttributedString(data: html2.dataUTF8, + options: [.documentType: NSAttributedString.DocumentType.html], + documentAttributes: nil) + XCTAssertEqual(attrString1?.string, text1) + XCTAssertTrue(html1.contains("font-size: 12")) + + XCTAssertEqual(attrString2?.string, text2) + XCTAssertTrue(html2.contains("font-size: 14")) + XCTAssertTrue(html2.contains("font-family: Arial")) + } } diff --git a/Tests/Foundation/Extensions/StringExtensionsTests.swift b/Tests/Foundation/Extensions/StringExtensionsTests.swift index bc047b7..0d2215d 100644 --- a/Tests/Foundation/Extensions/StringExtensionsTests.swift +++ b/Tests/Foundation/Extensions/StringExtensionsTests.swift @@ -3,117 +3,6 @@ import MasMagicPills import XCTest class StringExtensionsTests: XCTestCase { - func test_capitalizeWords() { - XCTAssertEqual("hola que tal?".capitalizeWords, "Hola Que Tal?") - } - - func test_capitalizeSentences() { - XCTAssertEqual("hola. soy un test".capitalizeSentences, "Hola. Soy un test") - } - - func test_addtrailingspaceifnotempty() { - XCTAssertEqual("".addTrailingSpaceIfNotEmpty, "") - XCTAssertEqual("hola".addTrailingSpaceIfNotEmpty, "hola ") - } - - func test_trimed() { - XCTAssertEqual(" hola ".trimmed, "hola") - } - - func test_capitalized_first_letter() { - XCTAssertEqual("".capitalizedFirstLetter, "") - XCTAssertEqual("Hola".capitalizedFirstLetter, "Hola") - XCTAssertEqual("HOLA".capitalizedFirstLetter, "HOLA") - XCTAssertEqual("hOLA".capitalizedFirstLetter, "HOLA") - XCTAssertEqual("¿hola?".capitalizedFirstLetter, "¿Hola?") - XCTAssertEqual("¡chau!".capitalizedFirstLetter, "¡Chau!") - } - - func test_capitalized_words() { - XCTAssertEqual("hola y chau".capitalizedWords, "Hola Y Chau") - } - - func test_capitalized_sentences() { - XCTAssertEqual("wawa. wewe we wi. wowo".capitalizedSentences, "Wawa. Wewe we wi. Wowo") - } - - func test_lowercased_all_least_the_first_unchanged() { - XCTAssertEqual("Hola".lowercasedLeastTheFirstUnchanged, "Hola") - XCTAssertEqual("HOLA".lowercasedLeastTheFirstUnchanged, "Hola") - XCTAssertEqual("hOLA".lowercasedLeastTheFirstUnchanged, "hola") - } - - func test_utf8_convertion() { - XCTAssertEqual("hola".dataUTF8.stringUTF8, "hola") - } - - func test_removing_whitespaces() { - XCTAssertEqual("hola que tal".removingWhiteSpaces, "holaquetal") - } - - func test_base64_encode_and_decode() { - XCTAssertEqual("hola".base64encoded.base64decoded, "hola") - XCTAssertEqual("chau".base64encoded.base64decoded, "chau") - XCTAssertEqual("😘".base64encoded.base64decoded, "😘") - XCTAssertNil("bad base64 string".base64decoded) - } - - func test_html_value() { - let text1 = "hola" - let text2 = "chau" - let html1 = text1.htmlValue(fontSize: 12) - let html2 = text2.htmlValue(fontSize: 14, fontFamily: "Arial") - - let attrString1 = try? NSAttributedString(data: html1.dataUTF8, - options: [.documentType: NSAttributedString.DocumentType.html], - documentAttributes: nil) - let attrString2 = try? NSAttributedString(data: html2.dataUTF8, - options: [.documentType: NSAttributedString.DocumentType.html], - documentAttributes: nil) - XCTAssertEqual(attrString1?.string, text1) - XCTAssertTrue(html1.contains("font-size: 12")) - - XCTAssertEqual(attrString2?.string, text2) - XCTAssertTrue(html2.contains("font-size: 14")) - XCTAssertTrue(html2.contains("font-family: Arial")) - } - - func test_satisfies_regex() { - XCTAssertTrue("X1234567X".satisfiesRegex("[XYZ][0-9]{7}[0-9A-Z]")) - XCTAssertTrue("X1234567X".satisfiesRegex("^[xyzXYZ]{1}[0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$")) - XCTAssertTrue("C12452341".satisfiesRegex("^([ABCDEFGHJKLMNPQRSUVW]{1})([0-9A-J]{8})$")) - XCTAssertTrue("85731245-Z".satisfiesRegex("^([0-9]{8})([-/]{0,1})([a-zA-Z])$")) - } - - func test_replacing_regex_matches() { - XCTAssertEqual(try? "+851 124 124 124".replacingRegexMatches(of: "\\+[0-9]{1,3}\\ ", with: ""), "124 124 124") - XCTAssertEqual(try? "+124 918918918".replacingRegexMatches(of: "\\+[0-9]{1,3}\\ ", with: "☎: "), "☎: 918918918") - } - - func test_first_range_ocurrence() { - let text = "hola chau" - - XCTAssertEqual(text.firstRangeOcurrence("hola"), NSRange(location: 0, length: 4)) - XCTAssertEqual(text.firstRangeOcurrence("chau"), NSRange(location: 5, length: 4)) - XCTAssertNil(text.firstRangeOcurrence("adios")) - } - - func test_ocurrences_ranges() { - let ocurrences = "hola hola, que tal?".ocurrencesRanges("hola") - - XCTAssertEqual(ocurrences, [NSRange(location: 0, length: 4), NSRange(location: 5, length: 4)]) - } - - func test_bodyRange() { - XCTAssertEqual("hola".bodyRange, NSRange(location: 0, length: 4)) - XCTAssertEqual("ho".bodyRange, NSRange(location: 0, length: 2)) - } - - func test_nsRange_of_string() { - XCTAssertNil("hola".nsRange(of: "chau")) - XCTAssertEqual("hola".nsRange(of: "la"), NSRange(location: 2, length: 2)) - } - func test_localized() { let bundle = Bundle(for: StringExtensionsTests.self) let tableName = "LocalizedSample" diff --git a/Tests/Foundation/Extensions/URLComponentsExtensionsTests.swift b/Tests/Foundation/Extensions/URLComponentsExtensionsTests.swift new file mode 100644 index 0000000..fad7a0b --- /dev/null +++ b/Tests/Foundation/Extensions/URLComponentsExtensionsTests.swift @@ -0,0 +1,12 @@ +import Foundation +import MasMagicPills +import XCTest + +class URLComponentsExtensionsTests: XCTestCase { + let components = URLComponents(string: "http://www.google.com/q?search=white%20vaporeon")! + + func test_subscript_for_queryitems() { + XCTAssertEqual(components[queryItem: "search"], "white vaporeon") + XCTAssertNil(components[queryItem: "wawa"]) + } +} diff --git a/Tests/Foundation/Extensions/URLExtensionsTests.swift b/Tests/Foundation/Extensions/URLExtensionsTests.swift index 389e52c..c556de8 100644 --- a/Tests/Foundation/Extensions/URLExtensionsTests.swift +++ b/Tests/Foundation/Extensions/URLExtensionsTests.swift @@ -3,6 +3,22 @@ import MasMagicPills import XCTest class URLExtensionsTests: XCTestCase { + func test_appendingFragment() { + var url = URL(string: "http://aaa.com")! + + let newUrl = url.appendingFragment("fasf") + + XCTAssertEqual(newUrl.absoluteString, "http://aaa.com#fasf") + } + + func test_appendingItems() throws { + var url = URL(string: "http://aaa.com")! + + let newUrl = try url.appendingItems(items: [.init(name: "fff", value: "aaaa")]) + + XCTAssertEqual(newUrl.absoluteString, "http://aaa.com?fff=aaaa") + } + func test_comparable() { let url1 = URL(string: "http://aaa.com")! let url2 = URL(string: "http://bbb.com")! @@ -27,10 +43,48 @@ class URLExtensionsTests: XCTestCase { XCTAssertEqual(URL(string: "wawa:asfasf.com")?.isClickToCall, false) } + func test_clicktocall_destination() { + XCTAssertEqual("tel:685685685".urlValue?.clickToCallDestination, "685685685") + XCTAssertEqual("wawa:fasf".urlValue?.clickToCallDestination, nil) + } + func test_mailto_url() { - XCTAssertEqual(URL(string: "mailto:ssss@ssss.com")?.isMailto, true) - XCTAssertEqual(URL(string: "tel:+34687687687")?.isMailto, false) - XCTAssertEqual(URL(string: "mailto:asfasf.com")?.isMailto, false) - XCTAssertEqual(URL(string: "mailto:124124")?.isMailto, false) + XCTAssertEqual(URL(string: "mailto:ssss@ssss.com")?.isMailTo, true) + XCTAssertEqual(URL(string: "mailto:ssss@ssss.com?subject=Hi")?.isMailTo, true) + XCTAssertEqual(URL(string: "tel:+34687687687")?.isMailTo, false) + XCTAssertEqual(URL(string: "mailto:asfasf.com")?.isMailTo, false) + XCTAssertEqual(URL(string: "mailto:124124")?.isMailTo, false) + } + + func test_mailto_destination() { + XCTAssertEqual("mailto:sss@ssss.com?subject=hi".urlValue?.mailToDestination, "sss@ssss.com") + XCTAssertEqual("mailto:sss@ssss.com".urlValue?.mailToDestination, "sss@ssss.com") + XCTAssertEqual("fafa:sss@ssss.com".urlValue?.mailToDestination, nil) + } + + func test_resourceSpecifier() { + XCTAssertEqual("https://www.google.com".urlValue?.resourceSpecifier, "//www.google.com") + XCTAssertEqual("mailto:sss@ssss.com".urlValue?.resourceSpecifier, "sss@ssss.com") + } + + func test_check_is_reachable_for_two_urls() async { + let validURL = "https://code.jquery.com/jquery-3.7.1.min.js".urlValue! + let wrongURL = "https://code.jquery.com/jquery-HAKUNA-MATATA.min.js".urlValue! + + let resultTrue = await validURL.checkIsReachableOnInternet() + let resultFalse = await wrongURL.checkIsReachableOnInternet() + + XCTAssertTrue(resultTrue) + XCTAssertFalse(resultFalse) + } + + func test_check_is_reachable_for_an_array_of_urls() async { + let validURL = "https://code.jquery.com/jquery-3.7.1.min.js".urlValue! + let wrongURL = "https://code.jquery.com/jquery-HAKUNA-MATATA.min.js".urlValue! + + let array = [validURL, validURL, wrongURL, wrongURL, validURL] + let filteredArray = await array.filterUnreachableUrls() + + XCTAssertEqual(filteredArray.count, 3) } } diff --git a/Tests/Foundation/Types/JWTTests.swift b/Tests/Foundation/Types/JWTTests.swift index 71e9726..5028d13 100644 --- a/Tests/Foundation/Types/JWTTests.swift +++ b/Tests/Foundation/Types/JWTTests.swift @@ -41,7 +41,6 @@ class JWTTests: XCTestCase { func test_init_HS256_token() { let jwt = try? JWT(HMACSHA256Claims: ["sub": "hola"], secret: "jijiji") - XCTAssertEqual(jwt?.rawValue, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJob2xhIn0.dXg3SitnRVRjMjV3dnFHRGwzQkZzK203RUlOVkFRR3F6UzRhZW1Sd09mUT0") XCTAssertEqual(jwt?.subject, "hola") } } diff --git a/Tests/Foundation/Types/SemVerTests.swift b/Tests/Foundation/Types/SemVerTests.swift index 33a2c74..73ea425 100644 --- a/Tests/Foundation/Types/SemVerTests.swift +++ b/Tests/Foundation/Types/SemVerTests.swift @@ -13,6 +13,13 @@ class SemVerTests: XCTestCase { XCTAssertEqual(versionFromComponents.major, 1) XCTAssertEqual(versionFromComponents.minor, 2) XCTAssertEqual(versionFromComponents.patch, 3) + + let fromNil = Semver(nil) + XCTAssertNil(fromNil) + + let optionalVersion: String? = "1.2.3" + let fromOptionalValue = Semver(optionalVersion) + XCTAssertNotNil(fromOptionalValue) } func test_comparacions() { @@ -35,4 +42,23 @@ class SemVerTests: XCTestCase { let version = Semver("1.2.3") XCTAssertEqual(version.description, "v1.2.3") } + + func test_simple_value() { + let version = Semver("1.2.3") + XCTAssertEqual(version.simple, "1.2") + } + + func test_full_value() { + let version = Semver("6.7.8") + XCTAssertEqual(version.full, "6.7.8") + } + + func test_init_from_processInfo() { + let versionFromObj = ProcessInfo.processInfo.operatingSystemVersion.semver + let versionFromValues = Semver(major: ProcessInfo.processInfo.operatingSystemVersion.majorVersion, + minor: ProcessInfo.processInfo.operatingSystemVersion.minorVersion, + patch: ProcessInfo.processInfo.operatingSystemVersion.patchVersion) + + XCTAssertEqual(versionFromObj, versionFromValues) + } } diff --git a/Tests/Helpers/FakeBundle.swift b/Tests/Helpers/FakeBundle.swift index 8d65ccf..cebeb8f 100644 --- a/Tests/Helpers/FakeBundle.swift +++ b/Tests/Helpers/FakeBundle.swift @@ -3,6 +3,15 @@ import Foundation class FakeBundle: Bundle { var versionNumberValue: String? var buildNumberValue: String? + var testflight = false + + override var appStoreReceiptURL: URL? { + guard testflight else { + return nil + } + + return "https://appstore.com/blabllablalab/sandboxReceipt".urlValue + } override func object(forInfoDictionaryKey key: String) -> Any? { switch key { diff --git a/Tests/SwiftUI/Extensions/ColorExtensionsTests.swift b/Tests/SwiftUI/Extensions/ColorExtensionsTests.swift new file mode 100644 index 0000000..e87afac --- /dev/null +++ b/Tests/SwiftUI/Extensions/ColorExtensionsTests.swift @@ -0,0 +1,13 @@ +import Foundation +import MasMagicPills +import SwiftUI +import XCTest + +class ColorExtensionsTests: XCTestCase { + func test_init_from_hex() throws { + XCTAssertEqual(try Color(hex: "FFFFFF"), .white) + XCTAssertEqual(try Color(hex: "#FFFFFF"), .white) + XCTAssertThrowsError(try Color(hex: "#kkkk")) + XCTAssertThrowsError(try Color(hex: "🤷")) + } +} diff --git a/Tests/SwiftUI/Utilities/PhaseChangeNotificationTests.swift b/Tests/SwiftUI/Utilities/PhaseChangeNotificationTests.swift new file mode 100644 index 0000000..4c96d95 --- /dev/null +++ b/Tests/SwiftUI/Utilities/PhaseChangeNotificationTests.swift @@ -0,0 +1,7 @@ +import Foundation +import MasMagicPills +import SwiftUI +import XCTest + +class PhaseChangeNotificationTests: XCTestCase { +}