Skip to content

Commit 66897d4

Browse files
authored
Merge pull request #5712 from lakshay776/issue-5135-timbre
added the unit tests for timbre widget
2 parents 83e9713 + b20a3aa commit 66897d4

File tree

2 files changed

+383
-71
lines changed

2 files changed

+383
-71
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
/**
2+
* MusicBlocks v3.6.2
3+
*
4+
* @author Lakshay
5+
*
6+
* @copyright 2026 Lakshay
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+
// --- Global Mocks (must be set before require) ---
24+
global._ = msg => msg;
25+
global.DEFAULTOSCILLATORTYPE = "sine";
26+
global.DEFAULTFILTERTYPE = "lowpass";
27+
global.OSCTYPES = ["sine", "triangle", "sawtooth", "square"];
28+
global.FILTERTYPES = ["lowpass", "highpass", "bandpass"];
29+
global.instrumentsFilters = [{}];
30+
global.instrumentsEffects = [{}];
31+
global.platformColor = {
32+
labelColor: "#90c100",
33+
selectorBackground: "#f0f0f0",
34+
selectorBackgroundHOVER: "#e0e0e0"
35+
};
36+
global.rationalToFraction = jest.fn(n => [n, 1]);
37+
global.oneHundredToFraction = jest.fn(n => n / 100);
38+
global.last = arr => arr[arr.length - 1];
39+
global.Singer = { RhythmActions: { getNoteValue: jest.fn(() => 0.25) } };
40+
global.delayExecution = jest.fn(ms => new Promise(r => setTimeout(r, ms)));
41+
global.docById = jest.fn(() => ({
42+
style: {},
43+
innerHTML: "",
44+
appendChild: jest.fn(),
45+
addEventListener: jest.fn(),
46+
setAttribute: jest.fn(),
47+
insertRow: jest.fn(() => ({
48+
insertCell: jest.fn(() => ({
49+
style: {},
50+
innerHTML: "",
51+
appendChild: jest.fn()
52+
}))
53+
}))
54+
}));
55+
global.docByName = jest.fn(() => []);
56+
57+
global.window = {
58+
innerWidth: 1200,
59+
widgetWindows: {
60+
windowFor: jest.fn().mockReturnValue({
61+
clear: jest.fn(),
62+
show: jest.fn(),
63+
addButton: jest.fn().mockReturnValue({ onclick: null }),
64+
getWidgetBody: jest.fn().mockReturnValue({
65+
appendChild: jest.fn(),
66+
append: jest.fn(),
67+
style: {},
68+
innerHTML: ""
69+
}),
70+
sendToCenter: jest.fn(),
71+
updateTitle: jest.fn(),
72+
onclose: null,
73+
onmaximize: null,
74+
destroy: jest.fn()
75+
})
76+
}
77+
};
78+
79+
global.document = {
80+
createElement: jest.fn(() => ({
81+
style: {},
82+
innerHTML: "",
83+
appendChild: jest.fn(),
84+
append: jest.fn(),
85+
setAttribute: jest.fn(),
86+
addEventListener: jest.fn(),
87+
removeEventListener: jest.fn(),
88+
insertRow: jest.fn(() => ({
89+
insertCell: jest.fn(() => ({
90+
style: {},
91+
innerHTML: "",
92+
appendChild: jest.fn()
93+
}))
94+
}))
95+
})),
96+
getElementById: jest.fn(() => ({
97+
style: {},
98+
innerHTML: ""
99+
}))
100+
};
101+
102+
const TimbreWidget = require("../timbre.js");
103+
104+
describe("TimbreWidget", () => {
105+
let timbre;
106+
107+
beforeEach(() => {
108+
global.instrumentsFilters = [{}];
109+
global.instrumentsEffects = [{}];
110+
timbre = new TimbreWidget();
111+
});
112+
113+
afterEach(() => {
114+
jest.clearAllMocks();
115+
});
116+
117+
describe("constructor", () => {
118+
test("should initialize with empty notesToPlay", () => {
119+
expect(timbre.notesToPlay).toEqual([]);
120+
});
121+
122+
test("should initialize with empty env", () => {
123+
expect(timbre.env).toEqual([]);
124+
});
125+
126+
test("should initialize with empty ENVs", () => {
127+
expect(timbre.ENVs).toEqual([]);
128+
});
129+
130+
test("should initialize synthVals with sine oscillator", () => {
131+
expect(timbre.synthVals.oscillator.type).toBe("sine6");
132+
expect(timbre.synthVals.oscillator.source).toBe(DEFAULTOSCILLATORTYPE);
133+
});
134+
135+
test("should initialize synthVals with default envelope", () => {
136+
expect(timbre.synthVals.envelope.attack).toBe(0.01);
137+
expect(timbre.synthVals.envelope.decay).toBe(0.5);
138+
expect(timbre.synthVals.envelope.sustain).toBe(0.6);
139+
expect(timbre.synthVals.envelope.release).toBe(0.01);
140+
});
141+
142+
test("should initialize adsrMap correctly", () => {
143+
expect(timbre.adsrMap).toEqual(["attack", "decay", "sustain", "release"]);
144+
});
145+
146+
test("should initialize amSynthParamvals", () => {
147+
expect(timbre.amSynthParamvals.harmonicity).toBe(3);
148+
});
149+
150+
test("should initialize fmSynthParamvals", () => {
151+
expect(timbre.fmSynthParamvals.modulationIndex).toBe(10);
152+
});
153+
154+
test("should initialize noiseSynthParamvals", () => {
155+
expect(timbre.noiseSynthParamvals.noise.type).toBe("white");
156+
});
157+
158+
test("should initialize duoSynthParamVals", () => {
159+
expect(timbre.duoSynthParamVals.vibratoAmount).toBe(0.5);
160+
expect(timbre.duoSynthParamVals.vibratoRate).toBe(5);
161+
});
162+
163+
test("should initialize empty effect arrays", () => {
164+
expect(timbre.fil).toEqual([]);
165+
expect(timbre.filterParams).toEqual([]);
166+
expect(timbre.osc).toEqual([]);
167+
expect(timbre.oscParams).toEqual([]);
168+
expect(timbre.tremoloEffect).toEqual([]);
169+
expect(timbre.tremoloParams).toEqual([]);
170+
expect(timbre.vibratoEffect).toEqual([]);
171+
expect(timbre.vibratoParams).toEqual([]);
172+
expect(timbre.chorusEffect).toEqual([]);
173+
expect(timbre.chorusParams).toEqual([]);
174+
expect(timbre.phaserEffect).toEqual([]);
175+
expect(timbre.phaserParams).toEqual([]);
176+
expect(timbre.distortionEffect).toEqual([]);
177+
expect(timbre.distortionParams).toEqual([]);
178+
});
179+
180+
test("should initialize empty synth arrays", () => {
181+
expect(timbre.AMSynthesizer).toEqual([]);
182+
expect(timbre.AMSynthParams).toEqual([]);
183+
expect(timbre.FMSynthesizer).toEqual([]);
184+
expect(timbre.FMSynthParams).toEqual([]);
185+
expect(timbre.NoiseSynthesizer).toEqual([]);
186+
expect(timbre.NoiseSynthParams).toEqual([]);
187+
expect(timbre.duoSynthesizer).toEqual([]);
188+
expect(timbre.duoSynthParams).toEqual([]);
189+
});
190+
191+
test("should initialize all activeParams as inactive", () => {
192+
const expectedParams = [
193+
"synth",
194+
"amsynth",
195+
"fmsynth",
196+
"noisesynth",
197+
"duosynth",
198+
"envelope",
199+
"oscillator",
200+
"filter",
201+
"effects",
202+
"chorus",
203+
"vibrato",
204+
"phaser",
205+
"distortion",
206+
"tremolo"
207+
];
208+
expect(timbre.activeParams).toEqual(expectedParams);
209+
for (const param of expectedParams) {
210+
expect(timbre.isActive[param]).toBe(false);
211+
}
212+
});
213+
214+
test("should set default instrumentName", () => {
215+
expect(timbre.instrumentName).toBe("custom");
216+
});
217+
218+
test("should set blockNo to null", () => {
219+
expect(timbre.blockNo).toBeNull();
220+
});
221+
222+
test("should initialize instrumentsFilters for custom instrument", () => {
223+
expect(instrumentsFilters[0]["custom"]).toEqual([]);
224+
});
225+
226+
test("should initialize instrumentsEffects for custom instrument", () => {
227+
expect(instrumentsEffects[0]["custom"]).toEqual([]);
228+
});
229+
230+
test("should initialize _eventListeners as empty object", () => {
231+
expect(timbre._eventListeners).toEqual({});
232+
});
233+
});
234+
235+
describe("synth parameters", () => {
236+
test("should allow updating synthVals envelope", () => {
237+
timbre.synthVals.envelope.attack = 0.1;
238+
timbre.synthVals.envelope.decay = 0.3;
239+
timbre.synthVals.envelope.sustain = 0.8;
240+
timbre.synthVals.envelope.release = 0.2;
241+
expect(timbre.synthVals.envelope.attack).toBe(0.1);
242+
expect(timbre.synthVals.envelope.decay).toBe(0.3);
243+
expect(timbre.synthVals.envelope.sustain).toBe(0.8);
244+
expect(timbre.synthVals.envelope.release).toBe(0.2);
245+
});
246+
247+
test("should allow updating oscillator type", () => {
248+
timbre.synthVals.oscillator.type = "triangle6";
249+
expect(timbre.synthVals.oscillator.type).toBe("triangle6");
250+
});
251+
252+
test("should allow updating amSynth harmonicity", () => {
253+
timbre.amSynthParamvals.harmonicity = 5;
254+
expect(timbre.amSynthParamvals.harmonicity).toBe(5);
255+
});
256+
257+
test("should allow updating fmSynth modulationIndex", () => {
258+
timbre.fmSynthParamvals.modulationIndex = 20;
259+
expect(timbre.fmSynthParamvals.modulationIndex).toBe(20);
260+
});
261+
262+
test("should allow updating noise type", () => {
263+
timbre.noiseSynthParamvals.noise.type = "pink";
264+
expect(timbre.noiseSynthParamvals.noise.type).toBe("pink");
265+
});
266+
267+
test("should allow updating duoSynth params", () => {
268+
timbre.duoSynthParamVals.vibratoAmount = 0.8;
269+
timbre.duoSynthParamVals.vibratoRate = 10;
270+
expect(timbre.duoSynthParamVals.vibratoAmount).toBe(0.8);
271+
expect(timbre.duoSynthParamVals.vibratoRate).toBe(10);
272+
});
273+
});
274+
275+
describe("active params management", () => {
276+
test("should toggle isActive for a parameter", () => {
277+
expect(timbre.isActive["synth"]).toBe(false);
278+
timbre.isActive["synth"] = true;
279+
expect(timbre.isActive["synth"]).toBe(true);
280+
});
281+
282+
test("should allow activating multiple params", () => {
283+
timbre.isActive["envelope"] = true;
284+
timbre.isActive["filter"] = true;
285+
expect(timbre.isActive["envelope"]).toBe(true);
286+
expect(timbre.isActive["filter"]).toBe(true);
287+
expect(timbre.isActive["effects"]).toBe(false);
288+
});
289+
});
290+
291+
describe("effect arrays", () => {
292+
test("should allow adding filter entries", () => {
293+
timbre.fil.push("lowpass");
294+
timbre.filterParams.push({ frequency: 400 });
295+
expect(timbre.fil).toHaveLength(1);
296+
expect(timbre.filterParams[0].frequency).toBe(400);
297+
});
298+
299+
test("should allow adding oscillator entries", () => {
300+
timbre.osc.push("sine");
301+
timbre.oscParams.push({ partialCount: 6 });
302+
expect(timbre.osc).toHaveLength(1);
303+
});
304+
305+
test("should allow adding effect entries", () => {
306+
timbre.tremoloEffect.push(true);
307+
timbre.tremoloParams.push({ frequency: 10, depth: 0.5 });
308+
timbre.vibratoEffect.push(true);
309+
timbre.vibratoParams.push({ frequency: 5, depth: 0.3 });
310+
expect(timbre.tremoloEffect).toHaveLength(1);
311+
expect(timbre.vibratoEffect).toHaveLength(1);
312+
});
313+
});
314+
315+
describe("notes management", () => {
316+
test("should allow adding notes to play", () => {
317+
timbre.notesToPlay.push(["C4", 4]);
318+
timbre.notesToPlay.push(["D4", 4]);
319+
expect(timbre.notesToPlay).toHaveLength(2);
320+
});
321+
322+
test("should allow clearing notesToPlay", () => {
323+
timbre.notesToPlay.push(["C4", 4]);
324+
timbre.notesToPlay = [];
325+
expect(timbre.notesToPlay).toHaveLength(0);
326+
});
327+
});
328+
});

0 commit comments

Comments
 (0)