@elisra-devops/docgen-data-provider 1.138.0 → 1.139.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.
@@ -202,6 +202,25 @@ export default class TicketsDataProvider {
202
202
  }>;
203
203
  private normalizeHistoricalAsOf;
204
204
  private normalizeHistoricalCompareValue;
205
+ /**
206
+ * Stores rich-text fields (Description, Steps) with only whitespace trimmed, preserving markup
207
+ * so the report renderer can clean/render it faithfully. Equality between two snapshots must go
208
+ * through canonicalizeHistoricalRichTextForCompare instead of comparing this value directly.
209
+ */
210
+ private rawHistoricalRichText;
211
+ /**
212
+ * Canonicalizes a rich-text field for equality comparison only. Intentionally identical to the
213
+ * whitespace collapse previously applied at storage time, so Changed/No-changes verdicts and
214
+ * summary counts are unaffected by storing the raw value for rendering.
215
+ */
216
+ private canonicalizeHistoricalRichTextForCompare;
217
+ /**
218
+ * Parses baseline/compareTo Steps XML into per-step Action/Expected arrays for a changed test
219
+ * case, so the report can render readable steps instead of raw Steps XML. Best-effort: a parse
220
+ * failure (malformed XML, shared-step lookup error) must not fail the whole comparison, so it
221
+ * logs and falls back to leaving the caller with only the raw XML strings.
222
+ */
223
+ private parseHistoricalStepsForDiff;
205
224
  private normalizeTestPhaseValue;
206
225
  private isTestCaseType;
207
226
  private toHistoricalRevision;
@@ -8,6 +8,7 @@ const tfs_data_3 = require("../models/tfs-data");
8
8
  const tfs_data_4 = require("../models/tfs-data");
9
9
  const tfs_data_5 = require("../models/tfs-data");
10
10
  const tfs_data_6 = require("../models/tfs-data");
11
+ const testStepParserHelper_1 = require("../utils/testStepParserHelper");
11
12
  const logger_1 = require("../utils/logger");
12
13
  const pLimit = require('p-limit');
13
14
  const HISTORICAL_WIT_API_VERSIONS = ['7.1', '5.1', null];
@@ -1409,6 +1410,52 @@ class TicketsDataProvider {
1409
1410
  }
1410
1411
  return String(value).trim();
1411
1412
  }
