From 26932957792c984fcf96230651c545be95391a72 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Sat, 13 Jul 2024 09:55:43 +0300 Subject: [PATCH] fix: duplicate error message (#47) --- src/utils/getStatusDetails.test.ts | 130 +++++++++++++++++++++++++++++ src/utils/getStatusDetails.ts | 4 +- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/utils/getStatusDetails.test.ts diff --git a/src/utils/getStatusDetails.test.ts b/src/utils/getStatusDetails.test.ts new file mode 100644 index 0000000..52cea3e --- /dev/null +++ b/src/utils/getStatusDetails.test.ts @@ -0,0 +1,130 @@ +import { getStatusDetails } from './getStatusDetails'; + +describe('getStatusDetails', () => { + it('should return undefined for falsy input', () => { + expect(getStatusDetails(null)).toBeUndefined(); + expect(getStatusDetails(void 0)).toBeUndefined(); + expect(getStatusDetails('')).toBeUndefined(); + expect(getStatusDetails(0)).toBeUndefined(); + expect(getStatusDetails(false)).toBeUndefined(); + }); + + it('should handle string input', () => { + const input = 'Test error message'; + expect(getStatusDetails(input)).toEqual({ message: input }); + }); + + it('should handle Error object with stack trace', () => { + const error = new Error('Test error'); + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toContain('Test error'); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should not duplicate error message if it starts with empty line', () => { + const error = new Error(' \nERROR:\nExpected: 5\nActual: 4'); + const message = getStatusDetails(error)!.message!; + expect(message.indexOf('Expected: 5')).toBeGreaterThan(0); + expect(message.lastIndexOf('Expected: 5')).toBe(message.indexOf('Expected: 5')); + }); + + it('should handle Error object without stack trace', () => { + const error = new Error('Test error'); + Object.defineProperty(error, 'stack', { value: undefined }); + const result = getStatusDetails(error); + expect(result).toEqual({ message: 'Error: Test error' }); + }); + + it('should handle custom error objects', () => { + class CustomError extends Error { + constructor(message: string) { + super(message); + this.name = 'CustomError'; + } + } + const error = new CustomError('Custom error message'); + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toContain('Custom error message'); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should handle non-Error objects', () => { + const input = { message: 'Custom error object' }; + const result = getStatusDetails(input); + expect(result).toEqual({ message: 'Custom error object' }); + }); + + it('should handle Error with empty message', () => { + // eslint-disable-next-line unicorn/error-message + const error = new Error(); + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toBeTruthy(); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should handle Error with multiline message', () => { + const error = new Error('Line 1\nLine 2\nLine 3'); + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toContain('Line 1\nLine 2\nLine 3'); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should handle Error with very long message', () => { + const longMessage = 'a'.repeat(10_000); + const error = new Error(longMessage); + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toContain(longMessage); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should handle Error with non-string properties', () => { + const error: any = new Error('Test error'); + error.code = 404; + error.details = { foo: 'bar' }; + const result = getStatusDetails(error); + expect(result).toHaveProperty('message'); + expect(result?.message).toContain('Test error'); + expect(result).toHaveProperty('trace'); + expect(result?.trace).toContain('at '); + }); + + it('should handle thrown string', () => { + let result; + try { + throw 'Thrown string'; + } catch (error) { + result = getStatusDetails(error); + } + expect(result).toEqual({ message: 'Thrown string' }); + }); + + it('should handle thrown number', () => { + let result; + try { + throw 404; + } catch (error) { + result = getStatusDetails(error); + } + expect(result).toEqual({ message: '404' }); + }); + + it('should handle thrown object', () => { + let result; + try { + throw { code: 'NETWORK_ERROR', details: 'Connection refused' }; + } catch (error) { + result = getStatusDetails(error); + } + expect(result).toEqual({ message: '{"code":"NETWORK_ERROR","details":"Connection refused"}' }); + }); +}); diff --git a/src/utils/getStatusDetails.ts b/src/utils/getStatusDetails.ts index 1790fc2..076451e 100644 --- a/src/utils/getStatusDetails.ts +++ b/src/utils/getStatusDetails.ts @@ -3,6 +3,8 @@ import type { StatusDetails } from 'jest-allure2-reporter'; import { autoIndent } from './autoIndent'; import { isError } from './vendor'; +const HAS_EMPTY_FIRST_LINE = /^\s*\n/; + export function getStatusDetails(maybeError: unknown): StatusDetails | undefined { if (!maybeError) { return; @@ -35,7 +37,7 @@ function getTrace(maybeError: unknown): string { function restoreStack(error: Error): string { const { message, name, stack } = error; - if (stack && message && hasEmptyFirstLine(stack)) { + if (stack && message && hasEmptyFirstLine(stack) && !HAS_EMPTY_FIRST_LINE.test(message)) { return `${name}: ${message}${stack.slice(stack.indexOf('\n'))}`; }