cargo-test-json-2-html 0.1.1

Convert cargo test JSON output to beautiful, self-contained HTML reports
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cargo Test Report</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
        .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        .header { background: #2563eb; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
        .content { padding: 20px; }
        
        /* Improved summary design */
        .summary { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; margin-bottom: 30px; }
        .summary-title { font-size: 16px; font-weight: 600; color: #374151; margin-bottom: 15px; }
        .stats-row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
        .stat-item { display: flex; align-items: center; gap: 8px; }
        .stat-item a { text-decoration: none; color: inherit; display: flex; align-items: center; gap: 8px; }
        .stat-item a:hover { opacity: 0.8; }
        .stat-icon { width: 16px; height: 16px; border-radius: 50%; }
        .stat-icon.passed { background: #10b981; }
        .stat-icon.failed { background: #ef4444; }
        .stat-icon.ignored { background: #f59e0b; }
        .stat-text { font-size: 14px; color: #374151; }
        .stat-number { font-weight: 600; }
        .duration { margin-left: auto; font-size: 14px; color: #6b7280; }
        
        .section { margin-bottom: 30px; background: #fafbfc; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; }
        .section-title { font-size: 18px; font-weight: 600; color: #1e293b; }
        .section-title.collapsible { cursor: pointer; user-select: none; display: flex; justify-content: space-between; align-items: center; }
        .section-title.collapsible:hover { color: #374151; }
        .section-content { transition: max-height 0.3s ease; overflow: hidden; padding-top: 15px; }
        .section-content.collapsed { max-height: 0; padding-top: 0; }
        .section-title.collapsible .section-info { display: flex; align-items: center; gap: 8px; }
        .section-title.collapsible::after { content: ''; font-size: 14px; transition: transform 0.2s; line-height: 1; }
        .section-title.collapsible.collapsed::after { transform: rotate(-90deg); }
        /* Improved clickable indication */
        .test-item { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 10px; }
        .test-item:has(.test-details.show) { border-radius: 6px 6px 0 0; }
        .test-details { border-top: 1px solid #e2e8f0; display: none; margin: 0 -1px -1px -1px; background: #1e293b; border-radius: 0 0 6px 6px; }
        .test-header { padding: 15px; display: flex; justify-content: space-between; align-items: center; }
        .test-header.clickable { cursor: pointer; transition: background-color 0.2s; }
        .test-header.clickable:hover { background: #f1f5f9; }
        
        .test-right { display: flex; align-items: center; gap: 10px; }
        .view-output { font-size: 12px; color: #6b7280; transition: opacity 0.2s; }
        .view-output.no-output { opacity: 0.6; }
        .test-header:hover .view-output { color: #374151; }
        .test-header:hover .view-output.no-output { opacity: 1; }
        
        .test-name { font-family: 'Monaco', 'Menlo', monospace; font-size: 14px; }
        .test-status { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
        .test-status.passed { background: #dcfce7; color: #166534; }
        .test-status.failed { background: #fecaca; color: #991b1b; }
        .test-status.ignored { background: #fef3c7; color: #92400e; }
        .test-details.show { display: block; }
        .test-output { color: #e2e8f0; padding: 15px; border-radius: 0; font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; white-space: pre-wrap; overflow-x: auto; }
        .test-output a { color: #60a5fa; text-decoration: underline; }
        .test-output a:hover { color: #93c5fd; }
        .errors { background: #fef2f2; border: 1px solid #fecaca; border-radius: 6px; padding: 15px; }
        .error-item { margin-bottom: 10px; font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; }
        .raw-lines { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 15px; }
        .raw-line { font-family: 'Monaco', 'Menlo', monospace; font-size: 13px; margin-bottom: 5px; }
        
        @media (max-width: 600px) {
            .test-status { display: none; }
        }
    </style>
    <script>
        function toggleDetails(element) {
            const details = element.nextElementSibling;
            const wasVisible = details.classList.contains('show');
            details.classList.toggle('show');
            
            // Keep the clicked element in view if we're expanding
            if (!wasVisible) {
                element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        }
        
        function toggleSection(element) {
            const content = element.nextElementSibling;
            const wasCollapsed = content.classList.contains('collapsed');
            content.classList.toggle('collapsed');
            element.classList.toggle('collapsed');
            
            // Keep the section header in view if we're expanding
            if (wasCollapsed) {
                element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        }
    </script>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>Cargo Test Report</h1>
        </div>
        <div class="content">
            <div class="summary">
                <div class="summary-title">Test Results Summary</div>
                <div class="stats-row">
                    <div class="stat-item">
                        <a href="#passed-tests">
                            <div class="stat-icon passed"></div>
                            <div class="stat-text"><span class="stat-number">9</span> passed</div>
                        </a>
                    </div>
                    <div class="stat-item">
                        <a href="#failed-tests">
                            <div class="stat-icon failed"></div>
                            <div class="stat-text"><span class="stat-number">4</span> failed</div>
                        </a>
                    </div>
                    <div class="stat-item">
                        <a href="#ignored-tests">
                            <div class="stat-icon ignored"></div>
                            <div class="stat-text"><span class="stat-number">2</span> ignored</div>
                        </a>
                    </div>
                    <div class="duration">⏱️ 2.456s</div>
                </div>
            </div>

            <div class="section">
                <div class="section-title collapsible" id="failed-tests" onclick="toggleSection(this)">
                    <div class="section-info">
                        <span>❌ Failed Tests</span>
                        <span>(4)</span>
                    </div>
                </div>
                <div class="section-content">
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">auth::test_login_invalid_password</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status failed">FAILED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">thread &#x27;auth::test_login_invalid_password&#x27; panicked at src/auth.rs:45:9:
assertion failed: &#x60;(left &#x3D;&#x3D; right)&#x60;
  left: &#x60;Err(InvalidPassword)&#x60;,
 right: &#x60;Ok(User { id: 1, name: &quot;test&quot; })&#x60;</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">database::test_query_users</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status failed">FAILED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">thread &#x27;database::test_query_users&#x27; panicked at src/database.rs:123:5:
Database connection failed: Connection refused (os error 111)</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">api::test_delete_user</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status failed">FAILED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">thread &#x27;api::test_delete_user&#x27; panicked at src/api.rs:89:9:
assertion failed: response.status() &#x3D;&#x3D; 204
Actual status: 500 Internal Server Error
Response body: Database error: foreign key constraint</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">security::test_xss_protection</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status failed">FAILED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">thread &#x27;security::test_xss_protection&#x27; panicked at src/security.rs:67:9:
XSS vulnerability detected: &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt; was not escaped</div>
                    </div>
                </div>
                </div>
            </div>

            <div class="section">
                <div class="section-title collapsible collapsed" id="passed-tests" onclick="toggleSection(this)">
                    <div class="section-info">
                        <span>✅ Passed Tests</span>
                        <span>(9)</span>
                    </div>
                </div>
                <div class="section-content collapsed">
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">auth::test_login_success</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">auth::test_logout</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">database::test_connection</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">Connected to database successfully
Running migrations...</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">api::test_get_user</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">GET /api/users/1
Status: 200 OK
Response: {&quot;id&quot;:1,&quot;name&quot;:&quot;John Doe&quot;}</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">api::test_create_user</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">utils::test_hash_password</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">utils::test_validate_email</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header clickable" onclick="toggleDetails(this)">
                        <span class="test-name">utils::test_generate_token</span>
                        <div class="test-right">
                            <span class="view-output">👁️ View output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                    <div class="test-details">
                        <div class="test-output">Generated token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...</div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">security::test_sql_injection</span>
                        <div class="test-right">
                            <span class="view-output no-output">No output</span>
                            <span class="test-status passed">PASSED</span>
                        </div>
                    </div>
                </div>
                </div>
            </div>

            <div class="section">
                <div class="section-title collapsible collapsed" id="ignored-tests" onclick="toggleSection(this)">
                    <div class="section-info">
                        <span>⏭️ Ignored Tests</span>
                        <span>(2)</span>
                    </div>
                </div>
                <div class="section-content collapsed">
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">integration::test_full_workflow</span>
                        <div class="test-right">
                            <span class="test-status ignored">IGNORED</span>
                        </div>
                    </div>
                </div>
                <div class="test-item">
                    <div class="test-header">
                        <span class="test-name">integration::test_performance</span>
                        <div class="test-right">
                            <span class="test-status ignored">IGNORED</span>
                        </div>
                    </div>
                </div>
                </div>
            </div>


            <div class="section">
                <div class="section-title collapsible collapsed" onclick="toggleSection(this)">
                    <div class="section-info">
                        <span>📄 Raw Output</span>
                    </div>
                </div>
                <div class="section-content collapsed">
                <div class="raw-lines">
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;auth::test_login_success&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;auth::test_login_invalid_password&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;auth::test_logout&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;database::test_connection&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;database::test_query_users&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;api::test_get_user&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;api::test_create_user&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;api::test_delete_user&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;utils::test_hash_password&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;utils::test_validate_email&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;utils::test_generate_token&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;integration::test_full_workflow&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;integration::test_performance&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;security::test_sql_injection&quot; }</div>
                    <div class="raw-line">{ &quot;type&quot;: &quot;test&quot;, &quot;event&quot;: &quot;started&quot;, &quot;name&quot;: &quot;security::test_xss_protection&quot; }</div>
                </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>