testcase-visualizer-pro / components /testcase-dashboard.js
ShadowWolf1999's picture
上方 TestCase Visualizer Pro 这个部分 也有jira的风格 说明一下 这个是哪个project 下 那么model
8791d66 verified
class TestcaseDashboard extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
--jira-bg: #F4F5F7;
--jira-card-bg: #FFFFFF;
--jira-text: #172B4D;
--jira-text-sub: #5E6C84;
--jira-border: #DFE1E6;
--jira-blue: #0052CC;
--jira-purple: #6554C0;
--jira-green: #00875A;
--jira-red: #DE350B;
--jira-yellow: #FF991F;
display: block;
font-family: 'Inter', sans-serif;
width: 100%;
min-height: 100vh;
background: var(--jira-bg);
}
.header {
background: linear-gradient(135deg, var(--jira-blue) 0%, var(--jira-purple) 100%);
color: white;
padding: 1.5rem 2rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.filter-bar {
display: flex;
gap: 0.75rem;
padding: 1rem;
background: var(--jira-card-bg);
border-radius: 4px;
margin: 1rem 0;
box-shadow: 0 1px 1px rgba(9, 30, 66, 0.25);
}
.search-input {
border: none;
border-bottom: 2px solid var(--jira-border);
padding: 0.5rem 0;
width: 200px;
transition: border-color 0.2s;
}
.search-input:focus {
outline: none;
border-bottom-color: var(--jira-blue);
}
.filter-btn {
background: transparent;
border: 1px solid transparent;
color: var(--jira-text-sub);
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.filter-btn:hover {
background: rgba(9,30,66,0.08);
}
.filter-btn.active {
background: #DEEBFF;
color: var(--jira-blue);
font-weight: 600;
}
.testcase-card {
background: var(--jira-card-bg);
border: 1px solid var(--jira-border);
border-radius: 4px;
margin-bottom: 0.75rem;
box-shadow: 0 1px 1px rgba(9, 30, 66, 0.25);
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.testcase-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(9, 30, 66, 0.15);
}
.testcase-card-header {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--jira-border);
background: rgba(244, 245, 247, 0.5);
}
.testcase-card-body {
padding: 1rem;
}
.testcase-card-footer {
display: flex;
justify-content: space-between;
padding: 0.75rem 1rem;
border-top: 1px solid var(--jira-border);
background: rgba(244, 245, 247, 0.5);
}
.project-badge {
background: #DEEBFF;
color: #0747A6;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 500;
margin-right: 0.5rem;
}
.module-badge {
background: #EAE6FF;
color: #403294;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 500;
margin-right: 0.5rem;
}
.fsd-badge {
background: #E3FCEF;
color: #006644;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 500;
margin-right: 0.5rem;
}
.user-story-badge {
background: #E3FCEF;
color: #006644;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 500;
}
.priority-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 500;
}
.priority-high {
background: #FFEBE6;
color: #DE350B;
}
.priority-medium {
background: #FFF0B3;
color: #FF991F;
}
.priority-low {
background: #E3FCEF;
color: #00875A;
}
.edit-btn {
background: transparent;
border: 1px solid var(--jira-border);
color: var(--jira-text-sub);
padding: 0.25rem 0.5rem;
border-radius: 3px;
cursor: pointer;
font-size: 0.75rem;
transition: all 0.2s;
}
.edit-btn:hover {
background: rgba(9,30,66,0.08);
color: var(--jira-blue);
}
.status-indicator {
width: 4px;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.status-pass { background: var(--jira-green); }
.status-fail { background: var(--jira-red); }
.status-none { background: var(--jira-border); }
.type-icon {
width: 24px;
height: 24px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 0.75rem;
}
.type-manual { background: #EAE6FF; color: #403294; }
.type-ui { background: #E3FCEF; color: #006644; }
.type-api { background: #DEEBFF; color: #0747A6; }
.container {
max-width: 1400px;
margin: 0 auto;
padding: 0 2rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 300px;
gap: 1.5rem;
}
.stats-card {
background: var(--jira-card-bg);
border-radius: 4px;
padding: 1rem;
box-shadow: 0 1px 1px rgba(9, 30, 66, 0.25);
position: sticky;
top: 100px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.stat-item {
background: rgba(244, 245, 247, 0.5);
border-radius: 4px;
padding: 0.75rem;
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: 600;
}
.stat-label {
font-size: 0.75rem;
color: var(--jira-text-sub);
}
.type-filter {
margin-top: 1rem;
}
.type-filter-item {
display: flex;
align-items: center;
padding: 0.5rem;
border-radius: 4px;
cursor: pointer;
}
.type-filter-item:hover {
background: rgba(9,30,66,0.08);
}
.type-filter-count {
margin-left: auto;
color: var(--jira-text-sub);
font-size: 0.75rem;
}
</style>
<div class="header">
<div class="container">
<h1>TestCase Visualizer Pro</h1>
<p>Project: FSD Integration • Module: Test Management</p>
</div>
</div>
<div class="container">
<div class="grid">
<main>
<div class="filter-bar">
<input type="text" class="search-input" placeholder="Search test cases...">
<select class="filter-btn" multiple>
<option value="SIT">SIT</option>
<option value="UAT">UAT</option>
<option value="PVT">PVT</option>
</select>
<select class="filter-btn" multiple>
<option value="Manual">Manual</option>
<option value="UI Auto">UI Auto</option>
<option value="API Auto">API Auto</option>
<option value="Performance">Performance</option>
<option value="Security">Security</option>
</select>
<select class="filter-btn" multiple>
<option value="Passed">Passed</option>
<option value="Failed">Failed</option>
<option value="Pending">Pending</option>
</select>
<button class="filter-btn active" id="clear-filters">Clear Filters</button>
<button class="filter-btn" id="save-filters">Save Filter Preset</button>
</div>
<div class="testcase-list">
${this.generateTestCases()}
</div>
</main>
<aside>
<div class="stats-card">
<h3>Statistics</h3>
<div class="stats-grid">
${this.generateStats()}
</div>
<div class="type-filter">
<h4 class="mt-4 mb-2">Test Types</h4>
<div class="type-filter-item">
<span>👨‍💻 Manual</span>
<span class="type-filter-count">12</span>
</div>
<div class="type-filter-item">
<span>🖥️ UI Auto</span>
<span class="type-filter-count">8</span>
</div>
<div class="type-filter-item">
<span>🔌 API Auto</span>
<span class="type-filter-count">15</span>
</div>
<div class="type-filter-item">
<span>⏱️ Performance</span>
<span class="type-filter-count">5</span>
</div>
<div class="type-filter-item">
<span>🔒 Security</span>
<span class="type-filter-count">3</span>
</div>
</div>
</div>
</aside>
</div>
</div>
`;
}
generateTestCases() {
const testCases = [
{
id: 'TC-001',
title: 'User authentication validation',
type: 'Manual',
status: 'Passed',
stages: ['SIT', 'UAT'],
priority: 'High',
project: 'PROJ-1',
module: 'AUTH-MOD',
fsd: 'FSD-101',
userStory: 'US-42',
description: 'Validate user can authenticate with valid credentials',
steps: '1. Navigate to login page\n2. Enter valid credentials\n3. Click login button',
expected: 'User should be authenticated and redirected to dashboard'
},
{
id: 'TC-002',
title: 'API endpoint response validation',
type: 'API Auto',
status: 'Failed',
stages: ['UAT', 'PVT'],
priority: 'Medium',
project: 'PROJ-1',
module: 'API-MOD',
fsd: 'FSD-102',
userStory: 'US-43',
description: 'Validate API returns correct response format',
steps: '1. Send GET request to /api/users\n2. Verify response format',
expected: 'Response should match OpenAPI specification'
},
{
id: 'TC-003',
title: 'UI component rendering test',
type: 'UI Auto',
status: 'Pending',
stages: ['SIT', 'UAT', 'PVT'],
priority: 'Low',
project: 'PROJ-2',
module: 'UI-MOD',
fsd: 'FSD-103',
userStory: 'US-44',
description: 'Verify dashboard components render correctly',
steps: '1. Load dashboard page\n2. Check all components',
expected: 'All components should render without errors'
},
{
id: 'TC-004',
title: 'Performance benchmark',
type: 'Performance',
status: 'Passed',
stages: ['SIT'],
priority: 'Medium',
project: 'PROJ-2',
module: 'PERF-MOD',
fsd: 'FSD-104',
userStory: 'US-45',
description: 'Measure API response times under load',
steps: '1. Run load test with 1000 concurrent users\n2. Record response times',
expected: '95% of requests should complete in <500ms'
},
{
id: 'TC-005',
title: 'Security vulnerability scan',
type: 'Security',
status: 'Failed',
stages: ['UAT', 'PVT'],
priority: 'High',
project: 'PROJ-3',
module: 'SEC-MOD',
fsd: 'FSD-105',
userStory: 'US-46',
description: 'Check for common security vulnerabilities',
steps: '1. Run OWASP ZAP scan\n2. Analyze results',
expected: 'No critical vulnerabilities found'
}
];
return testCases.map(tc => `
<div class="testcase-card" data-id="${tc.id}">
<div class="status-indicator status-${tc.status.toLowerCase()}"></div>
<div class="testcase-card-header">
<span class="project-badge">${tc.project}</span>
<span class="module-badge">${tc.module}</span>
<span class="fsd-badge">${tc.fsd}</span>
<span class="user-story-badge">${tc.userStory}</span>
<button class="edit-btn ml-auto" onclick="this.closest('.testcase-card').classList.toggle('expanded')">
Edit
</button>
</div>
<div class="testcase-card-body">
<div class="flex items-center">
<div class="type-icon type-${tc.type.toLowerCase().replace(' ', '-')}">
${this.getTypeIcon(tc.type)}
</div>
<div>
<div class="font-medium">${tc.title}</div>
<div class="text-sm text-gray-600">${tc.id}${tc.stages.join(', ')}</div>
</div>
<div class="ml-auto">
<span class="priority-badge priority-${tc.priority.toLowerCase()}">${tc.priority}</span>
</div>
</div>
<div class="mt-3">
<div class="text-sm"><strong>Description:</strong> ${tc.description}</div>
<div class="text-sm mt-1"><strong>Steps:</strong><pre>${tc.steps}</pre></div>
<div class="text-sm mt-1"><strong>Expected:</strong> ${tc.expected}</div>
</div>
</div>
<div class="testcase-card-footer">
<div class="text-xs text-gray-500">
Last updated: ${new Date().toLocaleDateString()}
Used in: ${tc.stages.map(s => `<span class="stage-tag">${s}</span>`).join(' ')}
</div>
<div class="flex gap-2">
<button class="edit-btn">Comment</button>
<button class="edit-btn">Attach</button>
<button class="edit-btn">Clone</button>
</div>
</div>
</div>
`).join('');
}
generateStats() {
return `
<div class="stat-item">
<div class="stat-value">24</div>
<div class="stat-label">Total</div>
</div>
<div class="stat-item">
<div class="stat-value text-green-600">18</div>
<div class="stat-label">Passed</div>
</div>
<div class="stat-item">
<div class="stat-value text-red-600">3</div>
<div class="stat-label">Failed</div>
</div>
<div class="stat-item">
<div class="stat-value text-yellow-600">3</div>
<div class="stat-label">Pending</div>
</div>
`;
}
getTypeIcon(type) {
const icons = {
'Manual': '👨‍💻',
'UI Auto': '🖥️',
'API Auto': '🔌',
'Performance': '⏱️',
'Security': '🔒',
'Accessibility': '♿',
'Compatibility': '🔄',
'Regression': '🔁'
};
return icons[type] || '📋';
}
}
customElements.define('testcase-dashboard', TestcaseDashboard);