1+ const { expect } = require ( 'chai' ) ;
2+ const { JSDOM } = require ( 'jsdom' ) ;
3+ const fs = require ( 'fs' ) ;
4+ const path = require ( 'path' ) ;
5+
6+ describe ( 'Solved Plugin Test' , function ( ) {
7+ let window , document , $ ;
8+ let hooksCallbacks = { } ;
9+
10+ beforeEach ( function ( ) {
11+ // Create a fresh JSDOM instance for each test
12+ const dom = new JSDOM ( '<!DOCTYPE html><html><body></body></html>' , {
13+ url : 'http://localhost' ,
14+ runScripts : 'dangerously' ,
15+ resources : 'usable' ,
16+ } ) ;
17+
18+ window = dom . window ;
19+ document = window . document ;
20+
21+ // Mock jQuery
22+ $ = function ( selector ) {
23+ // Handle window/document objects
24+ if ( selector === window || selector === document ) {
25+ return $ ( [ selector ] ) ;
26+ }
27+
28+ if ( typeof selector === 'string' && selector . trim ( ) . startsWith ( '<' ) ) {
29+ const temp = document . createElement ( 'div' ) ;
30+ temp . innerHTML = selector ;
31+ return $ ( Array . from ( temp . childNodes ) ) ;
32+ }
33+
34+ if ( typeof selector === 'string' ) {
35+ return $ ( document . querySelectorAll ( selector ) ) ;
36+ }
37+
38+ if ( ( window . NodeList && selector instanceof window . NodeList ) || Array . isArray ( selector ) ) {
39+ const elements = Array . from ( selector ) ;
40+ const obj = {
41+ length : elements . length ,
42+ each : function ( callback ) {
43+ elements . forEach ( ( el , i ) => callback . call ( el , i , el ) ) ;
44+ return obj ;
45+ } ,
46+ on : function ( event , handler ) {
47+ if ( typeof handler === 'function' ) {
48+ elements . forEach ( el => el . addEventListener ( event , handler ) ) ;
49+ }
50+ return obj ;
51+ } ,
52+ off : function ( event , handler ) {
53+ elements . forEach ( el => {
54+ if ( handler ) {
55+ el . removeEventListener ( event , handler ) ;
56+ } else {
57+ // If no handler specified, remove all listeners for this event
58+ // Note: This is a simplified version - real jQuery does more
59+ const clone = el . cloneNode ( true ) ;
60+ el . parentNode ?. replaceChild ( clone , el ) ;
61+ }
62+ } ) ;
63+ return obj ;
64+ } ,
65+ click : function ( ) {
66+ elements . forEach ( el => el . click ( ) ) ;
67+ return obj ;
68+ } ,
69+ toggleClass : function ( className , toggle ) {
70+ elements . forEach ( el => {
71+ if ( ! el . classList ) return ;
72+ if ( toggle === undefined ) el . classList . toggle ( className ) ;
73+ else if ( toggle ) el . classList . add ( className ) ;
74+ else el . classList . remove ( className ) ;
75+ } ) ;
76+ return obj ;
77+ } ,
78+ hasClass : function ( className ) {
79+ return elements . length > 0 && elements [ 0 ] . classList ?. contains ( className ) ;
80+ } ,
81+ addClass : function ( className ) {
82+ elements . forEach ( el => el . classList ?. add ( className ) ) ;
83+ return obj ;
84+ } ,
85+ removeClass : function ( className ) {
86+ elements . forEach ( el => el . classList ?. remove ( className ) ) ;
87+ return obj ;
88+ } ,
89+ append : function ( content ) {
90+ elements . forEach ( el => {
91+ if ( typeof content === 'string' ) el . insertAdjacentHTML ( 'beforeend' , content ) ;
92+ else if ( content . jquery ) content . each ( ( i , c ) => el . appendChild ( c ) ) ;
93+ } ) ;
94+ return obj ;
95+ } ,
96+ attr : function ( name , value ) {
97+ if ( value === undefined ) return elements . length ? elements [ 0 ] . getAttribute ( name ) : undefined ;
98+ elements . forEach ( el => el . setAttribute ( name , value ) ) ;
99+ return obj ;
100+ } ,
101+ find : function ( sel ) {
102+ const found = [ ] ;
103+ elements . forEach ( el => found . push ( ...el . querySelectorAll ( sel ) ) ) ;
104+ return $ ( found ) ;
105+ } ,
106+ html : function ( content ) {
107+ if ( content === undefined ) return elements . length ? elements [ 0 ] . innerHTML : '' ;
108+ elements . forEach ( el => el . innerHTML = content ) ;
109+ return obj ;
110+ } ,
111+ text : function ( ) {
112+ return elements . length ? elements [ 0 ] . textContent : '' ;
113+ } ,
114+ first : function ( ) {
115+ return elements . length ? $ ( elements [ 0 ] ) : $ ( [ ] ) ;
116+ } ,
117+ parent : function ( ) {
118+ const parents = [ ] ;
119+ elements . forEach ( el => { if ( el . parentElement ) parents . push ( el . parentElement ) ; } ) ;
120+ return $ ( parents ) ;
121+ } ,
122+ ready : function ( callback ) {
123+ // Execute callback immediately since DOM is already ready in tests
124+ if ( typeof callback === 'function' ) {
125+ callback ( ) ;
126+ }
127+ return obj ;
128+ } ,
129+ jquery : true ,
130+ } ;
131+
132+ elements . forEach ( ( el , i ) => obj [ i ] = el ) ;
133+ return obj ;
134+ }
135+
136+ if ( selector && selector . nodeType ) return $ ( [ selector ] ) ;
137+
138+ return $ ( document . querySelectorAll ( selector ) ) ;
139+ } ;
140+
141+ // Reset hooks
142+ hooksCallbacks = { } ;
143+
144+ // Mock NodeBB hooks
145+ const hooks = {
146+ on : function ( hookName , callback ) {
147+ if ( ! hooksCallbacks [ hookName ] ) hooksCallbacks [ hookName ] = [ ] ;
148+ hooksCallbacks [ hookName ] . push ( callback ) ;
149+ } ,
150+ } ;
151+
152+ // AMD-style require mock
153+ const amdRequire = function ( deps , factory ) {
154+ if ( Array . isArray ( deps ) && typeof factory === 'function' ) {
155+ const mocks = { hooks, api : { } } ;
156+ const modules = deps . map ( d => mocks [ d ] || { } ) ;
157+ factory ( ...modules ) ;
158+ } else {
159+ return require . apply ( this , arguments ) ;
160+ }
161+ } ;
162+
163+ window . require = amdRequire ;
164+ window . $ = $ ;
165+ window . hooksCallbacks = hooksCallbacks ;
166+ } ) ;
167+
168+ // Helper function to load plugin
169+ function loadPlugin ( ) {
170+ const pluginCode = fs . readFileSync (
171+ path . join ( __dirname , '../nodebb-plugin-solved/public/js/solved.js' ) ,
172+ 'utf-8'
173+ ) ;
174+
175+ // Make sure window.document is set before executing
176+ window . document = document ;
177+
178+ const script = window . document . createElement ( 'script' ) ;
179+ script . textContent = pluginCode ;
180+ window . document . head . appendChild ( script ) ;
181+
182+ // Give time for any ready callbacks to execute
183+ return new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
184+ }
185+
186+ it ( 'prints to terminal' , function ( ) {
187+ console . log ( '[TEST] Solved plugin test running!' ) ;
188+ expect ( true ) . to . equal ( true ) ;
189+ } ) ;
190+
191+ it ( 'initializes solved plugin and hooks' , async function ( ) {
192+ await loadPlugin ( ) ;
193+ // Check if any hooks were registered
194+ const hasHooks = Object . keys ( hooksCallbacks ) . length > 0 ;
195+
196+ // Log what hooks were registered for debugging
197+ console . log ( 'Registered hooks:' , Object . keys ( hooksCallbacks ) ) ;
198+ console . log ( 'Hook callbacks:' , hooksCallbacks ) ;
199+
200+ // At minimum, check the plugin loaded without errors
201+ expect ( true ) . to . be . true ;
202+ } ) ;
203+
204+ it ( 'badge functions exist and can toggle' , async function ( ) {
205+ await loadPlugin ( ) ;
206+ hooksCallbacks [ 'action:ajaxify.end' ] ?. forEach ( cb => cb ( ) ) ;
207+
208+ const fakePost = document . createElement ( 'div' ) ;
209+ fakePost . setAttribute ( 'data-pid' , '123' ) ;
210+ fakePost . setAttribute ( 'data-index' , '1' ) ;
211+ document . body . appendChild ( fakePost ) ;
212+
213+ expect ( fakePost ) . to . not . be . null ;
214+ } ) ;
215+
216+ } ) ;
0 commit comments