OSMD_Test.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import chai = require("chai");
  2. import { OpenSheetMusicDisplay } from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
  3. import { TestUtils } from "../../Util/TestUtils";
  4. import { VoiceEntry, Instrument, Note, Staff, Voice, GraphicalStaffEntry, GraphicalNote,
  5. Fraction, Pitch, AccidentalEnum, DrawingParametersEnum, IOSMDOptions, Cursor } from "../../../src";
  6. describe("OpenSheetMusicDisplay Main Export", () => {
  7. let container1: HTMLElement;
  8. it("no container", (done: Mocha.Done) => {
  9. chai.expect(() => {
  10. return new OpenSheetMusicDisplay(undefined);
  11. }).to.throw(/container/);
  12. done();
  13. });
  14. it("container", (done: Mocha.Done) => {
  15. const div: HTMLElement = TestUtils.getDivElement(document);
  16. chai.expect(() => {
  17. return new OpenSheetMusicDisplay(div);
  18. }).to.not.throw(Error);
  19. done();
  20. });
  21. it("multiple instances", () => {
  22. const musicSheetFragmentContainer: HTMLElement = TestUtils.getDivElement(document);
  23. const fullMusicSheetContainer: HTMLElement = TestUtils.getDivElement(document);
  24. const musicSheetFragmentOptions: IOSMDOptions = {
  25. drawComposer: false,
  26. drawCredits: false,
  27. drawFingerings: false,
  28. drawHiddenNotes: false,
  29. drawLyricist: false,
  30. drawPartAbbreviations: false,
  31. drawPartNames: false,
  32. drawSubtitle: false,
  33. drawTitle: false,
  34. drawUpToMeasureNumber: 1,
  35. drawingParameters: DrawingParametersEnum.compact
  36. };
  37. const fullMusicSheetOptions: IOSMDOptions = {
  38. drawUpToMeasureNumber: 10
  39. };
  40. const musicSheetFragment: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(
  41. musicSheetFragmentContainer,
  42. musicSheetFragmentOptions
  43. );
  44. const fullMusicSheet: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(fullMusicSheetContainer, fullMusicSheetOptions);
  45. const musicSheet: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  46. const musicSheetXML: string = new XMLSerializer().serializeToString(musicSheet);
  47. return musicSheetFragment.load(musicSheetXML)
  48. .then(() => {
  49. musicSheetFragment.render();
  50. return fullMusicSheet.load(musicSheetXML);
  51. })
  52. .then(() => {
  53. fullMusicSheet.render();
  54. // Verify that the music sheet fragment has its options set correctly.
  55. chai.expect(musicSheetFragment.Sheet.Rules.RenderComposer).to.equal(musicSheetFragmentOptions.drawComposer);
  56. chai.expect(musicSheetFragment.Sheet.Rules.RenderFingerings).to.equal(musicSheetFragmentOptions.drawFingerings);
  57. chai.expect(musicSheetFragment.Sheet.Rules.RenderLyricist).to.equal(musicSheetFragmentOptions.drawLyricist);
  58. chai.expect(musicSheetFragment.Sheet.Rules.RenderPartAbbreviations).to.equal(musicSheetFragmentOptions.drawPartAbbreviations);
  59. chai.expect(musicSheetFragment.Sheet.Rules.RenderPartNames).to.equal(musicSheetFragmentOptions.drawPartNames);
  60. chai.expect(musicSheetFragment.Sheet.Rules.RenderSubtitle).to.equal(musicSheetFragmentOptions.drawSubtitle);
  61. chai.expect(musicSheetFragment.Sheet.Rules.RenderTitle).to.equal(musicSheetFragmentOptions.drawTitle);
  62. chai.expect(musicSheetFragment.Sheet.Rules.MaxMeasureToDrawIndex).to.equal(musicSheetFragmentOptions.drawUpToMeasureNumber - 1);
  63. // Verify that the full music sheet has its options set correctly.
  64. chai.expect(fullMusicSheet.Sheet.Rules.RenderComposer).to.not.equal(musicSheetFragmentOptions.drawComposer);
  65. chai.expect(fullMusicSheet.Sheet.Rules.RenderFingerings).to.not.equal(musicSheetFragmentOptions.drawFingerings);
  66. chai.expect(fullMusicSheet.Sheet.Rules.RenderLyricist).to.not.equal(musicSheetFragmentOptions.drawLyricist);
  67. chai.expect(fullMusicSheet.Sheet.Rules.RenderPartAbbreviations).to.not.equal(musicSheetFragmentOptions.drawPartAbbreviations);
  68. chai.expect(fullMusicSheet.Sheet.Rules.RenderPartNames).to.not.equal(musicSheetFragmentOptions.drawPartNames);
  69. chai.expect(fullMusicSheet.Sheet.Rules.RenderSubtitle).to.not.equal(musicSheetFragmentOptions.drawSubtitle);
  70. chai.expect(fullMusicSheet.Sheet.Rules.RenderTitle).to.not.equal(musicSheetFragmentOptions.drawTitle);
  71. chai.expect(fullMusicSheet.Sheet.Rules.MaxMeasureToDrawIndex).to.equal(fullMusicSheetOptions.drawUpToMeasureNumber - 1);
  72. });
  73. });
  74. it("load MXL from string", (done: Mocha.Done) => {
  75. const mxl: string = TestUtils.getMXL("Mozart_Clarinet_Quintet_Excerpt.mxl");
  76. const div: HTMLElement = TestUtils.getDivElement(document);
  77. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  78. opensheetmusicdisplay.load(mxl).then(
  79. (_: {}) => {
  80. opensheetmusicdisplay.render();
  81. done();
  82. },
  83. done
  84. );
  85. });
  86. it("load invalid MXL from string", (done: Mocha.Done) => {
  87. const mxl: string = "\x50\x4b\x03\x04";
  88. const div: HTMLElement = TestUtils.getDivElement(document);
  89. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  90. opensheetmusicdisplay.load(mxl).then(
  91. (_: {}) => {
  92. done(new Error("Corrupted MXL appears to be loaded correctly"));
  93. },
  94. (exc: Error) => {
  95. if (exc.message.toLowerCase().match(/invalid/)) {
  96. done();
  97. } else {
  98. done(new Error("Unexpected error: " + exc.message));
  99. }
  100. }
  101. );
  102. });
  103. it("load XML string", (done: Mocha.Done) => {
  104. const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  105. const xml: string = new XMLSerializer().serializeToString(score);
  106. const div: HTMLElement = TestUtils.getDivElement(document);
  107. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  108. opensheetmusicdisplay.load(xml).then(
  109. (_: {}) => {
  110. opensheetmusicdisplay.render();
  111. done();
  112. },
  113. done
  114. );
  115. });
  116. it("load XML Document", (done: Mocha.Done) => {
  117. const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  118. const div: HTMLElement = TestUtils.getDivElement(document);
  119. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  120. opensheetmusicdisplay.load(score).then(
  121. (_: {}) => {
  122. opensheetmusicdisplay.render();
  123. done();
  124. },
  125. done
  126. );
  127. });
  128. it.skip("Timeout from server", (done: Mocha.Done) => {
  129. // TODO this test times out from time to time, even with osmd.loadUrlTimeout set to 5000.
  130. // the test is unreliable, which makes it hard to test.
  131. // also, it's better not to use OSMD to fetch one's score anyways.
  132. // also, the timeout adds unnecessary time to the testing suite.
  133. const score: string = "https://httpstat.us/408";
  134. const div: HTMLElement = TestUtils.getDivElement(document);
  135. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  136. opensheetmusicdisplay.load(score).then(
  137. (_: {}) => {
  138. done(new Error("Unexpected response from server"));
  139. },
  140. (exc: Error) => {
  141. done();
  142. }
  143. );
  144. });
  145. it("load MXL Document by URL", (done: Mocha.Done) => {
  146. const url: string = "base/test/data/Mozart_Clarinet_Quintet_Excerpt.mxl";
  147. const div: HTMLElement = TestUtils.getDivElement(document);
  148. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  149. opensheetmusicdisplay.load(url).then(
  150. (_: {}) => {
  151. opensheetmusicdisplay.render();
  152. done();
  153. },
  154. done
  155. );
  156. });
  157. // skip: this test is unnecessary and creates traffic (to google)
  158. it.skip("load something invalid by URL", (done: Mocha.Done) => {
  159. const url: string = "https://www.google.com";
  160. const div: HTMLElement = TestUtils.getDivElement(document);
  161. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  162. opensheetmusicdisplay.load(url).then(
  163. (_: {}) => {
  164. done(new Error("Invalid URL appears to be loaded correctly"));
  165. },
  166. (exc: Error) => {
  167. if (exc.message.toLowerCase().match(/opensheetmusicdisplay.*invalid/)) {
  168. done();
  169. } else {
  170. done(new Error("Unexpected error: " + exc.message));
  171. }
  172. }
  173. );
  174. }).timeout(5000);
  175. it("load invalid URL", (done: Mocha.Done) => {
  176. const url: string = "https://www.afjkhfjkauu2ui3z2uiu.com";
  177. const div: HTMLElement = TestUtils.getDivElement(document);
  178. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  179. opensheetmusicdisplay.load(url).then(
  180. (_: {}) => {
  181. done(new Error("Invalid URL appears to be loaded correctly"));
  182. },
  183. (exc: Error) => {
  184. if (exc.message.toLowerCase().match(/url/)) {
  185. done();
  186. } else {
  187. done(new Error("Unexpected error: " + exc.message));
  188. }
  189. }
  190. );
  191. }).timeout(5000);
  192. it("load invalid XML string", (done: Mocha.Done) => {
  193. const xml: string = "<?xml";
  194. const div: HTMLElement = TestUtils.getDivElement(document);
  195. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  196. opensheetmusicdisplay.load(xml).then(
  197. (_: {}) => {
  198. done(new Error("Corrupted XML appears to be loaded correctly"));
  199. },
  200. (exc: Error) => {
  201. if (exc.message.toLowerCase().match(/partwise/)) {
  202. done();
  203. } else {
  204. done(new Error("Unexpected error: " + exc.message));
  205. }
  206. }
  207. );
  208. });
  209. it("render without loading", (done: Mocha.Done) => {
  210. const div: HTMLElement = TestUtils.getDivElement(document);
  211. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  212. chai.expect(() => {
  213. return opensheetmusicdisplay.render();
  214. }).to.throw(/load/);
  215. done();
  216. });
  217. before((): void => {
  218. // Create the container for the "test width" test
  219. container1 = TestUtils.getDivElement(document);
  220. });
  221. after((): void => {
  222. // Destroy the container for the "test width" test
  223. document.body.removeChild(container1);
  224. });
  225. it("test width 500", (done: Mocha.Done) => {
  226. const div: HTMLElement = container1;
  227. div.style.width = "500px";
  228. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  229. const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  230. opensheetmusicdisplay.load(score).then(
  231. (_: {}) => {
  232. opensheetmusicdisplay.render();
  233. chai.expect(div.offsetWidth).to.equal(500);
  234. done();
  235. },
  236. done
  237. ).catch(done);
  238. });
  239. it("test width 200", (done: Mocha.Done) => {
  240. const div: HTMLElement = container1;
  241. div.style.width = "200px";
  242. const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
  243. const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  244. opensheetmusicdisplay.load(score).then(
  245. (_: {}) => {
  246. opensheetmusicdisplay.render();
  247. chai.expect(div.offsetWidth).to.equal(200);
  248. done();
  249. },
  250. done
  251. ).catch(done);
  252. });
  253. describe("cursor with hidden instrument", () => {
  254. let osmd: OpenSheetMusicDisplay;
  255. beforeEach(() => {
  256. const div: HTMLElement = TestUtils.getDivElement(document);
  257. osmd = TestUtils.createOpenSheetMusicDisplay(div);
  258. const score: Document =
  259. TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  260. return osmd.load(score)
  261. .then(() => {
  262. osmd.render();
  263. });
  264. });
  265. it("should move cursor after instrument is hidden", () => {
  266. osmd.Sheet.Instruments[1].Visible = false;
  267. osmd.render();
  268. osmd.cursors[0].show();
  269. for (let i: number = 0; i < 100; i++) {
  270. osmd.cursors[0].next();
  271. }
  272. // After 100 steps in the visible score, cursor reached 3rd note from 17, a C
  273. chai.expect(osmd.cursors[0].NotesUnderCursor()[0].halfTone).to.equal(60);
  274. });
  275. });
  276. describe("cursor", () => {
  277. let opensheetmusicdisplay: OpenSheetMusicDisplay;
  278. beforeEach((done: Mocha.Done) => {
  279. const div: HTMLElement = container1;
  280. opensheetmusicdisplay = TestUtils.createOpenSheetMusicDisplay(div);
  281. const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
  282. opensheetmusicdisplay.load(score).then(
  283. (_: {}) => {
  284. opensheetmusicdisplay.render();
  285. opensheetmusicdisplay.cursors[0].show();
  286. done();
  287. },
  288. done
  289. ).catch(done);
  290. });
  291. describe("next() and previous()", () => {
  292. it("is able to advance past end and beginning of sheet", () => {
  293. const cursor: Cursor = opensheetmusicdisplay.cursors[0];
  294. chai.expect(cursor.NotesUnderCursor().length).to.greaterThanOrEqual(1);
  295. cursor.previous(); // do previous from first timestamp in sheet ("beyond beginning")
  296. chai.expect(cursor.NotesUnderCursor().length).to.equal(0);
  297. cursor.next();
  298. chai.expect(cursor.NotesUnderCursor().length).to.greaterThanOrEqual(1);
  299. chai.expect(cursor.Iterator.currentTimeStamp.RealValue).to.equal(0);
  300. // go past end of sheet
  301. for (let i: number = 1; i <= 520; i++) { // 260 used to be enough when next() apparently ignored repeats
  302. cursor.next(); // go past end of sheet: after 258 times in Clementi 36/1/1, the last timestamp is reached
  303. }
  304. chai.expect(cursor.Iterator.EndReached).to.equal(true);
  305. // try to go back again after going beyond end of sheet
  306. cursor.previous();
  307. cursor.previous();
  308. chai.expect(cursor.Iterator.EndReached).to.equal(false);
  309. });
  310. });
  311. describe("get AllVoicesUnderCursor", () => {
  312. it("retrieves all voices under cursor", () => {
  313. const voiceEntries: VoiceEntry[] = opensheetmusicdisplay.cursors[0].VoicesUnderCursor();
  314. chai.expect(voiceEntries.length).to.equal(2);
  315. });
  316. });
  317. describe("VoicesUnderCursor", () => {
  318. it("retrieves voices for a specific instrument under cursor", () => {
  319. const voiceEntries: VoiceEntry[] = opensheetmusicdisplay.cursors[0].VoicesUnderCursor();
  320. chai.expect(voiceEntries.length).to.equal(2);
  321. });
  322. it("retrieves all voices under cursor when instrument not specified", () => {
  323. const instrument: Instrument = opensheetmusicdisplay.Sheet.Instruments[1];
  324. const voiceEntries: VoiceEntry[] = opensheetmusicdisplay.cursors[0].VoicesUnderCursor(instrument);
  325. chai.expect(voiceEntries.length).to.equal(1);
  326. });
  327. });
  328. describe("NotesUnderCursor", () => {
  329. it("gets notes for a specific instrument under cursor", () => {
  330. const instrument: Instrument = opensheetmusicdisplay.Sheet.Instruments[0];
  331. const notes: Note[] = opensheetmusicdisplay.cursors[0].NotesUnderCursor(instrument);
  332. chai.expect(notes.length).to.equal(1);
  333. });
  334. it("gets all notes under cursor when instrument unspecified", () => {
  335. const notes: Note[] = opensheetmusicdisplay.cursors[0].NotesUnderCursor();
  336. chai.expect(notes.length).to.equal(2);
  337. });
  338. });
  339. describe("updateGraphic", () => {
  340. it("updates the graphical sheet with mutations on the music sheet", () => {
  341. const staff: Staff = opensheetmusicdisplay.Sheet.Staves[0];
  342. const voice: Voice = staff.Voices[0];
  343. const voiceEntry: VoiceEntry = voice.VoiceEntries[0];
  344. const numNotesBefore: number = voiceEntry.Notes.length;
  345. // Validate current state
  346. {
  347. const graphicalStaffEntry: GraphicalStaffEntry = opensheetmusicdisplay.GraphicSheet.getStaffEntry(0);
  348. const graphicalNotes: GraphicalNote[] = graphicalStaffEntry.findVoiceEntryGraphicalNotes(voiceEntry);
  349. chai.expect(graphicalNotes.length).to.equal(numNotesBefore);
  350. }
  351. const newNote: Note = new Note(
  352. voiceEntry,
  353. voiceEntry.ParentSourceStaffEntry,
  354. new Fraction(1),
  355. new Pitch(11, 2, AccidentalEnum.NATURAL),
  356. voiceEntry.ParentSourceStaffEntry.VerticalContainerParent.ParentMeasure);
  357. // note: if the pitch is such that the voice entry frequencies aren't ordered correctly,
  358. // Vexflow will complain about unsorted pitches. see below
  359. voiceEntry.Notes.push(newNote);
  360. // we could do something like voiceEntry.sort() here to prevent the Vexflow warning about unsorted pitches,
  361. // but for now sort() only exists on GraphicalVoiceEntry.
  362. opensheetmusicdisplay.updateGraphic();
  363. {
  364. const graphicalStaffEntry: GraphicalStaffEntry = opensheetmusicdisplay.GraphicSheet.getStaffEntry(0);
  365. const graphicalNotes: GraphicalNote[] = graphicalStaffEntry.findVoiceEntryGraphicalNotes(voiceEntry);
  366. chai.expect(graphicalNotes.length).to.equal(numNotesBefore + 1);
  367. }
  368. });
  369. });
  370. });
  371. });