1413
+ /**
1414
+ * Stores rich-text fields (Description, Steps) with only whitespace trimmed, preserving markup
1415
+ * so the report renderer can clean/render it faithfully. Equality between two snapshots must go
1416
+ * through canonicalizeHistoricalRichTextForCompare instead of comparing this value directly.
1417
+ */
1418
+ rawHistoricalRichText(value) {
1419
+ if (value == null) {
1420
+ return '';
1421
+ }
1422
+ if (typeof value === 'string') {
1423
+ return value.trim();
1424
+ }
1425
+ return this.normalizeHistoricalCompareValue(value);
1426
+ }
1427
+ /**
1428
+ * Canonicalizes a rich-text field for equality comparison only. Intentionally identical to the
1429
+ * whitespace collapse previously applied at storage time, so Changed/No-changes verdicts and
1430
+ * summary counts are unaffected by storing the raw value for rendering.
1431
+ */
1432
+ canonicalizeHistoricalRichTextForCompare(value) {
1433
+ return this.normalizeHistoricalCompareValue(value);
1434
+ }
1435
+ /**
1436
+ * Parses baseline/compareTo Steps XML into per-step Action/Expected arrays for a changed test
1437
+ * case, so the report can render readable steps instead of raw Steps XML. Best-effort: a parse
1438
+ * failure (malformed XML, shared-step lookup error) must not fail the whole comparison, so it
1439
+ * logs and falls back to leaving the caller with only the raw XML strings.
1440
+ */
1441
+ async parseHistoricalStepsForDiff(testStepParserHelper, baselineStepsXml, compareToStepsXml) {
1442
+ try {
1443
+ const emptyRevisionMap = new Map();
1444
+ const [baselineSteps, compareToSteps] = await Promise.all([
1445
+ baselineStepsXml
1446
+ ? testStepParserHelper.parseTestSteps(baselineStepsXml, emptyRevisionMap)
1447
+ : Promise.resolve([]),
1448
+ compareToStepsXml
1449
+ ? testStepParserHelper.parseTestSteps(compareToStepsXml, emptyRevisionMap)
1450
+ : Promise.resolve([]),
1451
+ ]);
1452
+ return { baselineSteps, compareToSteps };
1453
+ }
1454
+ catch (err) {
1455
+ logger_1.default.warn(`Failed to parse historical Steps XML for diff rendering: ${(err === null || err === void 0 ? void 0 : err.message) || err}`);
1456
+ return null;
1457
+ }
1458
+ }
1412
1459
  normalizeTestPhaseValue(value) {
1413
1460
  const rendered = this.normalizeHistoricalCompareValue(value);
1414
1461
  if (!rendered) {
@@ -1780,8 +1827,8 @@ class TicketsDataProvider {
1780
1827
  iterationPath: this.normalizeHistoricalCompareValue(fields['System.IterationPath']),
1781
1828
  versionId: this.toHistoricalRevision((_d = workItem === null || workItem === void 0 ? void 0 : workItem.rev) !== null && _d !== void 0 ? _d : fields['System.Rev']),
1782
1829
  versionTimestamp: this.normalizeHistoricalCompareValue(fields['System.ChangedDate']),
1783
- description: this.normalizeHistoricalCompareValue(fields['System.Description']),
1784
- steps: this.normalizeHistoricalCompareValue(fields['Microsoft.VSTS.TCM.Steps']),
1830
+ description: this.rawHistoricalRichText(fields['System.Description']),
1831
+ steps: this.rawHistoricalRichText(fields['Microsoft.VSTS.TCM.Steps']),
1785
1832
  testPhase: this.normalizeTestPhaseValue(testPhaseRaw),
1786
1833
  relatedLinkCount,
1787
1834
  workItemUrl: `${this.orgUrl}${project}/_workitems/edit/${id}`,
@@ -1917,7 +1964,8 @@ class TicketsDataProvider {
1917
1964
  ...Array.from(compareTo.snapshotMap.keys()),
1918
1965
  ]);
1919
1966
  const sortedIds = Array.from(allIds.values()).sort((a, b) => a - b);
1920
- const rows = sortedIds.map((id) => {
1967
+ const testStepParserHelper = new testStepParserHelper_1.default(this.orgUrl, this.token);
1968
+ const rows = await Promise.all(sortedIds.map(async (id) => {
1921
1969
  const baselineRow = baseline.snapshotMap.get(id) || null;
1922
1970
  const compareToRow = compareTo.snapshotMap.get(id) || null;
1923
1971
  const workItemType = (compareToRow === null || compareToRow === void 0 ? void 0 : compareToRow.workItemType) || (baselineRow === null || baselineRow === void 0 ? void 0 : baselineRow.workItemType) || '';
@@ -1951,7 +1999,12 @@ class TicketsDataProvider {
1951
1999
  const safeBaseline = baselineRow;
1952
2000
  const safeCompareTo = compareToRow;
1953
2001
  const changedFields = [];
1954
- if (safeBaseline.description !== safeCompareTo.description) {
2002
+ const descriptionChanged = this.canonicalizeHistoricalRichTextForCompare(safeBaseline.description) !==
2003
+ this.canonicalizeHistoricalRichTextForCompare(safeCompareTo.description);
2004
+ const stepsChanged = isTestCase &&
2005
+ this.canonicalizeHistoricalRichTextForCompare(safeBaseline.steps) !==
2006
+ this.canonicalizeHistoricalRichTextForCompare(safeCompareTo.steps);
2007
+ if (descriptionChanged) {
1955
2008
  changedFields.push('Description');
1956
2009
  }
1957
2010
  if (safeBaseline.title !== safeCompareTo.title) {
@@ -1960,7 +2013,7 @@ class TicketsDataProvider {
1960
2013
  if (safeBaseline.state !== safeCompareTo.state) {
1961
2014
  changedFields.push('State');
1962
2015
  }
1963
- if (isTestCase && safeBaseline.steps !== safeCompareTo.steps) {
2016
+ if (stepsChanged) {
1964
2017
  changedFields.push('Steps');
1965
2018
  }
1966
2019
  if (safeBaseline.testPhase !== safeCompareTo.testPhase) {
@@ -1969,7 +2022,7 @@ class TicketsDataProvider {
1969
2022
  if (isTestCase && safeBaseline.relatedLinkCount !== safeCompareTo.relatedLinkCount) {
1970
2023
  changedFields.push('Related Link Count');
1971
2024
  }
1972
- const differences = changedFields.map((field) => {
2025
+ const differences = await Promise.all(changedFields.map(async (field) => {
1973
2026
  switch (field) {
1974
2027
  case 'Description':
1975
2028
  return { field, baseline: safeBaseline.description, compareTo: safeCompareTo.description };
@@ -1977,8 +2030,10 @@ class TicketsDataProvider {
1977
2030
  return { field, baseline: safeBaseline.title, compareTo: safeCompareTo.title };
1978
2031
  case 'State':
1979
2032
  return { field, baseline: safeBaseline.state, compareTo: safeCompareTo.state };
1980
- case 'Steps':
1981
- return { field, baseline: safeBaseline.steps, compareTo: safeCompareTo.steps };
2033
+ case 'Steps': {
2034
+ const parsed = await this.parseHistoricalStepsForDiff(testStepParserHelper, safeBaseline.steps, safeCompareTo.steps);
2035
+ return Object.assign({ field, baseline: safeBaseline.steps, compareTo: safeCompareTo.steps }, (parsed || {}));
2036
+ }
1982
2037
  case 'Test Phase':
1983
2038
  return { field, baseline: safeBaseline.testPhase, compareTo: safeCompareTo.testPhase };
1984
2039
  case 'Related Link Count':
@@ -1990,7 +2045,7 @@ class TicketsDataProvider {
1990
2045
  default:
1991
2046
  return { field, baseline: '', compareTo: '' };
1992
2047
  }
1993
- });
2048
+ }));
1994
2049
  return {
1995
2050
  id,
1996
2051
  workItemType,
@@ -2002,7 +2057,7 @@ class TicketsDataProvider {
2002
2057
  differences,
2003
2058
  workItemUrl: safeCompareTo.workItemUrl || safeBaseline.workItemUrl,
2004
2059
  };
2005
- });
2060
+ }));
2006
2061
  const summary = rows.reduce((acc, row) => {
2007
2062
  if (row.compareStatus === 'Added')
2008
2063
  acc.addedCount += 1;