Skip to content

Commit 44b2efd

Browse files
authored
Merge pull request #514 from GSA/1794-iterate-tooling-scan
1794: Expand Tooling Scan
2 parents 9e3d122 + accde1a commit 44b2efd

File tree

2 files changed

+592
-78
lines changed

2 files changed

+592
-78
lines changed

libs/core-scanner/src/scans/tooling.spec.ts

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import pino from 'pino';
55
const mockLogger = pino();
66

77
describe('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

Comments
 (0)