diff --git a/CHANGELOG.md b/CHANGELOG.md index b8308a6..aa2cef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Your contribution here. * [#41](https://github.com/Antondomashnev/FBSnapshotsViewer/pull/41): Add additional information for tests - [@antondomashnev](https://github.com/antondomashnev). +* [#44](https://github.com/Antondomashnev/FBSnapshotsViewer/pull/44): Compact view redesign - [@antondomashnev](https://github.com/antondomashnev). ### 0.5.0 (26.05.2017) diff --git a/FBSnapshotsViewer.xcodeproj/project.pbxproj b/FBSnapshotsViewer.xcodeproj/project.pbxproj index 3914f3b..3bc18d0 100644 --- a/FBSnapshotsViewer.xcodeproj/project.pbxproj +++ b/FBSnapshotsViewer.xcodeproj/project.pbxproj @@ -22,11 +22,19 @@ 6D130B6B1EC8E55F00E9642A /* PreferencesWireframeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D130B6A1EC8E55F00E9642A /* PreferencesWireframeSpec.swift */; }; 6D130B6D1EC8E82B00E9642A /* ConfigurationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D130B6C1EC8E82B00E9642A /* ConfigurationSpec.swift */; }; 6D130B6F1EC8EB2F00E9642A /* PreferencesDisplayInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D130B6E1EC8EB2F00E9642A /* PreferencesDisplayInfoSpec.swift */; }; + 6D1C40761EFA628F00D4D5F1 /* Colors+AppleInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1C40751EFA628F00D4D5F1 /* Colors+AppleInterfaceMode.swift */; }; 6D21E66D1EAE9E3600800B91 /* ApplicationLogReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D21E66C1EAE9E3600800B91 /* ApplicationLogReader.swift */; }; 6D21E66F1EAEAA8A00800B91 /* ApplicationLogReaderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D21E66E1EAEAA8A00800B91 /* ApplicationLogReaderSpec.swift */; }; 6D21E6721EAEACD300800B91 /* TestLog.log in Resources */ = {isa = PBXBuildFile; fileRef = 6D21E6711EAEACD300800B91 /* TestLog.log */; }; 6D21F7481E6CBAFA00BFCE4C /* MenuControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D21F7471E6CBAFA00BFCE4C /* MenuControllerSpec.swift */; }; 6D21F74A1E6CC17000BFCE4C /* MenuActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D21F7491E6CC17000BFCE4C /* MenuActions.swift */; }; + 6D2814781EED7F9300C4E67C /* TestResultsHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2814771EED7F9300C4E67C /* TestResultsHeader.swift */; }; + 6D28147A1EED7F9E00C4E67C /* TestResultsHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D2814791EED7F9E00C4E67C /* TestResultsHeader.xib */; }; + 6D28147C1EED9C2000C4E67C /* TestResultsSectionDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D28147B1EED9C2000C4E67C /* TestResultsSectionDisplayInfo.swift */; }; + 6D28147E1EED9E4100C4E67C /* TestResultsDisplayInfosCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D28147D1EED9E4100C4E67C /* TestResultsDisplayInfosCollector.swift */; }; + 6D2814801EEDC6C800C4E67C /* TestResultsSectionTitleDisplayInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D28147F1EEDC6C800C4E67C /* TestResultsSectionTitleDisplayInfoSpec.swift */; }; + 6D2814821EEDECDA00C4E67C /* TestResultsSectionDisplayInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2814811EEDECDA00C4E67C /* TestResultsSectionDisplayInfoSpec.swift */; }; + 6D2814841EEDEE3C00C4E67C /* TestResultsDisplayInfosCollectorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2814831EEDEE3C00C4E67C /* TestResultsDisplayInfosCollectorSpec.swift */; }; 6D30002B1EBD242E005B6103 /* ExternalViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D30002A1EBD242E005B6103 /* ExternalViewer.swift */; }; 6D30002D1EBD251A005B6103 /* KaleidoscopeViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D30002C1EBD251A005B6103 /* KaleidoscopeViewer.swift */; }; 6D30002F1EBD276A005B6103 /* OSXApplicationFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D30002E1EBD276A005B6103 /* OSXApplicationFinder.swift */; }; @@ -66,6 +74,8 @@ 6D5BB4C61E5915A10006BAE1 /* FolderEventFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BB4C41E5915A10006BAE1 /* FolderEventFilter.swift */; }; 6D5BB4C81E598F460006BAE1 /* FolderEventsListenerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BB4C71E598F460006BAE1 /* FolderEventsListenerError.swift */; }; 6D5BB4CB1E59A20E0006BAE1 /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BB4CA1E59A20E0006BAE1 /* FileWatcher.swift */; }; + 6D5BC1D81EF6D1B70003D1CB /* TestResultsDisplayInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5BC1D71EF6D1B70003D1CB /* TestResultsDisplayInfoSpec.swift */; }; + 6D5E1A0B1EF092DC0016A32C /* TestResultSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5E1A0A1EF092DC0016A32C /* TestResultSplitView.swift */; }; 6D5F91381E4F6D3500C6E5BF /* FolderEventsListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5F91341E4F6D3500C6E5BF /* FolderEventsListener.swift */; }; 6D5F91391E4F6D3500C6E5BF /* NonRecursiveFolderEventsListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5F91351E4F6D3500C6E5BF /* NonRecursiveFolderEventsListener.swift */; }; 6D5F913A1E4F6D3500C6E5BF /* RecursiveFolderEventsListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D5F91361E4F6D3500C6E5BF /* RecursiveFolderEventsListener.swift */; }; @@ -96,6 +106,8 @@ 6D848C5F1E68D7940000FC1E /* TestResultsModuleInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D848C5E1E68D7940000FC1E /* TestResultsModuleInterface.swift */; }; 6D848C611E68D7B80000FC1E /* TestResultsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D848C601E68D7B80000FC1E /* TestResultsPresenter.swift */; }; 6D848C631E6964D90000FC1E /* TestResultsCollectionViewOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D848C621E6964D90000FC1E /* TestResultsCollectionViewOutlets.swift */; }; + 6D89B9541EF088B600865C5F /* TestResultsDiffMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D89B9531EF088B600865C5F /* TestResultsDiffMode.swift */; }; + 6D89D17F1EF59C100082579E /* TestResultsTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D89D17E1EF59C100082579E /* TestResultsTopView.swift */; }; 6D8BD71F1EB13949004928F7 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8BD71E1EB13949004928F7 /* Application.swift */; }; 6D8BD7231EB144C0004928F7 /* ApplicationTestLogFilesListenerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8BD7221EB144C0004928F7 /* ApplicationTestLogFilesListenerSpec.swift */; }; 6D8BF2BD1ED0B78700C368D1 /* String+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8BF2BC1ED0B78700C368D1 /* String+HTML.swift */; }; @@ -104,6 +116,7 @@ 6D92E1CD1E73102C00DB73C7 /* AutoEquatable.stencil in Resources */ = {isa = PBXBuildFile; fileRef = 6D92E1CC1E73102C00DB73C7 /* AutoEquatable.stencil */; }; 6D92E1D11E73106700DB73C7 /* AutoEquatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D92E1D01E73106700DB73C7 /* AutoEquatable.generated.swift */; }; 6D9395D21EC1165B00B2583D /* PreferencesModuleDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9395D11EC1165B00B2583D /* PreferencesModuleDelegate.swift */; }; + 6D9EF95C1EF5DBD600AA15B1 /* TestResultsDisplayInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9EF95B1EF5DBD600AA15B1 /* TestResultsDisplayInfo.swift */; }; 6DA1BF981E7755110017D47B /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA1BF961E7755110017D47B /* Colors.swift */; }; 6DA1BF991E7755110017D47B /* Storyboards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA1BF971E7755110017D47B /* Storyboards.swift */; }; 6DA1BF9B1E7756010017D47B /* FolderEventFilterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA1BF9A1E7756010017D47B /* FolderEventFilterSpec.swift */; }; @@ -162,11 +175,19 @@ 6D130B6A1EC8E55F00E9642A /* PreferencesWireframeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWireframeSpec.swift; sourceTree = ""; }; 6D130B6C1EC8E82B00E9642A /* ConfigurationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationSpec.swift; sourceTree = ""; }; 6D130B6E1EC8EB2F00E9642A /* PreferencesDisplayInfoSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesDisplayInfoSpec.swift; sourceTree = ""; }; + 6D1C40751EFA628F00D4D5F1 /* Colors+AppleInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+AppleInterfaceMode.swift"; sourceTree = ""; }; 6D21E66C1EAE9E3600800B91 /* ApplicationLogReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationLogReader.swift; sourceTree = ""; }; 6D21E66E1EAEAA8A00800B91 /* ApplicationLogReaderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationLogReaderSpec.swift; sourceTree = ""; }; 6D21E6711EAEACD300800B91 /* TestLog.log */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TestLog.log; sourceTree = ""; }; 6D21F7471E6CBAFA00BFCE4C /* MenuControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuControllerSpec.swift; sourceTree = ""; }; 6D21F7491E6CC17000BFCE4C /* MenuActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuActions.swift; sourceTree = ""; }; + 6D2814771EED7F9300C4E67C /* TestResultsHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsHeader.swift; sourceTree = ""; }; + 6D2814791EED7F9E00C4E67C /* TestResultsHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TestResultsHeader.xib; sourceTree = ""; }; + 6D28147B1EED9C2000C4E67C /* TestResultsSectionDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsSectionDisplayInfo.swift; sourceTree = ""; }; + 6D28147D1EED9E4100C4E67C /* TestResultsDisplayInfosCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsDisplayInfosCollector.swift; sourceTree = ""; }; + 6D28147F1EEDC6C800C4E67C /* TestResultsSectionTitleDisplayInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsSectionTitleDisplayInfoSpec.swift; sourceTree = ""; }; + 6D2814811EEDECDA00C4E67C /* TestResultsSectionDisplayInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsSectionDisplayInfoSpec.swift; sourceTree = ""; }; + 6D2814831EEDEE3C00C4E67C /* TestResultsDisplayInfosCollectorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsDisplayInfosCollectorSpec.swift; sourceTree = ""; }; 6D30002A1EBD242E005B6103 /* ExternalViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExternalViewer.swift; sourceTree = ""; }; 6D30002C1EBD251A005B6103 /* KaleidoscopeViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KaleidoscopeViewer.swift; sourceTree = ""; }; 6D30002E1EBD276A005B6103 /* OSXApplicationFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSXApplicationFinder.swift; sourceTree = ""; }; @@ -209,6 +230,8 @@ 6D5BB4C41E5915A10006BAE1 /* FolderEventFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderEventFilter.swift; sourceTree = ""; }; 6D5BB4C71E598F460006BAE1 /* FolderEventsListenerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderEventsListenerError.swift; sourceTree = ""; }; 6D5BB4CA1E59A20E0006BAE1 /* FileWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileWatcher.swift; sourceTree = ""; }; + 6D5BC1D71EF6D1B70003D1CB /* TestResultsDisplayInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsDisplayInfoSpec.swift; sourceTree = ""; }; + 6D5E1A0A1EF092DC0016A32C /* TestResultSplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultSplitView.swift; sourceTree = ""; }; 6D5F91341E4F6D3500C6E5BF /* FolderEventsListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderEventsListener.swift; sourceTree = ""; }; 6D5F91351E4F6D3500C6E5BF /* NonRecursiveFolderEventsListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonRecursiveFolderEventsListener.swift; sourceTree = ""; }; 6D5F91361E4F6D3500C6E5BF /* RecursiveFolderEventsListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecursiveFolderEventsListener.swift; sourceTree = ""; }; @@ -238,6 +261,8 @@ 6D848C5E1E68D7940000FC1E /* TestResultsModuleInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestResultsModuleInterface.swift; sourceTree = ""; }; 6D848C601E68D7B80000FC1E /* TestResultsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestResultsPresenter.swift; sourceTree = ""; }; 6D848C621E6964D90000FC1E /* TestResultsCollectionViewOutlets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestResultsCollectionViewOutlets.swift; sourceTree = ""; }; + 6D89B9531EF088B600865C5F /* TestResultsDiffMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsDiffMode.swift; sourceTree = ""; }; + 6D89D17E1EF59C100082579E /* TestResultsTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsTopView.swift; sourceTree = ""; }; 6D8BD71E1EB13949004928F7 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 6D8BD7221EB144C0004928F7 /* ApplicationTestLogFilesListenerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationTestLogFilesListenerSpec.swift; sourceTree = ""; }; 6D8BF2BC1ED0B78700C368D1 /* String+HTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+HTML.swift"; sourceTree = ""; }; @@ -246,6 +271,7 @@ 6D92E1CC1E73102C00DB73C7 /* AutoEquatable.stencil */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AutoEquatable.stencil; sourceTree = ""; }; 6D92E1D01E73106700DB73C7 /* AutoEquatable.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoEquatable.generated.swift; sourceTree = ""; }; 6D9395D11EC1165B00B2583D /* PreferencesModuleDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesModuleDelegate.swift; sourceTree = ""; }; + 6D9EF95B1EF5DBD600AA15B1 /* TestResultsDisplayInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResultsDisplayInfo.swift; sourceTree = ""; }; 6DA1BF961E7755110017D47B /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 6DA1BF971E7755110017D47B /* Storyboards.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storyboards.swift; sourceTree = ""; }; 6DA1BF9A1E7756010017D47B /* FolderEventFilterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderEventFilterSpec.swift; sourceTree = ""; }; @@ -490,6 +516,10 @@ 6D51834A1EDF52C6007EC15C /* NaturalApproximationFormatterSpec.swift */, 6DB94C9E1EE56EFE00095C37 /* ApplicationNameExtractorSpec.swift */, 6DB94CA01EE5AB9800095C37 /* ApplicationNameExtractorFactorySpec.swift */, + 6D28147F1EEDC6C800C4E67C /* TestResultsSectionTitleDisplayInfoSpec.swift */, + 6D2814811EEDECDA00C4E67C /* TestResultsSectionDisplayInfoSpec.swift */, + 6D2814831EEDEE3C00C4E67C /* TestResultsDisplayInfosCollectorSpec.swift */, + 6D5BC1D71EF6D1B70003D1CB /* TestResultsDisplayInfoSpec.swift */, ); path = FBSnapshotsViewerTests; sourceTree = ""; @@ -562,6 +592,14 @@ path = Protocols; sourceTree = ""; }; + 6D89B9521EF0889F00865C5F /* Models */ = { + isa = PBXGroup; + children = ( + 6D89B9531EF088B600865C5F /* TestResultsDiffMode.swift */, + ); + path = Models; + sourceTree = ""; + }; 6D8BF2BB1ED0B6CF00C368D1 /* Extensions */ = { isa = PBXGroup; children = ( @@ -578,6 +616,7 @@ 6DA1BF961E7755110017D47B /* Colors.swift */, 6DA1BF971E7755110017D47B /* Storyboards.swift */, 6D3000381EBDE7ED005B6103 /* Fonts.swift */, + 6D1C40751EFA628F00D4D5F1 /* Colors+AppleInterfaceMode.swift */, ); path = SwiftGen; sourceTree = ""; @@ -648,6 +687,7 @@ 6DF15B701E662C7C00DFA181 /* Test Results */ = { isa = PBXGroup; children = ( + 6D89B9521EF0889F00865C5F /* Models */, 6DF15B781E662DFC00DFA181 /* Wireframe */, 6DF15B731E662CAF00DFA181 /* User Interface */, 6DF15B721E662CA600DFA181 /* Presenter */, @@ -669,9 +709,12 @@ 6DF15B721E662CA600DFA181 /* Presenter */ = { isa = PBXGroup; children = ( - 6D848C581E68D3960000FC1E /* TestResultDisplayInfo.swift */, 6D848C601E68D7B80000FC1E /* TestResultsPresenter.swift */, 6D848C621E6964D90000FC1E /* TestResultsCollectionViewOutlets.swift */, + 6D848C581E68D3960000FC1E /* TestResultDisplayInfo.swift */, + 6D28147B1EED9C2000C4E67C /* TestResultsSectionDisplayInfo.swift */, + 6D28147D1EED9E4100C4E67C /* TestResultsDisplayInfosCollector.swift */, + 6D9EF95B1EF5DBD600AA15B1 /* TestResultsDisplayInfo.swift */, ); path = Presenter; sourceTree = ""; @@ -683,6 +726,10 @@ 6DF15B761E662CCC00DFA181 /* TestResultsUserInterface.swift */, 6D848C541E68D0C90000FC1E /* TestResultCell.swift */, 6D848C551E68D0C90000FC1E /* TestResultCell.xib */, + 6D2814771EED7F9300C4E67C /* TestResultsHeader.swift */, + 6D2814791EED7F9E00C4E67C /* TestResultsHeader.xib */, + 6D5E1A0A1EF092DC0016A32C /* TestResultSplitView.swift */, + 6D89D17E1EF59C100082579E /* TestResultsTopView.swift */, ); path = "User Interface"; sourceTree = ""; @@ -799,6 +846,7 @@ buildActionMask = 2147483647; files = ( 6DF7ED851ED3880800A54C54 /* dsa_pub.pem in Resources */, + 6D28147A1EED7F9E00C4E67C /* TestResultsHeader.xib in Resources */, 6D4724C11E3E9F2400F38161 /* Assets.xcassets in Resources */, 6D848C571E68D0C90000FC1E /* TestResultCell.xib in Resources */, 6D4724C41E3E9F2400F38161 /* Main.storyboard in Resources */, @@ -948,7 +996,9 @@ 6D30003A1EBDE8DE005B6103 /* Fonts.swift in Sources */, 6DE347811E47DB19004147DF /* SnapshotTestResult.swift in Sources */, 6D5183491EDF4FAF007EC15C /* DateComponentsFormatter+NaturalApproximation.swift in Sources */, + 6D89D17F1EF59C100082579E /* TestResultsTopView.swift in Sources */, 6DE3477C1E47D9D0004147DF /* MenuInteractor.swift in Sources */, + 6D2814781EED7F9300C4E67C /* TestResultsHeader.swift in Sources */, 6D789CE21EC3BE7700EEF7EE /* PreferencesInteractor.swift in Sources */, 6D3000451EBFBE69005B6103 /* PreferencesUserInterface.swift in Sources */, 6D5F91381E4F6D3500C6E5BF /* FolderEventsListener.swift in Sources */, @@ -961,12 +1011,15 @@ 6D5BB4C61E5915A10006BAE1 /* FolderEventFilter.swift in Sources */, 6D3000311EBD2C64005B6103 /* ProcessLauncher.swift in Sources */, 6D21F74A1E6CC17000BFCE4C /* MenuActions.swift in Sources */, + 6D5E1A0B1EF092DC0016A32C /* TestResultSplitView.swift in Sources */, 6D342CE01EBBDF80006EEBEC /* AppleInterfaceMode.swift in Sources */, 6D7D79021EB081F50001C1A0 /* ApplicationSnapshotTestResultListenerFactory.swift in Sources */, + 6D1C40761EFA628F00D4D5F1 /* Colors+AppleInterfaceMode.swift in Sources */, 6D8362781E79422D000794AE /* AutoMockable.swift in Sources */, 6D789CE01EC3B74800EEF7EE /* AutoCases.generated.swift in Sources */, 6DDFBD8D1E63268300DFC0A4 /* FolderEvent.swift in Sources */, 6D30004B1EBFBECA005B6103 /* PreferencesPresenter.swift in Sources */, + 6D28147C1EED9C2000C4E67C /* TestResultsSectionDisplayInfo.swift in Sources */, 6DBD6C541EAD4B2B006F14DC /* ApplicationTestLogFilesListener.swift in Sources */, 6DDFBDA41E64D14700DFC0A4 /* MenuModuleInterface.swift in Sources */, 6D789CE81EC3C19F00EEF7EE /* ConfigurationStorage.swift in Sources */, @@ -974,6 +1027,7 @@ 6D7C1F941EEC198A0045117E /* Build.swift in Sources */, 6D848C591E68D3960000FC1E /* TestResultDisplayInfo.swift in Sources */, 6D5BB4C51E5915A10006BAE1 /* ApplicationTemporaryFolderEventFilter.swift in Sources */, + 6D28147E1EED9E4100C4E67C /* TestResultsDisplayInfosCollector.swift in Sources */, 6D848C5D1E68D6A50000FC1E /* TestResultsInteractor.swift in Sources */, 6D8362771E79422D000794AE /* AutoEquatable.swift in Sources */, 6D5F91391E4F6D3500C6E5BF /* NonRecursiveFolderEventsListener.swift in Sources */, @@ -996,6 +1050,7 @@ 6D848C5B1E68D5720000FC1E /* TestResultsInteractorIO.swift in Sources */, 6D3000431EBFBE14005B6103 /* PreferencesController.swift in Sources */, 6D5F913C1E4F6D4700C6E5BF /* FolderEventsListenerFactory.swift in Sources */, + 6D89B9541EF088B600865C5F /* TestResultsDiffMode.swift in Sources */, 6DA1BF991E7755110017D47B /* Storyboards.swift in Sources */, 6DE3477E1E47D9DF004147DF /* MenuInteractorIO.swift in Sources */, 6DBD6C501EACF1B0006F14DC /* DerivedDataFolder.swift in Sources */, @@ -1008,6 +1063,7 @@ 6DA1BF981E7755110017D47B /* Colors.swift in Sources */, 6D789CE61EC3BFA900EEF7EE /* Configuration.swift in Sources */, 6DBD99DB1E7AB3D400E1714E /* FileWatcherFactory.swift in Sources */, + 6D9EF95C1EF5DBD600AA15B1 /* TestResultsDisplayInfo.swift in Sources */, 6D3003B21ED20B13000CCAC8 /* Updater.swift in Sources */, 6D33A9D41EB4DAF60089AEDD /* AutoHashable.swift in Sources */, 6DDFBDA61E64E53D00DFC0A4 /* MenuStatusItemMenu.swift in Sources */, @@ -1048,6 +1104,7 @@ 6D12F4721E81758500AA4727 /* TestResultsControllerSpec.swift in Sources */, 6D33A9D21EB4D38D0089AEDD /* ApplicationSnapshotTestResultListenerSpec.swift in Sources */, 6D076B791E7F2FB5001FDA14 /* TestResultsInteractorSpec.swift in Sources */, + 6D2814821EEDECDA00C4E67C /* TestResultsSectionDisplayInfoSpec.swift in Sources */, 6DBD99D91E7AB31B00E1714E /* NonRecursiveFolderEventsListenerSpec.swift in Sources */, 6D4D844B1EC7B8E70005932D /* PreferencesInteractorSpec.swift in Sources */, 6D076B711E7EAC94001FDA14 /* FolderEventsListenerFactorySpec.swift in Sources */, @@ -1055,6 +1112,9 @@ 6D51834B1EDF52C6007EC15C /* NaturalApproximationFormatterSpec.swift in Sources */, 6D076B6D1E7CA1B9001FDA14 /* RecursiveFolderEventsListenerSpec.swift in Sources */, 6D3BA5631E6E063600CAD4EE /* MenuStatusItemMenuSpec.swift in Sources */, + 6D2814841EEDEE3C00C4E67C /* TestResultsDisplayInfosCollectorSpec.swift in Sources */, + 6D5BC1D81EF6D1B70003D1CB /* TestResultsDisplayInfoSpec.swift in Sources */, + 6D2814801EEDC6C800C4E67C /* TestResultsSectionTitleDisplayInfoSpec.swift in Sources */, 6D3000371EBDD81F005B6103 /* ProcessLauncherSpec.swift in Sources */, 6DB94CA11EE5AB9800095C37 /* ApplicationNameExtractorFactorySpec.swift in Sources */, 6D8BD7231EB144C0004928F7 /* ApplicationTestLogFilesListenerSpec.swift in Sources */, diff --git a/FBSnapshotsViewer/Base.lproj/Main.storyboard b/FBSnapshotsViewer/Base.lproj/Main.storyboard index fd414dd..023f0de 100644 --- a/FBSnapshotsViewer/Base.lproj/Main.storyboard +++ b/FBSnapshotsViewer/Base.lproj/Main.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -668,7 +669,7 @@ - + @@ -693,7 +694,7 @@ - + @@ -701,7 +702,7 @@ - + @@ -760,13 +761,13 @@ - + - + - + @@ -786,16 +787,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/FBSnapshotsViewer/ColorsPalette.txt b/FBSnapshotsViewer/ColorsPalette.txt index 2b7607c..7ccae64 100644 --- a/FBSnapshotsViewer/ColorsPalette.txt +++ b/FBSnapshotsViewer/ColorsPalette.txt @@ -1,13 +1,16 @@ -PrimaryText.LightMode : 0x212121DD -SecondaryText.LightMode : 0x7575758A -PrimaryLight.LightMode : 0xCFD8DCFF -Divider.LightMode : 0xBDBDBDFF -ButtonBackground.LightMode : 0x212121DD -ButtonTitle.LightMode : 0xFFFFFFDD +TestResultsTopViewColorTop : 0xE7E6E7FF +TestResultsTopViewColorBottom : 0xD1D0D1FF -PrimaryText.DarkMode : 0xFFFFFFDD -SecondaryText.DarkMode : 0xFFFFFF8A -PrimaryLight.DarkMode : 0xECEFF1FF -Divider.DarkMode : 0xF5F5F5FF -ButtonBackground.DarkMode : 0xFAFAFADD -ButtonTitle.DarkMode : 0xFFFFFFDD +PrimaryText.LightMode : 0x000000DE +SecondaryText.LightMode : 0x757575DE +PrimaryLight.LightMode : 0xCFD8DCDE +Divider.LightMode : 0xBDBDBDFF +ButtonBackground.LightMode : 0x212121DD +ButtonTitle.LightMode : 0xFFFFFFDD + +PrimaryText.DarkMode : 0xFFFFFFDE +SecondaryText.DarkMode : 0xFFFFFFDE +PrimaryLight.DarkMode : 0xECEFF1DE +Divider.DarkMode : 0xF5F5F5FF +ButtonBackground.DarkMode : 0xFAFAFADD +ButtonTitle.DarkMode : 0xFFFFFFDD diff --git a/FBSnapshotsViewer/Test Results/Models/TestResultsDiffMode.swift b/FBSnapshotsViewer/Test Results/Models/TestResultsDiffMode.swift new file mode 100644 index 0000000..c7e40bb --- /dev/null +++ b/FBSnapshotsViewer/Test Results/Models/TestResultsDiffMode.swift @@ -0,0 +1,14 @@ +// +// TestResultsDiffMode.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 13.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Foundation + +enum TestResultsDiffMode: AutoEquatable { + case diff + case mouseOver +} diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultDisplayInfo.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultDisplayInfo.swift index 46799cb..40d0158 100644 --- a/FBSnapshotsViewer/Test Results/Presenter/TestResultDisplayInfo.swift +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultDisplayInfo.swift @@ -16,10 +16,8 @@ struct TestResultDisplayInfo: AutoEquatable { let testContext: String let canBeViewedInKaleidoscope: Bool let testResult: SnapshotTestResult - let createdAt: String - let applicationName: String - init(testResult: SnapshotTestResult, kaleidoscopeViewer: ExternalViewer.Type = KaleidoscopeViewer.self, dateFormatter: DateComponentsFormatter = DateComponentsFormatter.naturalApproximationFormatter) { + init(testResult: SnapshotTestResult, kaleidoscopeViewer: ExternalViewer.Type = KaleidoscopeViewer.self) { self.testResult = testResult self.canBeViewedInKaleidoscope = kaleidoscopeViewer.isAvailable() && kaleidoscopeViewer.canView(snapshotTestResult: testResult) switch testResult { @@ -32,11 +30,8 @@ struct TestResultDisplayInfo: AutoEquatable { self.diffImageURL = URL(fileURLWithPath: diffImagePath) self.failedImageURL = URL(fileURLWithPath: failedImagePath) } - self.createdAt = dateFormatter.string(from: testResult.build.date, to: Date()) ?? "Just now" - let testNameComponents = testResult.testName.replacingOccurrences(of: "_", with: " ").components(separatedBy: " ") self.testContext = testNameComponents[0..<(testNameComponents.count - 1)].joined(separator: " ") self.testName = testNameComponents[testNameComponents.count - 1] - self.applicationName = testResult.build.applicationName } } diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultsCollectionViewOutlets.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultsCollectionViewOutlets.swift index e7eab68..40a697c 100644 --- a/FBSnapshotsViewer/Test Results/Presenter/TestResultsCollectionViewOutlets.swift +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultsCollectionViewOutlets.swift @@ -6,17 +6,19 @@ // Copyright © 2017 Anton Domashnev. All rights reserved. // -import Cocoa +import AppKit class TestResultsCollectionViewOutlets: NSObject { - var testResults: [TestResultDisplayInfo] = [] + var testResultsDisplayInfo: TestResultsDisplayInfo = TestResultsDisplayInfo() fileprivate weak var testResultCellDelegate: TestResultCellDelegate? init(collectionView: NSCollectionView, testResultCellDelegate: TestResultCellDelegate? = nil) { - guard let nib = NSNib(nibNamed: "TestResultCell", bundle: Bundle.main) else { - fatalError("TestResultCell is missing in bundle") + guard let testResultCellNib = NSNib(nibNamed: TestResultCell.itemIdentifier, bundle: Bundle.main), + let testResultHeaderNib = NSNib(nibNamed: TestResultsHeader.itemIdentifier, bundle: Bundle.main) else { + fatalError("TestResultCell || TestResultsHeader is missing in bundle") } - collectionView.register(nib, forItemWithIdentifier: TestResultCell.itemIdentifier) + collectionView.register(testResultCellNib, forItemWithIdentifier: TestResultCell.itemIdentifier) + collectionView.register(testResultHeaderNib, forSupplementaryViewOfKind: NSCollectionElementKindSectionHeader, withIdentifier: TestResultsHeader.itemIdentifier) self.testResultCellDelegate = testResultCellDelegate super.init() } @@ -24,28 +26,50 @@ class TestResultsCollectionViewOutlets: NSObject { extension TestResultsCollectionViewOutlets: NSCollectionViewDelegateFlowLayout { func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { - return NSSize(width: 530, height: 346) + return NSSize(width: 732, height: 408) } func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> EdgeInsets { return EdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize { + return NSSize(width: 732, height: 30) + } } extension TestResultsCollectionViewOutlets: NSCollectionViewDataSource { func numberOfSections(in collectionView: NSCollectionView) -> Int { - return 1 + return testResultsDisplayInfo.sectionInfos.count } func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { - return testResults.count + return testResultsDisplayInfo.sectionInfos[section].itemInfos.count } - + + func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> NSView { + guard let view = collectionView.makeSupplementaryView(ofKind: NSCollectionElementKindSectionHeader, withIdentifier: TestResultsHeader.itemIdentifier, for: indexPath) as? TestResultsHeader else { + fatalError("TestResultsHeader is not registered in collection view") + } + let titleInfo = testResultsDisplayInfo.sectionInfos[indexPath.section].titleInfo + view.configure(with: titleInfo) + return view + } + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { guard let item = collectionView.makeItem(withIdentifier: TestResultCell.itemIdentifier, for: indexPath) as? TestResultCell else { fatalError("TestResultCell is not registered in collection view") } - item.configure(with: testResults[indexPath.item]) + let testResultInfo = testResultsDisplayInfo.sectionInfos[indexPath.section].itemInfos[indexPath.item] + item.configure(with: testResultInfo, diffMode: testResultsDisplayInfo.testResultsDiffMode) item.delegate = testResultCellDelegate return item } diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfo.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfo.swift new file mode 100644 index 0000000..f30bff3 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfo.swift @@ -0,0 +1,22 @@ +// +// TestResultsDisplayInfo.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 17.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Foundation + +struct TestResultsDisplayInfo: AutoEquatable { + let sectionInfos: [TestResultsSectionDisplayInfo] + let topTitle: String + let testResultsDiffMode: TestResultsDiffMode + + init(sectionInfos: [TestResultsSectionDisplayInfo] = [], testResultsDiffMode: TestResultsDiffMode = .mouseOver) { + self.sectionInfos = sectionInfos + self.testResultsDiffMode = testResultsDiffMode + let numberOfTests = sectionInfos.reduce(0, { $0 + $1.itemInfos.count }) + self.topTitle = "\(numberOfTests) Test Result" + (numberOfTests > 1 ? "s" : "") + } +} diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfosCollector.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfosCollector.swift new file mode 100644 index 0000000..fc85b97 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultsDisplayInfosCollector.swift @@ -0,0 +1,49 @@ +// +// TestResultsDisplayInfoCollector.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Foundation + +class TestResultsDisplayInfosCollector { + // MARK: - Helpers + + private func groupTestResultsByAssociatedSectionTitle(_ testResults: [SnapshotTestResult]) -> [TestResultsSectionTitleDisplayInfo: [TestResultDisplayInfo]] { + var temporaryDictionary: [TestResultsSectionTitleDisplayInfo: [TestResultDisplayInfo]] = [:] + testResults.forEach { + let testResultInfo = TestResultDisplayInfo(testResult: $0) + let titleInfo = TestResultsSectionTitleDisplayInfo(build: $0.build, testContext: testResultInfo.testContext) + if var testResultInfos = temporaryDictionary[titleInfo] { + testResultInfos.append(testResultInfo) + temporaryDictionary[titleInfo] = testResultInfos + } + else { + temporaryDictionary[titleInfo] = [testResultInfo] + } + } + return temporaryDictionary + } + + private func createSectionDisplayInfos(with groupedTestResults: [TestResultsSectionTitleDisplayInfo: [TestResultDisplayInfo]]) -> [TestResultsSectionDisplayInfo] { + return groupedTestResults.map { TestResultsSectionDisplayInfo(title: $0.key, items: $0.value) }.sorted { + let timeAgo1 = $0.0.titleInfo.timeAgoDate + let timeAgo2 = $0.1.titleInfo.timeAgoDate + if timeAgo1 != timeAgo2 { + return timeAgo1 > timeAgo2 + } + else { + return $0.0.titleInfo.title > $0.1.titleInfo.title + } + } + } + + // MARK: - Interface + + func collect(testResults: [SnapshotTestResult] = []) -> [TestResultsSectionDisplayInfo] { + let goupedDictionary = groupTestResultsByAssociatedSectionTitle(testResults) + return createSectionDisplayInfos(with: goupedDictionary) + } +} diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultsPresenter.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultsPresenter.swift index 4f8bfda..1042d00 100644 --- a/FBSnapshotsViewer/Test Results/Presenter/TestResultsPresenter.swift +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultsPresenter.swift @@ -9,9 +9,15 @@ import Cocoa class TestResultsPresenter { + fileprivate var selectedDiffMode: TestResultsDiffMode = .mouseOver + fileprivate let testResultsCollector: TestResultsDisplayInfosCollector var interactor: TestResultsInteractorInput? var wireframe: TestResultsWireframe? weak var userInterface: TestResultsUserInterface? + + init(testResultsCollector: TestResultsDisplayInfosCollector = TestResultsDisplayInfosCollector()) { + self.testResultsCollector = testResultsCollector + } } extension TestResultsPresenter: TestResultsModuleInterface { @@ -19,10 +25,16 @@ extension TestResultsPresenter: TestResultsModuleInterface { guard let testResults = interactor?.testResults, !testResults.isEmpty else { return } - userInterface?.show(testResults: testResults.map { TestResultDisplayInfo(testResult: $0) }) + let testResultsDisplayInfo = TestResultsDisplayInfo(sectionInfos: testResultsCollector.collect(testResults: testResults), testResultsDiffMode: selectedDiffMode) + userInterface?.show(displayInfo: testResultsDisplayInfo) } func openInKaleidoscope(testResultDisplayInfo: TestResultDisplayInfo) { interactor?.openInKaleidoscope(testResult: testResultDisplayInfo.testResult) } + + func selectDiffMode(_ diffMode: TestResultsDiffMode) { + selectedDiffMode = diffMode + updateUserInterface() + } } diff --git a/FBSnapshotsViewer/Test Results/Presenter/TestResultsSectionDisplayInfo.swift b/FBSnapshotsViewer/Test Results/Presenter/TestResultsSectionDisplayInfo.swift new file mode 100644 index 0000000..36a0183 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/Presenter/TestResultsSectionDisplayInfo.swift @@ -0,0 +1,42 @@ +// +// TestResultsDisplayInfo.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Foundation + +struct TestResultsSectionTitleDisplayInfo: AutoEquatable, AutoHashable { + let title: String + let timeAgo: String + let timeAgoDate: Date + + init(build: Build, testContext: String, dateFormatter: DateComponentsFormatter = DateComponentsFormatter.naturalApproximationFormatter) { + self.title = "\(build.applicationName) | \(testContext)" + self.timeAgoDate = build.date + if let timeAgo = dateFormatter.string(from: build.date, to: Date()) { + self.timeAgo = timeAgo == "About 0 seconds" ? "Just now" : "\(timeAgo) ago" + } + else { + self.timeAgo = "Just now" + } + } + + init(title: String, timeAgo: String, timeAgoDate: Date) { + self.title = title + self.timeAgo = timeAgo + self.timeAgoDate = timeAgoDate + } +} + +struct TestResultsSectionDisplayInfo: AutoEquatable { + let titleInfo: TestResultsSectionTitleDisplayInfo + let itemInfos: [TestResultDisplayInfo] + + init(title: TestResultsSectionTitleDisplayInfo, items: [TestResultDisplayInfo] = []) { + self.titleInfo = title + self.itemInfos = items + } +} diff --git a/FBSnapshotsViewer/Test Results/TestResultsModuleInterface.swift b/FBSnapshotsViewer/Test Results/TestResultsModuleInterface.swift index 0017b7a..5eb231d 100644 --- a/FBSnapshotsViewer/Test Results/TestResultsModuleInterface.swift +++ b/FBSnapshotsViewer/Test Results/TestResultsModuleInterface.swift @@ -11,4 +11,5 @@ import Cocoa protocol TestResultsModuleInterface: class, AutoMockable { func updateUserInterface() func openInKaleidoscope(testResultDisplayInfo: TestResultDisplayInfo) + func selectDiffMode(_ diffMode: TestResultsDiffMode) } diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.swift index ff17bac..e43f573 100644 --- a/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.swift +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.swift @@ -6,7 +6,7 @@ // Copyright © 2017 Anton Domashnev. All rights reserved. // -import Cocoa +import AppKit protocol TestResultCellDelegate: class, AutoMockable { func testResultCell(_ cell: TestResultCell, viewInKaleidoscopeButtonClicked: NSButton) @@ -16,70 +16,72 @@ class TestResultCell: NSCollectionViewItem { static let itemIdentifier = "TestResultCell" weak var delegate: TestResultCellDelegate? - + + @IBOutlet private weak var actionsContainerView: NSView! @IBOutlet private weak var viewInKaleidoscopeButton: NSButton! - @IBOutlet private weak var referenceImageView: NSImageView! - @IBOutlet private weak var diffImageView: NSImageView! + + @IBOutlet private weak var imagesContainerView: NSView! @IBOutlet private weak var failedImageView: NSImageView! - @IBOutlet private weak var testNameLabel: NSTextField! + @IBOutlet private weak var referenceImageView: NSImageView! @IBOutlet private weak var referenceImageTitleLabel: NSTextField! - @IBOutlet private weak var diffImageTitleLabel: NSTextField! @IBOutlet private weak var failedImageTitleLabel: NSTextField! - @IBOutlet private weak var referenceSeparatorView: NSView! - @IBOutlet private weak var diffSeparatorView: NSView! - @IBOutlet private weak var failedSeparatorView: NSView! - - // MARK: - Helpers - - private func configureViewsBackgroundColor(for appleInterfaceMode: AppleInterfaceMode) { - switch appleInterfaceMode { - case .dark: - view.layer?.backgroundColor = NSColor(named: .primaryLightDarkMode).cgColor - case .light: - view.layer?.backgroundColor = NSColor(named: .primaryLightLightMode).cgColor - } + + @IBOutlet private weak var splitContainerView: TestResultSplitView! + + @IBOutlet private weak var diffContainerView: NSView! + @IBOutlet private weak var diffImageView: NSImageView! + + @IBOutlet private weak var testInfoContainerView: NSView! + @IBOutlet private weak var testNameLabel: NSTextField! + + @IBOutlet private weak var separatorView: NSView! + + override func awakeFromNib() { + super.awakeFromNib() + separatorView.isHidden = true } + + // MARK: - Helpers private func configureSeparatorsColorScheme(for appleInterfaceMode: AppleInterfaceMode) { - let dividerColor: NSColor - switch appleInterfaceMode { - case .dark: - dividerColor = NSColor(named: .dividerDarkMode) - case .light: - dividerColor = NSColor(named: .dividerLightMode) - } - referenceSeparatorView.layer?.backgroundColor = dividerColor.cgColor - diffSeparatorView.layer?.backgroundColor = dividerColor.cgColor - failedSeparatorView.layer?.backgroundColor = dividerColor.cgColor + let dividerColor = Color.divider(for: appleInterfaceMode) + separatorView.wantsLayer = true + separatorView.layer?.backgroundColor = dividerColor.cgColor } private func configureTitleLabelsColorScheme(for appleInterfaceMode: AppleInterfaceMode) { - let primaryTextColor: NSColor - let secondaryTextColor: NSColor - switch appleInterfaceMode { - case .dark: - primaryTextColor = NSColor(named: .primaryTextDarkMode) - secondaryTextColor = NSColor(named: .secondaryTextDarkMode) - case .light: - primaryTextColor = NSColor(named: .primaryTextLightMode) - secondaryTextColor = NSColor(named: .secondaryTextLightMode) + testNameLabel.textColor = Color.primaryText(for: appleInterfaceMode) + referenceImageTitleLabel.textColor = Color.secondaryText(for: appleInterfaceMode) + failedImageTitleLabel.textColor = Color.secondaryText(for: appleInterfaceMode) + } + + private func configureBordersColorScheme(for appleInterfaceMode: AppleInterfaceMode) { + let borderColor = Color.divider(for: appleInterfaceMode) + [failedImageView, referenceImageView, diffContainerView, splitContainerView].forEach { + $0?.wantsLayer = true + $0?.layer?.borderColor = borderColor.cgColor + $0?.layer?.borderWidth = 1 } - testNameLabel.textColor = primaryTextColor - referenceImageTitleLabel.textColor = secondaryTextColor - diffImageTitleLabel.textColor = secondaryTextColor - failedImageTitleLabel.textColor = secondaryTextColor } - - private func configureTitleLabelsBackgroundColorScheme() { - testNameLabel.layer?.backgroundColor = NSColor.clear.cgColor - referenceImageTitleLabel.layer?.backgroundColor = NSColor.clear.cgColor - diffImageTitleLabel.layer?.backgroundColor = NSColor.clear.cgColor - failedImageTitleLabel.layer?.backgroundColor = NSColor.clear.cgColor + + private func configureUI(for diffMode: TestResultsDiffMode) { + switch diffMode { + case .diff: + splitContainerView.isHidden = true + diffContainerView.isHidden = false + case .mouseOver: + diffContainerView.isHidden = true + splitContainerView.isHidden = false + } } - - // MARK: - Interface - - func configure(with testResult: TestResultDisplayInfo, appleInterfaceMode: AppleInterfaceMode = AppleInterfaceMode()) { + + private func configureUI(for appleInterfaceMode: AppleInterfaceMode) { + configureTitleLabelsColorScheme(for: appleInterfaceMode) + configureSeparatorsColorScheme(for: appleInterfaceMode) + configureBordersColorScheme(for: appleInterfaceMode) + } + + private func configureUI(with testResult: TestResultDisplayInfo) { if let referenceImage = NSImage(contentsOf: testResult.referenceImageURL) { referenceImageView.image = referenceImage } @@ -91,9 +93,15 @@ class TestResultCell: NSCollectionViewItem { } viewInKaleidoscopeButton.isHidden = !testResult.canBeViewedInKaleidoscope testNameLabel.stringValue = testResult.testName - configureTitleLabelsColorScheme(for: appleInterfaceMode) - configureSeparatorsColorScheme(for: appleInterfaceMode) - configureViewsBackgroundColor(for: appleInterfaceMode) + } + + // MARK: - Interface + + func configure(with testResult: TestResultDisplayInfo, appleInterfaceMode: AppleInterfaceMode = AppleInterfaceMode(), diffMode: TestResultsDiffMode = .mouseOver) { + configureUI(with: testResult) + configureUI(for: appleInterfaceMode) + configureUI(for: diffMode) + splitContainerView.configure(with: testResult, appleInterfaceMode: appleInterfaceMode) } // MARK: - Actions diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.xib b/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.xib index ebe2dbd..bdf8a33 100644 --- a/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.xib +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultCell.xib @@ -1,8 +1,8 @@ - + - + @@ -10,162 +10,222 @@ - + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + + + + + + + + + - - - - - - + + + + + + - - - + + + + - - - - - - - - - - - + + + + + + + + + + + + + + - - + + + + + + + + - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - + + + + + + + + + + + + - + diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultSplitView.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultSplitView.swift new file mode 100644 index 0000000..e11d0b8 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultSplitView.swift @@ -0,0 +1,97 @@ +// +// TestResultSplitView.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 13.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import AppKit + +class TestResultSplitView: NSView { + @IBOutlet private weak var splitReferenceImageView: NSImageView! + @IBOutlet private weak var splitFailedImageView: NSImageView! + @IBOutlet private weak var splitReferenceMaskView: NSView! + @IBOutlet private weak var splitFailedMaskView: NSView! + @IBOutlet private weak var splitSeparatorView: NSView! + @IBOutlet private weak var splitReferenceMaskViewTrailing: NSLayoutConstraint! + @IBOutlet private weak var splitFailedMaskViewLeading: NSLayoutConstraint! + @IBOutlet private weak var splitSeparatorViewHorizontalCenter: NSLayoutConstraint! + + private var trackingArea: NSTrackingArea! + private lazy var selfMidX: CGFloat = { + return self.bounds.width / 2 + }() + + override func awakeFromNib() { + super.awakeFromNib() + prepareUI() + resetConstraints() + createTrackingArea() + } + + // MARK: - Helpers + + private func createTrackingArea() { + trackingArea = NSTrackingArea(rect: bounds, options: NSTrackingAreaOptions.mouseEnteredAndExited.union(NSTrackingAreaOptions.mouseMoved).union(NSTrackingAreaOptions.activeAlways), owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + private func prepareUI() { + [splitReferenceMaskView, splitFailedMaskView].forEach { + $0?.wantsLayer = true + $0?.layer?.masksToBounds = true + } + } + + private func resetConstraints() { + splitReferenceMaskViewTrailing.constant = selfMidX + splitFailedMaskViewLeading.constant = selfMidX + splitSeparatorViewHorizontalCenter.constant = 0 + layoutSubtreeIfNeeded() + } + + private func configureBordersColorScheme(for appleInterfaceMode: AppleInterfaceMode) { + let borderColor = Color.divider(for: appleInterfaceMode) + splitSeparatorView.wantsLayer = true + splitSeparatorView.layer?.backgroundColor = borderColor.cgColor + } + + private func updateConstraints(with event: NSEvent) { + guard let contentView = event.window?.contentView else { + return + } + let convertedPoint = convert(event.locationInWindow, from: contentView) + let contentViewOrigin = contentView.frame.origin + let mousePosition = NSPoint(x: convertedPoint.x - contentViewOrigin.x, y: convertedPoint.y - contentViewOrigin.y) + splitSeparatorViewHorizontalCenter.constant = mousePosition.x - selfMidX + splitReferenceMaskViewTrailing.constant = bounds.width - mousePosition.x + splitFailedMaskViewLeading.constant = mousePosition.x + } + + // MARK: - Interface + + func configure(with testResult: TestResultDisplayInfo, appleInterfaceMode: AppleInterfaceMode = AppleInterfaceMode()) { + if let referenceImage = NSImage(contentsOf: testResult.referenceImageURL) { + splitReferenceImageView.image = referenceImage + } + if let failedImageURL = testResult.failedImageURL, let failedImage = NSImage(contentsOf: failedImageURL) { + splitFailedImageView.image = failedImage + } + configureBordersColorScheme(for: appleInterfaceMode) + } + + // MARK: - TrackingEvents + + override func mouseExited(with event: NSEvent) { + resetConstraints() + } + + override func mouseEntered(with event: NSEvent) { + updateConstraints(with: event) + } + + override func mouseMoved(with event: NSEvent) { + updateConstraints(with: event) + } +} diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultsController.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultsController.swift index e01da8f..f842fa4 100644 --- a/FBSnapshotsViewer/Test Results/User Interface/TestResultsController.swift +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultsController.swift @@ -10,35 +10,54 @@ import Cocoa class TestResultsController: NSViewController { @IBOutlet var collectionView: NSCollectionView! + @IBOutlet var topView: TestResultsTopView! var collectionViewOutlets: TestResultsCollectionViewOutlets! var eventHandler: TestResultsModuleInterface! override func viewDidLoad() { super.viewDidLoad() - collectionViewOutlets = TestResultsCollectionViewOutlets(collectionView: collectionView, testResultCellDelegate: self) - collectionView.delegate = collectionViewOutlets - collectionView.dataSource = collectionViewOutlets + topView.delegate = self + setupCollectionView() } override func viewWillAppear() { super.viewWillAppear() eventHandler.updateUserInterface() } + + // MARK: - Helpers + + private func setupCollectionView() { + collectionViewOutlets = TestResultsCollectionViewOutlets(collectionView: collectionView, testResultCellDelegate: self) + collectionView.delegate = collectionViewOutlets + collectionView.dataSource = collectionViewOutlets + } } extension TestResultsController: TestResultsUserInterface { - func show(testResults: [TestResultDisplayInfo]) { - collectionViewOutlets.testResults = testResults + func show(displayInfo: TestResultsDisplayInfo) { + topView.configure(with: displayInfo) + collectionViewOutlets.testResultsDisplayInfo = displayInfo collectionView.reloadData() } } extension TestResultsController: TestResultCellDelegate { func testResultCell(_ cell: TestResultCell, viewInKaleidoscopeButtonClicked: NSButton) { - guard let cellIndex = collectionView.indexPath(for: cell)?.item, collectionViewOutlets.testResults.count > cellIndex else { + let sectionInfos = collectionViewOutlets.testResultsDisplayInfo.sectionInfos + guard let cellIndexPath = collectionView.indexPath(for: cell), + sectionInfos.count > cellIndexPath.section, + sectionInfos[cellIndexPath.section].itemInfos.count > cellIndexPath.item else { assertionFailure("Unexpected TestResultCellDelegate callback about Kaleidoscope button click") return } - eventHandler.openInKaleidoscope(testResultDisplayInfo: collectionViewOutlets.testResults[cellIndex]) + let testResultInfo = sectionInfos[cellIndexPath.section].itemInfos[cellIndexPath.item] + eventHandler.openInKaleidoscope(testResultDisplayInfo: testResultInfo) + } +} + +extension TestResultsController: TestResultsTopViewDelegate { + func testResultsTopView(_ topView: TestResultsTopView, didSelect diffMode: TestResultsDiffMode) { + eventHandler.selectDiffMode(diffMode) } } diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.swift new file mode 100644 index 0000000..8cd33aa --- /dev/null +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.swift @@ -0,0 +1,31 @@ +// +// TestResultsHeader.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import AppKit + +class TestResultsHeader: NSView, NSCollectionViewSectionHeaderView { + static let itemIdentifier = "TestResultsHeader" + + @IBOutlet private weak var contextLabel: NSTextField! + @IBOutlet private weak var dateLabel: NSTextField! + + // MARK: - Helpers + + private func configureTitleLabelsColorScheme(for appleInterfaceMode: AppleInterfaceMode) { + contextLabel.textColor = Color.primaryText(for: appleInterfaceMode) + dateLabel.textColor = Color.secondaryText(for: appleInterfaceMode) + } + + // MARK: - Interface + + func configure(with sectionTitleInfo: TestResultsSectionTitleDisplayInfo, appleInterfaceMode: AppleInterfaceMode = AppleInterfaceMode()) { + contextLabel.stringValue = sectionTitleInfo.title + dateLabel.stringValue = sectionTitleInfo.timeAgo + configureTitleLabelsColorScheme(for: appleInterfaceMode) + } +} diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.xib b/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.xib new file mode 100644 index 0000000..13bfe70 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultsHeader.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultsTopView.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultsTopView.swift new file mode 100644 index 0000000..a2f16c9 --- /dev/null +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultsTopView.swift @@ -0,0 +1,54 @@ +// +// TestResultsTopView.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 17.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import AppKit + +protocol TestResultsTopViewDelegate: class { + func testResultsTopView(_ topView: TestResultsTopView, didSelect diffMode: TestResultsDiffMode) +} + +class TestResultsTopView: NSView { + weak var delegate: TestResultsTopViewDelegate? + + @IBOutlet private weak var numberOfTestsLabel: NSTextField! + @IBOutlet private weak var testResultsDiffModeSegementedControl: NSSegmentedControl! + + override func draw(_ dirtyRect: NSRect) { + let gradient = NSGradient(starting: Color(named: .testResultsTopViewColorBottom), ending: Color(named: .testResultsTopViewColorTop)) + gradient?.draw(in: bounds, angle: 90) + } + + override func awakeFromNib() { + super.awakeFromNib() + testResultsDiffModeSegementedControl.target = self + testResultsDiffModeSegementedControl.action = #selector(testResultsDiffModeSegementedControlValueChanged(sender:)) + } + + // MARK: - Helpers + + private func convertToDiffMode(testResultsDiffModeSegement: Int) -> TestResultsDiffMode { + return (testResultsDiffModeSegement == 1) ? TestResultsDiffMode.diff : TestResultsDiffMode.mouseOver + } + + private func convertToTestResultsDiffModeSegement(diffMode: TestResultsDiffMode) -> Int { + return (diffMode == .mouseOver) ? 0 : 1 + } + + // MARK: - Interface + + func configure(with testResultsDisplayInfo: TestResultsDisplayInfo) { + numberOfTestsLabel.stringValue = testResultsDisplayInfo.topTitle + testResultsDiffModeSegementedControl.setSelected(true, forSegment: convertToTestResultsDiffModeSegement(diffMode: testResultsDisplayInfo.testResultsDiffMode)) + } + + // MARK: - Actions + + @objc func testResultsDiffModeSegementedControlValueChanged(sender: NSSegmentedControl) { + delegate?.testResultsTopView(self, didSelect: convertToDiffMode(testResultsDiffModeSegement: sender.selectedSegment)) + } +} diff --git a/FBSnapshotsViewer/Test Results/User Interface/TestResultsUserInterface.swift b/FBSnapshotsViewer/Test Results/User Interface/TestResultsUserInterface.swift index d032eec..2517b52 100644 --- a/FBSnapshotsViewer/Test Results/User Interface/TestResultsUserInterface.swift +++ b/FBSnapshotsViewer/Test Results/User Interface/TestResultsUserInterface.swift @@ -9,5 +9,5 @@ import Foundation protocol TestResultsUserInterface: class, AutoMockable { - func show(testResults: [TestResultDisplayInfo]) + func show(displayInfo: TestResultsDisplayInfo) } diff --git a/FBSnapshotsViewer/Test Results/Wireframe/TestResultsWireframe.swift b/FBSnapshotsViewer/Test Results/Wireframe/TestResultsWireframe.swift index 081805b..9aeeca0 100644 --- a/FBSnapshotsViewer/Test Results/Wireframe/TestResultsWireframe.swift +++ b/FBSnapshotsViewer/Test Results/Wireframe/TestResultsWireframe.swift @@ -21,6 +21,7 @@ final class TestResultsWireframe { presenter.wireframe = self userInterface.eventHandler = presenter popover.contentViewController = userInterface + popover.contentSize = NSSize(width: 732, height: 600) popover.show(relativeTo: rect, of: view, preferredEdge: NSRectEdge.minY) } } diff --git a/FBSnapshotsViewerTests/ApplicationSnapshotTestResultListenerSpec.swift b/FBSnapshotsViewerTests/ApplicationSnapshotTestResultListenerSpec.swift index 8f1786e..f82b192 100644 --- a/FBSnapshotsViewerTests/ApplicationSnapshotTestResultListenerSpec.swift +++ b/FBSnapshotsViewerTests/ApplicationSnapshotTestResultListenerSpec.swift @@ -99,7 +99,6 @@ class ApplicationSnapshotTestResultListenerSpec: QuickSpec { applicationNameExtractor.extractApplicationNameReturnValue = "MyApp" snapshotTestResultFactory.createdSnapshotTestResultForLogLine[kaleidoscopeCommandMesageLogLine] = failedSnapshotTestResult snapshotTestResultFactory.createdSnapshotTestResultForLogLine[referenceImageSavedMessageLogLine] = recordedSnapshotTestResult - logReader.readLines = [applicationNameMessageLogLine, kaleidoscopeCommandMesageLogLine, unknownLogLine, referenceImageSavedMessageLogLine] listener.startListening { result in receivedSnapshotTestResults += [result] } @@ -120,32 +119,48 @@ class ApplicationSnapshotTestResultListenerSpec: QuickSpec { expect { fileWatcher.startClosure?(.updated(data: Data())) }.to(throwAssertion()) } } - + context("with valid updates") { let update: Data! = "new updated text".data(using: .utf8, allowLossyConversion: false) - context("when can not parse application name") { + context("when application name is unknown at the moment of reading snapshot test result log line") { beforeEach { - applicationNameExtractor.extractApplicationNameThrows = true + logReader.readLines = [unknownLogLine, kaleidoscopeCommandMesageLogLine, applicationNameMessageLogLine, referenceImageSavedMessageLogLine] } - it("outputs expected snapshot test resilts") { + it("thows assertion") { expect { fileWatcher.startClosure?(.updated(data: update)) }.to(throwAssertion()) } } - context("when can parse applicaion name") { + context("when application name is known at the moment of reading snapshot test result log line") { beforeEach { - fileWatcher.startClosure?(.updated(data: update)) + logReader.readLines = [applicationNameMessageLogLine, kaleidoscopeCommandMesageLogLine, unknownLogLine, referenceImageSavedMessageLogLine] } - it("outputs expected snapshot test resilts") { - expect(receivedSnapshotTestResults).to(equal([failedSnapshotTestResult, recordedSnapshotTestResult])) + context("when can not parse application name") { + beforeEach { + applicationNameExtractor.extractApplicationNameThrows = true + } + + it("outputs expected snapshot test resilts") { + expect { fileWatcher.startClosure?(.updated(data: update)) }.to(throwAssertion()) + } } - it("creates test results with correct build") { - expect(snapshotTestResultFactory.givenBuild.applicationName) == applicationNameExtractor.extractApplicationNameReturnValue - expect(snapshotTestResultFactory.givenBuild.date.timeIntervalSince1970).to(beCloseTo(Date().timeIntervalSince1970, within: 0.01)) + context("when can parse applicaion name") { + beforeEach { + fileWatcher.startClosure?(.updated(data: update)) + } + + it("outputs expected snapshot test resilts") { + expect(receivedSnapshotTestResults).to(equal([failedSnapshotTestResult, recordedSnapshotTestResult])) + } + + it("creates test results with correct build") { + expect(snapshotTestResultFactory.givenBuild.applicationName) == applicationNameExtractor.extractApplicationNameReturnValue + expect(snapshotTestResultFactory.givenBuild.date.timeIntervalSince1970).to(beCloseTo(Date().timeIntervalSince1970, within: 0.01)) + } } } } diff --git a/FBSnapshotsViewerTests/SnapshotTestResultFactorySpec.swift b/FBSnapshotsViewerTests/SnapshotTestResultFactorySpec.swift index 9cb6424..30ba7f7 100644 --- a/FBSnapshotsViewerTests/SnapshotTestResultFactorySpec.swift +++ b/FBSnapshotsViewerTests/SnapshotTestResultFactorySpec.swift @@ -33,6 +33,18 @@ class SnapshotTestResultFactorySpec: QuickSpec { expect(createdTestResult).to(beNil()) } } + + context("when application name log line") { + var createdTestResult: SnapshotTestResult? + + beforeEach { + createdTestResult = factory.createSnapshotTestResult(from: .applicationNameMessage(line: "MyApp"), build: build) + } + + it("doesnt create test result") { + expect(createdTestResult).to(beNil()) + } + } context("when reference image saved message log line") { var createdTestResult: SnapshotTestResult! diff --git a/FBSnapshotsViewerTests/TestResultDisplayInfoSpec.swift b/FBSnapshotsViewerTests/TestResultDisplayInfoSpec.swift index f4c639c..72c30be 100644 --- a/FBSnapshotsViewerTests/TestResultDisplayInfoSpec.swift +++ b/FBSnapshotsViewerTests/TestResultDisplayInfoSpec.swift @@ -12,12 +12,6 @@ import Foundation @testable import FBSnapshotsViewer -class TestResultDisplayInfo_MockDateComponentsFormatter: DateComponentsFormatter { - override func string(from startDate: Date, to endDate: Date) -> String? { - return "10 minutes ago" - } -} - class TestResultDisplayInfo_MockKaleidoscopeViewer: ExternalViewer { static var name: String = "" static var bundleID: String = "" @@ -49,13 +43,8 @@ class TestResultDisplayInfoSpec: QuickSpec { describe(".initWithTestInfo") { var build: Build! var testResult: SnapshotTestResult! - var dateFormatter: TestResultDisplayInfo_MockDateComponentsFormatter! let kaleidoscopeViewer: TestResultDisplayInfo_MockKaleidoscopeViewer.Type = TestResultDisplayInfo_MockKaleidoscopeViewer.self - beforeEach { - dateFormatter = TestResultDisplayInfo_MockDateComponentsFormatter() - } - afterEach { kaleidoscopeViewer.reset() } @@ -146,14 +135,13 @@ class TestResultDisplayInfoSpec: QuickSpec { } it("initializes object correctly") { - let displayInfo = TestResultDisplayInfo(testResult: testResult, kaleidoscopeViewer: kaleidoscopeViewer, dateFormatter: dateFormatter) + let displayInfo = TestResultDisplayInfo(testResult: testResult, kaleidoscopeViewer: kaleidoscopeViewer) expect(displayInfo.diffImageURL).to(equal(URL(fileURLWithPath: "diffImagePath.png"))) expect(displayInfo.referenceImageURL).to(equal(URL(fileURLWithPath: "referenceImagePath.png"))) expect(displayInfo.failedImageURL).to(equal(URL(fileURLWithPath: "failedImagePath.png"))) expect(displayInfo.testName).to(equal("testFailed")) expect(displayInfo.testContext).to(equal("TestClass")) expect(displayInfo.testResult).to(equal(testResult)) - expect(displayInfo.createdAt).to(equal("10 minutes ago")) } } @@ -163,11 +151,10 @@ class TestResultDisplayInfoSpec: QuickSpec { } it("initializes object correctly") { - let displayInfo = TestResultDisplayInfo(testResult: testResult, kaleidoscopeViewer: kaleidoscopeViewer, dateFormatter: dateFormatter) + let displayInfo = TestResultDisplayInfo(testResult: testResult, kaleidoscopeViewer: kaleidoscopeViewer) expect(displayInfo.referenceImageURL).to(equal(URL(fileURLWithPath: "referenceImagePath.png"))) expect(displayInfo.testName).to(equal("testRecord")) expect(displayInfo.testContext).to(equal("ExampleTestClass")) - expect(displayInfo.createdAt).to(equal("10 minutes ago")) } } } diff --git a/FBSnapshotsViewerTests/TestResultsCollectionViewOutletsSpec.swift b/FBSnapshotsViewerTests/TestResultsCollectionViewOutletsSpec.swift index 38c0ea9..ba2e94f 100644 --- a/FBSnapshotsViewerTests/TestResultsCollectionViewOutletsSpec.swift +++ b/FBSnapshotsViewerTests/TestResultsCollectionViewOutletsSpec.swift @@ -15,25 +15,52 @@ class TestResultsCollectionViewOutletsSpec: QuickSpec { override func spec() { var collectionView: NSCollectionView! var collectionViewOutlets: TestResultsCollectionViewOutlets! + var testResultsSections: [TestResultsSectionDisplayInfo] = [] + var testResultsDisplayInfo: TestResultsDisplayInfo! beforeEach { + let build1 = Build(date: Date(), applicationName: "MyApp") + let snapshotTestResult1 = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath.png", diffImagePath: "diffImagePath.png", failedImagePath: "failedImagePath.png", build: build1) + let testResult1 = TestResultDisplayInfo(testResult: snapshotTestResult1) + let sectionTitle1 = TestResultsSectionTitleDisplayInfo(build: build1, testContext: "Context1") + let section1 = TestResultsSectionDisplayInfo(title: sectionTitle1, items: [testResult1]) + + let build2 = Build(date: Date(), applicationName: "MyApp") + let snapshotTestResult2 = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath.png", diffImagePath: "diffImagePath.png", failedImagePath: "failedImagePath.png", build: build2) + let testResult2 = TestResultDisplayInfo(testResult: snapshotTestResult2) + let snapshotTestResult3 = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath.png", diffImagePath: "diffImagePath.png", failedImagePath: "failedImagePath.png", build: build2) + let testResult3 = TestResultDisplayInfo(testResult: snapshotTestResult3) + let sectionTitle2 = TestResultsSectionTitleDisplayInfo(build: build2, testContext: "Context2") + let section2 = TestResultsSectionDisplayInfo(title: sectionTitle2, items: [testResult2, testResult3]) + testResultsSections = [section1, section2] + testResultsDisplayInfo = TestResultsDisplayInfo(sectionInfos: testResultsSections, testResultsDiffMode: .mouseOver) + collectionView = NSCollectionView(frame: NSRect(x: 0, y: 0, width: 200, height: 200)) collectionViewOutlets = TestResultsCollectionViewOutlets(collectionView: collectionView) + collectionViewOutlets.testResultsDisplayInfo = testResultsDisplayInfo } describe(".numberOfSections") { it("returns one") { - expect(collectionViewOutlets.numberOfSections(in: collectionView)).to(equal(1)) + expect(collectionViewOutlets.numberOfSections(in: collectionView)).to(equal(2)) } } describe(".sizeForItemAt") { it("returns correct size") { - let expectedSize = NSSize(width: 530, height: 346) + let expectedSize = NSSize(width: 732, height: 408) let size = collectionViewOutlets.collectionView(collectionView, layout: NSCollectionViewLayout(), sizeForItemAt: IndexPath(item: 0, section: 0)) expect(size).to(equal(expectedSize)) } } + + describe(".referenceSizeForHeaderInSection") { + it("returns correct size") { + let expectedSize = NSSize(width: 732, height: 30) + let size = collectionViewOutlets.collectionView(collectionView, layout: NSCollectionViewLayout(), referenceSizeForHeaderInSection: 0) + expect(size).to(equal(expectedSize)) + } + } describe(".insetForSectionAt") { it("returns correct insets") { @@ -45,20 +72,25 @@ class TestResultsCollectionViewOutletsSpec: QuickSpec { expect(insets.left).to(equal(expectedInsets.left)) } } - - describe(".numberOfItemsInSection") { - var testResults: [TestResultDisplayInfo] = [] - - beforeEach { - let build = Build(date: Date(), applicationName: "MyApp") - let snapshotTestResult = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath.png", diffImagePath: "diffImagePath.png", failedImagePath: "failedImagePath.png", build: build) - let testResult = TestResultDisplayInfo(testResult: snapshotTestResult) - testResults = [testResult] - collectionViewOutlets.testResults = testResults + + describe(".minimumInteritemSpacingForSectionAt") { + it("returns correct spacing") { + let spacing = collectionViewOutlets.collectionView(collectionView, layout: NSCollectionViewLayout(), minimumInteritemSpacingForSectionAt: 0) + expect(spacing).to(equal(0)) + } + } + + describe(".minimumLineSpacingForSectionAt") { + it("returns correct spacing") { + let spacing = collectionViewOutlets.collectionView(collectionView, layout: NSCollectionViewLayout(), minimumLineSpacingForSectionAt: 0) + expect(spacing).to(equal(0)) } + } + describe(".numberOfItemsInSection") { it("returns correct number of rows") { expect(collectionViewOutlets.collectionView(collectionView, numberOfItemsInSection: 0)).to(equal(1)) + expect(collectionViewOutlets.collectionView(collectionView, numberOfItemsInSection: 1)).to(equal(2)) } } } diff --git a/FBSnapshotsViewerTests/TestResultsControllerSpec.swift b/FBSnapshotsViewerTests/TestResultsControllerSpec.swift index 5c6b5dd..ffd152f 100644 --- a/FBSnapshotsViewerTests/TestResultsControllerSpec.swift +++ b/FBSnapshotsViewerTests/TestResultsControllerSpec.swift @@ -24,6 +24,15 @@ class TestResultsController_MockNSCollectionView: NSCollectionView { } } +class TestResultsController_MockTestResultsTopView: TestResultsTopView { + var configureCalled = false + var configureTestResultsDisplayInfo: TestResultsDisplayInfo? + override func configure(with testResultsDisplayInfo: TestResultsDisplayInfo) { + configureTestResultsDisplayInfo = testResultsDisplayInfo + configureCalled = true + } +} + class TestResultsController_MockTestResultsCollectionViewOutlets: TestResultsCollectionViewOutlets {} class TestResultsControllerSpec: QuickSpec { @@ -33,8 +42,12 @@ class TestResultsControllerSpec: QuickSpec { var collectionView: TestResultsController_MockNSCollectionView! var controller: TestResultsController! var eventHandler: TestResultsModuleInterfaceMock! + var testResults: [TestResultsSectionDisplayInfo] = [] + var topView: TestResultsController_MockTestResultsTopView! + var displayInfo: TestResultsDisplayInfo! beforeEach { + topView = TestResultsController_MockTestResultsTopView() collectionView = TestResultsController_MockNSCollectionView(frame: NSRect.zero) collectionViewOutlets = TestResultsController_MockTestResultsCollectionViewOutlets(collectionView: collectionView) eventHandler = TestResultsModuleInterfaceMock() @@ -42,15 +55,18 @@ class TestResultsControllerSpec: QuickSpec { controller.eventHandler = eventHandler controller.collectionViewOutlets = collectionViewOutlets controller.collectionView = collectionView + controller.topView = topView + + let testResultDisplayInfo = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "Bla", referenceImagePath: "foo/bar.png", build: build)) + let titleInfo = TestResultsSectionTitleDisplayInfo(build: build, testContext: "Foo") + let sectionInfo = TestResultsSectionDisplayInfo(title: titleInfo, items: [testResultDisplayInfo]) + testResults = [sectionInfo] + displayInfo = TestResultsDisplayInfo(sectionInfos: testResults, testResultsDiffMode: .mouseOver) } describe(".show") { - var testResults: [TestResultDisplayInfo]! - beforeEach { - let testResultDisplayInfo = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "Bla", referenceImagePath: "foo/bar.png", build: build)) - testResults = [testResultDisplayInfo] - controller.show(testResults: testResults) + controller.show(displayInfo: displayInfo) } it("reloads collection view") { @@ -58,7 +74,12 @@ class TestResultsControllerSpec: QuickSpec { } it("shows test results") { - expect(collectionViewOutlets.testResults).to(equal(testResults)) + expect(collectionViewOutlets.testResultsDisplayInfo).to(equal(displayInfo)) + } + + it("configures top view") { + expect(topView.configureCalled).to(beTrue()) + expect(topView.configureTestResultsDisplayInfo).to(equal(displayInfo)) } } @@ -71,6 +92,16 @@ class TestResultsControllerSpec: QuickSpec { expect(eventHandler.updateUserInterfaceCalled).to(beTrue()) } } + + describe(".testResultsTopView:didSelect") { + beforeEach { + controller.testResultsTopView(topView, didSelect: TestResultsDiffMode.diff) + } + + it("selects diff mode") { + expect(eventHandler.selectDiffModeReceivedDiffMode).to(equal(TestResultsDiffMode.diff)) + } + } describe(".testResultCell:viewInKaleidoscopeButtonClicked") { var cell: TestResultCell! @@ -93,8 +124,7 @@ class TestResultsControllerSpec: QuickSpec { context("when test result is not presented in controller") { beforeEach { - let testResultDisplayInfo = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "Bla", referenceImagePath: "foo/bar.png", build: build)) - collectionViewOutlets.testResults = [testResultDisplayInfo] + collectionViewOutlets.testResultsDisplayInfo = displayInfo collectionView.indexPathForItemReturnValue = IndexPath(item: 1, section: 0) } @@ -104,18 +134,15 @@ class TestResultsControllerSpec: QuickSpec { } context("when test result is presented and cell is visible") { - var testResultDisplayInfo: TestResultDisplayInfo! - beforeEach { - testResultDisplayInfo = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "Bla", referenceImagePath: "foo/bar.png", build: build)) - collectionViewOutlets.testResults = [testResultDisplayInfo] + collectionViewOutlets.testResultsDisplayInfo = displayInfo collectionView.indexPathForItemReturnValue = IndexPath(item: 0, section: 0) controller.testResultCell(cell, viewInKaleidoscopeButtonClicked: viewInKaleidoscopeButton) } it("opens test result in kaleidoscope") { expect(eventHandler.openInKaleidoscopeCalled).to(beTrue()) - expect(eventHandler.openInKaleidoscopeReceivedTestResultDisplayInfo).to(equal(testResultDisplayInfo)) + expect(eventHandler.openInKaleidoscopeReceivedTestResultDisplayInfo).to(equal(testResults[0].itemInfos[0])) } } } diff --git a/FBSnapshotsViewerTests/TestResultsDisplayInfoSpec.swift b/FBSnapshotsViewerTests/TestResultsDisplayInfoSpec.swift new file mode 100644 index 0000000..a4d2ba3 --- /dev/null +++ b/FBSnapshotsViewerTests/TestResultsDisplayInfoSpec.swift @@ -0,0 +1,63 @@ +// +// TestResultsDisplayInfoSpec.swift +// FBSnapshotsViewerTests +// +// Created by Anton Domashnev on 18.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Quick +import Nimble + +@testable import FBSnapshotsViewer + +class TestResultsDisplayInfoSpec: QuickSpec { + override func spec() { + var displayInfo: TestResultsDisplayInfo! + var sectionInfos: [TestResultsSectionDisplayInfo] = [] + + describe(".init") { + beforeEach { + let build1 = Build(applicationName: "FBSnapshotsViewer") + let build2 = Build(applicationName: "AmazingWeather") + let build3 = Build(applicationName: "FBSnapshotsViewer") + let testResultInfo1 = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "foo_name", referenceImagePath: "foo/bar.png", build: build1)) + let testResultInfo2 = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "foo_name", referenceImagePath: "foo/bar.png", build: build2)) + let testResultInfo3 = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "foo_name", referenceImagePath: "foo/bar.png", build: build3)) + let testResultInfo4 = TestResultDisplayInfo(testResult: SnapshotTestResult.recorded(testName: "foo_name", referenceImagePath: "foo/bar.png", build: build3)) + let sectionTitleInfo1 = TestResultsSectionTitleDisplayInfo(build: build1, testContext: "context1") + let sectionInfo1 = TestResultsSectionDisplayInfo(title: sectionTitleInfo1, items: [testResultInfo1]) + let sectionTitleInfo2 = TestResultsSectionTitleDisplayInfo(build: build2, testContext: "context2") + let sectionInfo2 = TestResultsSectionDisplayInfo(title: sectionTitleInfo2, items: [testResultInfo2]) + let sectionTitleInfo3 = TestResultsSectionTitleDisplayInfo(build: build3, testContext: "context3") + let sectionInfo3 = TestResultsSectionDisplayInfo(title: sectionTitleInfo3, items: [testResultInfo3, testResultInfo4]) + sectionInfos = [sectionInfo1, sectionInfo2, sectionInfo3] + displayInfo = TestResultsDisplayInfo(sectionInfos: sectionInfos, testResultsDiffMode: .mouseOver) + } + + it("has correct section infos") { + expect(displayInfo.sectionInfos).to(equal(sectionInfos)) + } + + context("when one test result") { + beforeEach { + displayInfo = TestResultsDisplayInfo(sectionInfos: [sectionInfos[0]], testResultsDiffMode: .mouseOver) + } + + it("has correct topTitle") { + expect(displayInfo.topTitle).to(equal("1 Test Result")) + } + } + + context("when multiple test results") { + it("has correct topTitle") { + expect(displayInfo.topTitle).to(equal("4 Test Results")) + } + } + + it("has correct testResultsDiffMode") { + expect(displayInfo.testResultsDiffMode).to(equal(TestResultsDiffMode.mouseOver)) + } + } + } +} diff --git a/FBSnapshotsViewerTests/TestResultsDisplayInfosCollectorSpec.swift b/FBSnapshotsViewerTests/TestResultsDisplayInfosCollectorSpec.swift new file mode 100644 index 0000000..3e8dc9a --- /dev/null +++ b/FBSnapshotsViewerTests/TestResultsDisplayInfosCollectorSpec.swift @@ -0,0 +1,79 @@ +// +// TestResultsDisplayInfosCollectorSpec.swift +// FBSnapshotsViewerTests +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Quick +import Nimble + +@testable import FBSnapshotsViewer + +class TestResultsDisplayInfosCollectorSpec: QuickSpec { + override func spec() { + var subject: TestResultsDisplayInfosCollector! + var testResults: [SnapshotTestResult] = [] + let build1 = Build(date: Date(timeIntervalSinceNow: -300), applicationName: "FBSnapshotsViewer") + let build2 = Build(date: Date(timeIntervalSinceNow: -500), applicationName: "AmazingWeather") + let build3 = Build(date: Date(timeIntervalSinceNow: -700), applicationName: "FBSnapshotsViewer") + + beforeEach { + subject = TestResultsDisplayInfosCollector() + } + + describe(".collect") { + var collectedInfos: [TestResultsSectionDisplayInfo] = [] + + beforeEach { + let testResult1 = SnapshotTestResult.recorded(testName: "MainScreen basicState", referenceImagePath: "foo/bar/basic_state.png", build: build1) + let testResult2 = SnapshotTestResult.recorded(testName: "DetailScreen basicState", referenceImagePath: "foo/bar/detail_state.png", build: build1) + let testResult3 = SnapshotTestResult.failed(testName: "DetailScreen emptyState", referenceImagePath: "foo/bar/empty_state.png", diffImagePath: "foo/bar/diff_empty_state.png", failedImagePath: "foo/bar/failed_empty_state.png", build: build1) + let testResult4 = SnapshotTestResult.recorded(testName: "DetailScreen errorState", referenceImagePath: "foo/bar/error_state.png", build: build1) + let testResult5 = SnapshotTestResult.failed(testName: "MainScreen errorState", referenceImagePath: "foo/bar/error_state.png", diffImagePath: "foo/bar/diff_error_state.png", failedImagePath: "foo/bar/failed_error_state.png", build: build1) + let testResult6 = SnapshotTestResult.failed(testName: "HomeScreen emptyState", referenceImagePath: "foo/bar/empty_state.png", diffImagePath: "foo/bar/diff_empty_state.png", failedImagePath: "foo/bar/failed_empty_state.png", build: build2) + let testResult7 = SnapshotTestResult.recorded(testName: "HomeScreen errorState", referenceImagePath: "foo/bar/error_state.png", build: build2) + let testResult8 = SnapshotTestResult.failed(testName: "SettingsScreen errorState", referenceImagePath: "foo/bar/error_state.png", diffImagePath: "foo/bar/diff_error_state.png", failedImagePath: "foo/bar/failed_error_state.png", build: build2) + let testResult9 = SnapshotTestResult.recorded(testName: "MainScreen basicState", referenceImagePath: "foo/bar/basic_state.png", build: build3) + let testResult10 = SnapshotTestResult.recorded(testName: "DetailScreen basicState", referenceImagePath: "foo/bar/detail_state.png", build: build3) + testResults = [testResult1, testResult2, testResult3, testResult4, testResult5, testResult6, testResult7, testResult8, testResult9, testResult10] + collectedInfos = subject.collect(testResults: testResults) + } + + it("collects the correct display infos") { + expect(collectedInfos.count).to(equal(6)) + + let section1 = collectedInfos[0] + let exepctedSection1Title = TestResultsSectionTitleDisplayInfo(title: "FBSnapshotsViewer | MainScreen", timeAgo: "About 5 minutes ago", timeAgoDate: build1.date) + expect(section1.itemInfos.count).to(equal(2)) + expect(section1.titleInfo).to(equal(exepctedSection1Title)) + + let section2 = collectedInfos[1] + let exepctedSection2Title = TestResultsSectionTitleDisplayInfo(title: "FBSnapshotsViewer | DetailScreen", timeAgo: "About 5 minutes ago", timeAgoDate: build1.date) + expect(section2.itemInfos.count).to(equal(3)) + expect(section2.titleInfo).to(equal(exepctedSection2Title)) + + let section3 = collectedInfos[2] + let exepctedSection3Title = TestResultsSectionTitleDisplayInfo(title: "AmazingWeather | SettingsScreen", timeAgo: "About 8 minutes ago", timeAgoDate: build2.date) + expect(section3.itemInfos.count).to(equal(1)) + expect(section3.titleInfo).to(equal(exepctedSection3Title)) + + let section4 = collectedInfos[3] + let exepctedSection4Title = TestResultsSectionTitleDisplayInfo(title: "AmazingWeather | HomeScreen", timeAgo: "About 8 minutes ago", timeAgoDate: build2.date) + expect(section4.itemInfos.count).to(equal(2)) + expect(section4.titleInfo).to(equal(exepctedSection4Title)) + + let section5 = collectedInfos[4] + let exepctedSection5Title = TestResultsSectionTitleDisplayInfo(title: "FBSnapshotsViewer | MainScreen", timeAgo: "About 12 minutes ago", timeAgoDate: build3.date) + expect(section5.itemInfos.count).to(equal(1)) + expect(section5.titleInfo).to(equal(exepctedSection5Title)) + + let section6 = collectedInfos[5] + let exepctedSection6Title = TestResultsSectionTitleDisplayInfo(title: "FBSnapshotsViewer | DetailScreen", timeAgo: "About 12 minutes ago", timeAgoDate: build3.date) + expect(section6.itemInfos.count).to(equal(1)) + expect(section6.titleInfo).to(equal(exepctedSection6Title)) + } + } + } +} diff --git a/FBSnapshotsViewerTests/TestResultsPresenterSpec.swift b/FBSnapshotsViewerTests/TestResultsPresenterSpec.swift index ad1819c..bc05bc2 100644 --- a/FBSnapshotsViewerTests/TestResultsPresenterSpec.swift +++ b/FBSnapshotsViewerTests/TestResultsPresenterSpec.swift @@ -11,16 +11,27 @@ import Nimble @testable import FBSnapshotsViewer +class TestResultsPresenter_MockTestResultsDisplayInfosCollector: TestResultsDisplayInfosCollector { + var collectedTestResults: [TestResultsSectionDisplayInfo] = [] + var collectCalledTestResults: [SnapshotTestResult] = [] + override func collect(testResults: [SnapshotTestResult]) -> [TestResultsSectionDisplayInfo] { + collectCalledTestResults = testResults + return collectedTestResults + } +} + class TestResultsPresenterSpec: QuickSpec { override func spec() { var presenter: TestResultsPresenter! var interactor: TestResultsInteractorInputMock! var userInterface: TestResultsUserInterfaceMock! + var testResultsCollector: TestResultsPresenter_MockTestResultsDisplayInfosCollector! beforeEach { + testResultsCollector = TestResultsPresenter_MockTestResultsDisplayInfosCollector() interactor = TestResultsInteractorInputMock() userInterface = TestResultsUserInterfaceMock() - presenter = TestResultsPresenter() + presenter = TestResultsPresenter(testResultsCollector: testResultsCollector) presenter.interactor = interactor presenter.userInterface = userInterface } @@ -55,13 +66,18 @@ class TestResultsPresenterSpec: QuickSpec { } context("when interactor has test results") { + var expectTestResultsDisplayInfo: TestResultsDisplayInfo! var testResults: [SnapshotTestResult] = [] - var build: Build! beforeEach { - build = Build(applicationName: "FBSnapshotsViewer") - testResults = [SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath", diffImagePath: "diffImagePath", failedImagePath: "failedImagePath", build: build)] + let build = Build(applicationName: "FBSnapshotsViewer") + let snapshotTestResult = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath", diffImagePath: "diffImagePath", failedImagePath: "failedImagePath", build: build) + let titleInfo = TestResultsSectionTitleDisplayInfo(build: build, testContext: "Context") + let sectionInfo = TestResultsSectionDisplayInfo(title: titleInfo, items: [TestResultDisplayInfo(testResult: snapshotTestResult)]) + testResults = [snapshotTestResult] interactor.testResults = testResults + testResultsCollector.collectedTestResults = [sectionInfo, sectionInfo] + expectTestResultsDisplayInfo = TestResultsDisplayInfo(sectionInfos: testResultsCollector.collectedTestResults, testResultsDiffMode: .mouseOver) presenter.updateUserInterface() } @@ -70,9 +86,30 @@ class TestResultsPresenterSpec: QuickSpec { } it("shows correct test results in user interface") { - expect(userInterface.showReceivedTestResults?.count).to(equal(1)) + expect(userInterface.showReceivedDisplayInfo).to(equal(expectTestResultsDisplayInfo)) + } + + it("uses collector") { + expect(testResultsCollector.collectCalledTestResults).to(equal(testResults)) } } } + + describe(".selectDiffMode") { + var testResults: [SnapshotTestResult] = [] + + beforeEach { + let build = Build(applicationName: "FBSnapshotsViewer") + let snapshotTestResult = SnapshotTestResult.failed(testName: "testName", referenceImagePath: "referenceImagePath", diffImagePath: "diffImagePath", failedImagePath: "failedImagePath", build: build) + testResults = [snapshotTestResult] + interactor.testResults = testResults + presenter.selectDiffMode(TestResultsDiffMode.diff) + } + + it("updates user interface") { + expect(userInterface.showCalled).to(beTrue()) + expect(userInterface.showReceivedDisplayInfo?.testResultsDiffMode).to(equal(TestResultsDiffMode.diff)) + } + } } } diff --git a/FBSnapshotsViewerTests/TestResultsSectionDisplayInfoSpec.swift b/FBSnapshotsViewerTests/TestResultsSectionDisplayInfoSpec.swift new file mode 100644 index 0000000..10143e8 --- /dev/null +++ b/FBSnapshotsViewerTests/TestResultsSectionDisplayInfoSpec.swift @@ -0,0 +1,36 @@ +// +// TestResultsSectionDisplayInfoSpec.swift +// FBSnapshotsViewerTests +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Quick +import Nimble + +@testable import FBSnapshotsViewer + +class TestResultsSectionDisplayInfoSpec: QuickSpec { + override func spec() { + var displayInfo: TestResultsSectionDisplayInfo! + + describe(".init") { + var items: [TestResultDisplayInfo] = [] + var title: TestResultsSectionTitleDisplayInfo! + + beforeEach { + let build = Build(date: Date(), applicationName: "MyApp") + let testResult = SnapshotTestResult.recorded(testName: "TestName", referenceImagePath: "foo/bar.png", build: build) + title = TestResultsSectionTitleDisplayInfo(build: build, testContext: "TestContext") + items = [TestResultDisplayInfo(testResult: testResult)] + displayInfo = TestResultsSectionDisplayInfo(title: title, items: items) + } + + it("initializes all properties") { + expect(displayInfo.itemInfos).to(equal(items)) + expect(displayInfo.titleInfo).to(equal(title)) + } + } + } +} diff --git a/FBSnapshotsViewerTests/TestResultsSectionTitleDisplayInfoSpec.swift b/FBSnapshotsViewerTests/TestResultsSectionTitleDisplayInfoSpec.swift new file mode 100644 index 0000000..1172378 --- /dev/null +++ b/FBSnapshotsViewerTests/TestResultsSectionTitleDisplayInfoSpec.swift @@ -0,0 +1,64 @@ +// +// TestResultsSectionTitleDisplayInfoSpec.swift +// FBSnapshotsViewerTests +// +// Created by Anton Domashnev on 11.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Quick +import Nimble + +@testable import FBSnapshotsViewer + +class TestResultsSectionTitleDisplayInfo_MockDateComponentsFormatter: DateComponentsFormatter { + var formattedValue: String? + override func string(from startDate: Date, to endDate: Date) -> String? { + return formattedValue + } +} + +class TestResultsSectionTitleDisplayInfoSpec: QuickSpec { + override func spec() { + var build: Build! + var dateFormatter: TestResultsSectionTitleDisplayInfo_MockDateComponentsFormatter! + var displayInfo: TestResultsSectionTitleDisplayInfo! + + beforeEach { + build = Build(date: Date(), applicationName: "FBSnapshotsViewer") + dateFormatter = TestResultsSectionTitleDisplayInfo_MockDateComponentsFormatter() + } + + describe(".init") { + beforeEach { + displayInfo = TestResultsSectionTitleDisplayInfo(build: build, testContext: "TestContext", dateFormatter: dateFormatter) + } + + it("initializes correct title") { + expect(displayInfo.title).to(equal("FBSnapshotsViewer | TestContext")) + } + + context("when date can be formatter") { + beforeEach { + dateFormatter.formattedValue = "10 minutes" + displayInfo = TestResultsSectionTitleDisplayInfo(build: build, testContext: "TestContext", dateFormatter: dateFormatter) + } + + it("initializes correct timeAgo") { + expect(displayInfo.timeAgo).to(equal("10 minutes ago")) + } + } + + context("when date can not be formatter") { + beforeEach { + dateFormatter.formattedValue = nil + displayInfo = TestResultsSectionTitleDisplayInfo(build: build, testContext: "TestContext", dateFormatter: dateFormatter) + } + + it("initializes correct timeAgo") { + expect(displayInfo.timeAgo).to(equal("Just now")) + } + } + } + } +} diff --git a/Vendor/Sourcery/CodeGenerated/AutoEquatable.generated.swift b/Vendor/Sourcery/CodeGenerated/AutoEquatable.generated.swift index 9e13bee..a98aa84 100644 --- a/Vendor/Sourcery/CodeGenerated/AutoEquatable.generated.swift +++ b/Vendor/Sourcery/CodeGenerated/AutoEquatable.generated.swift @@ -40,8 +40,29 @@ internal func == (lhs: TestResultDisplayInfo, rhs: TestResultDisplayInfo) -> Boo guard lhs.testContext == rhs.testContext else { return false } guard lhs.canBeViewedInKaleidoscope == rhs.canBeViewedInKaleidoscope else { return false } guard lhs.testResult == rhs.testResult else { return false } - guard lhs.createdAt == rhs.createdAt else { return false } - guard lhs.applicationName == rhs.applicationName else { return false } + return true +} +// MARK: - TestResultsDisplayInfo AutoEquatable +extension TestResultsDisplayInfo: Equatable {} +internal func == (lhs: TestResultsDisplayInfo, rhs: TestResultsDisplayInfo) -> Bool { + guard lhs.sectionInfos == rhs.sectionInfos else { return false } + guard lhs.topTitle == rhs.topTitle else { return false } + guard lhs.testResultsDiffMode == rhs.testResultsDiffMode else { return false } + return true +} +// MARK: - TestResultsSectionDisplayInfo AutoEquatable +extension TestResultsSectionDisplayInfo: Equatable {} +internal func == (lhs: TestResultsSectionDisplayInfo, rhs: TestResultsSectionDisplayInfo) -> Bool { + guard lhs.titleInfo == rhs.titleInfo else { return false } + guard lhs.itemInfos == rhs.itemInfos else { return false } + return true +} +// MARK: - TestResultsSectionTitleDisplayInfo AutoEquatable +extension TestResultsSectionTitleDisplayInfo: Equatable {} +internal func == (lhs: TestResultsSectionTitleDisplayInfo, rhs: TestResultsSectionTitleDisplayInfo) -> Bool { + guard lhs.title == rhs.title else { return false } + guard lhs.timeAgo == rhs.timeAgo else { return false } + guard lhs.timeAgoDate == rhs.timeAgoDate else { return false } return true } @@ -134,5 +155,16 @@ internal func == (lhs: SnapshotTestResult, rhs: SnapshotTestResult) -> Bool { default: return false } } +// MARK: - TestResultsDiffMode AutoEquatable +extension TestResultsDiffMode: Equatable {} +internal func == (lhs: TestResultsDiffMode, rhs: TestResultsDiffMode) -> Bool { + switch (lhs, rhs) { + case (.diff, .diff): + return true + case (.mouseOver, .mouseOver): + return true + default: return false + } +} // MARK: - diff --git a/Vendor/Sourcery/CodeGenerated/AutoHashable.generated.swift b/Vendor/Sourcery/CodeGenerated/AutoHashable.generated.swift index 584bbdb..b41440e 100644 --- a/Vendor/Sourcery/CodeGenerated/AutoHashable.generated.swift +++ b/Vendor/Sourcery/CodeGenerated/AutoHashable.generated.swift @@ -26,6 +26,12 @@ extension Build: Hashable { return combineHashes([date.hashValue, applicationName.hashValue, 0]) } } +// MARK: - TestResultsSectionTitleDisplayInfo AutoHashable +extension TestResultsSectionTitleDisplayInfo: Hashable { + internal var hashValue: Int { + return combineHashes([title.hashValue, timeAgo.hashValue, timeAgoDate.hashValue, 0]) + } +} // MARK: - AutoHashable for Enums diff --git a/Vendor/Sourcery/CodeGenerated/AutoMockable.generated.swift b/Vendor/Sourcery/CodeGenerated/AutoMockable.generated.swift index 7f1cd9e..0bf9c4e 100644 --- a/Vendor/Sourcery/CodeGenerated/AutoMockable.generated.swift +++ b/Vendor/Sourcery/CodeGenerated/AutoMockable.generated.swift @@ -388,6 +388,16 @@ class TestResultsModuleInterfaceMock: TestResultsModuleInterface { openInKaleidoscopeCalled = true openInKaleidoscopeReceivedTestResultDisplayInfo = testResultDisplayInfo } + //MARK: - selectDiffMode + + var selectDiffModeCalled = false + var selectDiffModeReceivedDiffMode: TestResultsDiffMode? + + func selectDiffMode(_ diffMode: TestResultsDiffMode) { + + selectDiffModeCalled = true + selectDiffModeReceivedDiffMode = diffMode + } } class TestResultsUserInterfaceMock: TestResultsUserInterface { @@ -395,12 +405,12 @@ class TestResultsUserInterfaceMock: TestResultsUserInterface { //MARK: - show var showCalled = false - var showReceivedTestResults: [TestResultDisplayInfo]? + var showReceivedDisplayInfo: TestResultsDisplayInfo? - func show(testResults: [TestResultDisplayInfo]) { + func show(displayInfo: TestResultsDisplayInfo) { showCalled = true - showReceivedTestResults = testResults + showReceivedDisplayInfo = displayInfo } } class UpdaterMock: Updater { diff --git a/Vendor/SwiftGen/Colors+AppleInterfaceMode.swift b/Vendor/SwiftGen/Colors+AppleInterfaceMode.swift new file mode 100644 index 0000000..d26a3ce --- /dev/null +++ b/Vendor/SwiftGen/Colors+AppleInterfaceMode.swift @@ -0,0 +1,38 @@ +// +// Colors+AppleInterfaceMode.swift +// FBSnapshotsViewer +// +// Created by Anton Domashnev on 21.06.17. +// Copyright © 2017 Anton Domashnev. All rights reserved. +// + +import Foundation + +extension Color { + static func divider(for appleInterfaceMode: AppleInterfaceMode) -> Color { + switch appleInterfaceMode { + case .dark: + return Color(named: .dividerDarkMode) + case .light: + return Color(named: .dividerLightMode) + } + } + + static func primaryText(for appleInterfaceMode: AppleInterfaceMode) -> Color { + switch appleInterfaceMode { + case .dark: + return Color(named: .primaryTextDarkMode) + case .light: + return Color(named: .primaryTextLightMode) + } + } + + static func secondaryText(for appleInterfaceMode: AppleInterfaceMode) -> Color { + switch appleInterfaceMode { + case .dark: + return Color(named: .secondaryTextDarkMode) + case .light: + return Color(named: .secondaryTextLightMode) + } + } +} diff --git a/Vendor/SwiftGen/Colors.swift b/Vendor/SwiftGen/Colors.swift index c4a1f43..54c2aab 100644 --- a/Vendor/SwiftGen/Colors.swift +++ b/Vendor/SwiftGen/Colors.swift @@ -1,4 +1,4 @@ -// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen +// Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen #if os(iOS) || os(tvOS) || os(watchOS) import UIKit.UIColor @@ -8,6 +8,7 @@ typealias Color = NSColor #endif +// swiftlint:disable operator_usage_whitespace extension Color { convenience init(rgbaValue: UInt32) { let red = CGFloat((rgbaValue >> 24) & 0xff) / 255.0 @@ -18,6 +19,7 @@ extension Color { self.init(red: red, green: green, blue: blue, alpha: alpha) } } +// swiftlint:enable operator_usage_whitespace // swiftlint:disable file_length // swiftlint:disable line_length @@ -43,23 +45,29 @@ enum ColorName { /// Alpha: 100%
(0xbdbdbdff) case dividerLightMode /// - /// Alpha: 100%
(0xeceff1ff) + /// Alpha: 87%
(0xeceff1de) case primaryLightDarkMode /// - /// Alpha: 100%
(0xcfd8dcff) + /// Alpha: 87%
(0xcfd8dcde) case primaryLightLightMode /// - /// Alpha: 86%
(0xffffffdd) + /// Alpha: 87%
(0xffffffde) case primaryTextDarkMode - /// - /// Alpha: 86%
(0x212121dd) + /// + /// Alpha: 87%
(0x000000de) case primaryTextLightMode /// - /// Alpha: 54%
(0xffffff8a) + /// Alpha: 87%
(0xffffffde) case secondaryTextDarkMode /// - /// Alpha: 54%
(0x7575758a) + /// Alpha: 87%
(0x757575de) case secondaryTextLightMode + /// + /// Alpha: 100%
(0xd1d0d1ff) + case testResultsTopViewColorBottom + /// + /// Alpha: 100%
(0xe7e6e7ff) + case testResultsTopViewColorTop var rgbaValue: UInt32 { switch self { @@ -76,17 +84,21 @@ enum ColorName { case .dividerLightMode: return 0xbdbdbdff case .primaryLightDarkMode: - return 0xeceff1ff + return 0xeceff1de case .primaryLightLightMode: - return 0xcfd8dcff + return 0xcfd8dcde case .primaryTextDarkMode: - return 0xffffffdd + return 0xffffffde case .primaryTextLightMode: - return 0x212121dd + return 0x000000de case .secondaryTextDarkMode: - return 0xffffff8a + return 0xffffffde case .secondaryTextLightMode: - return 0x7575758a + return 0x757575de + case .testResultsTopViewColorBottom: + return 0xd1d0d1ff + case .testResultsTopViewColorTop: + return 0xe7e6e7ff } } @@ -101,3 +113,5 @@ extension Color { self.init(rgbaValue: name.rgbaValue) } } +// swiftlint:enable file_length +// swiftlint:enable line_length diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 29c0b0e..a77d5e5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -13,6 +13,12 @@ platform :mac do bundle_install end + desc "Generate colors with SwiftGen" + lane :generate_colors do + IO.popen("pbcopy", "w") { |pipe| pipe.puts `../Pods/SwiftGen/bin/swiftgen colors -t swift3 ../FBSnapshotsViewer/ColorsPalette.txt` } + UI.message "Generated code is copied in your pasteboard" + end + desc "Sanity check for the fastfile issues" lane :sanity_check do UI.message "Compiling Fastfile to check if the syntax if valid or not" diff --git a/fastlane/README.md b/fastlane/README.md index 7215a2c..5bbf711 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -30,6 +30,11 @@ xcode-select --install # Available Actions ## Mac +### mac generate_colors +``` +fastlane mac generate_colors +``` +Generate colors with SwiftGen ### mac sanity_check ``` fastlane mac sanity_check