feat: test case detail dialog

This commit is contained in:
Takeshi Kimata
2024-07-21 21:42:02 +09:00
parent f03222e49c
commit 5f095e1cf3
7 changed files with 138 additions and 59 deletions

View File

@@ -252,6 +252,8 @@
"no_cases_found": "No cases found", "no_cases_found": "No cases found",
"are_you_sure_leave": "Are you sure you want to leave the page?", "are_you_sure_leave": "Are you sure you want to leave the page?",
"type": "Type", "type": "Type",
"test_detail": "Test detail",
"steps": "Steps",
"preconditions": "Preconditions", "preconditions": "Preconditions",
"expected_result": "Expected result", "expected_result": "Expected result",
"details_of_the_step": "Details of the step", "details_of_the_step": "Details of the step",

View File

@@ -254,6 +254,7 @@
"are_you_sure_leave": "ページを離れてもよろしいですか", "are_you_sure_leave": "ページを離れてもよろしいですか",
"type": "タイプ", "type": "タイプ",
"test_detail": "テスト詳細", "test_detail": "テスト詳細",
"steps": "ステップ",
"preconditions": "前提条件", "preconditions": "前提条件",
"expected_result": "期待結果", "expected_result": "期待結果",
"details_of_the_step": "ステップ詳細", "details_of_the_step": "ステップ詳細",

View File

