use std::{collections::HashMap, path::Path};
use anyhow::Result;
use tempfile::TempDir;
use trane::{
course_builder::{AssetBuilder, CourseBuilder},
course_library::CourseLibrary,
data::{
CourseGenerator, CourseManifest, ExerciseType, LessonManifestBuilder, MasteryScore,
UserPreferences,
course_generator::{
knowledge_base::KnowledgeBaseConfig,
literacy::LiteracyConfig,
transcription::{
Instrument, TranscriptionAsset, TranscriptionConfig, TranscriptionPassages,
TranscriptionPreferences,
},
},
},
test_utils::{TraneSimulation, assert_simulation_scores, init_simulation},
};
fn course_manifest(id: &str, generator: CourseGenerator) -> CourseManifest {
CourseManifest {
id: id.into(),
name: id.into(),
dependencies: vec![],
encompassed: vec![],
superseded: vec![],
description: None,
authors: None,
metadata: None,
course_material: None,
course_instructions: None,
generator_config: Some(generator),
}
}
fn course_builder(
id: &str,
generator: CourseGenerator,
asset_builders: Vec<AssetBuilder>,
) -> CourseBuilder {
CourseBuilder {
directory_name: id.into(),
course_manifest: course_manifest(id, generator),
lesson_manifest_template: LessonManifestBuilder::default(),
lesson_builders: vec![],
asset_builders,
}
}
fn assert_all_generated_exercises_visited(
library_root: &Path,
course_builder: CourseBuilder,
user_preferences: UserPreferences,
expected_exercises: usize,
) -> Result<()> {
let mut trane = init_simulation(library_root, &[course_builder], Some(&user_preferences))?;
let exercise_ids = trane.get_all_exercise_ids(None);
assert_eq!(exercise_ids.len(), expected_exercises);
let mut simulation = TraneSimulation::new(
exercise_ids.len() * 10,
Box::new(|_| Some(MasteryScore::Five)),
);
simulation.run_simulation(&mut trane, &vec![], &None)?;
assert_eq!(simulation.answer_history.len(), exercise_ids.len());
for exercise_id in exercise_ids {
assert!(
simulation.answer_history.contains_key(&exercise_id),
"exercise {exercise_id} should have been scheduled"
);
assert_simulation_scores(exercise_id, &trane, &simulation.answer_history)?;
}
Ok(())
}
#[test]
fn knowledge_base_course_generator() -> Result<()> {
let generator = CourseGenerator::KnowledgeBase(KnowledgeBaseConfig { inlined: true });
let course_builder = course_builder(
"knowledge_base_course",
generator,
vec![
AssetBuilder {
file_name: "lesson_0.lesson/exercise_0.front.md".into(),
contents: "Front 0".into(),
},
AssetBuilder {
file_name: "lesson_0.lesson/exercise_0.back.md".into(),
contents: "Back 0".into(),
},
AssetBuilder {
file_name: "lesson_1.lesson/exercise_1.front.md".into(),
contents: "Front 1".into(),
},
AssetBuilder {
file_name: "lesson_1.lesson/exercise_1.back.md".into(),
contents: "Back 1".into(),
},
AssetBuilder {
file_name: "lesson_1.lesson/lesson.dependencies.json".into(),
contents: "[\"lesson_0\"]".into(),
},
],
);
let temp_dir = TempDir::new()?;
assert_all_generated_exercises_visited(
temp_dir.path(),
course_builder,
UserPreferences::default(),
2,
)
}
#[test]
fn literacy_course_generator() -> Result<()> {
let generator = CourseGenerator::Literacy(LiteracyConfig {
generate_dictation: true,
exercise_type: ExerciseType::Procedural,
});
let course_builder = course_builder(
"literacy_course",
generator,
vec![
AssetBuilder {
file_name: "lesson_0.lesson/example_0.example.md".into(),
contents: "Example 0".into(),
},
AssetBuilder {
file_name: "lesson_1.lesson/example_1.example.md".into(),
contents: "Example 1".into(),
},
AssetBuilder {
file_name: "lesson_1.lesson/lesson.dependencies.json".into(),
contents: "[\"lesson_0\"]".into(),
},
],
);
let temp_dir = TempDir::new()?;
assert_all_generated_exercises_visited(
temp_dir.path(),
course_builder,
UserPreferences::default(),
4,
)
}
#[test]
fn transcription_course_generator() -> Result<()> {
let passage = TranscriptionPassages {
asset: TranscriptionAsset::Track {
short_id: "track".into(),
track_name: "Track".into(),
artist_name: None,
album_name: None,
duration: None,
external_link: None,
},
intervals: HashMap::from([(0, ("0:00".into(), "0:10".into()))]),
};
let generator = CourseGenerator::Transcription(TranscriptionConfig {
transcription_dependencies: vec![],
passage_directory: "passages".into(),
inlined_passages: vec![],
skip_singing_lessons: false,
skip_advanced_lessons: false,
});
let course_builder = course_builder(
"transcription_course",
generator,
vec![AssetBuilder {
file_name: "passages/track.json".into(),
contents: serde_json::to_string(&passage)?,
}],
);
let user_preferences = UserPreferences {
transcription: Some(TranscriptionPreferences {
instruments: vec![Instrument {
name: "Piano".into(),
id: "piano".into(),
}],
..Default::default()
}),
..Default::default()
};
let temp_dir = TempDir::new()?;
assert_all_generated_exercises_visited(temp_dir.path(), course_builder, user_preferences, 4)
}