Skip to content

Latest commit

 

History

History
85 lines (68 loc) · 3.49 KB

File metadata and controls

85 lines (68 loc) · 3.49 KB

testing

Test-only infrastructure for exercising mdom. Nothing here ships in the public API (index.ts does not re-export it); it exists so tests can describe music instead of hand-writing MusicXML strings.

testing.musicxml

A lightweight MusicXML builder. The deliberate non-goal is being a complete MusicXML model (that is what @stringsync/musicxml is for, and it is too heavy-handed for fixtures). The goal is: describe the music a test cares about in a few lines, and emit valid MusicXML for mdom.parse.

There are two entry points, both namespaces with .partwise, .timewise, and .flavored taking the same builder callback. score returns a MusicXML string for mdom.parse:

const xml = score.partwise((s) => {
  s.part('Violin I', (p) => {
    p.measure({ time: [3, 4], key: 2, clef: ['G', 2], tempo: 120 }, (m) => {
      m.note('C4');
      m.note('E4', 1, (n) => n.staccato().slur('start'));
      m.note('G4', 1, (n) => n.slur('stop'));
    });
  });
});

element mirrors score but returns the parsed xml-js root element instead of a string. normalize() consumes that element rather than a string, so its unit tests describe music through the same builder — xml-js stays an implementation detail of testing, never imported by a test, and the hand-written-XML temptation is removed even for normalize-level coverage.

Nesting is expressed with closures, not chained .end() calls — scope is unambiguous and arbitrary depth (part → measure → note → mods) reads the way the hierarchy nests.

testing.durations

Tests think in quarter notes (matching mdom.timing), never in <divisions> ticks. A duration argument is quarter notes (default 1); the exported durations namespace names them — durations.eighth is 0.5, with parallel durations.dotted and durations.triplet namespaces so durations.triplet.eighth is 1/3 and durations.dotted.half is 3 — so call sites read as music and the 3:2 / dotted intent isn't hidden behind a bare number. Raw numbers still work. The builder derives the minimal integer <divisions> for the whole part so every duration lands on a tick, and emits <backup>/<forward> for multi-voice writing. score-level divisions(n) forces a specific value when a test asserts on it.

testing.pitch

A pitch is a scientific-notation string ('C4', 'C#4', 'Bb3', 'F##5') or { step, octave, alter }. A chord is one call with several pitches; a rest takes no pitch.

m.note('C#4', 0.5);
m.chord(['C4', 'E4', 'G4'], 2);
m.rest(1);

testing.mods

The optional last argument to note/rest/chord is a callback over a chainable mod configurator covering the mdom.mods surface — ties, slurs, beams, tuplets, articulations, ornaments, dynamics, lyrics — plus voice/staff placement and type/dot overrides. Spanning mods (slur, tie, beam) emit their MusicXML start/stop endpoints on the hosting notes; reconstructing the span is mdom's job, not the builder's.

testing.escapes

The builder models only what fixtures commonly need. For anything else — exotic elements, deliberately invalid or unsupported MusicXML, both score flavors — there is always a raw escape:

  • score.timewise(build) emits score-timewise instead of the default score-partwise (score.partwise(build)), so both mdom.parse normalization paths are testable from one description.
  • raw(xml) exists at score, measure, and note scope to inject verbatim XML, so a test never has to extend the builder to cover a one-off case.