Skip to content

Commit 8a9b177

Browse files
authored
Merge pull request #4974 from Inuth0603/test/tempo-widget
test: add unit tests for tempo.js
2 parents 699cea4 + 430a03c commit 8a9b177

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

js/widgets/tempo.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,6 @@ class Tempo {
438438
return this._save_lock;
439439
}
440440
}
441+
if (typeof module !== "undefined") {
442+
module.exports = Tempo;
443+
}

js/widgets/tempo.test.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* MusicBlocks v3.6.2
3+
*
4+
* @author Divyam Agarwal
5+
*
6+
* @copyright 2026 Divyam Agarwal
7+
*
8+
* @license
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*/
22+
23+
const Tempo = require("./tempo.js");
24+
25+
// --- 1. Global Mocks (Fake the Browser Environment) ---
26+
global._ = msg => msg; // Mock translation function
27+
global.getDrumSynthName = jest.fn();
28+
29+
// Mock the Window Manager
30+
global.window = {
31+
widgetWindows: {
32+
windowFor: jest.fn().mockReturnValue({
33+
clear: jest.fn(),
34+
show: jest.fn(),
35+
addButton: jest.fn().mockReturnValue({ onclick: () => {} }),
36+
addInputButton: jest.fn().mockImplementation(val => ({
37+
value: val,
38+
addEventListener: jest.fn()
39+
})),
40+
getWidgetBody: jest.fn().mockReturnValue({
41+
appendChild: jest.fn(),
42+
insertRow: jest.fn().mockReturnValue({
43+
insertCell: jest.fn().mockReturnValue({
44+
appendChild: jest.fn(),
45+
setAttribute: jest.fn()
46+
})
47+
})
48+
}),
49+
sendToCenter: jest.fn()
50+
})
51+
}
52+
};
53+
54+
// Mock Document (for creating the canvas)
55+
global.document = {
56+
createElement: jest.fn().mockReturnValue({
57+
style: {},
58+
getContext: jest.fn().mockReturnValue({
59+
clearRect: jest.fn(),
60+
beginPath: jest.fn(),
61+
fillStyle: "",
62+
ellipse: jest.fn(),
63+
fill: jest.fn(),
64+
closePath: jest.fn()
65+
})
66+
})
67+
};
68+
69+
describe("Tempo Widget", () => {
70+
let tempoWidget;
71+
let mockActivity;
72+
73+
beforeEach(() => {
74+
tempoWidget = new Tempo();
75+
76+
// Mock the Music Blocks Activity object
77+
mockActivity = {
78+
logo: {
79+
synth: { loadSynth: jest.fn() },
80+
firstNoteTime: 1000
81+
},
82+
blocks: {
83+
blockList: {}, // Empty block list for now
84+
loadNewBlocks: jest.fn()
85+
},
86+
refreshCanvas: jest.fn(),
87+
saveLocally: jest.fn(),
88+
textMsg: jest.fn(),
89+
errorMsg: jest.fn() // This is what the code calls!
90+
};
91+
92+
// --- FIX 1: Set the Global 'activity' variable ---
93+
// The widget code calls 'activity.errorMsg', relying on it being global.
94+
global.activity = mockActivity;
95+
96+
// Manually setup initial state usually handled by init()
97+
tempoWidget.activity = mockActivity;
98+
tempoWidget.BPMs = [100]; // Start at 100 BPM
99+
tempoWidget.BPMInputs = [{ value: 100 }]; // Fake input element
100+
tempoWidget._intervals = [600];
101+
tempoWidget.BPMBlocks = [null];
102+
103+
// --- FIX 2: Initialize 'isMoving' ---
104+
tempoWidget.isMoving = true;
105+
});
106+
107+
test("should initialize with default values", () => {
108+
expect(tempoWidget.BPMs[0]).toBe(100);
109+
expect(tempoWidget.isMoving).toBe(true);
110+
});
111+
112+
test("speedUp() should increase BPM by 10%", () => {
113+
// 100 + 10% = 110
114+
tempoWidget.speedUp(0);
115+
expect(tempoWidget.BPMs[0]).toBe(110);
116+
expect(tempoWidget.BPMInputs[0].value).toBe(110);
117+
});
118+
119+
test("slowDown() should decrease BPM by 10%", () => {
120+
// 100 - 10% = 90
121+
tempoWidget.slowDown(0);
122+
expect(tempoWidget.BPMs[0]).toBe(90);
123+
expect(tempoWidget.BPMInputs[0].value).toBe(90);
124+
});
125+
126+
test("should not exceed maximum BPM of 1000", () => {
127+
tempoWidget.BPMs[0] = 950;
128+
// 950 + 95 = 1045 -> Should clamp to 1000
129+
tempoWidget.speedUp(0);
130+
131+
expect(tempoWidget.BPMs[0]).toBe(1000);
132+
expect(mockActivity.errorMsg).toHaveBeenCalled();
133+
});
134+
135+
test("should not go below minimum BPM of 30", () => {
136+
tempoWidget.BPMs[0] = 32;
137+
// 32 - 3.2 = 28.8 -> Should clamp to 30
138+
tempoWidget.slowDown(0);
139+
140+
expect(tempoWidget.BPMs[0]).toBe(30);
141+
expect(mockActivity.errorMsg).toHaveBeenCalled();
142+
});
143+
});

0 commit comments

Comments
 (0)