@@ -25,8 +25,14 @@ const StatsWindow = require("./statistics.js");
2525describe ( "StatsWindow" , ( ) => {
2626 let mockActivity ;
2727 let mockWidgetWindow ;
28+ let mockWidgetBody ;
29+ let capturedCallback ;
2830
2931 beforeEach ( ( ) => {
32+ capturedCallback = null ;
33+
34+ mockWidgetBody = document . createElement ( "div" ) ;
35+
3036 // 1. Mock the Activity object
3137 mockActivity = {
3238 blocks : { showBlocks : jest . fn ( ) , hideBlocks : jest . fn ( ) , activeBlock : null } ,
@@ -43,7 +49,7 @@ describe("StatsWindow", () => {
4349 sendToCenter : jest . fn ( ) ,
4450 onclose : null ,
4551 onmaximize : null ,
46- getWidgetBody : jest . fn ( ) . mockReturnValue ( document . createElement ( "div" ) ) ,
52+ getWidgetBody : jest . fn ( ) . mockReturnValue ( mockWidgetBody ) ,
4753 getWidgetFrame : jest
4854 . fn ( )
4955 . mockReturnValue ( { getBoundingClientRect : ( ) => ( { height : 500 } ) } ) ,
@@ -55,29 +61,118 @@ describe("StatsWindow", () => {
5561 windowFor : jest . fn ( ) . mockReturnValue ( mockWidgetWindow )
5662 } ;
5763
58- // 4. CRITICAL FIX: Mock docById to return a fake Canvas with getContext
64+ // 4. Mock docById to return a fake Canvas with getContext
5965 global . docById = jest . fn ( ) . mockReturnValue ( {
60- getContext : jest . fn ( ) . mockReturnValue ( {
61- // Fake context methods if needed (not strictly needed for this test)
62- } )
66+ getContext : jest . fn ( ) . mockReturnValue ( { } )
6367 } ) ;
6468
6569 // 5. Mock external analysis functions (global scope)
6670 global . analyzeProject = jest . fn ( ) . mockReturnValue ( { } ) ;
6771 global . runAnalytics = jest . fn ( ) ;
6872 global . scoreToChartData = jest . fn ( ) . mockReturnValue ( { } ) ;
69- global . getChartOptions = jest . fn ( ) . mockReturnValue ( { } ) ;
73+ global . getChartOptions = jest . fn ( ) . mockImplementation ( cb => {
74+ capturedCallback = cb ;
75+ return { } ;
76+ } ) ;
7077
71- // Mock the Chart.js library
78+ // 6. Mock the Chart.js library — Radar returns an object with toBase64Image
7279 global . Chart = jest . fn ( ) . mockImplementation ( ( ) => ( {
73- Radar : jest . fn ( )
80+ Radar : jest . fn ( ) . mockReturnValue ( {
81+ toBase64Image : jest . fn ( ) . mockReturnValue ( "data:image/png;base64,fakedata" )
82+ } )
7483 } ) ) ;
7584 } ) ;
7685
77- test ( "displayInfo should correctly format note statistics and Hz calculations" , ( ) => {
78- const statsWindow = new StatsWindow ( mockActivity ) ;
86+ test ( "constructor initialises the widget and calls doAnalytics" , ( ) => {
87+ const sw = new StatsWindow ( mockActivity ) ;
88+
89+ expect ( window . widgetWindows . windowFor ) . toHaveBeenCalledWith ( sw , "stats" , "stats" ) ;
90+ expect ( mockWidgetWindow . clear ) . toHaveBeenCalled ( ) ;
91+ expect ( mockWidgetWindow . show ) . toHaveBeenCalled ( ) ;
92+ expect ( mockWidgetWindow . sendToCenter ) . toHaveBeenCalled ( ) ;
93+ expect ( sw . isOpen ) . toBe ( true ) ;
94+ // doAnalytics was called during construction
95+ expect ( global . analyzeProject ) . toHaveBeenCalledWith ( mockActivity ) ;
96+ expect ( global . runAnalytics ) . toHaveBeenCalledWith ( mockActivity ) ;
97+ } ) ;
98+
99+ test ( "onclose sets isOpen to false and cleans up" , ( ) => {
100+ const sw = new StatsWindow ( mockActivity ) ;
101+
102+ // Trigger the onclose callback
103+ mockWidgetWindow . onclose ( ) ;
104+
105+ expect ( sw . isOpen ) . toBe ( false ) ;
106+ expect ( mockActivity . blocks . showBlocks ) . toHaveBeenCalled ( ) ;
107+ expect ( mockWidgetWindow . destroy ) . toHaveBeenCalled ( ) ;
108+ expect ( mockActivity . logo . statsWindow ) . toBeNull ( ) ;
109+ } ) ;
110+
111+ test ( "onmaximize when maximized sets flex layout and re-runs analytics" , ( ) => {
112+ const sw = new StatsWindow ( mockActivity ) ;
113+
114+ // Reset call counts from constructor
115+ global . analyzeProject . mockClear ( ) ;
116+ global . runAnalytics . mockClear ( ) ;
117+
118+ // Set isMaximized to return true
119+ mockWidgetWindow . isMaximized . mockReturnValue ( true ) ;
120+
121+ // Trigger onmaximize
122+ mockWidgetWindow . onmaximize ( ) ;
123+
124+ expect ( mockWidgetBody . style . display ) . toBe ( "flex" ) ;
125+ expect ( mockWidgetBody . style . justifyContent ) . toBe ( "space-between" ) ;
126+ expect ( mockWidgetBody . style . padding ) . toBe ( "0px 2vw" ) ;
127+ // doAnalytics is re-called
128+ expect ( global . analyzeProject ) . toHaveBeenCalled ( ) ;
129+ } ) ;
130+
131+ test ( "onmaximize when not maximized resets padding" , ( ) => {
132+ const sw = new StatsWindow ( mockActivity ) ;
133+
134+ mockWidgetWindow . isMaximized . mockReturnValue ( false ) ;
135+ mockWidgetWindow . onmaximize ( ) ;
136+
137+ expect ( mockWidgetBody . style . padding ) . toBe ( "0px 0px" ) ;
138+ } ) ;
139+
140+ test ( "__callback in doAnalytics sets image width to 200 when not maximized" , ( ) => {
141+ mockWidgetWindow . isMaximized . mockReturnValue ( false ) ;
142+ const sw = new StatsWindow ( mockActivity ) ;
143+
144+ // capturedCallback was captured by our getChartOptions mock
145+ expect ( capturedCallback ) . not . toBeNull ( ) ;
146+ capturedCallback ( ) ;
147+
148+ // An <img> should have been appended to the widget body
149+ const imgs = mockWidgetBody . querySelectorAll ( "img" ) ;
150+ expect ( imgs . length ) . toBeGreaterThanOrEqual ( 1 ) ;
151+ const img = imgs [ imgs . length - 1 ] ;
152+ expect ( img . width ) . toBe ( 200 ) ;
153+
154+ expect ( mockActivity . blocks . hideBlocks ) . toHaveBeenCalled ( ) ;
155+ expect ( mockActivity . showBlocksAfterRun ) . toBe ( false ) ;
156+ expect ( document . body . style . cursor ) . toBe ( "default" ) ;
157+ } ) ;
158+
159+ test ( "__callback in doAnalytics sets image width from frame height when maximized" , ( ) => {
160+ mockWidgetWindow . isMaximized . mockReturnValue ( true ) ;
161+ const sw = new StatsWindow ( mockActivity ) ;
162+
163+ expect ( capturedCallback ) . not . toBeNull ( ) ;
164+ capturedCallback ( ) ;
165+
166+ const imgs = mockWidgetBody . querySelectorAll ( "img" ) ;
167+ expect ( imgs . length ) . toBeGreaterThanOrEqual ( 1 ) ;
168+ const img = imgs [ imgs . length - 1 ] ;
169+ // height (500) - 80 = 420
170+ expect ( img . width ) . toBe ( 420 ) ;
171+ } ) ;
172+
173+ test ( "displayInfo correctly formats all note statistics and Hz calculations" , ( ) => {
174+ const sw = new StatsWindow ( mockActivity ) ;
79175
80- // Prepare dummy data
81176 const mockStats = {
82177 duples : 5 ,
83178 triplets : 2 ,
@@ -90,16 +185,23 @@ describe("StatsWindow", () => {
90185 ornaments : 1
91186 } ;
92187
93- // Run the function
94- statsWindow . displayInfo ( mockStats ) ;
188+ sw . displayInfo ( mockStats ) ;
189+
190+ const html = sw . jsonObject . innerHTML ;
95191
96- // Check results
97- const outputHtml = statsWindow . jsonObject . innerHTML ;
192+ // Hz calculations: 440 + 0.5 = 440.5 → "441Hz"; 523.25 + 0.5 = 523.75 → "524Hz"
193+ expect ( html ) . toContain ( "441Hz" ) ;
194+ expect ( html ) . toContain ( "524Hz" ) ;
98195
99- expect ( outputHtml ) . toContain ( "441Hz" ) ; // 440 + 0.5 rounded
100- expect ( outputHtml ) . toContain ( "524Hz" ) ; // 523.25 + 0.5 rounded
101- expect ( outputHtml ) . toContain ( "duples: 5" ) ;
102- expect ( outputHtml ) . toContain ( "triplets: 2" ) ;
103- expect ( outputHtml ) . toContain ( "pitch names: A, C#, E" ) ;
196+ // All stat fields
197+ expect ( html ) . toContain ( "duples: 5" ) ;
198+ expect ( html ) . toContain ( "triplets: 2" ) ;
199+ expect ( html ) . toContain ( "quintuplets: 0" ) ;
200+ expect ( html ) . toContain ( "pitch names: A, C#, E" ) ;
201+ expect ( html ) . toContain ( "number of notes: 20" ) ;
202+ expect ( html ) . toContain ( "lowest note: A4" ) ;
203+ expect ( html ) . toContain ( "highest note: C5" ) ;
204+ expect ( html ) . toContain ( "rests used: 4" ) ;
205+ expect ( html ) . toContain ( "ornaments used: 1" ) ;
104206 } ) ;
105207} ) ;
0 commit comments