DEV Community

Rahul
Rahul

Posted on

Testcraft Studio frontend 31: src/pages/Readiness.jsx

import React, { useState } from 'react';
import Icon from '../Icon.jsx';
import TemplateUpload from '../TemplateUpload.jsx';
import { useStudio, readiness, uid, timestamp, contextKey, log } from '../store.jsx';
import { Badge, Button, ContextBar, Empty, Heading, Note, WorkspaceRequired } from '../ui.jsx';

export default function Readiness({ navigate }) {
  const { state: s, mutate, workspace: w } = useStudio();
  const [template, setTemplate] = useState(w?.templateId || s.templates.find(t => t.team === s.session.team && t.visibility !== 'workspace')?.id || '');
  if (!w) return <WorkspaceRequired navigate={navigate}/>;
  const status = readiness(s, w), jobs = s.jobs.filter(job => job.workspaceId === w.id), running = jobs.find(job => job.status === 'running');
  const templates = s.templates.filter(t => t.team === s.session.team && (t.visibility !== 'workspace' || t.workspaceId === w.id));
  function start() {
    if (!status.ready || running || !templates.some(t => t.id === template)) return;
    mutate(d => { d.jobs.unshift({ id: uid('run'), workspaceId: w.id, version: w.version, context: contextKey(w), templateId: template, provisional: false, status: 'running', stageIndex: 0, createdAt: timestamp(), stage: 'Preparing workspace evidence' }); log(d, `Started test suite for ${w.ref}`); });
  }
  return <><Heading eyebrow={`${w.ref} · FROM EVIDENCE TO TESTS`} title="Generate test cases" description="Choose your format and generate from the knowledge available in this workspace."/><ContextBar navigate={navigate}/><div className="generation-layout margin-top"><section className="card padded generation-main"><div className="section-heading inset"><h2>Your test-case format</h2><span className="soft-icon soft-purple"><Icon name="checkSquare" size={22}/></span></div><p className="supporting-copy">Use the standard format, select a saved template, or upload your own Excel template.</p><label className="form-field"><span>Test-case template</span><select value={template} onChange={e => setTemplate(e.target.value)}>{templates.map(t => <option value={t.id} key={t.id}>{t.name} · v{t.version}</option>)}</select></label><div className="template-upload-option"><TemplateUpload onSelected={setTemplate}/><small>Optional · Excel .xlsx or CSV · Headers in the first row</small></div><div className="generation-fields">{templates.find(t => t.id === template)?.fields.map(field => <span key={field}>{field}</span>)}</div><div className="generation-action"><Button variant="primary" icon="spark" className="full-width" disabled={!status.ready || !template || !!running} onClick={start}>{running ? 'Generating test cases…' : 'Generate test cases'}</Button><p>Generation uses your approved baseline and reviewed evidence.</p></div></section><aside><section className="card padded"><div className="section-heading inset"><h2>Workspace evidence</h2><Badge tone={status.ready ? 'green' : 'neutral'}>{status.ready ? 'Ready' : 'Set up evidence'}</Badge></div><div className="evidence-check-row"><Icon name="layers" size={18}/><span>{status.selection?.baseline?.ref || 'Connect an approved baseline'}</span></div><div className="evidence-check-row"><Icon name="file" size={18}/><span>{status.sources.length} accessible document{status.sources.length === 1 ? '' : 's'}</span></div>{status.blockers.map(issue => <Note key={issue}>{issue}</Note>)}{!status.ready && <Button className="full-width margin-top" onClick={() => navigate('workspace')}>Open workspace evidence</Button>}</section><section className="card padded margin-top optional-help"><span className="soft-icon soft-purple"><Icon name="chat" size={21}/></span><h2>Want to refine the results?</h2><p>You can answer optional clarifications or ask the system about uploaded documents. You can generate test cases without answering every question.</p><Button icon="chat" onClick={() => navigate('chat')}>Open workspace chat</Button></section></aside></div><section className="card margin-top"><div className="section-heading"><h2>Generation history</h2></div>{jobs.map(job => <div className="job-row" key={job.id}><span className={`soft-icon soft-${job.status === 'Completed' ? 'green' : 'purple'}`}><Icon name={job.status === 'Completed' ? 'check' : 'clock'} size={18}/></span><div><strong>Test suite · Context {job.version}</strong><p>{job.stage}</p><div className="stage-dots">{[0, 1, 2, 3].map(index => <span key={index} className={index <= job.stageIndex ? 'done' : ''}/>)}</div></div><Badge>{job.status}</Badge>{job.status === 'running' ? <Button onClick={() => mutate(d => { const current = d.jobs.find(item => item.id === job.id); current.status = 'Cancelled'; current.stage = 'Cancelled by user.'; })}>Cancel</Button> : ['Interrupted', 'Cancelled', 'Failed'].includes(job.status) ? <Button disabled={!status.ready} onClick={start}>Retry</Button> : <Button onClick={() => navigate('suites')}>Review test suite</Button>}</div>)}{!jobs.length && <Empty title="Your first suite starts here" description="Choose a template, then generate test cases."/>}</section></>;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)