diff --git a/test/stylish-formatter.test.js b/test/stylish-formatter.test.js index c942946..5f6f92e 100644 --- a/test/stylish-formatter.test.js +++ b/test/stylish-formatter.test.js @@ -4,11 +4,15 @@ * License under MIT * ========================================================================== */ +import { sep } from 'node:path'; import stylishFormatter from '../src/stylish-formatter.mjs'; import { cleanFormatterOutput } from './testUtils/cleanOutput.js'; -describe('stylishFormatter', () => { +import colors from 'ansi-colors'; +const { blue, dim, green, red, yellow } = colors; + +describe('Stylish built-in formatter', () => { let actualTTY; let actualColumns; @@ -22,12 +26,10 @@ describe('stylishFormatter', () => { process.stdout.columns = actualColumns; }); - it('should outputs no errors or warnings', () => { + it('should handle results with no warnings or errors gracefully', () => { const results = [ - { - source: 'path/to/file.css', - warnings: [], - }, + { source: 'path/to/file1.css', warnings: [] }, + { source: 'path/to/file2.css', warnings: [] } ]; const returnValue = { ruleMetadata: {} }; @@ -36,7 +38,7 @@ describe('stylishFormatter', () => { expect(output).toBe(''); }); - it('should outputs no valid files', () => { + it('should handle empty results array without throwing errors', () => { const results = []; const returnValue = { ruleMetadata: {} }; @@ -50,19 +52,13 @@ describe('stylishFormatter', () => { { source: 'path/to/file.css', warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'error', - text: 'Unexpected foo', - }, - ], - }, + { severity: 'error', line: 1, column: 1, text: 'Unexpected foo', rule: 'bar' } + ] + } ]; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × Unexpected foo bar', '', '× 1 problem (1 error, 0 warnings)' @@ -73,45 +69,27 @@ describe('stylishFormatter', () => { expect(output).toBe(expectedOutput); }); it('should outputs fixable error and warning counts', () => { - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 1, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - { - line: 2, - column: 1, - rule: 'no-bar', - severity: 'error', - text: 'Unexpected bar', - }, - { - line: 3, - column: 1, - rule: 'no-baz', - severity: 'warning', - text: 'Unexpected baz', - }, - ], - }, - ]; - - const returnValue = { - ruleMetadata: { - 'no-foo': { fixable: true }, - 'no-bar': { fixable: false }, - 'no-baz': { fixable: true }, - }, - }; + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Unexpected foo', rule: 'no-foo' }, + { severity: 'error', line: 2, column: 1, text: 'Unexpected bar', rule: 'no-bar' }, + { severity: 'warning', line: 3, column: 1, text: 'Unexpected baz', rule: 'no-baz' } + ] + } + ]; + + const returnValue = { + ruleMetadata: { + 'no-foo': { fixable: true }, + 'no-bar': { fixable: false }, + 'no-baz': { fixable: true } + } + }; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × Unexpected foo o no-foo', ' 2:1 × Unexpected bar no-bar', ' 3:1 ‼ Unexpected baz o no-baz', @@ -120,41 +98,29 @@ describe('stylishFormatter', () => { ' 1 error and 1 warning potentially fixable with the "fix: true" option.' ].join('\n'); - const output = cleanFormatterOutput(stylishFormatter, results, returnValue); + const output = cleanFormatterOutput(stylishFormatter, results, returnValue); - expect(output).toBe(expectedOutput); - }); + expect(output).toBe(expectedOutput); + }); it('should outputs fixable error counts', () => { - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 1, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - { - line: 2, - column: 1, - rule: 'no-bar', - severity: 'error', - text: 'Unexpected bar', - }, - ], - }, - ]; - - const returnValue = { - ruleMetadata: { - 'no-foo': { fixable: true }, - }, - }; + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Unexpected foo', rule: 'no-foo' }, + { severity: 'error', line: 2, column: 1, text: 'Unexpected bar', rule: 'no-bar' } + ] + } + ]; + + const returnValue = { + ruleMetadata: { + 'no-foo': { fixable: true } + } + }; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × Unexpected foo o no-foo', ' 2:1 × Unexpected bar no-bar', '', @@ -162,41 +128,29 @@ describe('stylishFormatter', () => { ' 1 error potentially fixable with the "fix: true" option.' ].join('\n'); - const output = cleanFormatterOutput(stylishFormatter, results, returnValue); + const output = cleanFormatterOutput(stylishFormatter, results, returnValue); - expect(output).toBe(expectedOutput); - }); + expect(output).toBe(expectedOutput); + }); it('should outputs fixable warning counts', () => { const results = [ { source: 'path/to/file.css', warnings: [ - { - line: 1, - column: 1, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - { - line: 2, - column: 1, - rule: 'no-bar', - severity: 'warning', - text: 'Unexpected bar', - }, - ], - }, + { severity: 'error', line: 1, column: 1, text: 'Unexpected foo', rule: 'no-foo' }, + { severity: 'warning', line: 2, column: 1, text: 'Unexpected bar', rule: 'no-bar' } + ] + } ]; const returnValue = { ruleMetadata: { - 'no-bar': { fixable: true }, - }, + 'no-bar': { fixable: true } + } }; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × Unexpected foo no-foo', ' 2:1 ‼ Unexpected bar o no-bar', '', @@ -213,41 +167,23 @@ describe('stylishFormatter', () => { { source: 'path/to/file.css', warnings: [ - { - line: 1, - column: 2, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - { - line: 1, - column: 2, - rule: 'no-bar', - severity: 'warning', - text: 'Unexpected bar', - }, - { - line: 1, - column: 2, - rule: 'no-baz', - severity: 'warning', - text: 'Unexpected baz', - }, - ], - }, + { severity: 'error', line: 1, column: 2, text: 'Unexpected foo', rule: 'no-foo' }, + { severity: 'warning', line: 1, column: 2, text: 'Unexpected bar', rule: 'no-bar' }, + { severity: 'warning', line: 1, column: 2, text: 'Unexpected baz', rule: 'no-baz' } + ] + } ]; const returnValue = { ruleMetadata: { 'no-foo': {}, // fixable should exist 'no-bar': { fixable: 900 }, // fixable should be a boolean - 'no-baz': { fixable: true }, - }, + 'no-baz': { fixable: true } + } }; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:2 × Unexpected foo no-foo', ' 1:2 ‼ Unexpected bar no-bar', ' 1:2 ‼ Unexpected baz o no-baz', @@ -260,33 +196,51 @@ describe('stylishFormatter', () => { expect(output).toBe(expectedOutput); }); + it('should correctly apply color formatting to error and warning messages', () => { + const results = [ + { + source: 'test.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Error message', rule: 'error-rule' }, + { severity: 'warning', line: 2, column: 2, text: 'Warning message', rule: 'warning-rule' } + ] + } + ]; + + const returnValue = { + ruleMetadata: { + 'error-rule': { fixable: true }, + 'warning-rule': { fixable: true } + } + }; + + const output = stylishFormatter(results, returnValue); + + expect(output).toContain(blue('test.css')); + expect(output).toContain(red('✖')); + expect(output).toContain(yellow('⚠')); + expect(output).toContain(green('♻')); + expect(output).toContain(dim('error-rule')); + expect(output).toContain(dim('warning-rule')); + expect(output).toContain(red('1 error')); + expect(output).toContain(yellow('1 warning')); + expect(output).toContain(green('"fix: true"')); + }); it('should outputs results with missing ruleMetadata object', () => { - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 2, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - { - line: 1, - column: 2, - rule: 'no-bar', - severity: 'warning', - text: 'Unexpected bar', - }, - ], - }, - ]; - - const returnValue = { ruleMetadata: null }; + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'error', line: 1, column: 2, text: 'Unexpected foo', rule: 'no-foo' }, + { severity: 'warning', line: 1, column: 2, text: 'Unexpected bar', rule: 'no-bar' } + ] + } + ]; + + const returnValue = { ruleMetadata: null }; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:2 × Unexpected foo no-foo', ' 1:2 ‼ Unexpected bar no-bar', '', @@ -302,19 +256,13 @@ describe('stylishFormatter', () => { { source: 'path/to/file.css', warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'warning', - text: 'Unexpected foo', - }, - ], + { severity: 'warning', line: 1, column: 1, text: 'Unexpected foo', rule: 'bar' } + ] } ]; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 ‼ Unexpected foo bar', '', '‼ 1 problem (0 errors, 1 warning)' @@ -329,34 +277,22 @@ describe('stylishFormatter', () => { { source: 'path/to/file-a.css', warnings: [ - { - line: 1, - column: 2, - rule: 'no-foo', - severity: 'error', - text: 'Unexpected foo', - }, - ], + { severity: 'error', line: 1, column: 2, text: 'Unexpected foo', rule: 'no-foo' } + ] }, { source: 'path/to/file-b.css', warnings: [ - { - line: 1, - column: 2, - rule: 'no-bar', - severity: 'warning', - text: 'Unexpected bar', - }, - ], - }, + { severity: 'warning', line: 1, column: 2, text: 'Unexpected bar', rule: 'no-bar' } + ] + } ]; const expectedOutput = [ - 'path/to/file-a.css', + `path${sep}to${sep}file-a.css`, ' 1:2 × Unexpected foo no-foo', '', - 'path/to/file-b.css', + `path${sep}to${sep}file-b.css`, ' 1:2 ‼ Unexpected bar no-bar', '', '× 2 problems (1 error, 1 warning)' @@ -366,24 +302,18 @@ describe('stylishFormatter', () => { expect(output).toBe(expectedOutput); }); - it('should removes rule name from warning text', () => { - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 1, - rule: 'rule-name', - severity: 'warning', - text: 'Unexpected foo (rule-name)', - }, - ], - }, - ]; + it('should removes rule name from warning text', () => { + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'warning', line: 1, column: 1, text: 'Unexpected foo (rule-name)', rule: 'rule-name' } + ] + } + ]; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 ‼ Unexpected foo rule-name', '', '‼ 1 problem (0 errors, 1 warning)' @@ -392,27 +322,21 @@ describe('stylishFormatter', () => { const output = cleanFormatterOutput(stylishFormatter, results); expect(output).toBe(expectedOutput); - }); - it('should outputs warnings without stdout `TTY`', () => { - process.stdout.isTTY = false; - - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'error', - text: 'Unexpected foo', - }, - ], - }, - ]; + }); + it('should outputs warnings without stdout `TTY`', () => { + process.stdout.isTTY = false; + + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Unexpected foo', rule: 'bar' } + ] + } + ]; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × Unexpected foo bar', '', '× 1 problem (1 error, 0 warnings)' @@ -421,7 +345,7 @@ describe('stylishFormatter', () => { const output = cleanFormatterOutput(stylishFormatter, results); expect(output).toBe(expectedOutput); - }); + }); it('should condenses deprecations and invalid option warnings', () => { const results = [ { @@ -429,31 +353,31 @@ describe('stylishFormatter', () => { deprecations: [ { text: 'Deprecated foo.', - reference: 'bar', - }, + reference: 'bar' + } ], invalidOptionWarnings: [ { - text: 'Unexpected option for baz', - }, + text: 'Unexpected option for baz' + } ], - warnings: [], + warnings: [] }, { source: 'path/to/file2.css', deprecations: [ { text: 'Deprecated foo.', - reference: 'bar', - }, + reference: 'bar' + } ], invalidOptionWarnings: [ { - text: 'Unexpected option for baz', - }, + text: 'Unexpected option for baz' + } ], - warnings: [], - }, + warnings: [] + } ]; const expectedOutput = [ @@ -474,47 +398,35 @@ describe('stylishFormatter', () => { deprecations: [ { text: 'Deprecated foo.', - reference: 'bar', - }, + reference: 'bar' + } ], invalidOptionWarnings: [ { - text: 'Unexpected option for baz', - }, + text: 'Unexpected option for baz' + } ], warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'warning', - text: 'Unexpected foo', - }, - ], + { severity: 'warning', line: 1, column: 1, text: 'Unexpected foo', rule: 'bar' } + ] }, { source: 'path/to/file2.css', deprecations: [ { text: 'Deprecated foo.', - reference: 'bar', - }, + reference: 'bar' + } ], invalidOptionWarnings: [ { - text: 'Unexpected option for baz', - }, + text: 'Unexpected option for baz' + } ], warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'warning', - text: 'Unexpected foo', - }, - ], - }, + { severity: 'warning', line: 1, column: 1, text: 'Unexpected foo', rule: 'bar' } + ] + } ]; const expectedOutput = [ @@ -523,10 +435,10 @@ describe('stylishFormatter', () => { 'Deprecation warnings:', ' - Deprecated foo. See: bar', '', - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 ‼ Unexpected foo bar', '', - 'path/to/file2.css', + `path${sep}to${sep}file2.css`, ' 1:1 ‼ Unexpected foo bar', '', '‼ 2 problems (0 errors, 2 warnings)' @@ -541,32 +453,26 @@ describe('stylishFormatter', () => { { source: 'path/to/file.css', warnings: [], - ignored: true, - }, + ignored: true + } ]; const output = cleanFormatterOutput(stylishFormatter, results); expect(output).toBe(''); }); - it('should handles empty messages', () => { - const results = [ - { - source: 'path/to/file.css', - warnings: [ - { - line: 1, - column: 1, - rule: 'bar', - severity: 'error', - text: '', - }, - ], - }, - ]; + it('should handles empty messages', () => { + const results = [ + { + source: 'path/to/file.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: '', rule: 'bar' } + ] + } + ]; const expectedOutput = [ - 'path/to/file.css', + `path${sep}to${sep}file.css`, ' 1:1 × bar', '', '× 1 problem (1 error, 0 warnings)' @@ -576,4 +482,46 @@ describe('stylishFormatter', () => { expect(output).toBe(expectedOutput); }); + it('should correctly pluralize "problem" based on the total count', () => { + const results = [ + { + source: 'file1.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Error 1', rule: 'error-rule-1' } + ] + }, + { + source: 'file2.css', + warnings: [ + { severity: 'warning', line: 1, column: 1, text: 'Warning 1', rule: 'warning-rule-1' }, + { severity: 'error', line: 2, column: 2, text: 'Error 2', rule: 'error-rule-2' } + ] + } + ]; + + const returnValue = { + ruleMetadata: { + 'error-rule-1': { }, + 'warning-rule-1': { }, + 'error-rule-2': { } + } + }; + + const output = cleanFormatterOutput(stylishFormatter, results, returnValue); + + expect(output).toContain('3 problems (2 errors, 1 warning)'); + + const singleResult = [ + { + source: 'file1.css', + warnings: [ + { severity: 'error', line: 1, column: 1, text: 'Error 1', rule: 'error-rule-1' } + ] + } + ]; + + const singleOutput = cleanFormatterOutput(stylishFormatter, singleResult, returnValue); + + expect(singleOutput).toContain('1 problem (1 error, 0 warnings)'); + }); }); diff --git a/test/testUtils/cleanOutput.js b/test/testUtils/cleanOutput.js index 0b2f2c3..17d8a37 100644 --- a/test/testUtils/cleanOutput.js +++ b/test/testUtils/cleanOutput.js @@ -16,8 +16,10 @@ symbolConversions.set('✖', '×'); symbolConversions.set('♻', 'o'); /** - * @param {string} output - * @returns {string} + * Cleans and transforms the output string by removing ANSI styling and replacing certain symbols. + * + * @param {string} output - The original output string to be cleaned. + * @returns {string} The cleaned and transformed output string. */ function getCleanOutput(output) { let cleanOutput = unstyle(output).trim(); @@ -30,10 +32,12 @@ function getCleanOutput(output) { } /** - * @param {import('stylelint').Formatter} formatter - * @param {import('stylelint').LintResult[]} results - * @param {Pick} [returnValue] - * @returns {string} + * Cleans and formats the output of a Stylelint formatter. + * + * @param {import('stylelint').Formatter} formatter - The Stylelint formatter function to be used. + * @param {import('stylelint').LintResult[]} results - An array of Stylelint lint results to be formatted. + * @param {Pick} [returnValue={ ruleMetadata: {} }] - Optional return value with rule metadata. + * @returns {string} The cleaned and formatted output string. */ export function cleanFormatterOutput(formatter, results, returnValue = { ruleMetadata: {} }) { return getCleanOutput(formatter(results, returnValue));