@@ -5,6 +5,8 @@ import pino from 'pino';
55const mockLogger = pino ( ) ;
66
77describe ( 'tooling scan' , ( ) => {
8+ // ── Existing tests ────────────────────────────────────────────────
9+
810 it ( 'detects Bootstrap via link href' , async ( ) => {
911 await newTestPage ( async ( { page } ) => {
1012 await page . setContent ( `
@@ -49,7 +51,7 @@ describe('tooling scan', () => {
4951 </html>
5052 ` ) ;
5153 expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
52- tooling : 'bootstrap, animate.css' ,
54+ tooling : 'animate.css,bootstrap ' ,
5355 } ) ;
5456 } ) ;
5557 } ) ;
@@ -68,6 +70,212 @@ describe('tooling scan', () => {
6870 } ) ;
6971 } ) ;
7072
73+ // ── Frontend framework detection ──────────────────────────────────
74+
75+ it ( 'detects Next.js via __NEXT_DATA__ script tag' , async ( ) => {
76+ await newTestPage ( async ( { page } ) => {
77+ await page . setContent ( `
78+ <html>
79+ <head></head>
80+ <body>
81+ <script id="__NEXT_DATA__" type="application/json">{"props":{}}</script>
82+ </body>
83+ </html>
84+ ` ) ;
85+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
86+ tooling : 'next.js' ,
87+ } ) ;
88+ } ) ;
89+ } ) ;
90+
91+ it ( 'detects Astro via custom elements' , async ( ) => {
92+ await newTestPage ( async ( { page } ) => {
93+ await page . setContent ( `
94+ <html>
95+ <head></head>
96+ <body>
97+ <astro-island>content</astro-island>
98+ </body>
99+ </html>
100+ ` ) ;
101+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
102+ tooling : 'astro' ,
103+ } ) ;
104+ } ) ;
105+ } ) ;
106+
107+ // ── Frontend library detection (window globals) ───────────────────
108+
109+ it ( 'detects jQuery via window.jQuery global' , async ( ) => {
110+ await newTestPage ( async ( { page } ) => {
111+ await page . setContent ( `<html><head></head><body></body></html>` ) ;
112+ await page . evaluate ( ( ) => {
113+ ( window as any ) . jQuery = { fn : { jquery : '3.7.1' } } ;
114+ } ) ;
115+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
116+ tooling : 'jquery' ,
117+ } ) ;
118+ } ) ;
119+ } ) ;
120+
121+ it ( 'detects Alpine.js via window.Alpine global' , async ( ) => {
122+ await newTestPage ( async ( { page } ) => {
123+ await page . setContent ( `<html><head></head><body></body></html>` ) ;
124+ await page . evaluate ( ( ) => {
125+ ( window as any ) . Alpine = { } ;
126+ } ) ;
127+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
128+ tooling : 'alpine.js' ,
129+ } ) ;
130+ } ) ;
131+ } ) ;
132+
133+ it ( 'detects Lit via window.litElementVersions global' , async ( ) => {
134+ await newTestPage ( async ( { page } ) => {
135+ await page . setContent ( `<html><head></head><body></body></html>` ) ;
136+ await page . evaluate ( ( ) => {
137+ ( window as any ) . litElementVersions = [ '3.0.0' ] ;
138+ } ) ;
139+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
140+ tooling : 'lit' ,
141+ } ) ;
142+ } ) ;
143+ } ) ;
144+
145+ // ── Component library detection ───────────────────────────────────
146+
147+ it ( 'detects MUI via characteristic class names' , async ( ) => {
148+ await newTestPage ( async ( { page } ) => {
149+ await page . setContent ( `
150+ <html>
151+ <head></head>
152+ <body>
153+ <button class="MuiButton-root">Click</button>
154+ </body>
155+ </html>
156+ ` ) ;
157+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
158+ tooling : 'mui' ,
159+ } ) ;
160+ } ) ;
161+ } ) ;
162+
163+ it ( 'detects Ant Design via link href' , async ( ) => {
164+ await newTestPage ( async ( { page } ) => {
165+ await page . setContent ( `
166+ <html>
167+ <head>
168+ <link rel="stylesheet" href="/css/antd.min.css">
169+ </head>
170+ <body></body>
171+ </html>
172+ ` ) ;
173+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
174+ tooling : 'ant-design' ,
175+ } ) ;
176+ } ) ;
177+ } ) ;
178+
179+ // ── False-positive regression tests ───────────────────────────────
180+
181+ it ( 'does not detect Solid from script src containing "consolidate"' , async ( ) => {
182+ await newTestPage ( async ( { page } ) => {
183+ await page . setContent ( `
184+ <html>
185+ <head></head>
186+ <body>
187+ <script src="/js/consolidate.js"></script>
188+ </body>
189+ </html>
190+ ` ) ;
191+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
192+ tooling : null ,
193+ } ) ;
194+ } ) ;
195+ } ) ;
196+
197+ it ( 'does not detect Lit from script src containing "split"' , async ( ) => {
198+ await newTestPage ( async ( { page } ) => {
199+ await page . setContent ( `
200+ <html>
201+ <head></head>
202+ <body>
203+ <script src="/js/split.js"></script>
204+ </body>
205+ </html>
206+ ` ) ;
207+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
208+ tooling : null ,
209+ } ) ;
210+ } ) ;
211+ } ) ;
212+
213+ it ( 'does not detect Angular from app-root alone' , async ( ) => {
214+ await newTestPage ( async ( { page } ) => {
215+ await page . setContent ( `
216+ <html>
217+ <head></head>
218+ <body>
219+ <app-root>My non-Angular app</app-root>
220+ </body>
221+ </html>
222+ ` ) ;
223+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
224+ tooling : null ,
225+ } ) ;
226+ } ) ;
227+ } ) ;
228+
229+ it ( 'does not detect Bulma from .is-primary alone' , async ( ) => {
230+ await newTestPage ( async ( { page } ) => {
231+ await page . setContent ( `
232+ <html>
233+ <head></head>
234+ <body>
235+ <button class="is-primary">Click</button>
236+ </body>
237+ </html>
238+ ` ) ;
239+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
240+ tooling : null ,
241+ } ) ;
242+ } ) ;
243+ } ) ;
244+
245+ // ── Regex-based script src detection ──────────────────────────────
246+
247+ it ( 'detects Solid via properly-patterned script src' , async ( ) => {
248+ await newTestPage ( async ( { page } ) => {
249+ await page . setContent ( `
250+ <html>
251+ <head></head>
252+ <body>
253+ <script src="/node_modules/solid-js/dist/solid.js"></script>
254+ </body>
255+ </html>
256+ ` ) ;
257+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
258+ tooling : 'solid' ,
259+ } ) ;
260+ } ) ;
261+ } ) ;
262+
263+ it ( 'detects Lit via properly-patterned script src' , async ( ) => {
264+ await newTestPage ( async ( { page } ) => {
265+ await page . setContent ( `
266+ <html>
267+ <head></head>
268+ <body>
269+ <script src="/node_modules/lit/index.js"></script>
270+ </body>
271+ </html>
272+ ` ) ;
273+ expect ( await buildToolingResult ( mockLogger , page ) ) . toEqual ( {
274+ tooling : 'lit' ,
275+ } ) ;
276+ } ) ;
277+ } ) ;
278+
71279 afterAll ( async ( ) => {
72280 if ( browserInstance ) {
73281 await browserInstance . close ( ) ;
0 commit comments