From f5415be0342877535b8b4f211ee9091ab44f0f55 Mon Sep 17 00:00:00 2001 From: saurabhsharma2u <41580629+saurabhsharma2u@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:31:10 +0000 Subject: [PATCH] Refactor generateHtml to use robust SITEGRAPH_HTML template - Consolidate visualization logic by reusing SITEGRAPH_HTML in generateHtml. - Remove duplicated D3 visualization code. - Ensure graph data is sanitized (remove html property) before injection. - Update tests to verify data injection instead of legacy HTML structure. --- plugins/core/src/report/html.ts | 234 ++----------------------- plugins/core/tests/html_report.test.ts | 28 ++- verification/sitegraph.png | Bin 0 -> 25498 bytes 3 files changed, 37 insertions(+), 225 deletions(-) create mode 100644 verification/sitegraph.png diff --git a/plugins/core/src/report/html.ts b/plugins/core/src/report/html.ts index ff9bd84..69e1312 100644 --- a/plugins/core/src/report/html.ts +++ b/plugins/core/src/report/html.ts @@ -1,227 +1,27 @@ import { Metrics } from '../graph/metrics.js'; +import { SITEGRAPH_HTML } from './sitegraph_template.js'; function safeJson(data: any): string { return JSON.stringify(data).replace(/ - - - - - Crawlith Site Graph - - - -
-

Crawlith Site Graph

- -
- Discovered Pages: - ${metrics.totalPages} -
- ${metrics.sessionStats ? ` -
- Session Crawl: - ${metrics.sessionStats.pagesFetched} pages -
- ${metrics.sessionStats.pagesCached > 0 ? ` -
- - Reuse Cached: - ${metrics.sessionStats.pagesCached} -
` : ''} - ` : ''} -
- Total Edges: - ${metrics.totalEdges} -
-
- Max Depth: - ${metrics.maxDepthFound} -
-
- Avg Out-Degree: - ${metrics.averageOutDegree.toFixed(2)} -
- -
-
Orphan (In-Links: 0)
-
Deep (Depth >= 4)
-
Normal
-
- - ${metrics.topAuthorityPages.length > 0 ? ` -

Top Authority

- - ` : ''} - - ${metrics.orphanPages.length > 0 ? ` -

Orphan Pages (${metrics.orphanPages.length})

-
- Show list - -
- ` : ''} -
-
-
- - - - -`; + `); } diff --git a/plugins/core/tests/html_report.test.ts b/plugins/core/tests/html_report.test.ts index bfd63e5..3401f64 100644 --- a/plugins/core/tests/html_report.test.ts +++ b/plugins/core/tests/html_report.test.ts @@ -3,7 +3,7 @@ import { generateHtml } from '../src/report/html.js'; import { Metrics } from '../src/graph/metrics.js'; describe('html report generator', () => { - test('generates valid html string with metrics', () => { + test('generates valid html string with injected data', () => { const mockMetrics: Metrics = { totalPages: 10, totalEdges: 20, @@ -27,22 +27,32 @@ describe('html report generator', () => { }; const mockGraphData = { - nodes: [{ url: 'https://example.com/', depth: 0, inLinks: 5, outLinks: 2, status: 200 }], + nodes: [{ url: 'https://example.com/', depth: 0, inLinks: 5, outLinks: 2, status: 200, html: 'Big content' }], edges: [] }; const html = generateHtml(mockGraphData, mockMetrics); + // Check for template content expect(html).toContain(''); expect(html).toContain('Crawlith Site Graph'); - expect(html).toContain('10'); // totalPages - expect(html).toContain('5 pages'); // pagesFetched - expect(html).toContain('2'); // pagesCached - expect(html).toContain('https://example.com/orphan'); + + // Check for injected data expect(html).toContain('window.GRAPH_DATA ='); + expect(html).toContain('window.METRICS_DATA ='); + + // Check specific data values in the JSON string + expect(html).toContain('"totalPages":10'); + expect(html).toContain('"pagesFetched":5'); + + // Check that HTML content is stripped + expect(html).not.toContain('Big content'); + + // Check for node data + expect(html).toContain('"url":"https://example.com/"'); }); - test('handles missing session stats', () => { + test('handles missing session stats gracefully', () => { const mockMetrics: any = { totalPages: 10, totalEdges: 20, @@ -53,6 +63,8 @@ describe('html report generator', () => { sessionStats: null }; const html = generateHtml({ nodes: [], edges: [] }, mockMetrics as any); - expect(html).not.toContain('Session Crawl:'); + + expect(html).toContain('window.METRICS_DATA ='); + expect(html).toContain('"sessionStats":null'); }); }); diff --git a/verification/sitegraph.png b/verification/sitegraph.png new file mode 100644 index 0000000000000000000000000000000000000000..9cedfdf79e0bc375f027ef6a78e6c8401ce5ca6c GIT binary patch literal 25498 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 z