Skip to content

Commit

Permalink
Merge branch 'sugarlabs:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
AliyanA1 authored Jan 30, 2025
2 parents e5c1841 + e908288 commit 527cd6e
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 24 deletions.
7 changes: 7 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,13 @@
accept=".json"
tabindex="-1"
/>
<input
class="file"
type="file"
id="audio"
accept=".mp3, .wav"
tabindex="-1"
/>
<input
class="file"
type="file"
Expand Down
64 changes: 64 additions & 0 deletions js/__tests__/background.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
describe("Browser Action Behavior", () => {
let mockBrowser;
let mockChrome;

beforeEach(() => {
// Mock objects
mockBrowser = {
browserAction: {
onClicked: { addListener: jest.fn() },
},
tabs: { create: jest.fn() },
runtime: {
onInstalled: { addListener: jest.fn() },
},
};

mockChrome = {
browserAction: {
onClicked: { addListener: jest.fn() },
},
runtime: {
onInstalled: { addListener: jest.fn() },
getURL: jest.fn((path) => `chrome-extension://fake-id/${path}`),
},
tabs: { create: jest.fn() },
};

global.browser = mockBrowser;
global.chrome = mockChrome;

Object.defineProperty(global.navigator, "userAgent", {
writable: true,
value: "",
});
});

afterEach(() => {
jest.clearAllMocks();
delete global.browser;
delete global.chrome;
});

it("should set up Firefox-specific listeners when user agent is Firefox", () => {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0";

jest.resetModules(); // Clear the module cache
const { isFirefox, browserAction } = require("../background.js");

expect(isFirefox).toBe(true);
expect(browserAction.onClicked.addListener).toHaveBeenCalledTimes(1);
expect(mockBrowser.runtime.onInstalled.addListener).toHaveBeenCalledTimes(1);
});

it("should set up Chrome-specific listeners when user agent is not Firefox", () => {
navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36";

jest.resetModules(); // Clear the module cache
const { isFirefox, browserAction } = require("../background.js");

expect(isFirefox).toBe(false);
expect(browserAction.onClicked.addListener).toHaveBeenCalledTimes(1);
expect(mockChrome.runtime.onInstalled.addListener).toHaveBeenCalledTimes(1);
});
});
151 changes: 151 additions & 0 deletions js/__tests__/mxml.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const saveMxmlOutput = require("../mxml");

