import vm from 'node:vm' import replMock from 'node:repl' import { describe, expect, it, vi } from 'vitest' import type { ReplConfig } from '../src/index.js' import WDIORepl from '../src/index.js' let runInContextFail = false vi.mock('vm', () => { class VMMock { createContext: () => any runInContext: () => any constructor () { this.createContext = vi.fn() this.runInContext = vi.fn().mockImplementation(() => { if (runInContextFail) { throw new Error('boom!') } return 'someResult' }) } } return { default: new VMMock() } }) vi.mock('repl', () => { const replInstance = { on: vi.fn().mockImplementation( (name, callback) => setTimeout( () => callback(name), 100 ) ) } return { default: { start: vi.fn().mockReturnValue(replInstance) } } }) const defaultArgs: ReplConfig = { commandTimeout: 5000, prompt: '\u203A ', useGlobal: true, useColor: true, eval: () => {} } interface SomeContext { foo: string; } describe('eval', () => { it('should return predefined responses', () => { const repl = new WDIORepl(defaultArgs) const callback = vi.fn() repl['_runCmd'] = vi.fn() repl.eval('browser', {}, '/some/filname.js', callback) expect(callback).toBeCalledWith(null, '[WebdriverIO REPL client]') callback.mockClear() repl.eval('driver', {}, '/some/filname.js', callback) expect(callback).toBeCalledWith(null, '[WebdriverIO REPL client]') callback.mockClear() repl.eval('$', {}, '/some/filname.js', callback) expect(callback).toBeCalledWith(null, '[Function: findElement]') callback.mockClear() repl.eval('$$', {}, '/some/filname.js', callback) expect(callback).toBeCalledWith(null, '[Function: findElements]') callback.mockClear() expect(repl['_runCmd']).toBeCalledTimes(0) }) it('should call _runCmd', () => { const repl = new WDIORepl(defaultArgs) repl['_runCmd'] = vi.fn() repl.eval('1+1', {}, '/some/filename', vi.fn()) expect(repl['_runCmd']) .toBeCalledWith('1+1', expect.any(Object), expect.any(Function)) expect(repl['_isCommandRunning']).toBe(true) }) it('should not be able to call a command twice', () => { const repl = new WDIORepl(defaultArgs) repl['_runCmd'] = vi.fn() repl.eval('1+1', {}, '/some/filename', vi.fn()) repl.eval('2+2', {}, '/some/filename', vi.fn()) expect(repl['_runCmd']).toBeCalledTimes(1) }) }) describe('runCmd', () => { it('should call result handler', () => { const repl = new WDIORepl(defaultArgs) repl['_handleResult'] = vi.fn() repl['_runCmd']('1+1', {}, vi.fn()) expect(vm.runInContext).toBeCalledWith('1+1', {}) expect(repl['_handleResult']) .toBeCalledWith('someResult', expect.any(Function)) }) it('should call back if failed', () => { const repl = new WDIORepl(defaultArgs) const callback = vi.fn() runInContextFail = true repl['_runCmd']('1+1', {}, callback) expect(callback).toBeCalled() expect(repl['_isCommandRunning']).toBe(false) }) }) describe('handleResult', () => { it('should return basic result types directly', () => { const repl = new WDIORepl(defaultArgs) const callback = vi.fn() repl['_isCommandRunning'] = true // @ts-expect-error repl['_handleResult'](null, callback) expect(callback).toBeCalledWith(null, null) expect(repl['_isCommandRunning']).toBe(false) callback.mockClear() // @ts-expect-error repl['_handleResult'](1, callback) expect(callback).toBeCalledWith(null, 1) }) it('should handle resolved promises', async () => { const repl = new WDIORepl(defaultArgs) const callback = vi.fn() const result = Promise.resolve('some result') repl['_isCommandRunning'] = true repl['_handleResult'](result, callback) await new Promise((resolve) => setTimeout(resolve, 10)) expect(callback).toBeCalledWith(null, 'some result') expect(repl['_isCommandRunning']).toBe(false) }) it('should handle rejected promises', async () => { const repl = new WDIORepl(defaultArgs) const callback = vi.fn() const result = Promise.reject(new Error('boom')) repl['_isCommandRunning'] = true repl['_handleResult'](result, callback) await new Promise((resolve) => setTimeout(resolve, 10)) expect(callback).toBeCalledWith(new Error('boom'), undefined) expect(repl['_isCommandRunning']).toBe(false) }) it('should timeout if successful command takes too long', async () => { const repl = new WDIORepl({ ...defaultArgs, commandTimeout: 100 }) const callback = vi.fn() const result = new Promise((resolve) => setTimeout(resolve, 200)) repl['_isCommandRunning'] = true repl['_handleResult'](result, callback) await new Promise((resolve) => setTimeout(resolve, 300)) expect(callback).toBeCalledTimes(1) expect(callback).toBeCalledWith(new Error('Command execution timed out'), undefined) expect(repl['_isCommandRunning']).toBe(false) }) it('should timeout if failing command takes too long', async () => { const repl = new WDIORepl({ ...defaultArgs, commandTimeout: 100 }) const callback = vi.fn() const result = new Promise((resolve, reject) => setTimeout(reject, 200)) repl['_isCommandRunning'] = true repl['_handleResult'](result, callback) await new Promise((resolve) => setTimeout(resolve, 300)) expect(callback).toBeCalledTimes(1) expect(callback).toBeCalledWith(new Error('Command execution timed out'), undefined) expect(repl['_isCommandRunning']).toBe(false) }) }) describe('start', () => { it('should throw repl server was already started', () => { const repl = new WDIORepl(defaultArgs) repl['_replServer'] = {} as replMock.REPLServer expect(() => repl.start({})).toThrow() }) it('should start and stop repl', async () => { const repl = new WDIORepl(defaultArgs) await repl.start({}) expect(replMock.start).toBeCalled() expect((repl['_replServer'] as replMock.REPLServer).on).toBeCalled() }) it('should allow run eval with own context', async () => { const config = { ...defaultArgs, eval: vi.fn().mockImplementation(function (this: SomeContext) { this.foo = 'foobar' }) } const repl = new WDIORepl(config) await repl.start({}) const context = { foo: 'bar' } as SomeContext repl['_config'].eval.call(context as unknown as replMock.REPLServer, '1+1', {}, '/some/filename', vi.fn()) expect(config.eval).toBeCalledWith( '1+1', expect.any(Object), '/some/filename', expect.any(Function) ) expect(context.foo).toBe('foobar') }) })