croma-core 1.0.0

Library-first ABC to MusicXML exporter
Documentation
use super::*;

#[test]
fn records_basic_field_and_note_spans() {
    let surface = analyze("X:1\nK:C\nC|z\n");

    assert_eq!(surface.tokens_of_kind(SurfaceKind::Field).count(), 2);
    assert_eq!(surface.tokens_of_kind(SurfaceKind::Note).count(), 1);
    assert_eq!(surface.tokens_of_kind(SurfaceKind::Barline).count(), 1);
    assert_eq!(surface.tokens_of_kind(SurfaceKind::Rest).count(), 1);
}

#[test]
fn records_spans_after_crlf_line_endings() {
    let source = SourceText::new("X:1\r\nK:C\r\nC|z");
    let surface = analyze_source(&source);

    let note = surface
        .tokens_of_kind(SurfaceKind::Note)
        .next()
        .expect("expected note token");

    assert_eq!(note.span, Span::new(10, 11));
    assert_eq!(source.slice(note.span), Some("C"));
}

#[test]
fn classifies_file_header_followed_by_multiple_tunes() {
    let source =
        SourceText::new("%abc-2.1\nM:4/4\nL:1/8\n\nX:1\nT:One\nK:C\nC|\n\nX:2\nT:Two\nK:G\nG|\n");
    let surface = analyze_source(&source);
    let line_map = &surface.line_map;

    assert_eq!(line_map.lines[0].kind, LineKind::VersionLine);
    assert_eq!(line_map.lines[1].context, LineContext::FileHeader);
    assert_eq!(line_map.lines[2].context, LineContext::FileHeader);
    assert_eq!(
        line_map.blocks_of_kind(SourceBlockKind::FileHeader).count(),
        1
    );
    assert_eq!(line_map.tunes.len(), 2);
    assert_eq!(line_map.tunes[0].header_line_start, 4);
    assert_eq!(line_map.tunes[0].body_line_start, 7);
    assert_eq!(line_map.tunes[0].terminator_line, Some(8));
    assert_eq!(line_map.tunes[1].header_line_start, 9);
    assert_eq!(line_map.lines[7].kind, LineKind::MusicCode);
    assert_eq!(line_map.lines[12].kind, LineKind::MusicCode);
}

#[test]
fn records_free_text_between_tunes() {
    let source =
        SourceText::new("X:1\nK:C\nC|\n\nnotes between tunes\nmore notes\n\nX:2\nK:G\nG|\n");
    let surface = analyze_source(&source);
    let line_map = &surface.line_map;

    assert_eq!(line_map.tunes.len(), 2);
    assert_eq!(line_map.lines[4].kind, LineKind::FreeText);
    assert_eq!(line_map.lines[5].kind, LineKind::FreeText);
    assert_eq!(line_map.lines[4].context, LineContext::FreeText);
    assert_eq!(
        line_map.blocks_of_kind(SourceBlockKind::FreeText).count(),
        1
    );
    assert_eq!(surface.tokens_of_kind(SurfaceKind::Note).count(), 2);
}

#[test]
fn k_field_ends_tune_header() {
    let surface = analyze("X:1\nT:Title\nK:C\nCDEF\n");
    let tune = surface
        .line_map
        .tunes
        .first()
        .expect("expected one tune block");

    assert_eq!(surface.line_map.lines[2].field_code(), Some('K'));
    assert_eq!(
        surface.line_map.lines[2].context,
        LineContext::TuneHeader { tune_index: 0 }
    );
    assert_eq!(
        surface.line_map.lines[3].context,
        LineContext::TuneBody { tune_index: 0 }
    );
    assert_eq!(tune.header_line_end, 3);
    assert_eq!(tune.body_line_start, 3);
}

#[test]
fn records_field_continuation_edges() {
    let source = SourceText::new("X:1\nT:Long title\n+:second line\nK:C\nC|\n");
    let surface = analyze_source(&source);
    let line_map = &surface.line_map;

    assert_eq!(line_map.lines[2].kind, LineKind::FieldContinuation);
    assert_eq!(
        line_map.lines[2].context,
        LineContext::TuneHeader { tune_index: 0 }
    );

    let edge = line_map
        .continuation_edges
        .iter()
        .find(|edge| edge.kind == ContinuationKind::FieldContinuation)
        .expect("expected field continuation edge");
    assert_eq!(edge.from_line, 1);
    assert_eq!(edge.to_line, 2);
    assert_eq!(source.slice(edge.marker_span), Some("+:"));
}