describe("saveMxmlOutput", () => {
it("should return a valid XML string for a basic input", () => {
const logo = {
notation: {
notationStaging: {
"0": [
[["C"], 4, 0],
],
"1": []
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain("<?xml version='1.0' encoding='UTF-8'?>");
expect(output).toContain("<score-partwise version=\"3.1\">");
expect(output).toContain("<part-list>");
expect(output).toContain("<score-part id=\"P1\">");
expect(output).toContain("<part id=\"P1\">");
});

it("should handle multiple voices", () => {
const logo = {
notation: {
notationStaging: {
"0": [
[["C"], 4, 0],
[["D"], 4, 0]
],
"1": [
[["E"], 4, 0],
[["F"], 4, 0]
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain("<score-part id=\"P1\">");
expect(output).toContain("<score-part id=\"P2\">");
expect(output).toContain("<part id=\"P1\">");
expect(output).toContain("<part id=\"P2\">");
expect(output).toContain("<step>C</step>");
expect(output).toContain("<step>E</step>");
});

it("should ignore specified elements", () => {
const logo = {
notation: {
notationStaging: {
"0": [
"voice one",
[["C"], 4, 0],
"voice two"
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).not.toContain("voice one");
expect(output).not.toContain("voice two");
expect(output).toContain("<step>C</step>");
});

it("should handle tempo changes", () => {
const logo = {
notation: {
notationStaging: {
"0": [
"tempo", 120, 4,
[["C"], 4, 0]
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain("<sound tempo=\"120\"/>");
expect(output).toContain("<step>C</step>");
});

it("should handle meter changes", () => {
const logo = {
notation: {
notationStaging: {
"0": [
"meter", 3, 4,
[["C"], 4, 0]
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain("<time>");
expect(output).toContain("<beat-type>4</beat-type>");
expect(output).toContain("<step>C</step>");
});

it("should handle crescendo and decrescendo markings", () => {
const logo = {
notation: {
notationStaging: {
"0": [
"begin crescendo",
[["C"], 4, 0],
"end crescendo",
"begin decrescendo",
[["D"], 4, 0],
"end decrescendo"
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain('<wedge type="crescendo"/>');
expect(output).toContain('<wedge type="diminuendo"/>');
expect(output).toContain('<wedge type="stop"/>');
expect(output).toContain("<step>C</step>");
expect(output).toContain("<step>D</step>");
});

it("should handle tied notes", () => {
const logo = {
notation: {
notationStaging: {
"0": [
[["C"], 4, 0],
"tie",
[["C"], 4, 0]
]
}
}
};

const output = saveMxmlOutput(logo);

expect(output).toContain('<tie type="start"/>');
expect(output).toContain('<tie type="stop"/>');
});
});
131 changes: 131 additions & 0 deletions js/__tests__/notation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const Notation = require('../notation');
const { durationToNoteValue, convertFactor } = require('../utils/musicutils');
global.convertFactor = convertFactor;
global.durationToNoteValue = durationToNoteValue;

jest.mock('../utils/musicutils.js', function() {
return {
durationToNoteValue: jest.fn().mockReturnValue([1, 1, 1, 1]),
convertFactor: jest.fn().mockReturnValue(4),
};
});

describe('Notation Class', () => {
let notation;

beforeEach(() => {
const mockActivity = {
turtles: {
ithTurtle: jest.fn().mockReturnValue({
singer: {
staccato: []
}
})
},
logo: {
updateNotation: jest.fn()
}
};
notation = new Notation(mockActivity);
notation._notationStaging = [[]];
notation._notationStaging['turtle1'] = [];
notation._notationDrumStaging['turtle1'] = [];
notation._pickupPOW2['turtle1'] = false;
notation._pickupPoint['turtle1'] = null;
});

describe('Setters and Getters', () => {
it('should correctly set and get notationStaging', () => {
const turtle = 'turtle1';
const staging = { 'turtle1': ['note1', 'note2'] };
notation.notationStaging = staging;
expect(notation.notationStaging).toEqual(staging);
});

it('should correctly set and get notationDrumStaging', () => {
const turtle = 'turtle1';
const drumStaging = { 'turtle1': ['drum1', 'drum2'] };
notation.notationDrumStaging = drumStaging;
expect(notation.notationDrumStaging).toEqual(drumStaging);
});

it('should correctly set and get notationMarkup', () => {
const markup = { 'turtle1': ['markup1', 'markup2'] };
notation.notationMarkup = markup;
expect(notation.notationMarkup).toEqual(markup);
});

it('should correctly return pickupPOW2 and pickupPoint', () => {
expect(notation.pickupPOW2).toEqual({ turtle1: false });
expect(notation.pickupPoint).toEqual({ turtle1: null });
});
});

describe('Notation Utility Methods', () => {
it('should update notation correctly with doUpdateNotation', () => {
const note = 'C4';
const duration = 4;
const turtle = 'turtle1';
const insideChord = false;
const drum = [];
notation.doUpdateNotation(note, duration, turtle, insideChord, drum);
expect(notation._notationStaging[turtle]).toContainEqual(expect.arrayContaining([note, expect.any(Number), expect.any(Number)]));
});

it('should add notation markup using _notationMarkup', () => {
const turtle = 'turtle1';
const markup = 'staccato';
const below = true;
notation._notationMarkup(turtle, markup, below);
expect(notation._notationStaging[turtle]).toEqual(["markdown", "staccato"]);
});

it('should add a pickup using notationPickup', () => {
const turtle = 'turtle1';
const factor = 2;

notation.notationPickup(turtle, factor);
expect(convertFactor).toHaveBeenCalledWith(factor);
expect(notation._notationStaging[turtle]).toEqual(['pickup', 4]);
});

it('should add a voice using notationVoices', () => {
const turtle = 'turtle1';
const voiceNumber = 2;
notation.notationVoices(turtle, voiceNumber);
expect(notation._notationStaging[turtle]).toContain('voice two');
});

it('should set meter using notationMeter', () => {
const turtle = 'turtle1';
const count = 4;
const value = 4;
notation.notationMeter(turtle, count, value);
expect(notation._notationStaging[turtle]).toEqual(['meter', count, value]);
});


it('should handle notationSwing', () => {
const turtle = 'turtle1';
notation.notationSwing(turtle);
expect(notation._notationStaging[turtle]).toContain('swing');
});

it('should handle notationTempo', () => {
const turtle = 'turtle1';
const bpm = 120;
const beatValue = 4;
notation.notationTempo(turtle, bpm, beatValue);
expect(notation._notationStaging[turtle]).toEqual(['tempo', bpm, 4]);
});
});

describe('Edge Cases', () => {
it('should handle empty or invalid input for notationPickup', () => {
const turtle = 'turtle1';
const factor = 0;
notation.notationPickup(turtle, factor);
expect(notation._notationStaging[turtle].length).toBe(0);
});
});
});
Loading

0 comments on commit 527cd6e

Please sign in to comment.