public void CachesTheApiResultsInMemorySoTheApiIsNotCalledTwiceForTheSameProjects(
                NonEmptyArray <NonNegativeInt> projectIds)
            {
                var actualProjectIds = projectIds.Get.Select(i => (long)i.Get).Distinct().ToArray();
                var idCount          = actualProjectIds.Length;

                if (idCount < 2)
                {
                    return;
                }
                var dbProjectCount  = (int)Math.Floor((float)idCount / 2);
                var apiProjectCount = idCount - dbProjectCount;
                var projectsInDb    = actualProjectIds.Take(dbProjectCount).ToArray();
                var projectsInApi   = actualProjectIds.TakeLast(apiProjectCount).ToArray();
                var summaries       = getSummaryList(actualProjectIds);

                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(projectsInDb, projectsInApi);
                configureApiToReturn(projectsInApi);

                ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();
                ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                ProjectsApi.Received(1)
                .Search(workspaceId, Arg.Is <long[]>(
                            calledIds => ensureExpectedIdsAreReturned(calledIds, projectsInApi)));
            }
            public async Task ReturnsSegmentsJustOnceWhenChangingDateRange()
            {
                var segments = new ChartSegment[2] {
                    new ChartSegment("Project 1", "Client 1", 50f, 10, 0, "ff0000"),
                    new ChartSegment("Project 2", "Client 2", 50f, 10, 0, "00ff00")
                };
                var projectsNotSyncedCount = 0;

                var currentDate = new DateTimeOffset(2018, 5, 23, 0, 0, 0, TimeSpan.Zero);
                var start       = new DateTimeOffset(2018, 5, 1, 0, 0, 0, TimeSpan.Zero);
                var end         = new DateTimeOffset(2018, 5, 7, 0, 0, 0, TimeSpan.Zero);

                TimeService.CurrentDateTime.Returns(currentDate);

                var delayed = Observable
                              .Return(new ProjectSummaryReport(segments, projectsNotSyncedCount))
                              .Delay(TimeSpan.FromMilliseconds(100));

                var instant = Observable
                              .Return(new ProjectSummaryReport(segments, projectsNotSyncedCount));

                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(delayed, instant);

                await Initialize();

                ViewModel.ChangeDateRangeCommand.Execute(
                    ReportsDateRangeParameter.WithDates(start, end));

                await delayed;

                ViewModel.Segments.Count.Should().Be(segments.Length);
            }
            public async Task IsSetToTrueWhenAReportIsLoading()
            {
                var now = DateTimeOffset.Now;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Never <ProjectSummaryReport>());

                await Initialize();

                ViewModel.IsLoading.Should().BeTrue();
            }
            public async Task IsSetToFalseWhenLoadingIsCompleted()
            {
                var now = DateTimeOffset.Now;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(new ChartSegment[0])));
                ViewModel.Prepare(WorkspaceId);
                await ViewModel.Initialize();

                ViewModel.IsLoading.Should().BeFalse();
            }
            public async Task IsSetToFalseWhenLoadingOverBecauseOfAnError()
            {
                var now = DateTimeOffset.Now;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Throw <ProjectSummaryReport>(new Exception()));
                ViewModel.Prepare(WorkspaceId);
                await ViewModel.Initialize();

                ViewModel.IsLoading.Should().BeFalse();
            }
            public void DoesNotCallTheApiIfAllProjectsAreInTheDatabase(NonEmptyArray <NonNegativeInt> projectIds)
            {
                var actualProjectIds = projectIds.Get.Select(i => (long)i.Get).Distinct().ToArray();
                var summaries        = getSummaryList(actualProjectIds);

                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(actualProjectIds);

                ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                ProjectsApi.DidNotReceive().Search(Arg.Any <long>(), Arg.Any <long[]>());
            }
            public async Task IsSetToTrueWhenAReportIsLoading()
            {
                var now = DateTimeOffset.Now;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Never <ProjectSummaryReport>());

                await Initialize();

                TestScheduler.Start();
                isLoadingObserver.Messages.Last().Value.Value.Should().BeTrue();
            }
            public void QueriesTheDatabaseForFindingProjects(NonEmptyArray <NonNegativeInt> projectIds)
            {
                var actualProjectIds = projectIds.Get.Select(i => (long)i.Get).Distinct().ToArray();
                var summaries        = getSummaryList(actualProjectIds);

                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(actualProjectIds);

                ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                ProjectsRepository.Received()
                .GetById(Arg.Is <long>(id => Array.IndexOf(actualProjectIds, id) >= 0));
            }
            public async Task IsSetToFalseWhenLoadingIsCompleted()
            {
                var now = DateTimeOffset.Now;
                var projectsNotSyncedCount = 0;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(new ChartSegment[0], projectsNotSyncedCount)));

                await Initialize();

                TestScheduler.Start();
                isLoadingObserver.Messages.Last().Value.Value.Should().BeFalse();
            }
            public void CreatesAChartSegmentWithNoProjectIfThereAreNullProjectIdsAmongTheApiResults(
                NonNegativeInt[] projectIds)
            {
                var actualProjectIds = projectIds.Select(i => (long)i.Get).Distinct().ToArray();
                var summaries        = getSummaryList(actualProjectIds);

                summaries.Add(new ProjectSummary());
                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(actualProjectIds);

                var report = ReportsProvider
                             .GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                report.Segments.Single(s => s.Color == Color.NoProject).ProjectName.Should().Be(Resources.NoProject);
            }
            public void IsSetToNullIfTheTotalTimeOfAReportIsZero(DateTimeOffset now)
            {
                var date = now.Date;
                var projectsNotSyncedCount = 0;

                TimeService.CurrentDateTime.Returns(now);
                var expectedStartDate = date.AddDays(1 - (int)date.DayOfWeek);

                ReportsProvider.GetProjectSummary(
                    WorkspaceId, expectedStartDate, expectedStartDate.AddDays(6))
                .Returns(Observable.Return(new ProjectSummaryReport(new ChartSegment[0], projectsNotSyncedCount)));

                ViewModel.Initialize().Wait();

                ViewModel.BillablePercentage.Should().BeNull();
            }
            public async Task IsSetToTrueWhenAReportIsLoading()
            {
                var loadingObserver = TestScheduler.CreateObserver <bool>();
                var now             = DateTimeOffset.Now;

                TimeService.CurrentDateTime.Returns(now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Never <ProjectSummaryReport>());
                ViewModel.IsLoadingObservable.Subscribe(loadingObserver);

                await Initialize();

                TestScheduler.Start();
                var isLoading = loadingObserver.Values().Last();

                isLoading.Should().BeTrue();
            }
            public async Task ShouldTriggerReloadForEveryAppearance(int numberOfAppearances)
            {
                TimeService.CurrentDateTime.Returns(DateTimeOffset.Now);
                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(),
                                                  Arg.Any <DateTimeOffset>())
                .ReturnsForAnyArgs(Observable.Empty <ProjectSummaryReport>(SchedulerProvider.TestScheduler));
                await ViewModel.Initialize();

                ViewModel.ViewAppeared(); // First call is skipped

                for (int i = 0; i < numberOfAppearances; ++i)
                {
                    ViewModel.ViewAppeared();
                }
                TestScheduler.Start();

                ReportsProvider.Received(numberOfAppearances).GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(),
                                                                                Arg.Any <DateTimeOffset>());
            }
            public void IsSetToNullIfTheTotalTimeOfAReportIsZero()
            {
                var billableObserver       = TestScheduler.CreateObserver <float?>();
                var projectsNotSyncedCount = 0;

                TimeService.CurrentDateTime.Returns(DateTime.Now);
                TimeService.MidnightObservable.Returns(Observable.Never <DateTimeOffset>());
                ReportsProvider
                .GetProjectSummary(WorkspaceId, Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(new ChartSegment[0], projectsNotSyncedCount)));

                ViewModel.BillablePercentageObservable.Subscribe(billableObserver);

                Initialize().Wait();

                TestScheduler.Start();

                var billablePercentage = billableObserver.Values().Last();

                billablePercentage.Should().BeNull();
            }
            public void ReturnsTheProjectOrderedByTotalTimeTracked(NonNegativeInt[] projectIds)
            {
                var actualProjectIds = projectIds.Select(i => (long)i.Get).Distinct().ToArray();
                var summaries        = getSummaryList(actualProjectIds);

                summaries.Add(new ProjectSummary());
                var summaryCount = summaries.Count;

                for (int i = 0; i < summaryCount; i++)
                {
                    var summary = (ProjectSummary)summaries[i];
                    summary.TrackedSeconds = i;
                }
                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(actualProjectIds);

                var report = ReportsProvider
                             .GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                report.Segments.Should().BeInDescendingOrder(s => s.Percentage);
            }
            public void ReturnsOnlyOneListIfItQueriesTheApi(NonEmptyArray <NonNegativeInt> projectIds)
            {
                var actualProjectIds = projectIds.Get.Select(i => (long)i.Get).Distinct().ToArray();

                if (actualProjectIds.Length < 2)
                {
                    return;
                }

                var projectsInDb  = actualProjectIds.Where((i, id) => i % 2 == 0).ToArray();
                var projectsInApi = actualProjectIds.Where((i, id) => i % 2 != 0).ToArray();
                var summaries     = getSummaryList(actualProjectIds);

                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(projectsInDb, projectsInApi);
                configureApiToReturn(projectsInApi);

                var lists = ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).ToList().Wait();

                lists.Should().HaveCount(1);
            }
            public async Task SetsOtherProjectWithOneSegmentToThatSegmentButWithOnePercentIfLessThanOnePercent()
            {
                ChartSegment[] segments =
                {
                    new ChartSegment("Project 1", "Client 1", 0.2f,  2, 0, "#666666"),
                    new ChartSegment("Project 2", "Client 2", 8.8f,  4, 0, "#ffffff"),
                    new ChartSegment("Project 3", "Client 3",   12, 12, 0, "#ffffff"),
                    new ChartSegment("Project 4", "Client 4",   23, 23, 0, "#ffffff"),
                    new ChartSegment("Project 5", "Client 5",   56, 56, 0, "#ffffff")
                };

                TimeService.CurrentDateTime.Returns(new DateTimeOffset(2018, 05, 15, 12, 00, 00, TimeSpan.Zero));
                ReportsProvider.GetProjectSummary(WorkspaceId, Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(segments, projectsNotSyncedCount)));

                var segmentsObservable        = TestScheduler.CreateObserver <IReadOnlyList <ChartSegment> >();
                var groupedSegmentsObservable = TestScheduler.CreateObserver <IReadOnlyList <ChartSegment> >();

                ViewModel.SegmentsObservable.Subscribe(segmentsObservable);
                ViewModel.GroupedSegmentsObservable.Subscribe(groupedSegmentsObservable);

                TestScheduler.Start();

                await Initialize();

                var actualSegments        = segmentsObservable.Values().Last();
                var actualGroupedSegments = groupedSegmentsObservable.Values().Last();

                actualSegments.Should().HaveCount(5);
                actualGroupedSegments.Should().HaveCount(5);
                actualGroupedSegments.Should().Contain(segment =>
                                                       segment.ProjectName == "Project 1" &&
                                                       segment.Percentage == 1f &&
                                                       segment.Color == "#666666");
                actualGroupedSegments
                .Where(project => project.ProjectName != "Project 1")
                .Select(segment => segment.Percentage)
                .ForEach(percentage => percentage.Should().BeGreaterOrEqualTo(5));
            }
            public async Task GroupsProjectSegmentsWithPercentageBetweenOneAndFiveIntoOtherIfTotalOfOtherLessThanFivePercent()
            {
                ChartSegment[] segments =
                {
                    new ChartSegment("Project 1", "Client 1",  0.9f,  2, 0, "#ffffff"),
                    new ChartSegment("Project 2", "Client 2",  0.9f,  3, 0, "#ffffff"),
                    new ChartSegment("Project 3", "Client 3",  2.5f,  4, 0, "#ffffff"),
                    new ChartSegment("Project 4", "Client 4",     4, 12, 0, "#ffffff"),
                    new ChartSegment("Project 5", "Client 5", 31.7f, 23, 0, "#ffffff"),
                    new ChartSegment("Project 6", "Client 6",    60, 56, 0, "#ffffff")
                };

                TimeService.CurrentDateTime.Returns(new DateTimeOffset(2018, 05, 15, 12, 00, 00, TimeSpan.Zero));
                ReportsProvider.GetProjectSummary(WorkspaceId, Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(segments, projectsNotSyncedCount)));

                var segmentsObservable        = TestScheduler.CreateObserver <IReadOnlyList <ChartSegment> >();
                var groupedSegmentsObservable = TestScheduler.CreateObserver <IReadOnlyList <ChartSegment> >();

                ViewModel.SegmentsObservable.Subscribe(segmentsObservable);
                ViewModel.GroupedSegmentsObservable.Subscribe(groupedSegmentsObservable);

                TestScheduler.Start();

                await Initialize();

                var actualSegments        = segmentsObservable.Values().Last();
                var actualGroupedSegments = groupedSegmentsObservable.Values().Last();

                actualSegments.Should().HaveCount(6);
                actualGroupedSegments.Should().HaveCount(4);
                actualGroupedSegments.Should().Contain(segment =>
                                                       segment.ProjectName == Resources.Other &&
                                                       segment.Percentage == segments[0].Percentage + segments[1].Percentage + segments[2].Percentage);
                actualGroupedSegments
                .Where(project => project.ProjectName != Resources.Other)
                .Select(segment => segment.Percentage)
                .ForEach(percentage => percentage.Should().BeGreaterOrEqualTo(4));
            }
            public async Task TracksAnEventWhenReportFailsToLoad()
            {
                var startDateRange = new DateTimeOffset(2018, 05, 05, 0, 0, 0, TimeSpan.Zero);
                var endDateRange   = startDateRange.AddDays(7);

                var totalDays       = (int)(endDateRange - startDateRange).TotalDays;
                var loadingDuration = TimeSpan.FromSeconds(5);
                var now             = new DateTimeOffset(2018, 01, 01, 0, 0, 0, TimeSpan.Zero);

                TimeService.CurrentDateTime.Returns(_ =>
                {
                    now = now + loadingDuration;
                    return(now);
                });

                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Throw <ProjectSummaryReport>(new Exception()));

                await Initialize();

                AnalyticsService.Received().ReportsFailure.Track(ReportsSource.Initial, totalDays, loadingDuration.TotalMilliseconds);
            }
            public async Task TracksAnEventWhenReportLoadsSuccessfully()
            {
                var startDateRange = new DateTimeOffset(2018, 05, 05, 0, 0, 0, TimeSpan.Zero);
                var endDateRange   = startDateRange.AddDays(7);

                var totalDays = (int)(endDateRange - startDateRange).TotalDays;
                var projectsNotSyncedCount = 0;
                var loadingDuration        = TimeSpan.FromSeconds(5);
                var now = new DateTimeOffset(2018, 01, 01, 0, 0, 0, TimeSpan.Zero);

                TimeService.CurrentDateTime.Returns(_ =>
                {
                    now = now + loadingDuration;
                    return(now);
                });

                ReportsProvider.GetProjectSummary(Arg.Any <long>(), Arg.Any <DateTimeOffset>(), Arg.Any <DateTimeOffset>())
                .Returns(Observable.Return(new ProjectSummaryReport(new ChartSegment[0], projectsNotSyncedCount)));

                await Initialize();

                AnalyticsService.Received().ReportsSuccess.Track(ReportsSource.Initial, totalDays, projectsNotSyncedCount, loadingDuration.TotalMilliseconds);
            }
            public void QueriesTheApiIfAnyProjectIsNotFoundInTheDatabase(NonEmptyArray <NonNegativeInt> projectIds)
            {
                var actualProjectIds = projectIds.Get.Select(i => (long)i.Get).Distinct().ToArray();

                if (actualProjectIds.Length < 2)
                {
                    return;
                }

                var projectsInDb  = actualProjectIds.Where((id, i) => i % 2 == 0).ToArray();
                var projectsInApi = actualProjectIds.Where((id, i) => i % 2 != 0).ToArray();
                var summaries     = getSummaryList(actualProjectIds);

                apiProjectsSummary.ProjectsSummaries.Returns(summaries);
                configureRepositoryToReturn(projectsInDb, projectsInApi);
                configureApiToReturn(projectsInApi);

                ReportsProvider.GetProjectSummary(workspaceId, DateTimeOffset.Now.AddDays(-7), DateTimeOffset.Now).Wait();

                ProjectsApi.Received()
                .Search(workspaceId, Arg.Is <long[]>(
                            calledIds => ensureExpectedIdsAreReturned(calledIds, projectsInApi)));
            }