Skip to content

Commit 654bc89

Browse files
Merge branch 'sugarlabs:master' into feature_v1
2 parents 13d6b63 + 84ee08c commit 654bc89

File tree

18 files changed

+1135
-436
lines changed

18 files changed

+1135
-436
lines changed

css/style.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,3 @@ input[type="range"]:focus::-ms-fill-upper {
114114
.lego-size-1 { width: 20px; height: 10px; }
115115
.lego-size-2 { width: 40px; height: 10px; }
116116
/* ... more sizes ... */
117-

env.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// File for static Musicblocks hosting e.g. https://sugarlabs.github.io/musicblocks/
2+
window.MB_ENV = "production";
3+
window.MB_IS_DEV = false;

index.html

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@
107107
});
108108
</script>
109109
<script src="lib/astring.min.js" defer></script>
110-
111110
<script src="lib/acorn.min.js" defer></script>
111+
<script src="env.js"></script>
112112

113113

114114
<script data-main="js/loader" src="lib/require.js" defer></script>
@@ -335,8 +335,9 @@
335335
<li>
336336
<a id="stop" class="left tooltipped"><i class="material-icons main">stop</i></a>
337337
</li>
338-
<li>
339-
<a id="record" class="left tooltipped" data-tooltip="Record"></a>
338+
<li style="display: flex; align-items: center;">
339+
<a id="record" class="left tooltipped" data-position="bottom" data-tooltip="Record"></a>
340+
<a id="recordDropdownArrow" class="left dropdown-trigger" data-activates="recorddropdown" style="margin-left: -5px; padding: 0; font-size: 28px;"></a>
340341
</li>
341342
</ul>
342343

@@ -502,6 +503,11 @@
502503
<li><a id="save-blockartwork-png"></a></li>
503504
</ul>
504505

