From b2f16d5c6fb72dddf084c87c0a26b7925c83c312 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Sun, 15 Mar 2020 17:40:04 -0700 Subject: [PATCH] Add VerifySortingOptions UI Tests --- .../Constants/SortingConstants.cs | 2 + .../Models/Interfaces/IRepository.cs | 8 ++- GitTrends.Shared/Models/Repository.cs | 3 +- GitTrends.UITests/Models/Repository.cs | 8 +++ GitTrends.UITests/Pages/RepositoryPage.cs | 18 +++++- GitTrends.UITests/Tests/RepositoriesTests.cs | 63 ++++++++++++++++++- GitTrends/Database/RepositoryDatabase.cs | 14 ++++- GitTrends/Pages/RepositoryPage.cs | 2 +- GitTrends/Services/SortingService.cs | 2 +- GitTrends/ViewModels/RepositoryViewModel.cs | 39 +++++++----- .../RepositoryDataTemplateSelector.cs | 38 +++++++++-- 11 files changed, 166 insertions(+), 31 deletions(-) diff --git a/GitTrends.Shared/Constants/SortingConstants.cs b/GitTrends.Shared/Constants/SortingConstants.cs index 717d93f6b..61ba5628e 100644 --- a/GitTrends.Shared/Constants/SortingConstants.cs +++ b/GitTrends.Shared/Constants/SortingConstants.cs @@ -5,6 +5,8 @@ namespace GitTrends.Shared { class SortingConstants { + public const SortingOption DefaultSortingOption = SortingOption.Stars; + readonly static Lazy> _sortingOptionsDictionaryHolder = new Lazy>(CreateSortingDictionary); public static Dictionary SortingOptionsDictionary => _sortingOptionsDictionaryHolder.Value; diff --git a/GitTrends.Shared/Models/Interfaces/IRepository.cs b/GitTrends.Shared/Models/Interfaces/IRepository.cs index c86594875..c7159a30a 100644 --- a/GitTrends.Shared/Models/Interfaces/IRepository.cs +++ b/GitTrends.Shared/Models/Interfaces/IRepository.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace GitTrends.Shared +namespace GitTrends.Shared { interface IRepository { @@ -12,5 +10,9 @@ interface IRepository string Description { get; } long ForkCount { get; } string Url { get; } + public long TotalViews { get; } + public long TotalUniqueViews { get; } + public long TotalClones { get; } + public long TotalUniqueClones { get; } } } diff --git a/GitTrends.Shared/Models/Repository.cs b/GitTrends.Shared/Models/Repository.cs index 24950cbbd..4b6c45790 100644 --- a/GitTrends.Shared/Models/Repository.cs +++ b/GitTrends.Shared/Models/Repository.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json; diff --git a/GitTrends.UITests/Models/Repository.cs b/GitTrends.UITests/Models/Repository.cs index 75f488099..068418ec3 100644 --- a/GitTrends.UITests/Models/Repository.cs +++ b/GitTrends.UITests/Models/Repository.cs @@ -19,5 +19,13 @@ public class Repository : IRepository public string OwnerAvatarUrl { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; + + public long TotalViews { get; set; } + + public long TotalUniqueViews { get; set; } + + public long TotalClones { get; set; } + + public long TotalUniqueClones { get; set; } } } diff --git a/GitTrends.UITests/Pages/RepositoryPage.cs b/GitTrends.UITests/Pages/RepositoryPage.cs index acad3623a..a716f0b86 100644 --- a/GitTrends.UITests/Pages/RepositoryPage.cs +++ b/GitTrends.UITests/Pages/RepositoryPage.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using GitTrends.Mobile.Shared; +using GitTrends.Shared; using Newtonsoft.Json; using Xamarin.UITest; using Query = System.Func; @@ -10,12 +11,13 @@ namespace GitTrends.UITests class RepositoryPage : BasePage { readonly Query _searchBar, _settingsButton, _collectionView, _refreshView, - _androidContextMenuOverflowButton, _androidSearchBarButton; + _androidContextMenuOverflowButton, _androidSearchBarButton, _sortButton; public RepositoryPage(IApp app) : base(app, PageTitles.RepositoryPage) { _searchBar = GenerateMarkedQuery(RepositoryPageAutomationIds.SearchBar); _settingsButton = GenerateMarkedQuery(RepositoryPageAutomationIds.SettingsButton); + _sortButton = GenerateMarkedQuery(RepositoryPageAutomationIds.SortButton); _collectionView = GenerateMarkedQuery(RepositoryPageAutomationIds.CollectionView); _refreshView = GenerateMarkedQuery(RepositoryPageAutomationIds.RefreshView); _androidContextMenuOverflowButton = x => x.Class("androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton"); @@ -24,6 +26,20 @@ public RepositoryPage(IApp app) : base(app, PageTitles.RepositoryPage) public void TriggerPullToRefresh() => App.InvokeBackdoorMethod(BackdoorMethodConstants.TriggerPullToRefresh); + public void SetSortingOption(SortingOption sortingOption) + { + if (App.Query(_androidContextMenuOverflowButton).Any()) + { + App.Tap(_androidContextMenuOverflowButton); + App.Screenshot("Tapped Android Search Bar Button"); + } + + App.Tap(_sortButton); + App.Screenshot("Sort Button Tapped"); + + App.Tap(SortingConstants.SortingOptionsDictionary[sortingOption]); + } + public void TapRepository(string repositoryName) { App.ScrollDownTo(repositoryName); diff --git a/GitTrends.UITests/Tests/RepositoriesTests.cs b/GitTrends.UITests/Tests/RepositoriesTests.cs index 8407e1f68..3ada5fcfe 100644 --- a/GitTrends.UITests/Tests/RepositoriesTests.cs +++ b/GitTrends.UITests/Tests/RepositoriesTests.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using GitTrends.Mobile.Shared; +using GitTrends.Shared; using NUnit.Framework; using Xamarin.UITest; @@ -17,6 +19,65 @@ public RepositoriesTests(Platform platform, UserType userType) : base(platform, { } + [TestCase(SortingConstants.DefaultSortingOption)] + [TestCase(SortingOption.Clones)] + [TestCase(SortingOption.Forks)] + [TestCase(SortingOption.Issues)] + [TestCase(SortingOption.Stars)] + [TestCase(SortingOption.UniqueClones)] + [TestCase(SortingOption.UniqueViews)] + [TestCase(SortingOption.Views)] + public void VerifySortingOptions(SortingOption sortingOption) + { + //Arrange + Repository finalTopRepository; + Repository finalSecondTopRepository; + Repository initialTopRepository = RepositoryPage.GetVisibleRepositoryList().First(); + Repository initialSecondTopRepository = RepositoryPage.GetVisibleRepositoryList().Skip(1).First(); + + //Act + RepositoryPage.SetSortingOption(sortingOption); + + //Assert + finalTopRepository = RepositoryPage.GetVisibleRepositoryList().First(); + finalSecondTopRepository = RepositoryPage.GetVisibleRepositoryList().Skip(1).First(); + + Assert.GreaterOrEqual(initialTopRepository.StarCount, initialSecondTopRepository.StarCount); + + Assert.AreNotEqual(initialTopRepository, finalTopRepository); + Assert.AreNotEqual(initialSecondTopRepository, finalSecondTopRepository); + Assert.AreNotEqual(initialTopRepository, initialSecondTopRepository); + Assert.AreNotEqual(finalTopRepository, finalSecondTopRepository); + + switch (sortingOption) + { + case SortingOption.Stars: + Assert.LessOrEqual(finalTopRepository.StarCount, finalSecondTopRepository.StarCount); + break; + case SortingOption.Clones: + Assert.GreaterOrEqual(finalTopRepository.TotalClones, finalSecondTopRepository.TotalClones); + break; + case SortingOption.Forks: + Assert.GreaterOrEqual(finalTopRepository.ForkCount, finalSecondTopRepository.ForkCount); + break; + case SortingOption.Issues: + Assert.GreaterOrEqual(finalTopRepository.IssuesCount, finalSecondTopRepository.IssuesCount); + break; + case SortingOption.UniqueClones: + Assert.GreaterOrEqual(finalTopRepository.TotalUniqueClones, finalSecondTopRepository.TotalUniqueClones); + break; + case SortingOption.UniqueViews: + Assert.GreaterOrEqual(finalTopRepository.TotalUniqueViews, finalSecondTopRepository.TotalUniqueViews); + break; + case SortingOption.Views: + Assert.GreaterOrEqual(finalTopRepository.TotalViews, finalSecondTopRepository.TotalViews); + break; + default: + throw new NotSupportedException(); + + }; + } + [Test] public async Task VerifyRepositoriesAfterLogin() { diff --git a/GitTrends/Database/RepositoryDatabase.cs b/GitTrends/Database/RepositoryDatabase.cs index 739d3f1c0..35514b389 100644 --- a/GitTrends/Database/RepositoryDatabase.cs +++ b/GitTrends/Database/RepositoryDatabase.cs @@ -86,6 +86,14 @@ class RepositoryDatabaseModel : IRepository public bool IsFork { get; set; } + public long TotalViews { get; set; } + + public long TotalUniqueViews { get; set; } + + public long TotalClones { get; set; } + + public long TotalUniqueClones { get; set; } + public static explicit operator Repository(RepositoryDatabaseModel repositoryDatabaseModel) { return new Repository(repositoryDatabaseModel.Name, @@ -110,7 +118,11 @@ public static implicit operator RepositoryDatabaseModel(Repository repository) Name = repository.Name, OwnerAvatarUrl = repository.OwnerAvatarUrl, OwnerLogin = repository.OwnerLogin, - IsFork = repository.IsFork + IsFork = repository.IsFork, + TotalClones = repository.TotalClones, + TotalUniqueClones = repository.TotalUniqueClones, + TotalViews = repository.TotalViews, + TotalUniqueViews = repository.TotalUniqueViews, }; } } diff --git a/GitTrends/Pages/RepositoryPage.cs b/GitTrends/Pages/RepositoryPage.cs index 77d90fe55..0d2b8b7e9 100644 --- a/GitTrends/Pages/RepositoryPage.cs +++ b/GitTrends/Pages/RepositoryPage.cs @@ -56,7 +56,7 @@ public RepositoryPage(RepositoryViewModel repositoryViewModel, Text = "Sort", IconImageSource = "Sort", Order = Device.RuntimePlatform is Device.Android ? ToolbarItemOrder.Secondary : ToolbarItemOrder.Default, - AutomationId = RepositoryPageAutomationIds.SettingsButton, + AutomationId = RepositoryPageAutomationIds.SortButton, }; sortToolbarItem.Clicked += HandleSortToolbarItemCliked; ToolbarItems.Add(sortToolbarItem); diff --git a/GitTrends/Services/SortingService.cs b/GitTrends/Services/SortingService.cs index f72ad23ce..c7bb3b47f 100644 --- a/GitTrends/Services/SortingService.cs +++ b/GitTrends/Services/SortingService.cs @@ -16,7 +16,7 @@ public bool IsReversed public SortingOption CurrentOption { - get => (SortingOption)Preferences.Get(nameof(CurrentOption), (int)SortingOption.Stars); + get => (SortingOption)Preferences.Get(nameof(CurrentOption), (int)SortingConstants.DefaultSortingOption); set => Preferences.Set(nameof(CurrentOption), (int)value); } diff --git a/GitTrends/ViewModels/RepositoryViewModel.cs b/GitTrends/ViewModels/RepositoryViewModel.cs index cb95513a8..83dd80f69 100644 --- a/GitTrends/ViewModels/RepositoryViewModel.cs +++ b/GitTrends/ViewModels/RepositoryViewModel.cs @@ -9,7 +9,9 @@ using Autofac; using GitTrends.Shared; using Refit; +using Xamarin.Essentials; using Xamarin.Forms; +using Xamarin.Forms.Xaml; namespace GitTrends { @@ -57,6 +59,8 @@ public event EventHandler PullToRefreshFailed public ICommand FilterRepositoriesCommand { get; } public ICommand SortRepositoriesCommand { get; } + public bool IsNotRefreshing => !IsRefreshing; + public IReadOnlyList VisibleRepositoryList { get => _visibleRepositoryList; @@ -66,7 +70,7 @@ public IReadOnlyList VisibleRepositoryList public bool IsRefreshing { get => _isRefreshing; - set => SetProperty(ref _isRefreshing, value); + set => SetProperty(ref _isRefreshing, value, () => MainThread.InvokeOnMainThreadAsync(() => OnPropertyChanged(nameof(IsNotRefreshing)))); } async Task ExecutePullToRefreshCommand(string repositoryOwner) @@ -77,24 +81,26 @@ async Task ExecutePullToRefreshCommand(string repositoryOwner) { await foreach (var retrievedRepositories in _gitHubGraphQLApiService.GetRepositories(repositoryOwner, repositoriesPerFetch).ConfigureAwait(false)) { - var completedRepoitories = new List(); + AddRepositoriesToCollection(retrievedRepositories, _searchBarText); + } + + var completedRepoitories = new List(); + await foreach (var retrievedRepositoriesWithViewsAndClonesData in GetRepositoryWithViewsAndClonesData(_repositoryList.ToList()).ConfigureAwait(false)) + { + _repositoryDatabase.SaveRepository(retrievedRepositoriesWithViewsAndClonesData).SafeFireAndForget(); + completedRepoitories.Add(retrievedRepositoriesWithViewsAndClonesData); - await foreach (var retrievedRepositoriesWithViewsAndClonesData in GetRepositoryWithViewsAndClonesData(retrievedRepositories.ToList()).ConfigureAwait(false)) + //Limit the VisibleRepositoryList Updates to avoid overworking the UI Thread + if (!GitHubAuthenticationService.IsDemoUser + && completedRepoitories.Count > repositoriesPerFetch / 20) { - _repositoryDatabase.SaveRepository(retrievedRepositoriesWithViewsAndClonesData).SafeFireAndForget(); - completedRepoitories.Add(retrievedRepositoriesWithViewsAndClonesData); - - //Limit the VisibleRepositoryList Updates to avoid overworking the UI Thread - if (completedRepoitories.Count > repositoriesPerFetch / 10) - { - AddRepositoriesToCollection(completedRepoitories, _searchBarText); - completedRepoitories.Clear(); - } + AddRepositoriesToCollection(completedRepoitories, _searchBarText); + completedRepoitories.Clear(); } - - //Add Any Remaining Repositories to VisibleRepositoryList - AddRepositoriesToCollection(completedRepoitories, _searchBarText); } + + //Add Any Remaining Repositories to VisibleRepositoryList + AddRepositoriesToCollection(completedRepoitories, _searchBarText); } catch (ApiException e) when (e.StatusCode is HttpStatusCode.Unauthorized) { @@ -189,7 +195,8 @@ void AddRepositoriesToCollection(IEnumerable repositories, string se UpdateVisibleRepositoryList(searchBarText, _sortingService.CurrentOption, _sortingService.IsReversed); - static IEnumerable RemoveForksAndDuplicates(in IEnumerable repositoriesList) => repositoriesList.Where(x => !x.IsFork).GroupBy(x => x.Name).Select(x => x.First()); + static IEnumerable RemoveForksAndDuplicates(in IEnumerable repositoriesList) => + repositoriesList.Where(x => !x.IsFork).OrderByDescending(x => x.TotalViews).GroupBy(x => x.Name).Select(x => x.FirstOrDefault(x => x.DailyViewsList.Any()) ?? x.First()); } void UpdateVisibleRepositoryList(in string searchBarText, in SortingOption sortingOption, in bool isReversed) diff --git a/GitTrends/Views/Repository/RepositoryDataTemplateSelector.cs b/GitTrends/Views/Repository/RepositoryDataTemplateSelector.cs index 198c29fd4..b744b2bc9 100644 --- a/GitTrends/Views/Repository/RepositoryDataTemplateSelector.cs +++ b/GitTrends/Views/Repository/RepositoryDataTemplateSelector.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; using FFImageLoading.Svg.Forms; using GitTrends.Shared; @@ -81,20 +83,46 @@ enum Column { Avatar, AvatarPadding, Emoji1, Statistic1, Emoji2, Statistic2, Emo { new AvatarImage().Row(Row.TopPadding).Column(Column.Avatar).RowSpan(6) .Bind(Image.SourceProperty, nameof(Repository.OwnerAvatarUrl)), + new RepositoryNameLabel(repository.Name).Row(Row.Title).Column(Column.Emoji1).ColumnSpan(9), + new RepositoryDescriptionLabel(repository.Description).Row(Row.Description).Column(Column.Emoji1).ColumnSpan(9), + new SmallNavyBlueSVGImage(shouldShowStarsForksIssues ? "star.svg" : "total_views.svg").Row(Row.Statistics).Column(Column.Emoji1), - new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.StarCount.ToString() : repository.TotalViews.ToString()).Row(Row.Statistics).Column(Column.Statistic1), + + //Only display the value when the Repository Data finishes loading. This avoid showing '0' while the data is loading. + shouldDisplayValue(repository.DailyClonesList) + ? new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.StarCount.ToString() : repository.TotalViews.ToString()).Row(Row.Statistics).Column(Column.Statistic1) + : new Label(), + new SmallNavyBlueSVGImage(shouldShowStarsForksIssues ? "repo_forked.svg" : "unique_views.svg").Row(Row.Statistics).Column(Column.Emoji2), - new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.ForkCount.ToString() : repository.TotalUniqueViews.ToString()).Row(Row.Statistics).Column(Column.Statistic2), + + //Only display the value when the Repository Data finishes loading. This avoid showing '0' while the data is loading. + shouldDisplayValue(repository.DailyClonesList) + ? new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.ForkCount.ToString() : repository.TotalUniqueViews.ToString()).Row(Row.Statistics).Column(Column.Statistic2) + : new Label(), + new SmallNavyBlueSVGImage(shouldShowStarsForksIssues ? "issue_opened.svg" : "total_clones.svg").Row(Row.Statistics).Column(Column.Emoji3), - new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.IssuesCount.ToString() : repository.TotalClones.ToString()).Row(Row.Statistics).Column(Column.Statistic3), + + //Only display the value when the Repository Data finishes loading. This avoid showing '0' while the data is loading. + shouldDisplayValue(repository.DailyClonesList) + ? new DarkBlueLabel(_smallFontSize - 1, shouldShowStarsForksIssues ? repository.IssuesCount.ToString() : repository.TotalClones.ToString()).Row(Row.Statistics).Column(Column.Statistic3) + : new Label(), //Column.Emoji4 & Column.Statistic4 are not needed for StarsForksIssues - shouldShowStarsForksIssues ? new SvgCachedImage() : new SmallNavyBlueSVGImage("unique_clones.svg").Row(Row.Statistics).Column(Column.Emoji4), - shouldShowStarsForksIssues ? new Label() : new DarkBlueLabel(_smallFontSize - 1, repository.TotalUniqueClones.ToString()).Row(Row.Statistics).Column(Column.Statistic4) + !shouldShowStarsForksIssues + ? new SmallNavyBlueSVGImage("unique_clones.svg").Row(Row.Statistics).Column(Column.Emoji4) + : new SvgCachedImage(), + + //Column.Emoji4 & Column.Statistic4 are not needed for StarsForksIssues + //Only display the value when the Repository Data finishes loading. This avoid showing '0' while the data is loading. + !shouldShowStarsForksIssues && shouldDisplayValue(repository.DailyClonesList) + ? new DarkBlueLabel(_smallFontSize - 1, repository.TotalUniqueClones.ToString()).Row(Row.Statistics).Column(Column.Statistic4) + : new Label() } }; + + static bool shouldDisplayValue(IList list) where T : BaseDailyModel => list.Any(); } class AvatarImage : CircleImage