Skip to content

Commit 01711df

Browse files
authored
Merge branch 'master' into fix-help-widget-aria
2 parents 09c4bc8 + d373588 commit 01711df

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

js/turtleactions/VolumeActions.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ function setupVolumeActions(activity) {
9797
);
9898
tur.singer.crescendoInitialVolume[synth].pop();
9999
}
100+
101+
tur.singer.inCrescendo.pop();
100102
};
101103

102104
activity.logo.setTurtleListener(turtle, listenerName, __listener);
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* MusicBlocks
3+
*
4+
* @author kh-ub-ayb
5+
*
6+
* @copyright 2026 kh-ub-ayb
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 fs = require("fs");
24+
const path = require("path");
25+
26+
// Load the AIDebuggerWidget class by reading the source and evaluating it
27+
const source = fs.readFileSync(path.resolve(__dirname, "../aidebugger.js"), "utf-8");
28+
new Function(
29+
source + "\nif (typeof global !== 'undefined') { global.AIDebuggerWidget = AIDebuggerWidget; }"
30+
)();
31+
32+
// Mock globals
33+
global._ = str => str;
34+
global._THIS_IS_MUSIC_BLOCKS_ = true;
35+
36+
describe("AIDebuggerWidget", () => {
37+
describe("Constructor", () => {
38+
test("initializes basic properties", () => {
39+
const debuggerWidget = new AIDebuggerWidget();
40+
41+
expect(debuggerWidget.chatHistory).toEqual([]);
42+
expect(debuggerWidget.promptCount).toBe(0);
43+
expect(typeof debuggerWidget.conversationId).toBe("string");
44+
expect(debuggerWidget.conversationId.startsWith("conv_")).toBe(true);
45+
46+
expect(debuggerWidget.activity).toBeNull();
47+
expect(debuggerWidget.widgetWindow).toBeNull();
48+
expect(debuggerWidget.chatLog).toBeNull();
49+
expect(debuggerWidget.messageInput).toBeNull();
50+
expect(debuggerWidget.sendButton).toBeNull();
51+
});
52+
53+
test("_generateConversationId returns unique IDs", () => {
54+
const debuggerWidget = new AIDebuggerWidget();
55+
const id1 = debuggerWidget._generateConversationId();
56+
const id2 = debuggerWidget._generateConversationId();
57+
58+
expect(id1).not.toBe(id2);
59+
expect(id1.startsWith("conv_")).toBe(true);
60+
});
61+
});
62+
63+
describe("Debugging Helper Methods", () => {
64+
let debuggerWidget;
65+
66+
beforeEach(() => {
67+
debuggerWidget = new AIDebuggerWidget();
68+
});
69+
70+
describe("_getNumericValue", () => {
71+
test("returns null for invalid blockId or blockMap", () => {
72+
expect(debuggerWidget._getNumericValue(null, {})).toBeNull();
73+
expect(debuggerWidget._getNumericValue("id1", {})).toBeNull();
74+
});
75+
76+
test("returns value for number block (simple)", () => {
77+
const blockMap = {
78+
id1: ["id1", "number"]
79+
};
80+
expect(debuggerWidget._getNumericValue("id1", blockMap)).toBe("number");
81+
});
82+
83+
test("returns value for number block (array format)", () => {
84+
const blockMap = {
85+
id1: ["id1", ["number", { value: 42 }]]
86+
};
87+
expect(debuggerWidget._getNumericValue("id1", blockMap)).toBe(42);
88+
});
89+
90+
test("returns null for non-number block", () => {
91+
const blockMap = {
92+
id1: ["id1", ["text", { value: "hello" }]]
93+
};
94+
expect(debuggerWidget._getNumericValue("id1", blockMap)).toBeNull();
95+
});
96+
});
97+
98+
describe("_getTextValue", () => {
99+
test("returns value for text block", () => {
100+
const blockMap = {
101+
id1: ["id1", ["text", { value: "hello world" }]]
102+
};
103+
expect(debuggerWidget._getTextValue("id1", blockMap)).toBe("hello world");
104+
});
105+
106+
test("returns null for non-text block", () => {
107+
const blockMap = {
108+
id1: ["id1", ["number", { value: 42 }]]
109+
};
110+
expect(debuggerWidget._getTextValue("id1", blockMap)).toBeNull();
111+
});
112+
});
113+
114+
describe("_getDrumName", () => {
115+
test("returns value for drumname block", () => {
116+
const blockMap = {
117+
id1: ["id1", ["drumname", { value: "snare" }]]
118+
};
119+
expect(debuggerWidget._getDrumName("id1", blockMap)).toBe("snare");
120+
});
121+
122+
test("returns null for non-drumname block", () => {
123+
const blockMap = {
124+
id1: ["id1", ["number", { value: 42 }]]
125+
};
126+
expect(debuggerWidget._getDrumName("id1", blockMap)).toBeNull();
127+
});
128+
});
129+
130+
describe("_getNamedBoxValue", () => {
131+
test("returns value for namedbox block", () => {
132+
const blockMap = {
133+
id1: ["id1", ["namedbox", { value: "myVar" }]]
134+
};
135+
expect(debuggerWidget._getNamedBoxValue("id1", blockMap)).toBe("myVar");
136+
});
137+
138+
test("returns value for namedarg block", () => {
139+
const blockMap = {
140+
id1: ["id1", ["namedarg", { value: "myArg" }]]
141+
};
142+
expect(debuggerWidget._getNamedBoxValue("id1", blockMap)).toBe("myArg");
143+
});
144+
145+
test("returns null for non-namedbox block", () => {
146+
const blockMap = {
147+
id1: ["id1", ["text", { value: "hello" }]]
148+
};
149+
expect(debuggerWidget._getNamedBoxValue("id1", blockMap)).toBeNull();
150+
});
151+
});
152+
153+
describe("_isBase64Data", () => {
154+
test("flags valid base64 image prefixes correctly", () => {
155+
expect(debuggerWidget._isBase64Data("data:image/png;base64,iVBORw0K")).toBe(true);
156+
expect(debuggerWidget._isBase64Data("data:audio/mp3;base64,SUQzBAA")).toBe(true);
157+
});
158+
159+
test("flags invalid base64 prefixes correctly", () => {
160+
expect(debuggerWidget._isBase64Data("data:text/html;base64,PGh0bWw+")).toBe(false);
161+
expect(debuggerWidget._isBase64Data("https://example.com/image.png")).toBe(false);
162+
expect(debuggerWidget._isBase64Data(12345)).toBe(false);
163+
expect(debuggerWidget._isBase64Data(null)).toBe(false);
164+
});
165+
});
166+
167+
describe("_getBlockRepresentation", () => {
168+
test("formats basic action block", () => {
169+
const blockMap = {
170+
action1: ["action1", ["action", null], [null, "action_name"]],
171+
action_name: ["action_name", ["text", { value: "Jump" }]]
172+
};
173+
const result = debuggerWidget._getBlockRepresentation(
174+
"action",
175+
null,
176+
blockMap["action1"],
177+
blockMap,
178+
1,
179+
false,
180+
null
181+
);
182+
expect(result).toBe('Action: "Jump"');
183+
});
184+
185+
test("formats forward block", () => {
186+
const blockMap = {
187+
forward1: ["forward1", ["forward", null], [null, "dist"]],
188+
dist: ["dist", ["number", { value: 100 }]]
189+
};
190+
const result = debuggerWidget._getBlockRepresentation(
191+
"forward",
192+
null,
193+
blockMap["forward1"],
194+
blockMap,
195+
1,
196+
false,
197+
null
198+
);
199+
expect(result).toBe("Move Forward → 100 Steps");
200+
});
201+
202+
test("formats setmasterbpm2 block", () => {
203+
const blockMap = {
204+
bpm1: ["bpm1", ["setmasterbpm2", null], null, [null, "val"]],
205+
val: ["val", ["number", { value: 120 }]]
206+
};
207+
const result = debuggerWidget._getBlockRepresentation(
208+
"setmasterbpm2",
209+
null,
210+
blockMap["bpm1"],
211+
blockMap,
212+
1,
213+
false,
214+
null
215+
);
216+
expect(result).toBe("Set Master BPM → 120 BPM");
217+
});
218+
});
219+
});
220+
});

0 commit comments

Comments
 (0)