import { TestBed } from "@angular/core/testing" ; import { YearProgressService } from "./year-progress.service" ; import { YearProgressActivitiesFixture } from "./year-progress-activities.fixture" ; import { YearProgressModel } from "../models/year-progress.model" ; import * as _ from "lodash" ; import { ActivityCountByTypeModel } from "../models/activity-count-by-type.model" ; import { ProgressModel } from "../models/progress.model" ; import * as moment from "moment" ; import { Moment } from "moment" ; import { ProgressAtDayModel } from "../models/progress-at-date.model" ; import { ProgressType } from "../enums/progress-type.enum" ; import { SyncedActivityModel } from "@elevate/shared/models" ; import { YearToDateProgressPresetModel } from "../models/year-to-date-progress-preset.model" ; import { YearProgressModule } from "../../year-progress.module" ; import { AppError } from "../../../shared/models/app-error.model" ; import { TargetProgressModel } from "../models/target-progress.model" ; import { DataStore } from "../../../shared/data-store/data-store" ; import { MockedDataStore } from "../../../shared/data-store/impl/spec/mocked-data-store.service" ; import { ProgressMode } from "../enums/progress-mode.enum" ; import { RollingProgressConfigModel } from "../models/rolling-progress-config.model" ; import { RollingProgressPresetModel } from "../models/rolling-progress-preset.model" ; import { YearToDateProgressConfigModel } from "../models/year-to-date-progress-config.model" ; import { Tools } from "@elevate/shared/tools" ; import Spy = jasmine . Spy ; describe ( "YearProgressService" , ( ) => { param date return const stravaStartTime = ( date ) => { return date + "T11:00:00+0000" ; } ; const isMetric = true ; let service ; let TEST_SYNCED_MODELS ; let getTodayMomentSpy ; beforeEach ( ( done ) => { const mockedDataStore = new MockedDataStore ( ) ; TestBed . configureTestingModule ( { imports : [ YearProgressModule ] , providers : [ { provide : DataStore , useValue : mockedDataStore } ] } ) ; service = TestBed . get ( YearProgressService ) ; TEST_SYNCED_MODELS = YearProgressActivitiesFixture . provide ( ) ; getTodayMomentSpy = spyOn ( service , "getTodayMoment" ) ; getTodayMomentSpy . and . returnValue ( moment ( "2018-03-01 12:00" , "YYYY-MM-DD hh:mm" ) ) ; done ( ) ; } ) ; it ( "should be created" , ( done ) => { expect ( service ) . toBeTruthy ( ) ; done ( ) ; } ) ; it ( "should provide activities count by types" , ( done ) => { const expectedResult = [ { type : "Ride" , count : 352 } , { type : "Run" , count : 178 } , { type : "VirtualRide" , count : 177 } ] ; const result = service . activitiesByTypes ( TEST_SYNCED_MODELS ) ; expect ( result ) . not . toBeNull ( ) ; expect ( result ) . toEqual ( expectedResult ) ; expect ( _ . first ( result ) . type ) . toEqual ( "Ride" ) ; expect ( result [ 1 ] . type ) . toEqual ( "Run" ) ; expect ( _ . last ( result ) . type ) . toEqual ( "VirtualRide" ) ; done ( ) ; } ) ; it ( "should provide all available years from user activities" , ( done ) => { const expectedResult = [ 2018 , 2017 , 2016 , 2015 ] ; const result = service . availableYears ( TEST_SYNCED_MODELS ) ; expect ( result ) . not . toBeNull ( ) ; expect ( result ) . toEqual ( expectedResult ) ; done ( ) ; } ) ; it ( "should notify subscribers when moment watched has changed" , ( done ) => { const expectedCallCount = 1 ; const spy = spyOn ( service . momentWatchedChanges , "next" ) ; const momentWatched = moment ( "2017-04-29 12:00" , "YYYY-MM-DD hh:mm" ) ; service . momentWatched = null ; service . onMomentWatchedChange ( momentWatched ) ; expect ( spy ) . toHaveBeenCalledTimes ( expectedCallCount ) ; expect ( spy ) . toHaveBeenCalledWith ( momentWatched ) ; expect ( service . momentWatched ) . toEqual ( momentWatched ) ; done ( ) ; } ) ; describe ( "compute year to date progression" , ( ) => { it ( "should compute progression on 4 years" , ( done ) => { const expectedLength = 4 ; const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedLength ) ; expect ( _ . last ( yearProgressions ) . year ) . toEqual ( 2018 ) ; expect ( _ . last ( yearProgressions ) . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; expect ( yearProgressions [ 0 ] . year ) . toEqual ( 2015 ) ; expect ( yearProgressions [ 1 ] . year ) . toEqual ( 2016 ) ; expect ( yearProgressions [ 2 ] . year ) . toEqual ( 2017 ) ; expect ( yearProgressions [ 3 ] . year ) . toEqual ( 2018 ) ; expect ( yearProgressions [ 0 ] . progressions . length ) . toEqual ( 365 ) ; expect ( yearProgressions [ 1 ] . progressions . length ) . toEqual ( 366 ) ; expect ( yearProgressions [ 2 ] . progressions . length ) . toEqual ( 365 ) ; expect ( yearProgressions [ 3 ] . progressions . length ) . toEqual ( 365 ) ; done ( ) ; } ) ; it ( "should compute progression by tagging future days of current year" , ( done ) => { const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; const yearProgressModel_2018 = yearProgressions [ 3 ] ; expect ( yearProgressModel_2018 . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; const pastDaysCount_2018 = _ . filter ( yearProgressModel_2018 . progressions , { isFuture : false } ) . length ; expect ( pastDaysCount_2018 ) . toEqual ( 60 ) ; const futureDaysCount_2018 = _ . filter ( yearProgressModel_2018 . progressions , { isFuture : true } ) . length ; expect ( futureDaysCount_2018 ) . toEqual ( 305 ) ; done ( ) ; } ) ; it ( "should compute progression on 4 years even with a walk done the 2016-06-06" , ( done ) => { const expectedLength = 4 ; const progressConfig = new YearToDateProgressConfigModel ( [ "Walk" ] , true , true ) ; const fakeWalkActivity = new SyncedActivityModel ( ) ; fakeWalkActivity . id = 99 ; fakeWalkActivity . name = "Walking" ; fakeWalkActivity . type = "Walk" ; fakeWalkActivity . display_type = "Walk" ; fakeWalkActivity . start_time = moment ( "2016-06-06" , "YYYY-MM-DD" ) . toISOString ( ) ; fakeWalkActivity . distance_raw = 3000 ; fakeWalkActivity . moving_time_raw = 3600 ; fakeWalkActivity . elapsed_time_raw = 3600 ; fakeWalkActivity . elevation_gain_raw = 0 ; TEST_SYNCED_MODELS . push ( fakeWalkActivity ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedLength ) ; expect ( yearProgressions [ 0 ] . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; expect ( yearProgressions [ 1 ] . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; expect ( yearProgressions [ 2 ] . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; expect ( yearProgressions [ 3 ] . mode ) . toEqual ( ProgressMode . YEAR_TO_DATE ) ; expect ( yearProgressions [ 0 ] . year ) . toEqual ( 2015 ) ; expect ( yearProgressions [ 1 ] . year ) . toEqual ( 2016 ) ; expect ( yearProgressions [ 2 ] . year ) . toEqual ( 2017 ) ; expect ( yearProgressions [ 3 ] . year ) . toEqual ( 2018 ) ; expect ( yearProgressions [ 0 ] . progressions . length ) . toEqual ( 365 ) ; expect ( yearProgressions [ 1 ] . progressions . length ) . toEqual ( 366 ) ; expect ( yearProgressions [ 2 ] . progressions . length ) . toEqual ( 365 ) ; expect ( yearProgressions [ 3 ] . progressions . length ) . toEqual ( 365 ) ; done ( ) ; } ) ; it ( "should compute progression with proper totals metrics" , ( done ) => { const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const expectedFirstDay2015 = new ProgressModel ( 2015 , 1 , 10 , 3600 / 3600 , 50 , 1 ) ; const expectedLastDay2015 = new ProgressModel ( 2015 , 365 , 6205 , 788400 / 3600 , 70080 , 292 ) ; const expectedFirstDay2016 = new ProgressModel ( 2016 , 1 , 10 , 3600 / 3600 , 50 , 1 ) ; const expectedLastDay2016 = new ProgressModel ( 2016 , 366 , 6215 , 792000 / 3600 , 70130 , 293 ) ; const expectedFirstDay2017 = new ProgressModel ( 2017 , 1 , 10 , 3600 / 3600 , 50 , 1 ) ; const expectedLastDay2017 = new ProgressModel ( 2017 , 365 , 2580 , 329400 / 3600 , 29250 , 122 ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; const firstDay2015 = _ . first ( yearProgressions [ 0 ] . progressions ) ; const lastDay2015 = _ . last ( yearProgressions [ 0 ] . progressions ) ; expect ( firstDay2015 ) . toEqual ( expectedFirstDay2015 ) ; expect ( lastDay2015 ) . toEqual ( expectedLastDay2015 ) ; const firstDay2016 = _ . first ( yearProgressions [ 1 ] . progressions ) ; const lastDay2016 = _ . last ( yearProgressions [ 1 ] . progressions ) ; expect ( firstDay2016 ) . toEqual ( expectedFirstDay2016 ) ; expect ( lastDay2016 ) . toEqual ( expectedLastDay2016 ) ; const firstDay2017 = _ . first ( yearProgressions [ 2 ] . progressions ) ; const lastDay2017 = _ . last ( yearProgressions [ 2 ] . progressions ) ; expect ( firstDay2017 ) . toEqual ( expectedFirstDay2017 ) ; expect ( lastDay2017 ) . toEqual ( expectedLastDay2017 ) ; done ( ) ; } ) ; it ( "should compute progression without commute rides and with proper totals metrics" , ( done ) => { const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , false , true ) ; const expectedLastDay2015 = new ProgressModel ( 2015 , 365 , 5110 , 657000 / 3600 , 69350 , 219 ) ; const expectedLastDay2016 = new ProgressModel ( 2016 , 366 , 5120 , 660600 / 3600 , 69400 , 220 ) ; const expectedLastDay2017 = new ProgressModel ( 2017 , 365 , 2130 , 275400 / 3600 , 28950 , 92 ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; const lastDay2015 = _ . last ( yearProgressions [ 0 ] . progressions ) ; expect ( lastDay2015 ) . toEqual ( expectedLastDay2015 ) ; const lastDay2016 = _ . last ( yearProgressions [ 1 ] . progressions ) ; expect ( lastDay2016 ) . toEqual ( expectedLastDay2016 ) ; const lastDay2017 = _ . last ( yearProgressions [ 2 ] . progressions ) ; expect ( lastDay2017 ) . toEqual ( expectedLastDay2017 ) ; done ( ) ; } ) ; it ( "should compute progression with imperial system unit" , ( done ) => { const isMetric = false ; const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const expectedLastDay2015 = new ProgressModel ( 2015 , 365 , 3856 , 788400 / 3600 , 229921 , 292 ) ; const expectedLastDay2016 = new ProgressModel ( 2016 , 366 , 3862 , 792000 / 3600 , 230085 , 293 ) ; const expectedLastDay2017 = new ProgressModel ( 2017 , 365 , 1603 , 329400 / 3600 , 95965 , 122 ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; const lastDay2015 = _ . last ( yearProgressions [ 0 ] . progressions ) ; expect ( lastDay2015 ) . toEqual ( expectedLastDay2015 ) ; const lastDay2016 = _ . last ( yearProgressions [ 1 ] . progressions ) ; expect ( lastDay2016 ) . toEqual ( expectedLastDay2016 ) ; const lastDay2017 = _ . last ( yearProgressions [ 2 ] . progressions ) ; expect ( lastDay2017 ) . toEqual ( expectedLastDay2017 ) ; done ( ) ; } ) ; it ( "should not compute progression with empty activities" , ( done ) => { const syncedActivityModels = [ ] ; const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const progressionMethodCall = ( ) => service . progressions ( progressConfig , isMetric , syncedActivityModels ) ; expect ( progressionMethodCall ) . toThrowError ( YearProgressService . ERROR_NO_SYNCED_ACTIVITY_MODELS ) ; done ( ) ; } ) ; it ( "should not compute progression with empty types filters" , ( done ) => { const progressConfig = new YearToDateProgressConfigModel ( [ ] , true , true ) ; const progressionMethodCall = ( ) => service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( progressionMethodCall ) . toThrowError ( YearProgressService . ERROR_NO_TYPES_FILTER ) ; done ( ) ; } ) ; it ( "should not compute progression with not existing type" , ( done ) => { const progressConfig = new YearToDateProgressConfigModel ( [ "FakeType" ] , true , true ) ; const progressionMethodCall = ( ) => service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; expect ( progressionMethodCall ) . toThrowError ( YearProgressService . ERROR_NO_YEAR_PROGRESS_MODELS ) ; done ( ) ; } ) ; it ( "should give progressions for a specific day" , ( done ) => { const expectedLength = 4 ; const progressConfig = new YearToDateProgressConfigModel ( [ "Ride" , "VirtualRide" , "Run" ] , true , true ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , TEST_SYNCED_MODELS ) ; const selectedYears = [ 2018 , 2017 , 2016 , 2015 ] ; const progressType = ProgressType . DISTANCE ; const todayDate = "2016-06-01 12:00" ; const momentDatePattern = "YYYY-MM-DD hh:mm" ; const dayMoment = moment ( todayDate , momentDatePattern ) . startOf ( "day" ) ; const yearsColorsMap = new Map < number , string > ( ) ; yearsColorsMap . set ( 2015 , "red" ) ; yearsColorsMap . set ( 2016 , "blue" ) ; yearsColorsMap . set ( 2017 , "green" ) ; yearsColorsMap . set ( 2018 , "purple" ) ; const progressAtDayModels = service . findProgressionsAtDay ( yearProgressions , dayMoment , progressType , selectedYears , yearsColorsMap ) ; expect ( progressAtDayModels ) . not . toBeNull ( ) ; expect ( progressAtDayModels . length ) . toEqual ( expectedLength ) ; expect ( progressAtDayModels [ 3 ] . year ) . toEqual ( 2015 ) ; expect ( progressAtDayModels [ 3 ] . date . getFullYear ( ) ) . toEqual ( 2015 ) ; expect ( moment ( progressAtDayModels [ 3 ] . date ) . dayOfYear ( ) ) . toEqual ( 152 ) ; expect ( progressAtDayModels [ 3 ] . value ) . toEqual ( 2580 ) ; expect ( progressAtDayModels [ 3 ] . color ) . toEqual ( "red" ) ; expect ( progressAtDayModels [ 2 ] . year ) . toEqual ( 2016 ) ; expect ( progressAtDayModels [ 2 ] . date . getFullYear ( ) ) . toEqual ( 2016 ) ; expect ( moment ( progressAtDayModels [ 2 ] . date ) . dayOfYear ( ) ) . toEqual ( 153 ) ; expect ( progressAtDayModels [ 2 ] . value ) . toEqual ( 2595 ) ; expect ( progressAtDayModels [ 1 ] . year ) . toEqual ( 2017 ) ; expect ( progressAtDayModels [ 1 ] . date . getFullYear ( ) ) . toEqual ( 2017 ) ; expect ( moment ( progressAtDayModels [ 1 ] . date ) . dayOfYear ( ) ) . toEqual ( 152 ) ; expect ( progressAtDayModels [ 1 ] . value ) . toEqual ( 2580 ) ; expect ( progressAtDayModels [ 0 ] . year ) . toEqual ( 2018 ) ; expect ( progressAtDayModels [ 0 ] . date . getFullYear ( ) ) . toEqual ( 2018 ) ; expect ( moment ( progressAtDayModels [ 0 ] . date ) . dayOfYear ( ) ) . toEqual ( 152 ) ; expect ( progressAtDayModels [ 0 ] . value ) . toEqual ( 0 ) ; expect ( progressAtDayModels [ 0 ] . color ) . toEqual ( "purple" ) ; done ( ) ; } ) ; } ) ; describe ( "compute rolling progression" , ( ) => { let syncedActivityModels = [ ] ; const createActivity = ( date , type , distance_raw , moving_time_raw , elevation_gain_raw ) => { return { distance_raw : distance_raw , moving_time_raw : moving_time_raw , elevation_gain_raw : elevation_gain_raw , start_time : stravaStartTime ( date ) , type : type } ; } ; beforeEach ( ( done ) => { syncedActivityModels = [ ] ; done ( ) ; } ) ; it ( "should calculate 1 week rolling distance progression (no previous year)" , ( done ) => { const expectedYearsLength = 1 ; const expectedYear = 2019 ; const expectedDaysInYear = 365 ; const rollingDays = moment . duration ( 1 , "week" ) . asDays ( ) ; const progressConfig = new RollingProgressConfigModel ( [ "Ride" ] , true , true , rollingDays ) ; const todayTime = "2019-02-15 20:00" ; getTodayMomentSpy . and . returnValue ( moment ( todayTime , "YYYY-MM-DD hh:mm" ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-01" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-02" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-03" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-04" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-05" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-06" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-07" , "Ride" , 10000 , 3600 , 100 ) ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , syncedActivityModels as SyncedActivityModel [ ] ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedYearsLength ) ; const yearProgressModel = yearProgressions [ 0 ] ; const rollingWeekProgress = yearProgressModel . progressions ; expect ( yearProgressModel . mode ) . toEqual ( ProgressMode . ROLLING ) ; expect ( yearProgressModel . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress . length ) . toEqual ( expectedDaysInYear ) ; expect ( rollingWeekProgress [ 0 ] . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress [ 0 ] . dayOfYear ) . toEqual ( 1 ) ; const januaryDaysOffset = 31 ; expect ( rollingWeekProgress [ januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; done ( ) ; } ) ; it ( "should calculate 1 week rolling distance progression (overlapping between 2 years with activities)" , ( done ) => { const expectedYearsLength = 2 ; const expectedDaysInYear = 365 ; const rollingDays = moment . duration ( 1 , "week" ) . asDays ( ) ; const progressConfig = new RollingProgressConfigModel ( [ "Ride" ] , true , true , rollingDays ) ; const todayTime = "2019-02-15 20:00" ; getTodayMomentSpy . and . returnValue ( moment ( todayTime , "YYYY-MM-DD hh:mm" ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-25" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-26" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-27" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-28" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-29" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-30" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2018-12-31" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-01" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-02" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-03" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-04" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-05" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-06" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-01-07" , "Ride" , 10000 , 3600 , 100 ) ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , syncedActivityModels as SyncedActivityModel [ ] ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedYearsLength ) ; const previousYearProgress = yearProgressions [ 0 ] ; const currentYearProgress = yearProgressions [ 1 ] ; expect ( previousYearProgress . mode ) . toEqual ( ProgressMode . ROLLING ) ; expect ( previousYearProgress . year ) . toEqual ( 2018 ) ; expect ( currentYearProgress . mode ) . toEqual ( ProgressMode . ROLLING ) ; expect ( currentYearProgress . year ) . toEqual ( 2019 ) ; const rollingWeekProgressOnPreviousYear = previousYearProgress . progressions ; const rollingWeekProgressOnCurrentYear = currentYearProgress . progressions ; expect ( rollingWeekProgressOnPreviousYear . length ) . toEqual ( expectedDaysInYear ) ; expect ( rollingWeekProgressOnPreviousYear [ 0 ] . year ) . toEqual ( 2018 ) ; expect ( rollingWeekProgressOnPreviousYear [ 0 ] . dayOfYear ) . toEqual ( 1 ) ; expect ( rollingWeekProgressOnCurrentYear . length ) . toEqual ( expectedDaysInYear ) ; expect ( rollingWeekProgressOnCurrentYear [ 0 ] . year ) . toEqual ( 2019 ) ; expect ( rollingWeekProgressOnCurrentYear [ 0 ] . dayOfYear ) . toEqual ( 1 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 9 ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 8 ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 7 ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 6 ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 5 ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 4 ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 3 ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 2 ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgressOnPreviousYear [ expectedDaysInYear - 1 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 0 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 1 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 2 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 3 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 4 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 5 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 6 ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgressOnCurrentYear [ 7 ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgressOnCurrentYear [ 8 ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgressOnCurrentYear [ 9 ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgressOnCurrentYear [ 10 ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgressOnCurrentYear [ 11 ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgressOnCurrentYear [ 12 ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgressOnCurrentYear [ 13 ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgressOnCurrentYear [ 14 ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgressOnCurrentYear [ 15 ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgressOnCurrentYear [ 16 ] . distance ) . toEqual ( 0 ) ; done ( ) ; } ) ; it ( "should calculate 1 week rolling 'distance, time, elevation & count' progression with multiple activities per day (no previous year)" , ( done ) => { const expectedYearsLength = 1 ; const expectedYear = 2019 ; const expectedDaysInYear = 365 ; const rollingDays = moment . duration ( 1 , "week" ) . asDays ( ) ; const progressConfig = new RollingProgressConfigModel ( [ "Ride" ] , true , true , rollingDays ) ; const todayTime = "2019-02-15 20:00" ; getTodayMomentSpy . and . returnValue ( moment ( todayTime , "YYYY-MM-DD hh:mm" ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-01" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-02" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-03" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-04" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-05" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-06" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-07" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-07" , "Ride" , 10000 , 3600 , 100 ) ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , syncedActivityModels as SyncedActivityModel [ ] ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedYearsLength ) ; const yearProgressModel = yearProgressions [ 0 ] ; const rollingWeekProgress = yearProgressModel . progressions ; expect ( yearProgressModel . mode ) . toEqual ( ProgressMode . ROLLING ) ; expect ( yearProgressModel . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress . length ) . toEqual ( expectedDaysInYear ) ; expect ( rollingWeekProgress [ 0 ] . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress [ 0 ] . dayOfYear ) . toEqual ( 1 ) ; const januaryDaysOffset = 31 ; expect ( rollingWeekProgress [ januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . distance ) . toEqual ( 80 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . distance ) . toEqual ( 70 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . distance ) . toEqual ( 60 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . distance ) . toEqual ( 50 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . distance ) . toEqual ( 40 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . distance ) . toEqual ( 30 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . distance ) . toEqual ( 20 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ januaryDaysOffset ] . time ) . toEqual ( 1 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . time ) . toEqual ( 2 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . time ) . toEqual ( 3 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . time ) . toEqual ( 4 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . time ) . toEqual ( 5 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . time ) . toEqual ( 6 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . time ) . toEqual ( 8 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . time ) . toEqual ( 7 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . time ) . toEqual ( 6 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . time ) . toEqual ( 5 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . time ) . toEqual ( 4 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . time ) . toEqual ( 3 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . time ) . toEqual ( 2 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . time ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . time ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . time ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ januaryDaysOffset ] . elevation ) . toEqual ( 100 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . elevation ) . toEqual ( 200 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . elevation ) . toEqual ( 300 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . elevation ) . toEqual ( 400 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . elevation ) . toEqual ( 500 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . elevation ) . toEqual ( 600 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . elevation ) . toEqual ( 800 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . elevation ) . toEqual ( 700 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . elevation ) . toEqual ( 600 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . elevation ) . toEqual ( 500 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . elevation ) . toEqual ( 400 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . elevation ) . toEqual ( 300 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . elevation ) . toEqual ( 200 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . elevation ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . elevation ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . elevation ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ januaryDaysOffset ] . count ) . toEqual ( 1 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . count ) . toEqual ( 2 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . count ) . toEqual ( 3 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . count ) . toEqual ( 4 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . count ) . toEqual ( 5 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . count ) . toEqual ( 6 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . count ) . toEqual ( 8 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . count ) . toEqual ( 7 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . count ) . toEqual ( 6 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . count ) . toEqual ( 5 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . count ) . toEqual ( 4 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . count ) . toEqual ( 3 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . count ) . toEqual ( 2 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . count ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . count ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . count ) . toEqual ( 0 ) ; done ( ) ; } ) ; it ( "should calculate 1 day rolling distance progression with multiple activities per day (no previous year)" , ( done ) => { const expectedYearsLength = 1 ; const expectedYear = 2019 ; const expectedDaysInYear = 365 ; const rollingDays = 1 ; const progressConfig = new RollingProgressConfigModel ( [ "Ride" ] , true , true , rollingDays ) ; const todayTime = "2019-02-15 20:00" ; getTodayMomentSpy . and . returnValue ( moment ( todayTime , "YYYY-MM-DD hh:mm" ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-01" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-02" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-02" , "Ride" , 15000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-03" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-04" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-05" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-06" , "Ride" , 10000 , 3600 , 100 ) ) ; syncedActivityModels . push ( createActivity ( "2019-02-07" , "Ride" , 10000 , 3600 , 100 ) ) ; const yearProgressions = service . progressions ( progressConfig , isMetric , syncedActivityModels as SyncedActivityModel [ ] ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions ) . not . toBeNull ( ) ; expect ( yearProgressions . length ) . toEqual ( expectedYearsLength ) ; const yearProgressModel = yearProgressions [ 0 ] ; const rollingWeekProgress = yearProgressModel . progressions ; expect ( yearProgressModel . mode ) . toEqual ( ProgressMode . ROLLING ) ; expect ( yearProgressModel . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress . length ) . toEqual ( expectedDaysInYear ) ; expect ( rollingWeekProgress [ 0 ] . year ) . toEqual ( expectedYear ) ; expect ( rollingWeekProgress [ 0 ] . dayOfYear ) . toEqual ( 1 ) ; const januaryDaysOffset = 31 ; expect ( rollingWeekProgress [ januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 1 + januaryDaysOffset ] . distance ) . toEqual ( 25 ) ; expect ( rollingWeekProgress [ 2 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 3 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 4 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 5 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 6 + januaryDaysOffset ] . distance ) . toEqual ( 10 ) ; expect ( rollingWeekProgress [ 7 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 8 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 9 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 10 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 11 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 12 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 13 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 14 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; expect ( rollingWeekProgress [ 15 + januaryDaysOffset ] . distance ) . toEqual ( 0 ) ; done ( ) ; } ) ; } ) ; describe ( "compute target progression" , ( ) => { describe ( "Year to date target progression" , ( ) => { it ( "should compute year to date target progression on non leap year" , ( done ) => { const year = 2018 ; const targetValue = 5000 ; const expectedTargetProgressionLength = 365 ; const expectedStep = 13.698 ; const targetProgressModels = service . yearToDateTargetProgression ( year , targetValue ) ; expect ( targetProgressModels ) . not . toBeNull ( ) ; expect ( targetProgressModels . length ) . toEqual ( expectedTargetProgressionLength ) ; expect ( _ . floor ( targetProgressModels [ 1 ] . value - targetProgressModels [ 0 ] . value , 3 ) ) . toEqual ( expectedStep ) ; done ( ) ; } ) ; it ( "should compute year to date target progression on leap year" , ( done ) => { const year = 2016 ; const targetValue = 5000 ; const expectedTargetProgressionLength = 366 ; const expectedStep = 13.661 ; const targetProgressModels = service . yearToDateTargetProgression ( year , targetValue ) ; expect ( targetProgressModels ) . not . toBeNull ( ) ; expect ( targetProgressModels . length ) . toEqual ( expectedTargetProgressionLength ) ; expect ( _ . floor ( targetProgressModels [ 1 ] . value - targetProgressModels [ 0 ] . value , 3 ) ) . toEqual ( expectedStep ) ; done ( ) ; } ) ; } ) ; describe ( "Rolling target progression" , ( ) => { it ( "should compute rolling distance target progression" , ( done ) => { const year = 2018 ; const targetValue = 140 ; const expectedTargetProgressionLength = 365 ; const targetProgressModels = service . rollingTargetProgression ( year , targetValue ) ; expect ( targetProgressModels ) . not . toBeNull ( ) ; expect ( targetProgressModels . length ) . toEqual ( expectedTargetProgressionLength ) ; expect ( Math . floor ( _ . first ( targetProgressModels ) . value ) ) . toEqual ( targetValue ) ; expect ( Math . floor ( _ . last ( targetProgressModels ) . value ) ) . toEqual ( targetValue ) ; const targetEachDays = Math . floor ( _ . sumBy ( targetProgressModels , ( targetProgressModel ) => { return targetProgressModel . value ; } ) / expectedTargetProgressionLength ) ; expect ( targetEachDays ) . toEqual ( targetValue ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "format human readable time" , ( ) => { it ( "should format 24 hours to human readable time" , ( done ) => { const hours = 24 ; const expected = "24h" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 24 hours to human readable time" , ( done ) => { const hours = 1 ; const expected = "1h" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 50 hours to human readable time" , ( done ) => { const hours = 50 ; const expected = "50h" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 76.25 hours to human readable time" , ( done ) => { const hours = 76.25 ; const expected = "76h, 15m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 29.5 hours to human readable time" , ( done ) => { const hours = 29.5 ; const expected = "29h, 30m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 15 hours to human readable time" , ( done ) => { const hours = 15 ; const expected = "15h" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 5.815 hours to human readable time" , ( done ) => { const hours = 5.815 ; const expected = "5h, 48m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 15.3333333 hours to human readable time" , ( done ) => { const hours = 15.3333333 ; const expected = "15h, 19m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 0.25 hours to human readable time" , ( done ) => { const hours = 0.25 ; const expected = "15m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format -12.5 negative hours to human readable time" , ( done ) => { const hours = - 12.5 ; const expected = "12h, 30m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 0 hours to human readable time" , ( done ) => { const hours = 0 ; const expected = "0h" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; it ( "should format 25.55 hours to human readable time" , ( done ) => { const hours = 25.55 ; const expected = "25h, 33m" ; const result = service . readableTimeProgress ( hours ) ; expect ( result ) . toEqual ( expected ) ; done ( ) ; } ) ; } ) ; describe ( "manage presets" , ( ) => { it ( "should list presets" , ( done ) => { const expected = [ new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) ] ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( expected ) ) ; const promise = service . fetchPresets ( ) ; promise . then ( ( list ) => { expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( list ) . not . toBeNull ( ) ; expect ( list ) . toEqual ( expected ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should add a year to date preset" , ( done ) => { const modelToBeAdded = new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Ride" , "VirtualRide" ] , true , true , 5000 ) ; const progressPresetModels = [ new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Ride" , "VirtualRide" ] , true , true ) , new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) , ] ; const expected = _ . union ( progressPresetModels , [ modelToBeAdded ] ) ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . returnValue ( Promise . resolve ( expected ) ) ; const promise = service . addPreset ( modelToBeAdded ) ; promise . then ( ( list ) => { expect ( list ) . not . toBeNull ( ) ; expect ( saveDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( list ) . toEqual ( expected ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should add a rolling progress preset" , ( done ) => { const modelToBeAdded = new RollingProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 , "Months" , 2 ) ; const progressPresetModels = [ new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) , new RollingProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 , "Months" , 1 ) ] ; const expected = _ . union ( progressPresetModels , [ modelToBeAdded ] ) ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . callThrough ( ) ; const promise = service . addPreset ( modelToBeAdded ) ; promise . then ( ( list ) => { expect ( list ) . not . toBeNull ( ) ; expect ( saveDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( list ) . toEqual ( expected ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should reject adding a year to date preset already existing (with target)" , ( done ) => { const modelToBeAdded = new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) ; const progressPresetModels = [ new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , modelToBeAdded , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) , ] ; const expectedErrorMessage = "You already saved this preset." ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . callThrough ( ) ; const promise = service . addPreset ( modelToBeAdded ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , ( error ) => { expect ( error ) . not . toBeNull ( ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveDaoSpy ) . not . toHaveBeenCalled ( ) ; expect ( error . code ) . toEqual ( AppError . YEAR_PROGRESS_PRESETS_ALREADY_EXISTS ) ; expect ( error . message ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should reject adding a year to date preset already existing (without target)" , ( done ) => { const modelToBeAdded = new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Ride" , "VirtualRide" ] , true , true ) ; const progressPresetModels = [ modelToBeAdded , new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) , ] ; const expectedErrorMessage = "You already saved this preset." ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . callThrough ( ) ; const promise = service . addPreset ( modelToBeAdded ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , ( error ) => { expect ( error ) . not . toBeNull ( ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveDaoSpy ) . not . toHaveBeenCalled ( ) ; expect ( error . code ) . toEqual ( AppError . YEAR_PROGRESS_PRESETS_ALREADY_EXISTS ) ; expect ( error . message ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should reject adding a rolling preset already existing" , ( done ) => { const modelToBeAdded = new RollingProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 , "Months" , 2 ) ; const progressPresetModels = [ modelToBeAdded , new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) , new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) , new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Ride" , "VirtualRide" ] , true , true ) ] ; const expectedErrorMessage = "You already saved this preset." ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . callThrough ( ) ; const promise = service . addPreset ( modelToBeAdded ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , ( error ) => { expect ( error ) . not . toBeNull ( ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveDaoSpy ) . not . toHaveBeenCalled ( ) ; expect ( error . code ) . toEqual ( AppError . YEAR_PROGRESS_PRESETS_ALREADY_EXISTS ) ; expect ( error . message ) . toEqual ( expectedErrorMessage ) ; done ( ) ; } ) ; } ) ; it ( "should delete preset" , ( done ) => { const model_0 = new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) ; const model_1 = new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) ; const model_2 = new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) ; const progressPresetModels = [ model_0 , model_1 , model_2 ] ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . returnValue ( Promise . resolve ( ) ) ; const deleteId = model_1 . id ; const promise = service . deletePreset ( deleteId ) ; promise . then ( ( ) => { expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveDaoSpy ) . toHaveBeenCalledWith ( [ model_0 , model_2 ] ) ; done ( ) ; } , error => { expect ( error ) . toBeNull ( ) ; done ( ) ; } ) ; } ) ; it ( "should reject delete preset" , ( done ) => { const model_0 = new YearToDateProgressPresetModel ( ProgressType . DISTANCE , [ "Run" ] , false , false ) ; const model_1 = new YearToDateProgressPresetModel ( ProgressType . ELEVATION , [ "Ride" ] , true , true , 5000 ) ; const model_2 = new YearToDateProgressPresetModel ( ProgressType . COUNT , [ "VirtualRide" ] , false , false ) ; const progressPresetModels = [ model_0 , model_1 , model_2 ] ; const fetchDaoSpy = spyOn ( service . yearProgressPresetDao , "fetch" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const saveDaoSpy = spyOn ( service . yearProgressPresetDao , "save" ) . and . returnValue ( Promise . resolve ( progressPresetModels ) ) ; const fakeId = Tools . genId ( ) ; const promise = service . deletePreset ( fakeId ) ; promise . then ( ( ) => { expect ( false ) . toBeTruthy ( "Whoops! I should not be here!" ) ; done ( ) ; } , ( error ) => { expect ( error ) . not . toBeNull ( ) ; expect ( fetchDaoSpy ) . toHaveBeenCalledTimes ( 1 ) ; expect ( saveDaoSpy ) . not . toHaveBeenCalled ( ) ; expect ( error . code ) . toEqual ( AppError . YEAR_PROGRESS_PRESETS_DO_NOT_EXISTS ) ; done ( ) ; } ) ; } ) ; } ) ; } ) ;