506+
<ul id="recorddropdown" class="dropdown-content">
507+
<li><a id="record-with-menus">Record canvas and toolbars</a></li>
508+
<li><a id="record-canvas-only">Record canvas only</a></li>
509+
</ul>
510+
505511
<ul id="languagedropdown" class="dropdown-content">
506512
<li><a id="enUS"></a></li>
507513
<li><a id="enUK"></a></li>
@@ -571,13 +577,15 @@
571577
<script>
572578
// Register service worker only in production (not on localhost)
573579
if ("serviceWorker" in navigator) {
574-
// Detect if running on localhost for development
575-
const isLocalDev = window.location.hostname === "localhost" ||
576-
window.location.hostname === "127.0.0.1";
580+
577581
// Localhost SW is disabled to avoid caching; set allowLocalPwa to test installs.
578582
const allowLocalPwa = localStorage.getItem("allowLocalPwa") === "true";
579583

580-
if (!isLocalDev || allowLocalPwa) {
584+
// Detect if running on localhost for development
585+
const runtimeEnv = window.MB_ENV
586+
const isDevMode = runtimeEnv !== "production";
587+
588+
if (!isDevMode || allowLocalPwa) {
581589
// Production: Register service worker for offline mode
582590
if (navigator.serviceWorker.controller) {
583591
console.debug(
@@ -600,7 +608,7 @@
600608
} else {
601609
// Development: Skip service worker for instant updates
602610
console.log(
603-
"🔥 [DEV MODE] Service worker disabled on localhost for instant code updates"
611+
"🔥 [DEV MODE] Service worker disabled for instant code updates"
604612
);
605613
console.log(
606614
"Set localStorage.allowLocalPwa = 'true' to enable PWA install testing on localhost."

index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ const app = express();
77
// Detect environment (default to development for safety)
88
const isDev = process.env.NODE_ENV !== "production";
99

10+
// runtime environment for browser
11+
app.get("/env.js", (req, res) => {
12+
res.type("application/javascript");
13+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
14+
res.setHeader("Pragma", "no-cache");
15+
res.setHeader("Expires", "0");
16+
res.setHeader("Surrogate-Control", "no-store");
17+
res.send(
18+
`window.MB_ENV=${JSON.stringify(process.env.NODE_ENV || "development")};` +
19+
`window.MB_IS_DEV=${JSON.stringify(isDev)};`
20+
);
21+
});
22+
1023
// Enable compression for all responses
1124
app.use(
1225
compression({

js/__tests__/logoconstants.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @license
3+
* MusicBlocks v3.4.1
4+
* Copyright (C) 2026 Music Blocks Contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
global._ = jest.fn(s => s);
20+
21+
const constants = require("../logoconstants");
22+
23+
describe("logoconstants", () => {
24+
test("module loads and exports an object", () => {
25+
expect(constants).toBeDefined();
26+
expect(typeof constants).toBe("object");
27+
});
28+
29+
test("numeric defaults have correct values", () => {
30+
expect(constants.DEFAULTVOLUME).toBe(50);
31+
expect(constants.PREVIEWVOLUME).toBe(80);
32+
expect(constants.DEFAULTDELAY).toBe(500);
33+
expect(constants.OSCVOLUMEADJUSTMENT).toBe(1.5);
34+
expect(constants.TONEBPM).toBe(240);
35+
expect(constants.TARGETBPM).toBe(90);
36+
expect(constants.TURTLESTEP).toBe(-1);
37+
expect(constants.NOTEDIV).toBe(8);
38+
});
39+
40+
test("error message strings are non-empty strings", () => {
41+
const msgKeys = [
42+
"NOMICERRORMSG",
43+
"NANERRORMSG",
44+
"NOSTRINGERRORMSG",
45+
"NOBOXERRORMSG",
46+
"NOACTIONERRORMSG",
47+
"NOINPUTERRORMSG",
48+
"NOSQRTERRORMSG",
49+
"ZERODIVIDEERRORMSG",
50+
"EMPTYHEAPERRORMSG",
51+
"POSNUMBER"
52+
];
53+
for (const key of msgKeys) {
54+
expect(typeof constants[key]).toBe("string");
55+
expect(constants[key].length).toBeGreaterThan(0);
56+
}
57+
});
58+
59+
test("INVALIDPITCH has the expected translation string", () => {
60+
expect(typeof constants.INVALIDPITCH).toBe("string");
61+
expect(constants.INVALIDPITCH).toBe("Not a valid pitch name");
62+
});
63+
64+
test("notation index constants are sequential integers starting at 0", () => {
65+
expect(constants.NOTATIONNOTE).toBe(0);
66+
expect(constants.NOTATIONDURATION).toBe(1);
67+
expect(constants.NOTATIONDOTCOUNT).toBe(2);
68+
expect(constants.NOTATIONTUPLETVALUE).toBe(3);
69+
expect(constants.NOTATIONROUNDDOWN).toBe(4);
70+
expect(constants.NOTATIONINSIDECHORD).toBe(5);
71+
expect(constants.NOTATIONSTACCATO).toBe(6);
72+
});
73+
74+
test("exports exactly the expected set of keys", () => {
75+
const expectedKeys = [
76+
"DEFAULTVOLUME",
77+
"PREVIEWVOLUME",
78+
"DEFAULTDELAY",
79+
"OSCVOLUMEADJUSTMENT",
80+
"TONEBPM",
81+
"TARGETBPM",
82+
"TURTLESTEP",
83+
"NOTEDIV",
84+
"NOMICERRORMSG",
85+
"NANERRORMSG",
86+
"NOSTRINGERRORMSG",
87+
"NOBOXERRORMSG",
88+
"NOACTIONERRORMSG",
89+
"NOINPUTERRORMSG",
90+
"NOSQRTERRORMSG",
91+
"ZERODIVIDEERRORMSG",
92+
"EMPTYHEAPERRORMSG",
93+
"POSNUMBER",
94+
"INVALIDPITCH",
95+
"NOTATIONNOTE",
96+
"NOTATIONDURATION",
97+
"NOTATIONDOTCOUNT",
98+
"NOTATIONTUPLETVALUE",
99+
"NOTATIONROUNDDOWN",
100+
"NOTATIONINSIDECHORD",
101+
"NOTATIONSTACCATO"
102+
];
103+
expect(Object.keys(constants).sort()).toEqual(expectedKeys.sort());
104+
});
105+
106+
test("does not pollute the global scope", () => {
107+
expect(global.DEFAULTVOLUME).toBeUndefined();
108+
expect(global.TONEBPM).toBeUndefined();
109+
expect(global.NOTATIONNOTE).toBeUndefined();
110+
});
111+
});

js/__tests__/toolbar.test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ describe("Toolbar Class", () => {
240240
},
241241
stop: {
242242
style: { color: "" },
243-
addEventListener: jest.fn()
243+
addEventListener: jest.fn(),
244+
removeEventListener: jest.fn()
244245
},
245246
record: {
246247
className: ""
@@ -267,6 +268,10 @@ describe("Toolbar Class", () => {
267268
expect(global.saveButtonAdvanced.disabled).toBe(true);
268269
expect(global.saveButton.className).toBe("grey-text inactiveLink");
269270
expect(elements.record.className).toBe("grey-text inactiveLink");
271+
expect(elements.stop.removeEventListener).toHaveBeenCalledWith(
272+
"click",
273+
expect.any(Function)
274+
);
270275
expect(elements.stop.addEventListener).toHaveBeenCalledWith("click", expect.any(Function));
271276

272277
const stopClickHandler = elements.stop.addEventListener.mock.calls[0][1];

js/__tests__/turtles.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ global.setupVolumeActions = jest.fn();
4040
global.setupDrumActions = jest.fn();
4141
global.setupDictActions = jest.fn();
4242

43+
global.LEADING = 35;
44+
global.CARTESIANBUTTON = "";
45+
global.CLEARBUTTON = "";
46+
global.COLLAPSEBUTTON = "";
47+
global.EXPANDBUTTON = "";
48+
global.MBOUNDARY = "";
49+
global.piemenuGrid = {};
50+
global.base64Encode = jest.fn(str => str);
51+
4352
global.Turtle = jest.fn().mockImplementation(() => ({
4453
painter: {
4554
doSetHeading: jest.fn(),
@@ -113,6 +122,9 @@ describe("Turtles Class", () => {
113122
turtles.addTurtleGraphicProps = jest.fn();
114123
turtles.isShrunk = jest.fn().mockReturnValue(false);
115124
document.body.innerHTML = '<div id="loader"></div>';
125+
window.jQuery = jest.fn().mockReturnValue({
126+
tooltip: jest.fn()
127+
});
116128
});
117129

118130
test("should initialize properly", () => {
@@ -382,6 +394,7 @@ describe("setBackgroundColor", () => {
382394
turtles._scale = 1.0;
383395
global.platformColor = { background: "#ffffff" };
384396
turtles._backgroundColor = platformColor.background;
397+
turtles._borderContainer = new createjs.Container();
385398
});
386399

387400
test("should set default background color when index is -1", () => {
@@ -447,6 +460,7 @@ describe("doScale", () => {
447460
turtles._locked = false;
448461
turtles._queue = [];
449462
turtles._backgroundColor = "#ffffff";
463+
turtles._borderContainer = new createjs.Container();
450464
});
451465

452466
test("should update scale, width, and height when not locked", () => {

0 commit comments

Comments
 (0)