diff --git a/packages/network-debugger/package.json b/packages/network-debugger/package.json index 3dbbae5..8367c1e 100644 --- a/packages/network-debugger/package.json +++ b/packages/network-debugger/package.json @@ -18,7 +18,7 @@ } }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "vitest", "build": "vite build", "dev": "vite build --watch" }, @@ -47,7 +47,7 @@ "tslib": "2.6.2", "vite": "^5.2.10", "vite-plugin-dts": "^3.8.3", - "vitest": "^1.5.0" + "vitest": "^2.0.3" }, "dependencies": { "iconv-lite": "^0.6.3", diff --git a/packages/network-debugger/src/common.test.ts b/packages/network-debugger/src/common.test.ts new file mode 100644 index 0000000..e1ec6ce --- /dev/null +++ b/packages/network-debugger/src/common.test.ts @@ -0,0 +1,25 @@ +import { vi, describe, beforeEach, test, expect } from 'vitest' +import { RequestDetail, __filename, __dirname } from './common' + +describe('RequestDetail', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('constructor', () => { + test('should initialize with a unique id and default properties', () => { + const requestDetail = new RequestDetail() + + expect(requestDetail.id).toBeDefined() + expect(requestDetail.responseInfo).toEqual({}) + expect(requestDetail.initiator).not.toBeUndefined() + }) + + test('should increment id for each new instance', () => { + const requestDetail1 = new RequestDetail() + const requestDetail2 = new RequestDetail() + + expect(Number(requestDetail2.id)).toBe(Number(requestDetail1.id) + 1) + }) + }) +}) diff --git a/packages/network-debugger/src/fork/pipe/request-header-transformer.test.ts b/packages/network-debugger/src/fork/pipe/request-header-transformer.test.ts new file mode 100644 index 0000000..3930ab6 --- /dev/null +++ b/packages/network-debugger/src/fork/pipe/request-header-transformer.test.ts @@ -0,0 +1,52 @@ +import { RequestHeaderPipe } from './request-header-transformer' +import { describe, test, expect } from 'vitest' + +describe('RequestHeaderPipe', () => { + describe('constructor', () => { + test('should initialize with empty headers when no headers are provided', () => { + const requestHeaderPipe = new RequestHeaderPipe() + expect(requestHeaderPipe.getData()).toEqual({}) + }) + + test('should initialize with provided headers', () => { + const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } + const requestHeaderPipe = new RequestHeaderPipe(headers) + expect(requestHeaderPipe.getData()).toEqual(headers) + }) + }) + + describe('getHeader', () => { + test('should return the value of the header if it exists (case-insensitive)', () => { + const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } + const requestHeaderPipe = new RequestHeaderPipe(headers) + + expect(requestHeaderPipe.getHeader('content-type')).toBe('application/json') + expect(requestHeaderPipe.getHeader('Content-Type')).toBe('application/json') + }) + + test('should return undefined if the header does not exist', () => { + const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } + const requestHeaderPipe = new RequestHeaderPipe(headers) + + expect(requestHeaderPipe.getHeader('Authorization')).toBeUndefined() + }) + }) + + describe('getData', () => { + test('should return all headers', () => { + const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } + const requestHeaderPipe = new RequestHeaderPipe(headers) + + expect(requestHeaderPipe.getData()).toEqual(headers) + }) + }) + + describe('valueOf', () => { + test('should return the headers object', () => { + const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } + const requestHeaderPipe = new RequestHeaderPipe(headers) + + expect(requestHeaderPipe.valueOf()).toEqual(headers) + }) + }) +}) diff --git a/packages/network-debugger/src/utils/call-site.test.ts b/packages/network-debugger/src/utils/call-site.test.ts new file mode 100644 index 0000000..3f2e7b3 --- /dev/null +++ b/packages/network-debugger/src/utils/call-site.test.ts @@ -0,0 +1,97 @@ +import { CallSite } from './call-site' +import { describe, test, expect } from 'vitest' + +describe('CallSite', () => { + test('parses a simple stack trace line', () => { + const line = 'at Object. (/path/to/file.js:10:15)' + const callSite = new CallSite(line) + + expect(callSite.fileName).toBe('/path/to/file.js') + expect(callSite.lineNumber).toBe(10) + expect(callSite.columnNumber).toBe(15) + expect(callSite.functionName).toBe(null) + expect(callSite.typeName).toBe('Object') + expect(callSite.methodName).toBe(null) + expect(callSite.native).toBe(false) + }) + + test('parses a stack trace line with a function name', () => { + const line = 'at functionName (/path/to/file.js:10:15)' + const callSite = new CallSite(line) + + expect(callSite.fileName).toBe('/path/to/file.js') + expect(callSite.lineNumber).toBe(10) + expect(callSite.columnNumber).toBe(15) + expect(callSite.functionName).toBe('functionName') + expect(callSite.typeName).toBe(null) + expect(callSite.methodName).toBe(null) + expect(callSite.native).toBe(false) + }) + + test('parses a native stack trace line', () => { + const line = 'at native' + const callSite = new CallSite(line) + + expect(callSite.fileName).toBe(null) + expect(callSite.lineNumber).toBe(null) + expect(callSite.columnNumber).toBe(null) + expect(callSite.functionName).toBe(null) + expect(callSite.typeName).toBe(null) + expect(callSite.methodName).toBe(null) + expect(callSite.native).toBe(true) + }) + + test('parses a line with object and method name', () => { + const line = 'at MyObject.myMethod (/path/to/file.js:10:15)' + const callSite = new CallSite(line) + + expect(callSite.fileName).toBe('/path/to/file.js') + expect(callSite.lineNumber).toBe(10) + expect(callSite.columnNumber).toBe(15) + expect(callSite.functionName).toBe('MyObject.myMethod') + expect(callSite.typeName).toBe('MyObject') + expect(callSite.methodName).toBe('myMethod') + expect(callSite.native).toBe(false) + }) + + test('parses a line with anonymous function', () => { + const line = 'at (/path/to/file.js:10:15)' + const callSite = new CallSite(line) + + expect(callSite.fileName).toBe('/path/to/file.js') + expect(callSite.lineNumber).toBe(10) + expect(callSite.columnNumber).toBe(15) + expect(callSite.functionName).toBe('') + expect(callSite.typeName).toBe(null) + expect(callSite.methodName).toBe(null) + expect(callSite.native).toBe(false) + }) + + test('copying properties from another CallSite instance', () => { + const site1 = new CallSite('at functionName (/path/to/file.js:10:15)') + const site2 = new CallSite(site1) + + expect(site2.fileName).toBe('/path/to/file.js') + expect(site2.lineNumber).toBe(10) + expect(site2.columnNumber).toBe(15) + expect(site2.functionName).toBe('functionName') + expect(site2.typeName).toBe(null) + expect(site2.methodName).toBe(null) + expect(site2.native).toBe(false) + }) + + test('converts CallSite to string', () => { + const callSite = new CallSite('at MyObject.myMethod (/path/to/file.js:10:15)') + const expectedString = JSON.stringify({ + fileName: '/path/to/file.js', + lineNumber: 10, + functionName: 'MyObject.myMethod', + typeName: 'MyObject', + methodName: 'myMethod', + columnNumber: 15, + native: false + }) + + expect(callSite.toString()).toBe(expectedString) + }) +}) diff --git a/packages/network-debugger/src/utils/stack.test.ts b/packages/network-debugger/src/utils/stack.test.ts new file mode 100644 index 0000000..7828784 --- /dev/null +++ b/packages/network-debugger/src/utils/stack.test.ts @@ -0,0 +1,72 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest' +import { CallSite } from './call-site' +import { getStackFrames, initiatorStackPipe } from './stack' // Adjust the path as needed + +vi.mock('./call-site', () => { + return { + CallSite: vi.fn().mockImplementation((line) => { + const instance = { + fileName: null, + lineNumber: null, + functionName: null, + typeName: null, + methodName: null, + columnNumber: null, + native: false + } + + const parseMock = vi.fn((l: string) => instance) + + if (typeof line === 'string') { + parseMock(line) + } + + return instance + }) + } +}) + +describe('Stack Frame Utilities', () => { + beforeEach(() => { + ;(CallSite as any).mockClear() + }) + + describe('getStackFrames', () => { + test('generates stack frames from an error stack', () => { + const stack = + 'Error\n' + + ' at functionName (/path/to/file.js:10:15)\n' + + ' at Object. (/another/path/file.js:20:25)' + const frames = getStackFrames(stack) + + expect(CallSite).toHaveBeenCalledTimes(2) + expect(frames.length).toBe(2) + }) + + test('captures stack frames when no stack is provided', () => { + const frames = getStackFrames() + + // Expect the captureStackTrace to have been called + // Since we can't control the stack trace of the current environment in Jest, + // we'll just check that the frames are not empty. + expect(frames.length).toBeGreaterThan(0) + }) + }) + + describe('initiatorStackPipe', () => { + test('filters out frames based on ignoreList', () => { + const mockFrames = [ + { fileName: '/path/to/file.js', lineNumber: 10 }, + { fileName: '(internal/async_hooks.js:1:1)', lineNumber: 1 }, + { fileName: '/node_modules/module/file.js', lineNumber: 5 }, + { fileName: '/another/path/file.js', lineNumber: 20 } + ].map((frame) => Object.assign(new CallSite(''), frame)) + + const filteredFrames = initiatorStackPipe(mockFrames) + + expect(filteredFrames.length).toBe(2) + expect(filteredFrames[0].fileName).toBe('/path/to/file.js') + expect(filteredFrames[1].fileName).toBe('/another/path/file.js') + }) + }) +})