diff --git a/src/outputProcessor/consoleProcessor.ts b/src/outputProcessor/consoleProcessor.ts index b30821a..88c73ab 100644 --- a/src/outputProcessor/consoleProcessor.ts +++ b/src/outputProcessor/consoleProcessor.ts @@ -1,5 +1,6 @@ import CONSTANTS from '../constants'; import type {Colors, Log, LogSymbols, LogType, MessageData} from '../types'; +import utils from '../utils'; import type {PluginOptions} from '../installLogsPrinter.types'; import chalk from 'chalk'; @@ -110,9 +111,7 @@ function consoleProcessor(messages: Log[], options: PluginOptions, data: Message messages.forEach(({type, message, severity, timeString}) => { let processedMessage = message; - - let {color, icon, trim} = TYPE_COMPUTE[type](options); - trim = trim || options.defaultTrimLength || 800; + let {color, icon, trim = options.defaultTrimLength || 800} = TYPE_COMPUTE[type](options); if (severity === CONSTANTS.SEVERITY.ERROR) { color = COLORS.RED; @@ -122,8 +121,17 @@ function consoleProcessor(messages: Log[], options: PluginOptions, data: Message icon = LOG_SYMBOLS.WARNING; } - if (message.length > trim) { - processedMessage = message.substring(0, trim) + ' ...'; + const maybeTrimLength = (msg: string) => + msg.length > trim ? msg.substring(0, trim) + ' ...' : msg; + + if (type == 'cy:log') { + processedMessage = utils.applyMessageMarkdown(processedMessage, { + bold: chalk.bold, + italic: chalk.italic, + processContents: maybeTrimLength, + }); + } else { + processedMessage = maybeTrimLength(processedMessage); } if (timeString) { diff --git a/src/utils.ts b/src/utils.ts index 082d7ff..be9fdf7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,21 @@ import jsonPrune from './jsonPrune'; import {compare} from 'compare-versions'; import type {Failure} from 'superstruct'; +// Markdown regex: https://gist.github.com/elfefe/ef08e583e276e7617cd316ba2382fc40 +function getMarkdownRegex(numWrapperChars: number) { + const asteriskWrapper = `(?:\\*){${numWrapperChars}}`; + const underscoreWrapper = `(?:_){${numWrapperChars}}`; + return new RegExp( + `^${asteriskWrapper}(.+?)${asteriskWrapper}$|^${underscoreWrapper}(.+?)${underscoreWrapper}$` + ); +} + +const MARKDOWN_REGEX = { + BOLD_AND_ITALIC: getMarkdownRegex(3), + BOLD: getMarkdownRegex(2), + ITALIC: getMarkdownRegex(1), +}; + const utils = { nonQueueTask: async (name: string, data: Record) => { if (Cypress.testingType === 'component' && compare(Cypress.version, '12.15.0', '>=')) { @@ -97,6 +112,47 @@ const utils = { '\n' + errorList.map((error) => ` => ${error.path.join('.')}: ${error.message}`).join('\n') + '\n', + + /** + * The Cypress GUI runner allows markdown in `cy.log` messages. We can take this + * into account for our loggers as well. + */ + applyMessageMarkdown( + message: string, + { + bold, + italic, + processContents, + }: { + bold: (str: string) => string; + italic: (str: string) => string; + processContents?: (str: string) => string; + } + ) { + let contentsHaveBeenProcessed = false; + const maybeProcessContents = (str: string) => { + if (contentsHaveBeenProcessed || !processContents) return str; + contentsHaveBeenProcessed = true; + return processContents(str); + }; + + // bold and italic, i.e. ***text*** or ___text___ + message = message.replace(MARKDOWN_REGEX.BOLD_AND_ITALIC, (str, group1, group2) => + bold(italic(maybeProcessContents(group1 || group2))) + ); + + // bold, i.e. **text** or __text__ + message = message.replace(MARKDOWN_REGEX.BOLD, (str, group1, group2) => + bold(maybeProcessContents(group1 || group2)) + ); + + // italic, i.e. *text* or _text_ + message = message.replace(MARKDOWN_REGEX.ITALIC, (str, group1, group2) => + italic(maybeProcessContents(group1 || group2)) + ); + + return maybeProcessContents(message); + }, }; export default utils; diff --git a/test/cypress/integration/logMarkdown.spec.js b/test/cypress/integration/logMarkdown.spec.js new file mode 100644 index 0000000..189ebb2 --- /dev/null +++ b/test/cypress/integration/logMarkdown.spec.js @@ -0,0 +1,21 @@ +describe('cy.log markdown.', () => { + /** + * Covers: cy.log markdown to console + */ + it('cy.log markdown.', () => { + cy.log('_This is an_italic* log._'); + cy.log('*This is an_italic* log.*'); + cy.log('**This is a__bold* log.**'); + cy.log('__This is a__bold* log.__'); + cy.log('***This is a_bold and italic* log.***'); + cy.log('___This is a_bold and italic* log.___'); + cy.log('_This is a normal log'); + cy.log('This is a normal log_'); + cy.log('__This is a normal log'); + cy.log('This is a normal log__'); + cy.log('*This is a normal log'); + cy.log('This is a normal log*'); + cy.log('**This is a normal log'); + cy.log('This is a normal log**'); + }); +}); diff --git a/test/specs/commandsLogging.spec.js b/test/specs/commandsLogging.spec.js index a7c9753..4d19285 100755 --- a/test/specs/commandsLogging.spec.js +++ b/test/specs/commandsLogging.spec.js @@ -1,5 +1,6 @@ -import {PADDING, ICONS, clean, runTest, commandBase, logLastRun} from '../utils'; +import {PADDING, ICONS, clean, runTest, commandBase, logLastRun, runTestContinuous} from '../utils'; import {expect} from 'chai'; +import chalk from 'chalk'; describe('Commands logging.', () => { afterEach(function () { @@ -238,4 +239,35 @@ describe('Commands logging.', () => { ); }); }).timeout(60000); + + it('Should apply chalk markdown to console', async () => { + // runTestContinuous to use spawn instead of exec, in order to get unicode stdout + await runTestContinuous( + commandBase( + ['printLogsToConsoleAlways=1', 'enableContinuousLogging=1'], + ['logMarkdown.spec.js'] + ), + (error, stdout, stderr) => { + const lines = stdout.split('\n'); + [ + chalk.italic('This is an_italic* log.'), + chalk.italic('This is an_italic* log.'), + chalk.bold('This is a__bold* log.'), + chalk.bold('This is a__bold* log.'), + chalk.bold(chalk.italic('This is a_bold and italic* log.')), + chalk.bold(chalk.italic('This is a_bold and italic* log.')), + '_This is a normal log', + 'This is a normal log_', + '__This is a normal log', + 'This is a normal log__', + '*This is a normal log', + 'This is a normal log*', + '**This is a normal log', + 'This is a normal log**', + ].forEach((msg, index) => { + expect(lines[index + 4]).to.equal(` cy:log ${ICONS.info} ${msg}`); + }); + } + ); + }).timeout(60000); }); diff --git a/test/specs/utils.spec.js b/test/specs/utils.spec.js new file mode 100644 index 0000000..c192ca1 --- /dev/null +++ b/test/specs/utils.spec.js @@ -0,0 +1,36 @@ +import utils from '../../src/utils'; +import {expect} from 'chai'; + +const {applyMessageMarkdown} = utils; + +describe('utils', () => { + describe('applyMessageMarkdown', () => { + it('correctly detects markdown and returns processed message with functions applied', () => { + const LONG_MSG = 'abc123'.repeat(5); + const tests = [ + ['*text text*', 'text text'], + ['_text text_', 'text text'], + ['**text text**', 'text text'], + ['__text text__', 'text text'], + ['***text text***', 'text text'], + ['___text text___', 'text text'], + ['_text text_text_', 'text text_text'], + ['text text_', 'text text_'], + ['*text text', '*text text'], + ['*text text**', 'text text*'], + ['**text text*', '*text text'], + [`**${LONG_MSG}**`, `${LONG_MSG.substring(0, 20)}...`], + ]; + tests.forEach(([message, expected]) => { + expect( + applyMessageMarkdown(message, { + bold: (str) => `${str}`, + italic: (str) => `${str}`, + processContents: (c) => (c.length > 20 ? c.substring(0, 20) + '...' : c), + }), + message + ).to.deep.equal(expected); + }); + }); + }); +});