@@ -37,47 +37,62 @@ global.Synth = jest.fn().mockImplementation(() => ({
3737 disposeAllInstruments : jest . fn ( ) ,
3838 changeInTemperament : false ,
3939 recorder : null ,
40- transport : {
41- get isAvailable ( ) {
42- return typeof global . Tone !== "undefined" && ! ! global . Tone . Transport ;
43- } ,
44- start ( ) {
45- if ( this . isAvailable ) global . Tone . Transport . start ( ) ;
46- } ,
47- stop ( ) {
48- if ( this . isAvailable ) global . Tone . Transport . stop ( ) ;
49- } ,
50- cancel ( ) {
51- if ( this . isAvailable && typeof global . Tone . Transport . cancel === "function" ) {
52- global . Tone . Transport . cancel ( ) ;
53- }
54- } ,
55- clear ( id ) {
56- if ( this . isAvailable && typeof global . Tone . Transport . clear === "function" ) {
57- global . Tone . Transport . clear ( id ) ;
58- }
59- } ,
60- schedule ( callback , time ) {
61- if ( this . isAvailable && typeof global . Tone . Transport . schedule === "function" ) {
62- return global . Tone . Transport . schedule ( callback , time ) ;
63- }
64- return null ;
65- } ,
66- get seconds ( ) {
67- if ( this . isAvailable ) return global . Tone . Transport . seconds ;
68- return 0 ;
69- } ,
70- set seconds ( v ) {
71- if ( this . isAvailable ) global . Tone . Transport . seconds = v ;
72- } ,
73- getSecondsAtTime ( time ) {
74- if ( this . isAvailable && typeof global . Tone . Transport . getSecondsAtTime === "function" ) {
75- return global . Tone . Transport . getSecondsAtTime ( time ) ;
76- }
77- return this . seconds ;
40+ transport : createTransportMock ( )
41+ } ) ) ;
42+
43+ /**
44+ * Returns a transport mock that mirrors the wrapper in synthutils.js.
45+ * Centralised here so both the Synth mock and test-local overrides stay in sync.
46+ */
47+ const createTransportMock = ( ) => ( {
48+ get isAvailable ( ) {
49+ return typeof global . Tone !== "undefined" && ! ! global . Tone . Transport ;
50+ } ,
51+ get isClockRunning ( ) {
52+ return (
53+ this . isAvailable &&
54+ typeof global . Tone . context !== "undefined" &&
55+ global . Tone . context . state === "running" &&
56+ global . Tone . Transport . state === "started"
57+ ) ;
58+ } ,
59+ start ( ) {
60+ if ( this . isAvailable ) global . Tone . Transport . start ( ) ;
61+ } ,
62+ stop ( ) {
63+ if ( this . isAvailable ) global . Tone . Transport . stop ( ) ;
64+ } ,
65+ cancel ( ) {
66+ if ( this . isAvailable && typeof global . Tone . Transport . cancel === "function" ) {
67+ global . Tone . Transport . cancel ( ) ;
68+ }
69+ } ,
70+ clear ( id ) {
71+ if ( this . isAvailable && typeof global . Tone . Transport . clear === "function" ) {
72+ global . Tone . Transport . clear ( id ) ;
73+ }
74+ } ,
75+ schedule ( callback , time ) {
76+ if ( this . isAvailable && typeof global . Tone . Transport . schedule === "function" ) {
77+ return global . Tone . Transport . schedule ( callback , time ) ;
7878 }
79+ return null ;
80+ } ,
81+ get seconds ( ) {
82+ if ( this . isAvailable ) return global . Tone . Transport . seconds ;
83+ return 0 ;
84+ } ,
85+ set seconds ( v ) {
86+ if ( this . isAvailable ) global . Tone . Transport . seconds = v ;
87+ } ,
88+ getSecondsAtTime ( time ) {
89+ if ( this . isAvailable && typeof global . Tone . Transport . getSecondsAtTime === "function" ) {
90+ return global . Tone . Transport . getSecondsAtTime ( time ) ;
91+ }
92+ return this . seconds ;
7993 }
80- } ) ) ;
94+ } ) ;
95+
8196global . Singer = {
8297 setSynthVolume : jest . fn ( ) ,
8398 setMasterVolume : jest . fn ( ) ,
@@ -997,26 +1012,7 @@ describe("Logo doStopTurtles", () => {
9971012 stopSound : jest . fn ( ) ,
9981013 disposeAllInstruments : jest . fn ( ) ,
9991014 recorder : { state : "recording" , stop : jest . fn ( ) } ,
1000- transport : {
1001- get isAvailable ( ) {
1002- return typeof global . Tone !== "undefined" && ! ! global . Tone . Transport ;
1003- } ,
1004- cancel ( ) {
1005- if (
1006- this . isAvailable &&
1007- typeof global . Tone . Transport . cancel === "function"
1008- ) {
1009- global . Tone . Transport . cancel ( ) ;
1010- }
1011- } ,
1012- get seconds ( ) {
1013- if ( this . isAvailable ) return global . Tone . Transport . seconds ;
1014- return 0 ;
1015- } ,
1016- set seconds ( v ) {
1017- if ( this . isAvailable ) global . Tone . Transport . seconds = v ;
1018- }
1019- }
1015+ transport : createTransportMock ( )
10201016 } ;
10211017 } ) ;
10221018
@@ -1275,7 +1271,7 @@ describe("Logo runFromBlock", () => {
12751271 } ) ;
12761272
12771273 describe ( "Transport scheduling" , ( ) => {
1278- test ( "uses Transport.schedule when available" , ( ) => {
1274+ test ( "uses Transport.schedule when available and clock is running " , ( ) => {
12791275 const getSecondsAtTimeMock = jest . fn ( t => t + 1 ) ;
12801276 let scheduledCallback = null ;
12811277 const scheduleSpy = jest . fn ( ( fn , time ) => {
@@ -1284,6 +1280,7 @@ describe("Logo runFromBlock", () => {
12841280 } ) ;
12851281 global . Tone = {
12861282 ...savedTone ,
1283+ context : { state : "running" } ,
12871284 Transport : {
12881285 start : jest . fn ( ) ,
12891286 stop : jest . fn ( ) ,
@@ -1293,7 +1290,10 @@ describe("Logo runFromBlock", () => {
12931290 get seconds ( ) {
12941291 return 0 ;
12951292 } ,
1296- set seconds ( v ) { }
1293+ set seconds ( v ) { } ,
1294+ get state ( ) {
1295+ return "started" ;
1296+ }
12971297 }
12981298 } ;
12991299
@@ -1330,6 +1330,114 @@ describe("Logo runFromBlock", () => {
13301330 expect ( timeoutSpy ) . toHaveBeenCalled ( ) ;
13311331 expect ( logo . runFromBlockNow ) . toHaveBeenCalledWith ( logo , 0 , 3 , 1 , "x" ) ;
13321332 } ) ;
1333+
1334+ test ( "falls back to setTimeout when AudioContext is suspended" , ( ) => {
1335+ global . Tone = {
1336+ ...savedTone ,
1337+ context : { state : "suspended" } ,
1338+ Transport : {
1339+ start : jest . fn ( ) ,
1340+ stop : jest . fn ( ) ,
1341+ schedule : jest . fn ( ) ,
1342+ cancel : jest . fn ( ) ,
1343+ getSecondsAtTime : jest . fn ( ( ) => 0 ) ,
1344+ get seconds ( ) {
1345+ return 5 ;
1346+ } ,
1347+ set seconds ( v ) { } ,
1348+ get state ( ) {
1349+ return "started" ;
1350+ }
1351+ }
1352+ } ;
1353+ timeoutSpy = jest . spyOn ( global , "setTimeout" ) . mockImplementation ( fn => {
1354+ fn ( ) ;
1355+ return 5 ;
1356+ } ) ;
1357+
1358+ logo . runFromBlockNow = jest . fn ( ) ;
1359+ logo . turtleDelay = 0 ;
1360+ logo . stopTurtle = false ;
1361+ turtle0 . waitTime = 200 ;
1362+ turtle0 . _transportTime = 10 ;
1363+
1364+ logo . runFromBlock ( logo , 0 , 3 , 1 , "x" ) ;
1365+
1366+ expect ( global . Tone . Transport . schedule ) . not . toHaveBeenCalled ( ) ;
1367+ expect ( timeoutSpy ) . toHaveBeenCalled ( ) ;
1368+ expect ( logo . runFromBlockNow ) . toHaveBeenCalledWith ( logo , 0 , 3 , 1 , "x" ) ;
1369+ } ) ;
1370+
1371+ test ( "falls back to setTimeout when Transport clock is stopped" , ( ) => {
1372+ global . Tone = {
1373+ ...savedTone ,
1374+ context : { state : "running" } ,
1375+ Transport : {
1376+ start : jest . fn ( ) ,
1377+ stop : jest . fn ( ) ,
1378+ schedule : jest . fn ( ) ,
1379+ cancel : jest . fn ( ) ,
1380+ getSecondsAtTime : jest . fn ( ( ) => 0 ) ,
1381+ get seconds ( ) {
1382+ return 3 ;
1383+ } ,
1384+ set seconds ( v ) { } ,
1385+ get state ( ) {
1386+ return "stopped" ;
1387+ }
1388+ }
1389+ } ;
1390+ timeoutSpy = jest . spyOn ( global , "setTimeout" ) . mockImplementation ( fn => {
1391+ fn ( ) ;
1392+ return 5 ;
1393+ } ) ;
1394+
1395+ logo . runFromBlockNow = jest . fn ( ) ;
1396+ logo . turtleDelay = 0 ;
1397+ logo . stopTurtle = false ;
1398+ turtle0 . waitTime = 200 ;
1399+ turtle0 . _transportTime = 10 ;
1400+
1401+ logo . runFromBlock ( logo , 0 , 3 , 1 , "x" ) ;
1402+
1403+ expect ( global . Tone . Transport . schedule ) . not . toHaveBeenCalled ( ) ;
1404+ expect ( timeoutSpy ) . toHaveBeenCalled ( ) ;
1405+ expect ( logo . runFromBlockNow ) . toHaveBeenCalledWith ( logo , 0 , 3 , 1 , "x" ) ;
1406+ } ) ;
1407+
1408+ test ( "clamps transportTime to prevent scheduling in the past" , ( ) => {
1409+ const scheduleSpy = jest . fn ( ( fn , time ) => "evt-clamp" ) ;
1410+ global . Tone = {
1411+ ...savedTone ,
1412+ context : { state : "running" } ,
1413+ Transport : {
1414+ start : jest . fn ( ) ,
1415+ stop : jest . fn ( ) ,
1416+ schedule : scheduleSpy ,
1417+ cancel : jest . fn ( ) ,
1418+ getSecondsAtTime : jest . fn ( t => t ) ,
1419+ get seconds ( ) {
1420+ return 20 ;
1421+ } ,
1422+ set seconds ( v ) { } ,
1423+ get state ( ) {
1424+ return "started" ;
1425+ }
1426+ }
1427+ } ;
1428+
1429+ logo . runFromBlockNow = jest . fn ( ) ;
1430+ logo . turtleDelay = 0 ;
1431+ logo . stopTurtle = false ;
1432+ turtle0 . waitTime = 100 ;
1433+ turtle0 . _transportTime = 10 ;
1434+
1435+ logo . runFromBlock ( logo , 0 , 3 , 1 , "x" ) ;
1436+
1437+ const computedTime = 10 + 100 / 1000 ;
1438+ expect ( computedTime ) . toBeLessThan ( 20 ) ;
1439+ expect ( scheduleSpy ) . toHaveBeenCalledWith ( expect . any ( Function ) , 20 ) ;
1440+ } ) ;
13331441 } ) ;
13341442} ) ;
13351443
0 commit comments