🏡 index : quine-global/webdriverio.git

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')
    })
})