@@ -39,6 +39,7 @@ import { useTheme } from 'next-themes';
import { useFormGuard } from '@/utils/formGuard'; import { useFormGuard } from '@/utils/formGuard';
import { PriorityMessages } from '@/types/priority'; import { PriorityMessages } from '@/types/priority';
import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status'; import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status';
import { TestTypeMessages } from '@/types/testType';
const defaultTestRun = { const defaultTestRun = {
id: 0, id: 0,
@@ -58,6 +59,7 @@ type Props = {
runStatusMessages: RunStatusMessages; runStatusMessages: RunStatusMessages;
testRunCaseStatusMessages: TestRunCaseStatusMessages; testRunCaseStatusMessages: TestRunCaseStatusMessages;
priorityMessages: PriorityMessages; priorityMessages: PriorityMessages;
testTypeMessages: TestTypeMessages;
locale: string; locale: string;
}; };
@@ -68,6 +70,7 @@ export default function RunEditor({
runStatusMessages, runStatusMessages,
testRunCaseStatusMessages, testRunCaseStatusMessages,
priorityMessages, priorityMessages,
testTypeMessages,
locale, locale,
}: Props) { }: Props) {
const context = useContext(TokenContext); const context = useContext(TokenContext);
@@ -333,6 +336,7 @@ export default function RunEditor({
messages={messages} messages={messages}
testRunCaseStatusMessages={testRunCaseStatusMessages} testRunCaseStatusMessages={testRunCaseStatusMessages}
priorityMessages={priorityMessages} priorityMessages={priorityMessages}
testTypeMessages={testTypeMessages}
/> />
</div> </div>
</div> </div>

View File

@@ -1,31 +1,84 @@
import { Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react'; import { useState, useEffect, useContext } from 'react';
import { Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Avatar, Textarea } from '@nextui-org/react';
import { testTypes, templates } from '@/config/selection'; import { testTypes, templates } from '@/config/selection';
import { RunMessages } from '@/types/run'; import { RunMessages } from '@/types/run';
import { CaseType } from '@/types/case'; import { CaseType, StepType } from '@/types/case';
import { PriorityMessages } from '@/types/priority'; import { PriorityMessages } from '@/types/priority';
import TestCasePriority from '@/components/TestCasePriority'; import TestCasePriority from '@/components/TestCasePriority';
import { TokenContext } from '@/utils/TokenProvider';
import { fetchCase } from '@/utils/caseControl';
import { TestTypeMessages } from '@/types/testType';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
testCase: CaseType; caseId: number;
onCancel: () => void; onCancel: () => void;
onChangeStatus: (changeCaseId: number, status: number) => {}; onChangeStatus: (changeCaseId: number, status: number) => {};
messages: RunMessages; messages: RunMessages;
testTypeMessages: TestTypeMessages;
priorityMessages: PriorityMessages; priorityMessages: PriorityMessages;
}; };
export default function showTestCaseDetailDialog({ const defaultTestCase = {
id: 0,
title: '',
state: 0,
priority: 0,
type: 0,
automationStatus: 0,
description: '',
template: 0,
preConditions: '',
expectedResults: '',
folderId: 0,
};
export default function TestCaseDetailDialog({
isOpen, isOpen,
testCase, caseId,
onCancel, onCancel,
onChangeStatus, onChangeStatus,
messages, messages,
testTypeMessages,
priorityMessages, priorityMessages,
}: Props) { }: Props) {
const context = useContext(TokenContext);
const [testCase, setTestCase] = useState<CaseType>(defaultTestCase);
useEffect(() => {
async function fetchDataEffect() {
if (!context.isSignedIn()) {
return;
}
if (!caseId || caseId <= 0) {
return;
}
try {
const data = await fetchCase(context.token.access_token, Number(caseId));
if (data.Steps && data.Steps.length > 0) {
data.Steps.sort((a: StepType, b: StepType) => {
const stepNoA = a.caseSteps.stepNo;
const stepNoB = b.caseSteps.stepNo;
return stepNoA - stepNoB;
});
}
setTestCase(data);
} catch (error: any) {
console.error('Error in effect:', error.message);
}
}
fetchDataEffect();
}, [context, caseId]);
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
size="3xl" size="5xl"
scrollBehavior="outside"
onOpenChange={() => { onOpenChange={() => {
onCancel(); onCancel();
}} }}
@@ -48,40 +101,55 @@ export default function showTestCaseDetailDialog({
<div className="w-1/2"> <div className="w-1/2">
<p className={'font-bold'}>{messages.type}</p> <p className={'font-bold'}>{messages.type}</p>
<div>{messages[testTypes[testCase.type].uid]}</div> <div>{testTypeMessages[testTypes[testCase.type].uid]}</div>
</div> </div>
</div> </div>
</ModalBody> </ModalBody>
<ModalBody> <ModalBody>
{templates[testCase.template].uid === 'text' ? ( {templates[testCase.template].uid === 'text' ? (
<>
<p className={'font-bold mt-2'}>{messages.testDetail}</p>
<div className="flex my-2"> <div className="flex my-2">
<div className="w-1/2"> <div className="w-1/2">
<p className={'font-bold'}>{messages.preconditions}</p> <p className={'font-bold'}>{messages.preconditions}</p>
<div>{testCase.preConditions}</div> <div>{testCase.preConditions}</div>
</div> </div>
<div className="w-1/2"> <div className="w-1/2">
<p className={'font-bold'}>{messages.expectedResult}</p> <p className={'font-bold'}>{messages.expectedResult}</p>
<div>{testCase.expectedResults}</div> <div>{testCase.expectedResults}</div>
</div> </div>
</div> </div>
) : (
<div className="flex my-2">
{testCase.Steps &&
testCase.Steps.map((step, index) => (
<>
<div className="w-1/2">
<p className={'font-bold'}>{messages.preconditions}</p>
<div>{step.step}</div>
</div>
<div className="w-1/2">
<p className={'font-bold'}>{messages.expectedResult}</p>
<div>{step.result}</div>
</div>
</> </>
))} ) : (
<>
<p className={'font-bold mt-2'}>{messages.steps}</p>
{testCase.Steps &&
testCase.Steps.map((step) => (
<div key={step.id} className="flex items-center my-1">
<Avatar className="me-2" size="sm" name={step.caseSteps.stepNo.toString()} />
<div key={step.id} className="grow flex gap-2">
<div className="w-1/2">
<Textarea
isReadOnly
size="sm"
variant="flat"
label={messages.detailsOfTheStep}
value={step.step}
/>
</div> </div>
<div className="w-1/2">
<Textarea
isReadOnly
size="sm"
variant="flat"
label={messages.expectedResult}
value={step.result}
/>
</div>
</div>
</div>
))}
</>
)} )}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

@@ -32,7 +32,8 @@ import { RunMessages } from '@/types/run';
import TestCaseDetailDialog from './TestCaseDetailDialog'; import TestCaseDetailDialog from './TestCaseDetailDialog';
import { PriorityMessages } from '@/types/priority'; import { PriorityMessages } from '@/types/priority';
import TestCasePriority from '@/components/TestCasePriority'; import TestCasePriority from '@/components/TestCasePriority';
import { TestRunCaseStatusMessages } from '@/types/testRunCaseStatus'; import { TestTypeMessages } from '@/types/testType';
import { TestRunCaseStatusMessages } from '@/types/status';
type Props = { type Props = {
cases: CaseType[]; cases: CaseType[];
@@ -45,6 +46,7 @@ type Props = {
messages: RunMessages; messages: RunMessages;
testRunCaseStatusMessages: TestRunCaseStatusMessages; testRunCaseStatusMessages: TestRunCaseStatusMessages;
priorityMessages: PriorityMessages; priorityMessages: PriorityMessages;
testTypeMessages: TestTypeMessages;
}; };
export default function TestCaseSelector({ export default function TestCaseSelector({
@@ -57,6 +59,7 @@ export default function TestCaseSelector({
onExcludeCase, onExcludeCase,
messages, messages,
testRunCaseStatusMessages, testRunCaseStatusMessages,
testTypeMessages,
priorityMessages, priorityMessages,
}: Props) { }: Props) {
const headerColumns = [ const headerColumns = [
@@ -138,7 +141,7 @@ export default function TestCaseSelector({
size="sm" size="sm"
variant="light" variant="light"
className="data-[hover=true]:bg-transparent" className="data-[hover=true]:bg-transparent"
onPress={() => showTestCaseDetailDialog(testCase)} onPress={() => showTestCaseDetailDialog(testCase.id)}
> >
<span className={NextUiLinkClasses}>{cellValue}</span> <span className={NextUiLinkClasses}>{cellValue}</span>
</Button> </Button>
@@ -244,22 +247,10 @@ export default function TestCaseSelector({
// Test Case Detail // Test Case Detail
const [isTestCaseDetailDialogOpen, setIsTestCaseDetailDialogOpen] = useState(false); const [isTestCaseDetailDialogOpen, setIsTestCaseDetailDialogOpen] = useState(false);
const [showingTestCase, setShowingTestCase] = useState<CaseType>({ const [showingTestCaseId, setShowingTestCaseId] = useState<number>(0);
id: 0, const showTestCaseDetailDialog = (showTestCaseId: number) => {
title: '',
state: 0,
priority: 0,
type: 0,
automationStatus: 0,
description: '',
template: 0,
preConditions: '',
expectedResults: '',
folderId: 0,
});
const showTestCaseDetailDialog = (showTestCase: CaseType) => {
setIsTestCaseDetailDialogOpen(true); setIsTestCaseDetailDialogOpen(true);
setShowingTestCase(showTestCase); setShowingTestCaseId(showTestCaseId);
}; };
const hideTestCaseDetailDialog = () => { const hideTestCaseDetailDialog = () => {
setIsTestCaseDetailDialogOpen(false); setIsTestCaseDetailDialogOpen(false);
@@ -302,11 +293,12 @@ export default function TestCaseSelector({
<TestCaseDetailDialog <TestCaseDetailDialog
isOpen={isTestCaseDetailDialogOpen} isOpen={isTestCaseDetailDialogOpen}
testCase={showingTestCase} caseId={showingTestCaseId}
onCancel={hideTestCaseDetailDialog} onCancel={hideTestCaseDetailDialog}
onChangeStatus={(showingCaseId, newStatus) => onChangeStatus(showingCaseId, newStatus)} onChangeStatus={(showingCaseId, newStatus) => onChangeStatus(showingCaseId, newStatus)}
messages={messages} messages={messages}
priorityMessages={priorityMessages} priorityMessages={priorityMessages}
testTypeMessages={testTypeMessages}
/> />
</> </>
); );

View File

@@ -3,6 +3,7 @@ import { useTranslations } from 'next-intl';
import { RunMessages } from '@/types/run'; import { RunMessages } from '@/types/run';
import { PriorityMessages } from '@/types/priority'; import { PriorityMessages } from '@/types/priority';
import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status'; import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status';
import { TestTypeMessages } from '@/types/testType';
export default function Page({ params }: { params: { projectId: string; runId: string; locale: string } }) { export default function Page({ params }: { params: { projectId: string; runId: string; locale: string } }) {
const t = useTranslations('Run'); const t = useTranslations('Run');
@@ -26,6 +27,8 @@ export default function Page({ params }: { params: { projectId: string; runId: s
noCasesFound: t('no_cases_found'), noCasesFound: t('no_cases_found'),
areYouSureLeave: t('are_you_sure_leave'), areYouSureLeave: t('are_you_sure_leave'),
type: t('type'), type: t('type'),
testDetail: t('test_detail'),
steps: t('steps'),
preconditions: t('preconditions'), preconditions: t('preconditions'),
expectedResult: t('expected_result'), expectedResult: t('expected_result'),
detailsOfTheStep: t('details_of_the_step'), detailsOfTheStep: t('details_of_the_step'),
@@ -59,6 +62,23 @@ export default function Page({ params }: { params: { projectId: string; runId: s
low: pt('low'), low: pt('low'),
}; };
const tt = useTranslations('Type');
const testTypeMessages: TestTypeMessages = {
other: tt('other'),
security: tt('security'),
performance: tt('performance'),
accessibility: tt('accessibility'),
functional: tt('functional'),
acceptance: tt('acceptance'),
usability: tt('usability'),
smokeSanity: tt('smoke_sanity'),
compatibility: tt('compatibility'),
destructive: tt('destructive'),
regression: tt('regression'),
automated: tt('automated'),
manual: tt('manual'),
};
return ( return (
<RunEditor <RunEditor
projectId={params.projectId} projectId={params.projectId}
@@ -67,6 +87,7 @@ export default function Page({ params }: { params: { projectId: string; runId: s
runStatusMessages={runStatusMessages} runStatusMessages={runStatusMessages}
testRunCaseStatusMessages={testRunCaseStatusMessages} testRunCaseStatusMessages={testRunCaseStatusMessages}
priorityMessages={priorityMessages} priorityMessages={priorityMessages}
testTypeMessages={testTypeMessages}
locale={params.locale} locale={params.locale}
/> />
); );

View File

@@ -59,20 +59,9 @@ type RunMessages = {
title: string; title: string;
pleaseEnter: string; pleaseEnter: string;
description: string; description: string;
new: string;
inProgress: string;
underReview: string;
rejected: string;
done: string;
closed: string;
priority: string; priority: string;
status: string; status: string;
actions: string; actions: string;
untested: string;
passed: string;
failed: string;
retest: string;
skipped: string;
selectTestCase: string; selectTestCase: string;
testCaseSelection: string; testCaseSelection: string;
includeInRun: string; includeInRun: string;
@@ -80,6 +69,8 @@ type RunMessages = {
noCasesFound: string; noCasesFound: string;
areYouSureLeave: string; areYouSureLeave: string;
type: string; type: string;
testDetail: string;
steps: string;
preconditions: string; preconditions: string;
expectedResult: string; expectedResult: string;
detailsOfTheStep: string; detailsOfTheStep: string;