Skip to content

Commit

Permalink
Feat/add the next method to return card and log results (#101)
Browse files Browse the repository at this point in the history
* add next method

* add test&doc
  • Loading branch information
ishiko732 committed Jul 21, 2024
1 parent ba57899 commit adb03e7
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
27 changes: 19 additions & 8 deletions __tests__/FSRSV5.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import {
} from '../src/fsrs'

describe('FSRS V5 ', () => {
const f: FSRS = fsrs({
w: [
0.4197, 1.1869, 3.0412, 15.2441, 7.1434, 0.6477, 1.0007, 0.0674, 1.6597,
0.1712, 1.1178, 2.0225, 0.0904, 0.3025, 2.1214, 0.2498, 2.9466, 0.4891,
0.6468,
],
enable_fuzz: false,
})
const w = [
0.4197, 1.1869, 3.0412, 15.2441, 7.1434, 0.6477, 1.0007, 0.0674, 1.6597,
0.1712, 1.1178, 2.0225, 0.0904, 0.3025, 2.1214, 0.2498, 2.9466, 0.4891,
0.6468,
]
const f: FSRS = fsrs({ w })
const grade: Grade[] = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy]
it('ivl_history', () => {
let card = createEmptyCard()
Expand Down Expand Up @@ -48,6 +46,9 @@ describe('FSRS V5 ', () => {
expect(scheduling_cards[check].log.elapsed_days).toEqual(
card.last_review ? now.diff(card.last_review as Date, 'days') : 0
)
const _f = fsrs({ w })
const next = _f.next(card, now, check)
expect(scheduling_cards[check]).toEqual(next)
}
card = scheduling_cards[rating].card
const ivl = card.scheduled_days
Expand Down Expand Up @@ -127,3 +128,13 @@ describe('get retrievability', () => {
})
})
})

describe('fsrs.next method', () => {
const fsrs = new FSRS({})
test('invalid grade', () => {
const card = createEmptyCard()
const now = new Date()
const g = Rating.Manual as unknown as Grade
expect(() => fsrs.next(card, now, g)).toThrow('Cannot review a manual rating')
})
})
46 changes: 46 additions & 0 deletions __tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,27 @@ describe('afterHandler', () => {
// }
// return record;
// }
function nextAfterHandler(recordLogItem: RecordLogItem) {
const recordItem = {
card: {
...(recordLogItem.card as Card & { cid: string }),
due: recordLogItem.card.due.getTime(),
state: State[recordLogItem.card.state] as StateType,
last_review: recordLogItem.card.last_review
? recordLogItem.card.last_review!.getTime()
: null,
},
log: {
...recordLogItem.log,
cid: (recordLogItem.card as Card & { cid: string }).cid,
due: recordLogItem.log.due.getTime(),
review: recordLogItem.log.review.getTime(),
state: State[recordLogItem.log.state] as StateType,
rating: Rating[recordLogItem.log.rating] as RatingType,
},
}
return recordItem
}

function forgetAfterHandler(recordLogItem: RecordLogItem): RepeatRecordLog {
return {
Expand Down Expand Up @@ -159,6 +180,31 @@ describe('afterHandler', () => {
}
})

it('next[afterHandler]', () => {
const emptyCardFormAfterHandler = createEmptyCard(now, cardAfterHandler)
for (const grade of Grades) {
const next = f.next(
emptyCardFormAfterHandler,
now,
grade,
nextAfterHandler
)
expect('card' in next).toEqual(true)
expect('log' in next).toEqual(true)

expect(Number.isSafeInteger(next.card.due)).toEqual(true)
expect(typeof next.card.state === 'string').toEqual(true)
expect(Number.isSafeInteger(next.card.last_review)).toEqual(true)

expect(Number.isSafeInteger(next.log.due)).toEqual(true)
expect(Number.isSafeInteger(next.log.review)).toEqual(true)
expect(typeof next.log.state === 'string').toEqual(true)
expect(typeof next.log.rating === 'string').toEqual(true)
expect(next.card.cid).toEqual('test001')
expect(next.log.cid).toEqual(next.card.cid)
}
})

it('rollback[afterHandler]', () => {
const emptyCardFormAfterHandler = createEmptyCard(now, cardAfterHandler)
const repeatFormAfterHandler = f.repeat(
Expand Down
76 changes: 76 additions & 0 deletions src/fsrs/fsrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CardInput,
DateInput,
FSRSParameters,
Grade,
Rating,
RecordLog,
RecordLogItem,
Expand All @@ -25,6 +26,7 @@ export class FSRS extends FSRSAlgorithm {
}

/**
* Display the collection of cards and logs for the four scenarios after scheduling the card at the current time.
* @param card Card to be processed
* @param now Current time or scheduled time
* @param afterHandler Convert the result to another type. (Optional)
Expand Down Expand Up @@ -96,6 +98,80 @@ export class FSRS extends FSRSAlgorithm {
}
}

/**
* Display the collection of cards and logs for the card scheduled at the current time, after applying a specific grade rating.
* @param card Card to be processed
* @param now Current time or scheduled time
* @param grade Rating of the review (Again, Hard, Good, Easy)
* @param afterHandler Convert the result to another type. (Optional)
* @example
* ```
* const card: Card = createEmptyCard(new Date());
* const f = fsrs();
* const recordLogItem = f.next(card, new Date(), Rating.Again);
* ```
* @example
* ```
* interface RevLogUnchecked
* extends Omit<ReviewLog, "due" | "review" | "state" | "rating"> {
* cid: string;
* due: Date | number;
* state: StateType;
* review: Date | number;
* rating: RatingType;
* }
*
* interface NextRecordLog {
* card: CardUnChecked; //see method: createEmptyCard
* log: RevLogUnchecked;
* }
*
function nextAfterHandler(recordLogItem: RecordLogItem) {
const recordItem = {
card: {
...(recordLogItem.card as Card & { cid: string }),
due: recordLogItem.card.due.getTime(),
state: State[recordLogItem.card.state] as StateType,
last_review: recordLogItem.card.last_review
? recordLogItem.card.last_review!.getTime()
: null,
},
log: {
...recordLogItem.log,
cid: (recordLogItem.card as Card & { cid: string }).cid,
due: recordLogItem.log.due.getTime(),
review: recordLogItem.log.review.getTime(),
state: State[recordLogItem.log.state] as StateType,
rating: Rating[recordLogItem.log.rating] as RatingType,
},
};
return recordItem
}
* const card: Card = createEmptyCard(new Date(), cardAfterHandler); //see method: createEmptyCard
* const f = fsrs();
* const recordLogItem = f.repeat(card, new Date(), Rating.Again, nextAfterHandler);
* ```
*/
next<R = RecordLog>(
card: CardInput | Card,
now: DateInput,
grade: Grade,
afterHandler?: (recordLog: RecordLogItem) => R
): R {
const Schduler = this.Schduler
const instace = new Schduler(card, now, this satisfies FSRSAlgorithm)
const g = TypeConvert.rating(grade)
if (g === Rating.Manual) {
throw new Error('Cannot review a manual rating')
}
const recordLogItem = instace.review(g)
if (afterHandler && typeof afterHandler === 'function') {
return afterHandler(recordLogItem)
} else {
return recordLogItem as R
}
}

/**
* Get the retrievability of the card
* @param card Card to be processed
Expand Down

0 comments on commit adb03e7

Please sign in to comment.