diff --git a/packages/allure-js-commons/src/sdk/utils.ts b/packages/allure-js-commons/src/sdk/utils.ts index dfdf3a068..15754f741 100644 --- a/packages/allure-js-commons/src/sdk/utils.ts +++ b/packages/allure-js-commons/src/sdk/utils.ts @@ -44,7 +44,14 @@ export const stripAnsi = (str: string): string => { return str.replace(regex, ""); }; -export const getMessageAndTraceFromError = (error: Error | { message?: string; stack?: string }): StatusDetails => { +export const getMessageAndTraceFromError = ( + error: + | Error + | { + message?: string; + stack?: string; + }, +): StatusDetails => { const { message, stack } = error; const actual = "actual" in error && error.actual !== undefined ? { actual: serialize(error.actual) } : {}; const expected = "expected" in error && error.expected !== undefined ? { expected: serialize(error.expected) } : {}; @@ -56,13 +63,40 @@ export const getMessageAndTraceFromError = (error: Error | { message?: string; s }; }; -export const allureIdRegexp = /(?:^|\s)@?allure\.id[:=](?[^\s]+)/; -export const allureIdRegexpGlobal = new RegExp(allureIdRegexp, "g"); +type AllureTitleMetadataMatch = RegExpMatchArray & { + groups: { + type?: string; + v1?: string; + v2?: string; + v3?: string; + v4?: string; + }; +}; + +export const allureTitleMetadataRegexp = /(?:^|\s)@?allure\.(?\S+)[:=]("[^"]+"|'[^']+'|`[^`]+`|\S+)/; +export const allureTitleMetadataRegexpGlobal = new RegExp(allureTitleMetadataRegexp, "g"); +export const allureIdRegexp = /(?:^|\s)@?allure\.id[:=](?\S+)/; export const allureLabelRegexp = /(?:^|\s)@?allure\.label\.(?[^:=\s]+)[:=](?[^\s]+)/; -export const allureLabelRegexpGlobal = new RegExp(allureLabelRegexp, "g"); + +export const getTypeFromAllureTitleMetadataMatch = (match: AllureTitleMetadataMatch) => { + return match?.[1]; +}; + +export const getValueFromAllureTitleMetadataMatch = (match: AllureTitleMetadataMatch) => { + const quotesRegexp = /['"`]/; + const quoteOpenRegexp = new RegExp(`^${quotesRegexp.source}`); + const quoteCloseRegexp = new RegExp(`${quotesRegexp.source}$`); + const matchedValue = match?.[2] ?? ""; + + if (quoteOpenRegexp.test(matchedValue) && quoteCloseRegexp.test(matchedValue)) { + return matchedValue.slice(1, -1); + } + + return matchedValue; +}; export const isMetadataTag = (tag: string) => { - return allureIdRegexp.test(tag) || allureLabelRegexp.test(tag); + return allureTitleMetadataRegexp.test(tag); }; export const extractMetadataFromString = ( @@ -72,25 +106,45 @@ export const extractMetadataFromString = ( cleanTitle: string; } => { const labels = [] as Label[]; - - title.split(" ").forEach((val) => { - const idValue = val.match(allureIdRegexp)?.groups?.id; - - if (idValue) { - labels.push({ name: LabelName.ALLURE_ID, value: idValue }); + const metadata = title.matchAll(allureTitleMetadataRegexpGlobal); + const cleanTitle = title + .replaceAll(allureTitleMetadataRegexpGlobal, "") + .split(" ") + .filter(Boolean) + .reduce((acc, word) => { + if (/^[\n\r]/.test(word)) { + return acc + word; + } + + return `${acc} ${word}`; + }, "") + .trim(); + + for (const m of metadata) { + const match = m as AllureTitleMetadataMatch; + const type = getTypeFromAllureTitleMetadataMatch(match); + const value = getValueFromAllureTitleMetadataMatch(match); + + if (!type || !value) { + continue; } - const labelMatch = val.match(allureLabelRegexp); - const { name, value } = labelMatch?.groups || {}; + const [subtype, name] = type.split("."); - if (name && value) { - labels?.push({ name, value }); + switch (subtype) { + case "id": + labels.push({ name: LabelName.ALLURE_ID, value }); + break; + case "label": + labels.push({ name, value }); + break; } - }); - - const cleanTitle = title.replace(allureLabelRegexpGlobal, "").replace(allureIdRegexpGlobal, "").trim(); + } - return { labels, cleanTitle }; + return { + labels, + cleanTitle, + }; }; export const isAnyStepFailed = (item: StepResult | TestResult | FixtureResult): boolean => { diff --git a/packages/allure-js-commons/test/sdk/utils.spec.ts b/packages/allure-js-commons/test/sdk/utils.spec.ts index b19d1715c..569d2d567 100644 --- a/packages/allure-js-commons/test/sdk/utils.spec.ts +++ b/packages/allure-js-commons/test/sdk/utils.spec.ts @@ -296,6 +296,49 @@ describe("extractMetadataFromString", () => { ], }); }); + + it("should support values in single quotes", () => { + expect( + extractMetadataFromString("foo @allure.label.l1='foo bar baz' and bar @allure.id=beep @allure.label.l1=boop"), + ).toEqual({ + cleanTitle: "foo and bar", + labels: [ + { name: "l1", value: "foo bar baz" }, + { name: LabelName.ALLURE_ID, value: "beep" }, + { name: "l1", value: "boop" }, + ], + }); + }); + + it("should support values in double quotes", () => { + expect(extractMetadataFromString('foo @allure.label.l1="foo bar baz"')).toEqual({ + cleanTitle: "foo", + labels: [{ name: "l1", value: "foo bar baz" }], + }); + }); + + it("should support values in backticks", () => { + expect(extractMetadataFromString("foo @allure.label.l1=`foo bar baz`")).toEqual({ + cleanTitle: "foo", + labels: [{ name: "l1", value: "foo bar baz" }], + }); + }); + + it("should support mixed values at the same time", () => { + expect( + extractMetadataFromString( + "foo @allure.label.l1=foo @allure.label.l1=`foo 1` bar @allure.label.l1='foo 2' baz @allure.label.l1=\"foo 3\"", + ), + ).toEqual({ + cleanTitle: "foo bar baz", + labels: [ + { name: "l1", value: "foo" }, + { name: "l1", value: "foo 1" }, + { name: "l1", value: "foo 2" }, + { name: "l1", value: "foo 3" }, + ], + }); + }); }); describe("isMetadataTag", () => {