Adds a full flashcards system: FSRS-based scheduling, cloze/basic card types, CSV import/export, media storage, and study UI, wired into navigation and the dashboard alongside existing question flows.
93 lines
3.7 KiB
C#
93 lines
3.7 KiB
C#
using System.Text.Json;
|
|
using QuestionsApi.Models;
|
|
using QuestionsApi.Services.Flashcards;
|
|
using Xunit;
|
|
|
|
namespace QuestionsApi.Tests;
|
|
|
|
public sealed class FlashcardImportExportModelTests
|
|
{
|
|
[Fact]
|
|
public void JsonExport_RoundTrips_NotesCardsAndReviewHistory()
|
|
{
|
|
var export = new FlashcardJsonExport
|
|
{
|
|
SchemaVersion = 1,
|
|
Settings = new FlashcardSettings { UserId = "user-1", NewCardsPerDay = 20, ReviewsPerDay = 200 },
|
|
Decks = [new FlashcardDeck { Id = "deck-1", UserId = "user-1", Name = "Cardio", IsDefault = true }],
|
|
Notes =
|
|
[
|
|
new FlashcardJsonExportNote
|
|
{
|
|
Note = new FlashcardNote
|
|
{
|
|
Id = "note-1", UserId = "user-1", DeckId = "deck-1", StableId = "stable-1",
|
|
Type = FlashcardTypes.Cloze, Prompt = "{{c1::Paris}} is the capital of France",
|
|
Tags = ["geography"]
|
|
},
|
|
Cards =
|
|
[
|
|
new FlashcardSchedule
|
|
{
|
|
Id = "card-1", UserId = "user-1", NoteId = "note-1", Variant = 1,
|
|
State = FlashcardScheduleStates.Review, Stability = 12.3, Difficulty = 4.5,
|
|
DueUtc = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc)
|
|
}
|
|
],
|
|
ReviewLogs =
|
|
[
|
|
new FlashcardReviewLog
|
|
{
|
|
Id = "log-1", UserId = "user-1", CardId = "card-1", Rating = FlashcardRatings.Good,
|
|
ReviewedUtc = new DateTime(2025, 12, 31, 0, 0, 0, DateTimeKind.Utc)
|
|
}
|
|
],
|
|
Images = [new FlashcardJsonExportImage { AssetId = "asset-1", FileName = "x.png", ContentType = "image/png", Base64Bytes = Convert.ToBase64String([1, 2, 3]) }]
|
|
}
|
|
]
|
|
};
|
|
|
|
var json = JsonSerializer.Serialize(export);
|
|
var roundTripped = JsonSerializer.Deserialize<FlashcardJsonExport>(json);
|
|
|
|
Assert.NotNull(roundTripped);
|
|
var note = Assert.Single(roundTripped!.Notes);
|
|
Assert.Equal("stable-1", note.Note.StableId);
|
|
Assert.Equal(FlashcardTypes.Cloze, note.Note.Type);
|
|
var card = Assert.Single(note.Cards);
|
|
Assert.Equal(1, card.Variant);
|
|
Assert.Equal(12.3, card.Stability);
|
|
var log = Assert.Single(note.ReviewLogs);
|
|
Assert.Equal(FlashcardRatings.Good, log.Rating);
|
|
var image = Assert.Single(note.Images);
|
|
Assert.Equal([1, 2, 3], Convert.FromBase64String(image.Base64Bytes));
|
|
}
|
|
|
|
[Fact]
|
|
public void Fingerprint_IsStable_ForSameTypePromptAndAnswer_IgnoringSurroundingWhitespace()
|
|
{
|
|
var a = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, " What is 2+2? ", "4");
|
|
var b = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "4");
|
|
|
|
Assert.Equal(a, b);
|
|
}
|
|
|
|
[Fact]
|
|
public void Fingerprint_DiffersWhenAnswerDiffers()
|
|
{
|
|
var a = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "4");
|
|
var b = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "What is 2+2?", "five");
|
|
|
|
Assert.NotEqual(a, b);
|
|
}
|
|
|
|
[Fact]
|
|
public void Fingerprint_DiffersByType_ForSamePromptAndAnswer()
|
|
{
|
|
var basic = FlashcardNoteStore.Fingerprint(FlashcardTypes.Basic, "text", "text");
|
|
var cloze = FlashcardNoteStore.Fingerprint(FlashcardTypes.Cloze, "text", "text");
|
|
|
|
Assert.NotEqual(basic, cloze);
|
|
}
|
|
}
|