1+ const Tempo = require ( "./tempo.js" ) ;
2+
3+ // --- 1. Global Mocks (Fake the Browser Environment) ---
4+ global . _ = ( msg ) => msg ; // Mock translation function
5+ global . getDrumSynthName = jest . fn ( ) ;
6+
7+ // Mock the Window Manager
8+ global . window = {
9+ widgetWindows : {
10+ windowFor : jest . fn ( ) . mockReturnValue ( {
11+ clear : jest . fn ( ) ,
12+ show : jest . fn ( ) ,
13+ addButton : jest . fn ( ) . mockReturnValue ( { onclick : ( ) => { } } ) ,
14+ addInputButton : jest . fn ( ) . mockImplementation ( ( val ) => ( {
15+ value : val ,
16+ addEventListener : jest . fn ( )
17+ } ) ) ,
18+ getWidgetBody : jest . fn ( ) . mockReturnValue ( {
19+ appendChild : jest . fn ( ) ,
20+ insertRow : jest . fn ( ) . mockReturnValue ( {
21+ insertCell : jest . fn ( ) . mockReturnValue ( {
22+ appendChild : jest . fn ( ) ,
23+ setAttribute : jest . fn ( )
24+ } )
25+ } )
26+ } ) ,
27+ sendToCenter : jest . fn ( )
28+ } )
29+ }
30+ } ;
31+
32+ // Mock Document (for creating the canvas)
33+ global . document = {
34+ createElement : jest . fn ( ) . mockReturnValue ( {
35+ style : { } ,
36+ getContext : jest . fn ( ) . mockReturnValue ( {
37+ clearRect : jest . fn ( ) ,
38+ beginPath : jest . fn ( ) ,
39+ fillStyle : "" ,
40+ ellipse : jest . fn ( ) ,
41+ fill : jest . fn ( ) ,
42+ closePath : jest . fn ( )
43+ } )
44+ } )
45+ } ;
46+
47+ describe ( "Tempo Widget" , ( ) => {
48+ let tempoWidget ;
49+ let mockActivity ;
50+
51+ beforeEach ( ( ) => {
52+ tempoWidget = new Tempo ( ) ;
53+
54+ // Mock the Music Blocks Activity object
55+ mockActivity = {
56+ logo : {
57+ synth : { loadSynth : jest . fn ( ) } ,
58+ firstNoteTime : 1000
59+ } ,
60+ blocks : {
61+ blockList : { } , // Empty block list for now
62+ loadNewBlocks : jest . fn ( )
63+ } ,
64+ refreshCanvas : jest . fn ( ) ,
65+ saveLocally : jest . fn ( ) ,
66+ textMsg : jest . fn ( ) ,
67+ errorMsg : jest . fn ( ) // This is what the code calls!
68+ } ;
69+
70+ // --- FIX 1: Set the Global 'activity' variable ---
71+ // The widget code calls 'activity.errorMsg', relying on it being global.
72+ global . activity = mockActivity ;
73+
74+ // Manually setup initial state usually handled by init()
75+ tempoWidget . activity = mockActivity ;
76+ tempoWidget . BPMs = [ 100 ] ; // Start at 100 BPM
77+ tempoWidget . BPMInputs = [ { value : 100 } ] ; // Fake input element
78+ tempoWidget . _intervals = [ 600 ] ;
79+ tempoWidget . BPMBlocks = [ null ] ;
80+
81+ // --- FIX 2: Initialize 'isMoving' ---
82+ tempoWidget . isMoving = true ;
83+ } ) ;
84+
85+ test ( "should initialize with default values" , ( ) => {
86+ expect ( tempoWidget . BPMs [ 0 ] ) . toBe ( 100 ) ;
87+ expect ( tempoWidget . isMoving ) . toBe ( true ) ;
88+ } ) ;
89+
90+ test ( "speedUp() should increase BPM by 10%" , ( ) => {
91+ // 100 + 10% = 110
92+ tempoWidget . speedUp ( 0 ) ;
93+ expect ( tempoWidget . BPMs [ 0 ] ) . toBe ( 110 ) ;
94+ expect ( tempoWidget . BPMInputs [ 0 ] . value ) . toBe ( 110 ) ;
95+ } ) ;
96+
97+ test ( "slowDown() should decrease BPM by 10%" , ( ) => {
98+ // 100 - 10% = 90
99+ tempoWidget . slowDown ( 0 ) ;
100+ expect ( tempoWidget . BPMs [ 0 ] ) . toBe ( 90 ) ;
101+ expect ( tempoWidget . BPMInputs [ 0 ] . value ) . toBe ( 90 ) ;
102+ } ) ;
103+
104+ test ( "should not exceed maximum BPM of 1000" , ( ) => {
105+ tempoWidget . BPMs [ 0 ] = 950 ;
106+ // 950 + 95 = 1045 -> Should clamp to 1000
107+ tempoWidget . speedUp ( 0 ) ;
108+
109+ expect ( tempoWidget . BPMs [ 0 ] ) . toBe ( 1000 ) ;
110+ expect ( mockActivity . errorMsg ) . toHaveBeenCalled ( ) ;
111+ } ) ;
112+
113+ test ( "should not go below minimum BPM of 30" , ( ) => {
114+ tempoWidget . BPMs [ 0 ] = 32 ;
115+ // 32 - 3.2 = 28.8 -> Should clamp to 30
116+ tempoWidget . slowDown ( 0 ) ;
117+
118+ expect ( tempoWidget . BPMs [ 0 ] ) . toBe ( 30 ) ;
119+ expect ( mockActivity . errorMsg ) . toHaveBeenCalled ( ) ;
120+ } ) ;
121+ } ) ;
0 commit comments