import { TaskComponentState } from "../entities/task" ; import { TaskRepetitionOutcome } from "../event" ; import { createSpacedRepetitionScheduler , defaultSpacedRepetitionSchedulerConfiguration , } from "./spacedRepetitionScheduler" ; const scheduler = createSpacedRepetitionScheduler ( ) ; describe ( "first repetition" , ( ) => { const state = { createdAtTimestampMillis : 1000 , lastRepetitionTimestampMillis : null , dueTimestampMillis : 1000 , intervalMillis : 0 , } ; test ( "remembered almost immediately" , ( ) => { const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( state , 2000 , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis ) . toBe ( defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval ) ; expect ( dueTimestampMillis - ( 2000 + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "remembered with long delay" , ( ) => { const reviewTimestampMillis = state . dueTimestampMillis + defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( state , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis ) . toBe ( Math . floor ( defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 * defaultSpacedRepetitionSchedulerConfiguration . intervalGrowthFactor ) ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "forgotten" , ( ) => { const reviewTimestampMillis = 10000 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( state , reviewTimestampMillis , TaskRepetitionOutcome . Forgotten ) ; expect ( intervalMillis ) . toBe ( 0 ) ; expect ( dueTimestampMillis - reviewTimestampMillis ) . toMatchInlineSnapshot ( `600000` ) ; } ) ; } ) ; const testState = { createdAtTimestampMillis : 0 , lastRepetitionTimestampMillis : 1000 , dueTimestampMillis : defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 , intervalMillis : defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 , } ; describe ( "successful repetition" , ( ) => { test ( "typical successful repetition" , ( ) => { const reviewTimestampMillis = testState . dueTimestampMillis + 100000 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( testState , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis / ( testState . intervalMillis * defaultSpacedRepetitionSchedulerConfiguration . intervalGrowthFactor ) ) . toBeCloseTo ( 1 ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "very delayed successful repetition" , ( ) => { const reviewTimestampMillis = testState . dueTimestampMillis + testState . intervalMillis ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( testState , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis / testState . intervalMillis / defaultSpacedRepetitionSchedulerConfiguration . intervalGrowthFactor ) . toBeCloseTo ( 2 ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "too-early successful repetition" , ( ) => { const reviewTimestampMillis = testState . lastRepetitionTimestampMillis ! + testState . intervalMillis / 2.0 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( testState , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis / testState . intervalMillis ) . toBeGreaterThan ( 1 ) ; expect ( intervalMillis / testState . intervalMillis / defaultSpacedRepetitionSchedulerConfiguration . intervalGrowthFactor ) . toBeCloseTo ( 0.5 ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; } ) ; test . each ( [ { reviewTimestampMillis : testState . dueTimestampMillis + 100000 , label : "typical" , } , { reviewTimestampMillis : testState . dueTimestampMillis + testState . intervalMillis / 2 , label : "early" , } , { reviewTimestampMillis : testState . dueTimestampMillis + testState . intervalMillis * 2 , label : "late" , } , ] ) ( "forgotten repetition: $label" , ( { reviewTimestampMillis } ) => { const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( testState , reviewTimestampMillis , TaskRepetitionOutcome . Forgotten ) ; expect ( intervalMillis ) . toBeLessThan ( testState . intervalMillis ) ; expect ( dueTimestampMillis - reviewTimestampMillis ) . toMatchInlineSnapshot ( `600000` ) ; } ) ; describe ( "retrying" , ( ) => { test ( "successful repetition after retry, not yet successful" , ( ) => { const reviewTimestampMillis = 10000 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( { createdAtTimestampMillis : 0 , lastRepetitionTimestampMillis : 1000 , dueTimestampMillis : 1000 , intervalMillis : 0 , } , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis ) . toEqual ( defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "successful repetition after retry, with past success" , ( ) => { const lastRepetitionTimestampMillis = defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 4 ; const reviewTimestampMillis = lastRepetitionTimestampMillis + 10000 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( { createdAtTimestampMillis : 0 , lastRepetitionTimestampMillis , dueTimestampMillis : lastRepetitionTimestampMillis , intervalMillis : defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 , } , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis ) . toEqual ( defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; test ( "successful repetition after very delayed retry, with past success" , ( ) => { const lastRepetitionTimestampMillis = defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 4 ; const reviewTimestampMillis = lastRepetitionTimestampMillis + defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 4 ; const { dueTimestampMillis , intervalMillis } = scheduler . computeNextDueIntervalMillisForRepetition ( { createdAtTimestampMillis : 0 , lastRepetitionTimestampMillis , dueTimestampMillis : lastRepetitionTimestampMillis , intervalMillis : defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 , } , reviewTimestampMillis , TaskRepetitionOutcome . Remembered ) ; expect ( intervalMillis ) . toBeGreaterThan ( defaultSpacedRepetitionSchedulerConfiguration . initialReviewInterval * 2 ) ; expect ( dueTimestampMillis - ( reviewTimestampMillis + intervalMillis ) ) . toMatchInlineSnapshot ( `0` ) ; } ) ; } ) ;