@elisra-devops/docgen-data-provider 1.90.0 → 1.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/modules/ResultDataProvider.d.ts +1 -0
- package/bin/modules/ResultDataProvider.js +105 -50
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +157 -4
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +121 -57
- package/src/tests/modules/ResultDataProvider.test.ts +181 -4
package/package.json
CHANGED
|
@@ -762,6 +762,37 @@ export default class ResultDataProvider {
|
|
|
762
762
|
allRequirements,
|
|
763
763
|
scopedRequirementKeys?.size ? scopedRequirementKeys : undefined
|
|
764
764
|
);
|
|
765
|
+
const linkedFullCodesByTestCase = new Map<number, Set<string>>();
|
|
766
|
+
const linkedFamilyCodesAcrossTestCases = new Map<string, Set<string>>();
|
|
767
|
+
const linkedFullCodesAcrossTestCases = new Set<string>();
|
|
768
|
+
const linkedBaseKeysAcrossTestCases = new Set<string>();
|
|
769
|
+
|
|
770
|
+
for (const [linkedTestCaseId, links] of linkedRequirementsByTestCase.entries()) {
|
|
771
|
+
const rawFullCodes = links?.fullCodes || new Set<string>();
|
|
772
|
+
const filteredFullCodes =
|
|
773
|
+
scopedRequirementKeys?.size && rawFullCodes.size > 0
|
|
774
|
+
? new Set<string>(
|
|
775
|
+
[...rawFullCodes].filter((code) =>
|
|
776
|
+
scopedRequirementKeys.has(this.toRequirementKey(code))
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
: rawFullCodes;
|
|
780
|
+
linkedFullCodesByTestCase.set(linkedTestCaseId, filteredFullCodes);
|
|
781
|
+
|
|
782
|
+
for (const code of filteredFullCodes) {
|
|
783
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(code);
|
|
784
|
+
if (!normalizedCode) continue;
|
|
785
|
+
linkedFullCodesAcrossTestCases.add(normalizedCode);
|
|
786
|
+
|
|
787
|
+
const baseKey = this.toRequirementKey(normalizedCode);
|
|
788
|
+
if (!baseKey) continue;
|
|
789
|
+
linkedBaseKeysAcrossTestCases.add(baseKey);
|
|
790
|
+
if (!linkedFamilyCodesAcrossTestCases.has(baseKey)) {
|
|
791
|
+
linkedFamilyCodesAcrossTestCases.set(baseKey, new Set<string>());
|
|
792
|
+
}
|
|
793
|
+
linkedFamilyCodesAcrossTestCases.get(baseKey)!.add(normalizedCode);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
765
796
|
|
|
766
797
|
const rows: MewpInternalValidationRow[] = [];
|
|
767
798
|
const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
|
|
@@ -844,18 +875,7 @@ export default class ResultDataProvider {
|
|
|
844
875
|
[...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
845
876
|
);
|
|
846
877
|
|
|
847
|
-
const
|
|
848
|
-
const linkedFullCodes =
|
|
849
|
-
scopedRequirementKeys?.size && linkedFullCodesRaw.size > 0
|
|
850
|
-
? new Set<string>(
|
|
851
|
-
[...linkedFullCodesRaw].filter((code) =>
|
|
852
|
-
scopedRequirementKeys.has(this.toRequirementKey(code))
|
|
853
|
-
)
|
|
854
|
-
)
|
|
855
|
-
: linkedFullCodesRaw;
|
|
856
|
-
const linkedBaseKeys = new Set<string>(
|
|
857
|
-
[...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
858
|
-
);
|
|
878
|
+
const linkedFullCodes = linkedFullCodesByTestCase.get(testCaseId) || new Set<string>();
|
|
859
879
|
|
|
860
880
|
const mentionedCodesByBase = new Map<string, Set<string>>();
|
|
861
881
|
for (const code of mentionedL2Only) {
|
|
@@ -865,13 +885,14 @@ export default class ResultDataProvider {
|
|
|
865
885
|
mentionedCodesByBase.get(baseKey)!.add(code);
|
|
866
886
|
}
|
|
867
887
|
|
|
868
|
-
//
|
|
869
|
-
// 1)
|
|
870
|
-
//
|
|
871
|
-
//
|
|
888
|
+
// Direction A logic:
|
|
889
|
+
// 1) Base mention ("SR0054") is parent-level and considered covered only when
|
|
890
|
+
// the whole family is covered across scoped test cases:
|
|
891
|
+
// - if family has children, all children must be linked;
|
|
892
|
+
// - if family has no children, the base item itself must be linked.
|
|
893
|
+
// 2) Child mention ("SR0054-1") is exact-match and checked across scoped test cases.
|
|
872
894
|
const missingBaseWhenFamilyUncovered = new Set<string>();
|
|
873
895
|
const missingSpecificMentionedNoFamily = new Set<string>();
|
|
874
|
-
const missingFamilyMembers = new Set<string>();
|
|
875
896
|
for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
|
|
876
897
|
const familyCodes = requirementFamilies.get(baseKey);
|
|
877
898
|
const mentionedCodesList = [...mentionedCodes];
|
|
@@ -879,30 +900,27 @@ export default class ResultDataProvider {
|
|
|
879
900
|
const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
|
|
880
901
|
|
|
881
902
|
if (familyCodes?.size) {
|
|
882
|
-
|
|
883
|
-
|
|
903
|
+
const familyLinkedCodes = linkedFamilyCodesAcrossTestCases.get(baseKey) || new Set<string>();
|
|
904
|
+
|
|
905
|
+
// Base mention ("SR0054") requires full family coverage across selected test cases.
|
|
884
906
|
if (hasBaseMention) {
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
missingFamilyMembers.add(code);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
907
|
+
const normalizedFamilyMembers = [...familyCodes]
|
|
908
|
+
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
909
|
+
.filter((code) => !!code);
|
|
910
|
+
const specificFamilyMembers = normalizedFamilyMembers.filter((code) => /-\d+$/.test(code));
|
|
911
|
+
const requiredFamilyMembers =
|
|
912
|
+
specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
|
|
913
|
+
const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) =>
|
|
914
|
+
familyLinkedCodes.has(memberCode)
|
|
915
|
+
);
|
|
916
|
+
if (!isWholeFamilyCovered) {
|
|
917
|
+
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
899
918
|
}
|
|
900
|
-
continue;
|
|
901
919
|
}
|
|
902
920
|
|
|
903
|
-
// Specific mention ("SR0054-1") validates as exact-match only.
|
|
921
|
+
// Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
|
|
904
922
|
for (const code of mentionedSpecificMembers) {
|
|
905
|
-
if (!
|
|
923
|
+
if (!familyLinkedCodes.has(code)) {
|
|
906
924
|
missingSpecificMentionedNoFamily.add(code);
|
|
907
925
|
}
|
|
908
926
|
}
|
|
@@ -913,10 +931,10 @@ export default class ResultDataProvider {
|
|
|
913
931
|
for (const code of mentionedCodes) {
|
|
914
932
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
915
933
|
if (hasSpecificSuffix) {
|
|
916
|
-
if (!
|
|
934
|
+
if (!linkedFullCodesAcrossTestCases.has(code)) {
|
|
917
935
|
missingSpecificMentionedNoFamily.add(code);
|
|
918
936
|
}
|
|
919
|
-
} else if (!
|
|
937
|
+
} else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
|
|
920
938
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
921
939
|
}
|
|
922
940
|
}
|
|
@@ -929,15 +947,26 @@ export default class ResultDataProvider {
|
|
|
929
947
|
if (!baseKey) return false;
|
|
930
948
|
return !mentionedBaseKeys.has(baseKey);
|
|
931
949
|
});
|
|
932
|
-
const mentionedButNotLinkedByStep = new Map<
|
|
950
|
+
const mentionedButNotLinkedByStep = new Map<
|
|
951
|
+
string,
|
|
952
|
+
{ baseIds: Set<string>; specificIds: Set<string> }
|
|
953
|
+
>();
|
|
933
954
|
const appendMentionedButNotLinked = (requirementId: string, stepRef: string) => {
|
|
934
955
|
const normalizedRequirementId = this.normalizeMewpRequirementCodeWithSuffix(requirementId);
|
|
935
956
|
if (!normalizedRequirementId) return;
|
|
936
957
|
const normalizedStepRef = String(stepRef || 'Step ?').trim() || 'Step ?';
|
|
937
958
|
if (!mentionedButNotLinkedByStep.has(normalizedStepRef)) {
|
|
938
|
-
mentionedButNotLinkedByStep.set(normalizedStepRef,
|
|
959
|
+
mentionedButNotLinkedByStep.set(normalizedStepRef, {
|
|
960
|
+
baseIds: new Set<string>(),
|
|
961
|
+
specificIds: new Set<string>(),
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
const entry = mentionedButNotLinkedByStep.get(normalizedStepRef)!;
|
|
965
|
+
if (/-\d+$/.test(normalizedRequirementId)) {
|
|
966
|
+
entry.specificIds.add(normalizedRequirementId);
|
|
967
|
+
} else {
|
|
968
|
+
entry.baseIds.add(normalizedRequirementId);
|
|
939
969
|
}
|
|
940
|
-
mentionedButNotLinkedByStep.get(normalizedStepRef)!.add(normalizedRequirementId);
|
|
941
970
|
};
|
|
942
971
|
|
|
943
972
|
const sortedMissingSpecificMentionedNoFamily = [...missingSpecificMentionedNoFamily].sort((a, b) =>
|
|
@@ -946,7 +975,6 @@ export default class ResultDataProvider {
|
|
|
946
975
|
const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) =>
|
|
947
976
|
a.localeCompare(b)
|
|
948
977
|
);
|
|
949
|
-
const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
|
|
950
978
|
for (const code of sortedMissingSpecificMentionedNoFamily) {
|
|
951
979
|
const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
|
|
952
980
|
appendMentionedButNotLinked(code, stepRef);
|
|
@@ -955,11 +983,6 @@ export default class ResultDataProvider {
|
|
|
955
983
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
956
984
|
appendMentionedButNotLinked(baseKey, stepRef);
|
|
957
985
|
}
|
|
958
|
-
for (const code of sortedMissingFamilyMembers) {
|
|
959
|
-
const baseKey = this.toRequirementKey(code);
|
|
960
|
-
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
961
|
-
appendMentionedButNotLinked(code, stepRef);
|
|
962
|
-
}
|
|
963
986
|
|
|
964
987
|
const sortedExtraLinked = [...new Set(extraLinked)]
|
|
965
988
|
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
@@ -978,12 +1001,23 @@ export default class ResultDataProvider {
|
|
|
978
1001
|
if (stepOrderA !== stepOrderB) return stepOrderA - stepOrderB;
|
|
979
1002
|
return String(a[0]).localeCompare(String(b[0]));
|
|
980
1003
|
})
|
|
981
|
-
.map(([stepRef,
|
|
982
|
-
const
|
|
983
|
-
|
|
1004
|
+
.map(([stepRef, requirementIdsByType]) => {
|
|
1005
|
+
const specificCodes = [...requirementIdsByType.specificIds].sort((a, b) =>
|
|
1006
|
+
this.compareMewpRequirementCodes(a, b)
|
|
1007
|
+
);
|
|
1008
|
+
const specificFamilies = new Set<string>(
|
|
1009
|
+
specificCodes.map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
1010
|
+
);
|
|
1011
|
+
const baseCodes = [...requirementIdsByType.baseIds]
|
|
1012
|
+
.filter((baseCode) => !specificFamilies.has(baseCode))
|
|
1013
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b));
|
|
1014
|
+
const displayCodes = [...specificCodes, ...baseCodes];
|
|
1015
|
+
if (displayCodes.length === 0) return `${stepRef}:`;
|
|
1016
|
+
return `${stepRef}: ${displayCodes.join('; ')}`.trim();
|
|
984
1017
|
})
|
|
985
|
-
.join('\n')
|
|
986
|
-
|
|
1018
|
+
.join('\n')
|
|
1019
|
+
.trim();
|
|
1020
|
+
const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked).trim();
|
|
987
1021
|
const validationStatus: 'Pass' | 'Fail' =
|
|
988
1022
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
989
1023
|
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
@@ -993,8 +1027,7 @@ export default class ResultDataProvider {
|
|
|
993
1027
|
`stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
|
|
994
1028
|
`linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${
|
|
995
1029
|
sortedMissingSpecificMentionedNoFamily.length +
|
|
996
|
-
sortedMissingBaseWhenFamilyUncovered.length
|
|
997
|
-
sortedMissingFamilyMembers.length
|
|
1030
|
+
sortedMissingBaseWhenFamilyUncovered.length
|
|
998
1031
|
} ` +
|
|
999
1032
|
`linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
|
|
1000
1033
|
`customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`
|
|
@@ -2615,6 +2648,36 @@ export default class ResultDataProvider {
|
|
|
2615
2648
|
return `SR${match[1]}`;
|
|
2616
2649
|
}
|
|
2617
2650
|
|
|
2651
|
+
private compareMewpRequirementCodes(a: string, b: string): number {
|
|
2652
|
+
const normalizeComparableCode = (value: string): { base: number; hasSuffix: number; suffix: number; raw: string } => {
|
|
2653
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(value);
|
|
2654
|
+
const match = /^SR(\d+)(?:-(\d+))?$/i.exec(normalizedCode);
|
|
2655
|
+
if (!match) {
|
|
2656
|
+
return {
|
|
2657
|
+
base: Number.POSITIVE_INFINITY,
|
|
2658
|
+
hasSuffix: 1,
|
|
2659
|
+
suffix: Number.POSITIVE_INFINITY,
|
|
2660
|
+
raw: String(value || ''),
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
return {
|
|
2665
|
+
base: Number(match[1]),
|
|
2666
|
+
hasSuffix: match[2] ? 1 : 0,
|
|
2667
|
+
suffix: match[2] ? Number(match[2]) : -1,
|
|
2668
|
+
raw: normalizedCode,
|
|
2669
|
+
};
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
const left = normalizeComparableCode(a);
|
|
2673
|
+
const right = normalizeComparableCode(b);
|
|
2674
|
+
|
|
2675
|
+
if (left.base !== right.base) return left.base - right.base;
|
|
2676
|
+
if (left.hasSuffix !== right.hasSuffix) return left.hasSuffix - right.hasSuffix;
|
|
2677
|
+
if (left.suffix !== right.suffix) return left.suffix - right.suffix;
|
|
2678
|
+
return left.raw.localeCompare(right.raw);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2618
2681
|
private formatRequirementCodesGroupedByFamily(codes: Iterable<string>): string {
|
|
2619
2682
|
const byBaseKey = new Map<string, Set<string>>();
|
|
2620
2683
|
for (const rawCode of codes || []) {
|
|
@@ -2628,11 +2691,12 @@ export default class ResultDataProvider {
|
|
|
2628
2691
|
if (byBaseKey.size === 0) return '';
|
|
2629
2692
|
|
|
2630
2693
|
return [...byBaseKey.entries()]
|
|
2631
|
-
.sort((a, b) => a[0]
|
|
2694
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a[0], b[0]))
|
|
2632
2695
|
.map(([baseKey, members]) => {
|
|
2633
|
-
const sortedMembers = [...members].sort((a, b) =>
|
|
2696
|
+
const sortedMembers = [...members].sort((a, b) => this.compareMewpRequirementCodes(a, b));
|
|
2634
2697
|
if (sortedMembers.length <= 1) return sortedMembers[0];
|
|
2635
|
-
|
|
2698
|
+
// Direction B display is family-level when multiple members exist.
|
|
2699
|
+
return baseKey;
|
|
2636
2700
|
})
|
|
2637
2701
|
.join('\n');
|
|
2638
2702
|
}
|
|
@@ -1937,7 +1937,7 @@ describe('ResultDataProvider', () => {
|
|
|
1937
1937
|
);
|
|
1938
1938
|
});
|
|
1939
1939
|
|
|
1940
|
-
it('should emit Direction A rows
|
|
1940
|
+
it('should emit Direction A rows only for specifically mentioned child requirements', async () => {
|
|
1941
1941
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1942
1942
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1943
1943
|
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
@@ -1981,7 +1981,7 @@ describe('ResultDataProvider', () => {
|
|
|
1981
1981
|
stepId: '2',
|
|
1982
1982
|
stepPosition: '2',
|
|
1983
1983
|
action: '',
|
|
1984
|
-
expected: 'SR0001
|
|
1984
|
+
expected: 'SR0001-1',
|
|
1985
1985
|
isSharedStepTitle: false,
|
|
1986
1986
|
},
|
|
1987
1987
|
]);
|
|
@@ -2003,6 +2003,72 @@ describe('ResultDataProvider', () => {
|
|
|
2003
2003
|
);
|
|
2004
2004
|
});
|
|
2005
2005
|
|
|
2006
|
+
it('should keep explicit child IDs when multiple specifically mentioned children are missing', async () => {
|
|
2007
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2008
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
2009
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
2010
|
+
{
|
|
2011
|
+
testPointsItems: [{ testCaseId: 111, testCaseName: 'TC 111' }],
|
|
2012
|
+
testCasesItems: [
|
|
2013
|
+
{
|
|
2014
|
+
workItem: {
|
|
2015
|
+
id: 111,
|
|
2016
|
+
workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
|
|
2017
|
+
},
|
|
2018
|
+
},
|
|
2019
|
+
],
|
|
2020
|
+
},
|
|
2021
|
+
]);
|
|
2022
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2023
|
+
{
|
|
2024
|
+
workItemId: 5101,
|
|
2025
|
+
requirementId: 'SR0054-1',
|
|
2026
|
+
baseKey: 'SR0054',
|
|
2027
|
+
title: 'Req child 1',
|
|
2028
|
+
responsibility: 'ESUK',
|
|
2029
|
+
linkedTestCaseIds: [],
|
|
2030
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
workItemId: 5102,
|
|
2034
|
+
requirementId: 'SR0054-2',
|
|
2035
|
+
baseKey: 'SR0054',
|
|
2036
|
+
title: 'Req child 2',
|
|
2037
|
+
responsibility: 'ESUK',
|
|
2038
|
+
linkedTestCaseIds: [],
|
|
2039
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2040
|
+
},
|
|
2041
|
+
]);
|
|
2042
|
+
jest
|
|
2043
|
+
.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase')
|
|
2044
|
+
.mockResolvedValueOnce(new Map([[111, { baseKeys: new Set<string>(), fullCodes: new Set<string>() }]]));
|
|
2045
|
+
jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
2046
|
+
{
|
|
2047
|
+
stepId: '4',
|
|
2048
|
+
stepPosition: '4',
|
|
2049
|
+
action: '',
|
|
2050
|
+
expected: 'SR0054-1; SR0054-2',
|
|
2051
|
+
isSharedStepTitle: false,
|
|
2052
|
+
},
|
|
2053
|
+
]);
|
|
2054
|
+
|
|
2055
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2056
|
+
'123',
|
|
2057
|
+
mockProjectName,
|
|
2058
|
+
[1]
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
expect(result.rows).toHaveLength(1);
|
|
2062
|
+
expect(result.rows[0]).toEqual(
|
|
2063
|
+
expect.objectContaining({
|
|
2064
|
+
'Test Case ID': 111,
|
|
2065
|
+
'Mentioned but Not Linked': 'Step 4: SR0054-1; SR0054-2',
|
|
2066
|
+
'Linked but Not Mentioned': '',
|
|
2067
|
+
'Validation Status': 'Fail',
|
|
2068
|
+
})
|
|
2069
|
+
);
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2006
2072
|
it('should pass when a base SR mention is fully covered by its only linked child', async () => {
|
|
2007
2073
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2008
2074
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
@@ -2077,6 +2143,115 @@ describe('ResultDataProvider', () => {
|
|
|
2077
2143
|
);
|
|
2078
2144
|
});
|
|
2079
2145
|
|
|
2146
|
+
it('should support cross-test-case family coverage when siblings are linked on different test cases', async () => {
|
|
2147
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2148
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
2149
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
2150
|
+
{
|
|
2151
|
+
testPointsItems: [
|
|
2152
|
+
{ testCaseId: 501, testCaseName: 'TC 501 - sibling 1' },
|
|
2153
|
+
{ testCaseId: 502, testCaseName: 'TC 502 - sibling 2' },
|
|
2154
|
+
],
|
|
2155
|
+
testCasesItems: [
|
|
2156
|
+
{
|
|
2157
|
+
workItem: {
|
|
2158
|
+
id: 501,
|
|
2159
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-501\"></steps>' }],
|
|
2160
|
+
},
|
|
2161
|
+
},
|
|
2162
|
+
{
|
|
2163
|
+
workItem: {
|
|
2164
|
+
id: 502,
|
|
2165
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-502\"></steps>' }],
|
|
2166
|
+
},
|
|
2167
|
+
},
|
|
2168
|
+
],
|
|
2169
|
+
},
|
|
2170
|
+
]);
|
|
2171
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2172
|
+
{
|
|
2173
|
+
workItemId: 9301,
|
|
2174
|
+
requirementId: 'SR0054-1',
|
|
2175
|
+
baseKey: 'SR0054',
|
|
2176
|
+
title: 'SR0054 child 1',
|
|
2177
|
+
responsibility: 'ESUK',
|
|
2178
|
+
linkedTestCaseIds: [],
|
|
2179
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
workItemId: 9302,
|
|
2183
|
+
requirementId: 'SR0054-2',
|
|
2184
|
+
baseKey: 'SR0054',
|
|
2185
|
+
title: 'SR0054 child 2',
|
|
2186
|
+
responsibility: 'ESUK',
|
|
2187
|
+
linkedTestCaseIds: [],
|
|
2188
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2189
|
+
},
|
|
2190
|
+
]);
|
|
2191
|
+
jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
|
|
2192
|
+
new Map([
|
|
2193
|
+
[
|
|
2194
|
+
501,
|
|
2195
|
+
{
|
|
2196
|
+
baseKeys: new Set(['SR0054']),
|
|
2197
|
+
fullCodes: new Set(['SR0054-1']),
|
|
2198
|
+
},
|
|
2199
|
+
],
|
|
2200
|
+
[
|
|
2201
|
+
502,
|
|
2202
|
+
{
|
|
2203
|
+
baseKeys: new Set(['SR0054']),
|
|
2204
|
+
fullCodes: new Set(['SR0054-2']),
|
|
2205
|
+
},
|
|
2206
|
+
],
|
|
2207
|
+
])
|
|
2208
|
+
);
|
|
2209
|
+
jest
|
|
2210
|
+
.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
|
|
2211
|
+
.mockResolvedValueOnce([
|
|
2212
|
+
{
|
|
2213
|
+
stepId: '1',
|
|
2214
|
+
stepPosition: '1',
|
|
2215
|
+
action: 'Parent mention on first test case',
|
|
2216
|
+
expected: 'SR0054',
|
|
2217
|
+
isSharedStepTitle: false,
|
|
2218
|
+
},
|
|
2219
|
+
])
|
|
2220
|
+
.mockResolvedValueOnce([
|
|
2221
|
+
{
|
|
2222
|
+
stepId: '1',
|
|
2223
|
+
stepPosition: '1',
|
|
2224
|
+
action: 'Parent mention on second test case',
|
|
2225
|
+
expected: 'SR0054',
|
|
2226
|
+
isSharedStepTitle: false,
|
|
2227
|
+
},
|
|
2228
|
+
]);
|
|
2229
|
+
|
|
2230
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2231
|
+
'123',
|
|
2232
|
+
mockProjectName,
|
|
2233
|
+
[1]
|
|
2234
|
+
);
|
|
2235
|
+
|
|
2236
|
+
const byTestCase = new Map<number, any>(
|
|
2237
|
+
result.rows.map((row: any) => [Number(row['Test Case ID']), row])
|
|
2238
|
+
);
|
|
2239
|
+
expect(byTestCase.get(501)).toEqual(
|
|
2240
|
+
expect.objectContaining({
|
|
2241
|
+
'Mentioned but Not Linked': '',
|
|
2242
|
+
'Linked but Not Mentioned': '',
|
|
2243
|
+
'Validation Status': 'Pass',
|
|
2244
|
+
})
|
|
2245
|
+
);
|
|
2246
|
+
expect(byTestCase.get(502)).toEqual(
|
|
2247
|
+
expect.objectContaining({
|
|
2248
|
+
'Mentioned but Not Linked': '',
|
|
2249
|
+
'Linked but Not Mentioned': '',
|
|
2250
|
+
'Validation Status': 'Pass',
|
|
2251
|
+
})
|
|
2252
|
+
);
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2080
2255
|
it('should group linked-but-not-mentioned requirements by SR family', async () => {
|
|
2081
2256
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2082
2257
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
@@ -2151,7 +2326,9 @@ describe('ResultDataProvider', () => {
|
|
|
2151
2326
|
|
|
2152
2327
|
expect(result.rows).toHaveLength(1);
|
|
2153
2328
|
expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).toBe('');
|
|
2154
|
-
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).toContain('SR0054
|
|
2329
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).toContain('SR0054');
|
|
2330
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).not.toContain('SR0054-1');
|
|
2331
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).not.toContain('SR0054-2');
|
|
2155
2332
|
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).toContain('SR0100-1');
|
|
2156
2333
|
});
|
|
2157
2334
|
|
|
@@ -2562,7 +2739,7 @@ describe('ResultDataProvider', () => {
|
|
|
2562
2739
|
expect(byTestCase.get(201)).toEqual(
|
|
2563
2740
|
expect.objectContaining({
|
|
2564
2741
|
'Test Case Title': 'TC 201 - Mixed discrepancies',
|
|
2565
|
-
'Mentioned but Not Linked': 'Step 1: SR0095-3
|
|
2742
|
+
'Mentioned but Not Linked': 'Step 1: SR0095-3; SR0511',
|
|
2566
2743
|
'Linked but Not Mentioned': 'SR8888',
|
|
2567
2744
|
'Validation Status': 'Fail',
|
|
2568
2745
|
})
|