DEV Community

Rahul
Rahul

Posted on

Testcraft Studio frontend 32: src/pages/Suites.jsx

import React, { useEffect, useState } from 'react';
import Icon from '../Icon.jsx';
import { defaultFields } from '../seed.js';
import { useStudio, canReview, download, log } from '../store.jsx';
import { suiteExport } from '../suiteExport.js';
import { Badge, Button, ContextBar, Empty, Field, Heading, Modal, Note, Search, SourceDialog, Tabs, WorkspaceRequired } from '../ui.jsx';

export default function Suites({ navigate, initialSuiteId = '' }) {
  const { state: s, workspace: w, mutate, notify } = useStudio();
  const [suiteId, setSuite] = useState(initialSuiteId), [testId, setTest] = useState(''), [query, setQuery] = useState(''), [filter, setFilter] = useState('All'), [tab, setTab] = useState('Scenarios'), [editing, setEditing] = useState(null), [source, setSource] = useState('');
  useEffect(() => { setSuite(initialSuiteId); setTest(''); }, [initialSuiteId]);
  if (!w) return <WorkspaceRequired navigate={navigate}/>;
  const list = s.suites.filter(t => t.workspaceId === w.id), suite = list.find(t => t.id === suiteId) || list[0];
  const tests = (suite?.tests || []).filter(t => `${t.id} ${t.title} ${t.requirement}`.toLowerCase().includes(query.toLowerCase()) && (filter === 'All' || t.status === filter));
  const selected = suite?.tests.find(t => t.id === testId) || tests[0];
  const template = suite?.templateSnapshot || s.templates.find(t => t.id === suite?.templateId);
  const citationFor = id => suite?.sourceSnapshot?.find(item => item.id === id) || s.documents.find(item => item.id === id);
  const extraFields = (template?.fields || []).filter(field => !defaultFields.some(known => known.toLowerCase() === field.toLowerCase()));
  const includesField = name => !template || template.fields.some(field => field.toLowerCase() === name.toLowerCase());
  function decide(status) { if (!selected || !canReview(s)) return; mutate(d => { const t = d.suites.find(t => t.id === suite.id); const test = t.tests.find(test => test.id === selected.id); test.status = status; test.reviewedBy = s.session.name; test.revision = (test.revision || 1) + 1; t.status = t.provisional ? 'PROVISIONAL' : t.tests.every(test => test.status === 'Accepted') ? 'Accepted' : 'Ready for review'; log(d, `${status}: ${selected.id}`); }); notify(`Scenario ${status.toLowerCase()}.`); }
  function exportSuite() {
    const output = suiteExport(s, w, suite);
    download(output.filename, output.content, output.mimeType); notify('Test cases exported in the selected template column order.');
  }
  return <><Heading eyebrow={`${w.ref} · REVIEW WITH TRACEABILITY`} title="Test suites" description="Review the scenario, its expected behavior, and the evidence behind it."><div className="row-actions"><Button onClick={() => navigate('readiness')}>New generation</Button><Button variant="primary" icon="external" disabled={!suite} onClick={exportSuite}>Export CSV</Button></div></Heading><ContextBar navigate={navigate}/>{!suite ? <section className="card margin-top"><Empty title="No test suites yet" description="Review readiness and generate a test suite for this workspace." action={<Button variant="primary" onClick={() => navigate('readiness')}>Review readiness</Button>}/></section> : <><div className="suite-toolbar"><select className="wide-select" value={suite.id} onChange={e => { setSuite(e.target.value); setTest(''); }}>{list.map(t => <option key={t.id} value={t.id}>{t.name} · Context {t.version}</option>)}</select><Badge>{suite.status}</Badge><span className="subtle">{template?.name} · v{template?.version}</span></div>{suite.version !== w.version && <Note tone="amber">This historic suite is pinned to context {suite.version}. The current workspace uses context {w.version}. Reviewing it does not change its knowledge version.</Note>}{suite.provisional && <Note tone="amber">PROVISIONAL · {suite.gaps.length} unresolved requirements. Unsupported expected outcomes remain unresolved. This suite does not establish complete coverage.</Note>}<Tabs value={tab} onChange={setTab} items={['Scenarios', 'Coverage & gaps']}/>{tab === 'Coverage & gaps' ? <section className="card"><div className="section-heading"><h2>Concrete coverage and review status</h2><span className="count-pill">{suite.tests.length} tests</span></div><div className="readiness-tiles padded"><div><strong>{new Set(suite.tests.map(t => t.requirement)).size}</strong><span>Requirements represented</span></div><div><strong>{suite.tests.filter(t => !t.sourceIds.length && !t.answerEvidence).length}</strong><span>Missing citations</span></div><div><strong>{suite.tests.filter(t => t.expected === 'Expected result unresolved').length}</strong><span>Unsupported outcomes</span></div><div><strong>{suite.tests.filter(t => t.status === 'Accepted').length}</strong><span>Accepted tests</span></div></div><div className="table-scroll"><table><thead><tr><th>REQUIREMENT</th><th>TEST CASES</th><th>REVIEW</th></tr></thead><tbody>{[...new Set(suite.tests.map(t => t.requirement))].map(id => <tr key={id}><td>{id}</td><td>{suite.tests.filter(t => t.requirement === id).map(t => t.id).join(', ')}</td><td><Badge tone={suite.tests.some(t => t.requirement === id && t.expected === 'Expected result unresolved') ? 'amber' : 'green'}>{suite.tests.some(t => t.requirement === id && t.expected === 'Expected result unresolved') ? 'Evidence gap' : 'Source linked'}</Badge></td></tr>)}</tbody></table></div><div className="padded"><Note>These measures describe this suite only. They do not claim full requirement coverage or model accuracy.</Note></div></section> : <div className="suite-grid"><section className="card scenario-list"><div className="padded"><Search value={query} onChange={setQuery} placeholder="Search scenarios…"/><select className="wide-select margin-top" value={filter} onChange={e => setFilter(e.target.value)}>{['All', 'Generated', 'Accepted', 'Revision requested', 'Rejected'].map(v => <option key={v}>{v}</option>)}</select></div>{tests.map(t => <button className={`scenario-item ${selected?.id === t.id ? 'selected' : ''}`} key={t.id} onClick={() => setTest(t.id)}><div><span>{t.id}</span><Badge>{t.status}</Badge></div><strong>{t.title}</strong><small>{t.category} · {t.requirement}</small></button>)}{!tests.length && <Empty title="No matching scenarios" description="Clear your search or status filter."/>}</section>{selected && <section className="card padded scenario-detail"><div className="section-heading inset"><span className="code-label">{selected.id}</span><Button icon="file" onClick={() => setEditing({ ...selected, steps: selected.steps.join('\n') })}>Edit scenario</Button></div><h2 className="scenario-title">{selected.title}</h2><div className="detail-summary"><Badge tone="purple">{selected.category}</Badge><Badge>{selected.status}</Badge><span>{selected.requirement} · Revision {selected.revision || 1}</span></div>{[['Objective', selected.objective], ['Preconditions', selected.preconditions], ['Test data', selected.data]].filter(([label]) => includesField(label)).map(([label, value]) => <div className="test-field" key={label}><h3>{label}</h3><p>{value || 'Not specified'}</p></div>)}<div className="test-field"><h3>Steps</h3><ol className="test-steps">{selected.steps.map((step, i) => <li key={i}><span>{i + 1}</span>{step}</li>)}</ol></div><div className="test-field"><h3>Expected result</h3><div className={`expected-result ${selected.expected === 'Expected result unresolved' ? 'unresolved' : ''}`}>{selected.expected}</div></div><div className="test-field"><h3>Priority · {selected.priority}</h3><p>{selected.rationale}</p></div>{extraFields.map(field => <div className="test-field" key={field}><h3>{field}</h3><p>{selected.customFields?.[field] || "Not provided"}</p></div>)}<div className="test-field"><h3>Evidence citations</h3>{selected.sourceIds.map(id => <button className="source-citation" key={id} onClick={() => setSource(id)}><Icon name="file" size={19}/><span><strong>{citationFor(id)?.name || 'Source unavailable'}</strong><small>{citationFor(id)?.locator} · Revision {citationFor(id)?.revision}</small></span><Icon name="external" size={14}/></button>)}{selected.answerEvidence && <Note>User-provided reviewed evidence: {selected.answerEvidence}</Note>}</div><Field label="Review notes"><textarea value={selected.notes || ''} rows={2} onChange={e => mutate(d => { d.suites.find(t => t.id === suite.id).tests.find(t => t.id === selected.id).notes = e.target.value; })} placeholder="Document changes or team review notes…"/></Field><div className="review-actions"><Button variant="primary" icon="check" disabled={!canReview(s) || selected.expected === 'Expected result unresolved'} onClick={() => decide('Accepted')}>Accept scenario</Button><Button disabled={!canReview(s)} onClick={() => decide('Revision requested')}>Request revision</Button><Button disabled={!canReview(s)} onClick={() => decide('Rejected')}>Reject</Button></div>{!canReview(s) && <p className="supporting-copy">Sign in to review this scenario.</p>}</section>}</div>}</>}{editing && <Modal title="Edit test scenario" close={() => setEditing(null)} wide><form onSubmit={e => { e.preventDefault(); mutate(d => { const t = d.suites.find(t => t.id === suite.id).tests.find(t => t.id === editing.id); Object.assign(t, editing, { steps: editing.steps.split('\n').map(step => step.trim()).filter(Boolean), status: 'Revision requested', revision: (t.revision || 1) + 1 }); log(d, `Edited ${editing.id}`); }); setEditing(null); notify('Scenario revision saved. Team review acceptance reset.'); }}><Field label="Title"><input required value={editing.title} onChange={e => setEditing(t => ({ ...t, title: e.target.value }))}/></Field>{[['Objective', 'objective'], ['Preconditions', 'preconditions'], ['Test data', 'data'], ['Steps (one per line)', 'steps'], ['Expected result', 'expected']].map(([label, key]) => <Field key={key} label={label}><textarea required={['steps', 'expected'].includes(key)} rows={key === 'steps' ? 4 : 2} value={editing[key]} onChange={e => setEditing(t => ({ ...t, [key]: e.target.value }))}/></Field>)}<Field label="Priority"><select value={editing.priority} onChange={e => setEditing(t => ({ ...t, priority: e.target.value }))}>{['High', 'Medium', 'Low', 'Unassigned'].map(v => <option key={v}>{v}</option>)}</select></Field>{extraFields.map(field => <Field key={field} label={field}><input value={editing.customFields?.[field] || ""} onChange={e => setEditing(t => ({ ...t, customFields: { ...t.customFields, [field]: e.target.value } }))}/></Field>)}<Note>Edits are user-provided revisions. Check every changed expected result against the pinned evidence before accepting.</Note><div className="modal-actions"><Button onClick={() => setEditing(null)}>Cancel</Button><Button type="submit" variant="primary">Save revision</Button></div></form></Modal>}{source && <SourceDialog sourceId={source} citation={suite?.sourceSnapshot?.find(item => item.id === source)} close={() => setSource('')}/>}</>;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)