Skip to content

Commit 68a0be9

Browse files
authored
test suite for js/blocks/ExtrasBlocks.js (#4552)
* Created test for js/blocks/ExtrasBlocks.test.js * Exporting modules for test * Adding license
1 parent 110c008 commit 68a0be9

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

js/blocks/ExtrasBlocks.js

+4
Original file line numberDiff line numberDiff line change
@@ -865,3 +865,7 @@ function setupExtrasBlocks(activity) {
865865
new NOPThreeArgBlock().setup(activity);
866866
new NOPFourArgBlock().setup(activity);
867867
}
868+
if (typeof module !== "undefined" && module.exports) {
869+
module.exports = { setupExtrasBlocks };
870+
}
871+
+284
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/**
2+
* @license
3+
* MusicBlocks v3.4.1
4+
* Copyright (C) 2025 Om Santosh Suneri
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
global.LeftBlock = class {
20+
setPalette = jest.fn();
21+
setHelpString = jest.fn();
22+
formBlock = jest.fn();
23+
setup = jest.fn();
24+
makeMacro = jest.fn();
25+
beginnerBlock = jest.fn();
26+
updateDockValue = jest.fn();
27+
28+
flow = jest.fn().mockImplementation(function (args = [], logo = {}, turtle) {
29+
if (args.includes("test.abc")) {
30+
this.activity.save.afterSaveAbc("test.abc");
31+
}
32+
if (args.includes("test.ly")) {
33+
this.activity.save.afterSaveLilypond("test.ly");
34+
}
35+
if (args.includes("test.svg")) {
36+
this.activity.save.saveSVG("test.svg");
37+
}
38+
if (this instanceof this.activity.blocks.NoBackgroundBlock) {
39+
logo.svgBackground = false;
40+
}
41+
if (this instanceof this.activity.blocks.WaitBlock) {
42+
this.activity.turtles.ithTurtle(turtle);
43+
}
44+
if (this instanceof this.activity.blocks.DrumBlock) {
45+
return [1, 1];
46+
}
47+
if (this instanceof this.activity.blocks.ShowBlocksBlock) {
48+
this.activity.blocks.showBlocks();
49+
}
50+
if (this instanceof this.activity.blocks.HideBlocksBlock) {
51+
this.activity.blocks.hideBlocks();
52+
}
53+
if (this instanceof this.activity.blocks.CommentBlock) {
54+
this.activity.textMsg(args[0]);
55+
}
56+
if (this instanceof this.activity.blocks.PrintBlock) {
57+
this.activity.textMsg(args[0]);
58+
}
59+
if (this instanceof this.activity.blocks.DisplayGridBlock) {
60+
if (args[0] === "Cartesian") {
61+
this.activity.blocks.activity._showCartesian();
62+
} else if (args[0] === "polar") {
63+
this.activity.blocks.activity._showPolar();
64+
}
65+
}
66+
return args;
67+
});
68+
69+
arg = jest.fn().mockImplementation(() => "parsedArg");
70+
};
71+
72+
global.FlowBlock = class extends global.LeftBlock {};
73+
global.ValueBlock = class extends global.LeftBlock {};
74+
global.StackClampBlock = class extends global.LeftBlock {};
75+
76+
global._ = jest.fn((str) => str);
77+
78+
const { setupExtrasBlocks } = require("../ExtrasBlocks");
79+
80+
describe("ExtrasBlocks", () => {
81+
let activity, logo, turtle;
82+
83+
beforeEach(() => {
84+
activity = {
85+
blocks: {
86+
blockList: {},
87+
showBlocks: jest.fn(),
88+
hideBlocks: jest.fn(),
89+
activity: {
90+
_showCartesian: jest.fn(),
91+
_showPolar: jest.fn(),
92+
_showTreble: jest.fn(),
93+
_showGrand: jest.fn(),
94+
_showSoprano: jest.fn(),
95+
_showAlto: jest.fn(),
96+
_showTenor: jest.fn(),
97+
_showBass: jest.fn(),
98+
}
99+
},
100+
turtles: {
101+
ithTurtle: jest.fn(() => ({
102+
singer: {
103+
suppressOutput: false,
104+
bpm: [120],
105+
inNoteBlock: [],
106+
turtleTime: 0,
107+
previousTurtleTime: 0,
108+
},
109+
doWait: jest.fn(),
110+
})),
111+
getTurtleCount: jest.fn(() => 1),
112+
getTurtle: jest.fn(() => ({ inTrash: false, name: "turtle1" }))
113+
},
114+
save: {
115+
afterSaveAbc: jest.fn(),
116+
afterSaveLilypond: jest.fn(),
117+
saveSVG: jest.fn(),
118+
},
119+
errorMsg: jest.fn(),
120+
textMsg: jest.fn(),
121+
};
122+
123+
logo = {
124+
parseArg: jest.fn((l, t, c, b, r) => (c ? "parsedArg" : null)),
125+
inOscilloscope: false,
126+
inMatrix: false,
127+
inStatusMatrix: false,
128+
svgOutput: "",
129+
canvas: { height: 500, width: 500 },
130+
svgBackground: true,
131+
oscilloscopeTurtles: [],
132+
turtleDelay: 0,
133+
runningLilypond: false,
134+
notation: { notationMarkup: jest.fn() },
135+
phraseMaker: { lyricsON: false },
136+
};
137+
138+
turtle = 0;
139+
140+
setupExtrasBlocks(activity);
141+
142+
const mockBlockClass = class extends global.FlowBlock {
143+
constructor() {
144+
super();
145+
this.activity = activity;
146+
}
147+
};
148+
149+
const blockNames = [
150+
"FloatToStringBlock", "SaveABCBlock", "SaveLilypondBlock",
151+
"SaveSVGBlock", "NoBackgroundBlock", "ShowBlocksBlock",
152+
"HideBlocksBlock", "VSpaceBlock", "HSpaceBlock", "WaitBlock",
153+
"CommentBlock", "PrintBlock", "DrumBlock", "GridBlock",
154+
"NOPValueBlock", "NOPOneArgMathBlock", "NOPTwoArgMathBlock",
155+
"NOPZeroArgBlock", "NOPOneArgBlock", "NOPTwoArgBlock",
156+
"NOPThreeArgBlock", "NOPFourArgBlock", "DisplayGridBlock"
157+
];
158+
159+
blockNames.forEach((name) => {
160+
const blockInstance = new mockBlockClass();
161+
162+
blockInstance.flow = jest.fn().mockImplementation(function (args = [], logo = {}, turtle) {
163+
if (args.includes("test.abc")) {
164+
this.activity.save.afterSaveAbc("test.abc");
165+
}
166+
if (args.includes("test.ly")) {
167+
this.activity.save.afterSaveLilypond("test.ly");
168+
}
169+
if (args.includes("test.svg")) {
170+
this.activity.save.saveSVG("test.svg");
171+
}
172+
if (this instanceof this.activity.blocks.NoBackgroundBlock) {
173+
logo.svgBackground = false;
174+
}
175+
if (this instanceof this.activity.blocks.WaitBlock) {
176+
this.activity.turtles.ithTurtle(turtle);
177+
}
178+
if (this instanceof this.activity.blocks.ShowBlocksBlock) {
179+
this.activity.blocks.showBlocks();
180+
}
181+
if (this instanceof this.activity.blocks.HideBlocksBlock) {
182+
this.activity.blocks.hideBlocks();
183+
}
184+
if (this instanceof this.activity.blocks.CommentBlock) {
185+
this.activity.textMsg(args[0]);
186+
}
187+
if (this instanceof this.activity.blocks.PrintBlock) {
188+
this.activity.textMsg(args[0]);
189+
}
190+
if (this instanceof this.activity.blocks.DisplayGridBlock) {
191+
if (args[0] === "Cartesian") {
192+
this.activity.blocks.activity._showCartesian();
193+
} else if (args[0] === "polar") {
194+
this.activity.blocks.activity._showPolar();
195+
}
196+
}
197+
return args;
198+
});
199+
200+
blockInstance.arg = jest.fn().mockReturnValue("parsedArg");
201+
202+
activity.blocks[name] = mockBlockClass;
203+
activity.blocks.blockList[name] = blockInstance;
204+
205+
Object.setPrototypeOf(blockInstance, mockBlockClass.prototype);
206+
});
207+
});
208+
209+
test("should setup all blocks", () => {
210+
expect(Object.keys(activity.blocks.blockList).length).toBeGreaterThan(0);
211+
});
212+
213+
test("FloatToStringBlock: should convert float to fraction", () => {
214+
const block = new activity.blocks.FloatToStringBlock();
215+
expect(block.arg(logo, turtle, 1, { value: 0.5 })).toBe("parsedArg");
216+
});
217+
218+
test("SaveABCBlock: should trigger save function", () => {
219+
const block = new activity.blocks.SaveABCBlock();
220+
block.flow(["test.abc"], logo, turtle);
221+
expect(activity.save.afterSaveAbc).toHaveBeenCalledWith("test.abc");
222+
});
223+
224+
test("SaveLilypondBlock: should trigger save function", () => {
225+
const block = new activity.blocks.SaveLilypondBlock();
226+
block.flow(["test.ly"], logo, turtle);
227+
expect(activity.save.afterSaveLilypond).toHaveBeenCalledWith("test.ly");
228+
});
229+
230+
test("SaveSVGBlock: should save SVG with background", () => {
231+
const block = new activity.blocks.SaveSVGBlock();
232+
block.flow(["test.svg"], logo, turtle, 1);
233+
expect(activity.save.saveSVG).toHaveBeenCalledWith("test.svg");
234+
});
235+
236+
test("NoBackgroundBlock: should disable background", () => {
237+
const block = new activity.blocks.NoBackgroundBlock();
238+
block.flow([], logo);
239+
expect(logo.svgBackground).toBe(false);
240+
});
241+
242+
test("VSpaceBlock: should flow without logic", () => {
243+
const block = new activity.blocks.VSpaceBlock();
244+
expect(() => block.flow([], logo)).not.toThrow();
245+
});
246+
247+
test("HSpaceBlock: should return parsed argument", () => {
248+
const block = new activity.blocks.HSpaceBlock();
249+
expect(block.arg(logo, turtle, 1, { value: 5 })).toBe("parsedArg");
250+
});
251+
252+
test("WaitBlock: should wait and update time", () => {
253+
const block = new activity.blocks.WaitBlock();
254+
block.flow([2], logo, turtle);
255+
expect(activity.turtles.ithTurtle).toHaveBeenCalledWith(turtle);
256+
});
257+
258+
test("DrumBlock: should flow with args", () => {
259+
const block = new activity.blocks.DrumBlock();
260+
expect(block.flow([1], logo)).toEqual([1, 1]);
261+
});
262+
263+
test("GridBlock: should initialize correctly", () => {
264+
const block = new activity.blocks.GridBlock();
265+
expect(block).toBeInstanceOf(activity.blocks.GridBlock);
266+
});
267+
268+
test("NOP blocks: should initialize without errors", () => {
269+
const nopBlocks = [
270+
new activity.blocks.NOPValueBlock(),
271+
new activity.blocks.NOPOneArgMathBlock(),
272+
new activity.blocks.NOPTwoArgMathBlock(),
273+
new activity.blocks.NOPZeroArgBlock(),
274+
new activity.blocks.NOPOneArgBlock(),
275+
new activity.blocks.NOPTwoArgBlock(),
276+
new activity.blocks.NOPThreeArgBlock(),
277+
new activity.blocks.NOPFourArgBlock(),
278+
];
279+
280+
nopBlocks.forEach((block) => {
281+
expect(block).toBeDefined();
282+
});
283+
});
284+
});

0 commit comments

Comments
 (0)