#[test]
fn records_backslash_suppressed_score_line_breaks() {
    let source = SourceText::new("X:1\nK:C\nC D \\\n% comment in between\n%%score V1\nE F\n");
    let surface = analyze_source(&source);
    let line_map = &surface.line_map;

    assert_eq!(
        line_map.lines[2].score_line_break,
        ScoreLineBreak::Suppressed {
            marker_span: Span::new(12, 13)
        }
    );

    let edge = line_map
        .continuation_edges
        .iter()
        .find(|edge| edge.kind == ContinuationKind::MusicBackslash)
        .expect("expected music continuation edge");
    assert_eq!(edge.from_line, 2);
    assert_eq!(edge.to_line, 5);
    assert_eq!(source.slice(edge.marker_span), Some("\\"));
}

#[test]
fn comments_before_and_after_music_do_not_terminate_tune() {
    let surface = analyze("X:1\nK:C\n% before\nC|\n% after\nD|\n");
    let line_map = &surface.line_map;

    assert_eq!(line_map.tunes.len(), 1);
    assert_eq!(line_map.lines[2].kind, LineKind::Comment);
    assert_eq!(
        line_map.lines[2].context,
        LineContext::TuneBody { tune_index: 0 }
    );
    assert_eq!(line_map.lines[4].kind, LineKind::Comment);
    assert_eq!(
        line_map.lines[4].context,
        LineContext::TuneBody { tune_index: 0 }
    );
    assert_eq!(line_map.lines[5].kind, LineKind::MusicCode);
    assert_eq!(line_map.tunes[0].terminator_line, None);
}

#[test]
fn directives_free_text_and_field_continuations_do_not_enter_note_stream() {
    let surface = analyze(
        "X:1\nT:Title\n+:Letters ABC\nK:C\n%%text ABC\nC|\n\nfree ABC text\n\nX:2\nK:G\nG|\n",
    );

    assert_eq!(surface.line_map.lines[2].kind, LineKind::FieldContinuation);
    assert_eq!(
        surface.line_map.lines[4].kind,
        LineKind::TypesetTextDirective
    );
    assert_eq!(surface.line_map.lines[7].kind, LineKind::FreeText);
    assert_eq!(surface.tokens_of_kind(SurfaceKind::Note).count(), 2);
}

#[test]
fn preserves_non_note_items_with_spans() {
    let source = SourceText::new("X:1\nT:Title\n+:More title\nK:C\nC % inline\n%%text printable\n");
    let surface = analyze_source(&source);
    let non_notes = &surface.line_map.non_note_items;

    let continuation = non_notes
        .iter()
        .find(|item| item.kind == NonNoteKind::FieldContinuation)
        .expect("expected field continuation item");
    assert_eq!(source.slice(continuation.span), Some("+:More title"));

    let inline_comment = non_notes
        .iter()
        .find(|item| item.kind == NonNoteKind::InlineComment)
        .expect("expected inline comment item");
    assert_eq!(source.slice(inline_comment.span), Some("% inline"));

    let typeset = non_notes
        .iter()
        .find(|item| item.kind == NonNoteKind::TypesetTextDirective)
        .expect("expected typeset text item");
    assert_eq!(source.slice(typeset.span), Some("%%text printable"));
}

#[test]
fn classifies_stylesheet_and_typeset_text_directives() {
    let surface =
        analyze("X:1\nK:C\n%%score V1\n%%text printable\n%%begintext\n%%inside\n%%endtext\nC|\n");
    let line_map = &surface.line_map;

    assert_eq!(line_map.lines[2].kind, LineKind::StylesheetDirective);
    assert_eq!(line_map.lines[3].kind, LineKind::TypesetTextDirective);
    assert_eq!(line_map.lines[4].kind, LineKind::TypesetTextDirective);
    assert_eq!(line_map.lines[5].kind, LineKind::TypesetTextDirective);
    assert_eq!(line_map.lines[6].kind, LineKind::TypesetTextDirective);
    assert_eq!(
        line_map
            .blocks_of_kind(SourceBlockKind::TypesetText)
            .count(),
        1
    );
}