开玩笑+酶+ react16:<img> src:请求未发送

时间:2019-06-04 07:07:56

标签: javascript node.js typescript

我正在使用玩笑+酶来测试我的反应组件“ AnimateImage”,其中包含一个图像元素:

import * as React from 'react';
import { PureComponent } from 'react';

interface Props {
    src: string;
}

class AnimateImage extends PureComponent<Props> {

    onImgLoad = (e: Event | {target: HTMLImageElement}) => {
        console.log("yes!");
    };
    render() {
        return (
                <div className="app-image-container">
                    <img
                        ref={c => {
                            if (!c) {
                                return;
                            }
                            c.onerror = function(e){
                                console.log("error:" , e);
                            }
                            if(!c.onload){
                                c.onload = this.onImgLoad;
                                if (c && c.complete && c.naturalWidth !== 0) {
                                    this.onImgLoad({
                                        target: c
                                    })
                                }
                            }
                        }}
                        src={this.props.src}
                    />
                </div>
        );
    }
}
export default AnimateImage;


测试代码:

test("image ", () => {
    const component = mount(<AnimateImage src={url_test}/>);

    expect(component).toMatchSnapshot();

    console.log("end ##################################################################");
})

预期结果:

该图像的onload处理函数被调用,我可以看到“是的!”打印在控制台中。

真实结果:

未调用图片的onload处理程序,图片的complete属性为false。

我的笑话配置:

    verbose: true,
    transform: {
        '.(ts|tsx)': 'ts-jest'
    },
    snapshotSerializers: ['enzyme-to-json/serializer'],
    moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
    testEnvironment: "jest-environment-jsdom-fourteen",
    testEnvironmentOptions: { "resources": 'usable' },

调试步骤:

  1. 我已经确认Canvas已成功安装并且可以在jsdom中正常运行。

  2. jsdom的资源加载器使用“ request-promise-native”包来获取HTTP资源。 “ request-promise-native”包的核心是“ request”包。

在“请求”包中,request.js文件声明了一个名为Request的类来处理HTTP请求。

但是我发现从来没有调用Request.start()函数,而以请求状态为“ abort”的defer函数被调用。

顺便说一句,我在函数中放置了两个“ console.log()”,其中模拟的“ window”和“ document”调用了“ close”函数,而“ console.log('abort')”则放在了处理请求的地方。

  1. 结果显示,在真正的HTTP请求开始传出之前,jsdom“窗口”已关闭,然后此请求的状态设置为“中止”。
bogon:  yarn test:dom
yarn run v1.10.1
$ jest --config jest.config.js
 PASS  animate-image.spec.tsx
  ✓ image (75ms)

  console.log xxxxxxxxx/animate-image.spec.tsx:34
    end ##################################################################

window close
document close
http://XXXXX.cdn.com
abort

request.js中的一些代码可能有助于理解问题:

var defer = typeof setImmediate === 'undefined'
  ? process.nextTick
  : setImmediate
 defer(function () {
    if (self._aborted) {
      return
    }

    var end = function () {
      if (self._form) {
        if (!self._auth.hasAuth) {
          self._form.pipe(self)
        } else if (self._auth.hasAuth && self._auth.sentAuth) {
          self._form.pipe(self)
        }
      }
      if (self._multipart && self._multipart.chunked) {
        self._multipart.body.pipe(self)
      }
      if (self.body) {
        if (isstream(self.body)) {
          self.body.pipe(self)
        } else {
          setContentLength()
          if (Array.isArray(self.body)) {
            self.body.forEach(function (part) {
              self.write(part)
            })
          } else {
            self.write(self.body)
          }
          self.end()
        }
      } else if (self.requestBodyStream) {
        console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
        self.requestBodyStream.pipe(self)
      } else if (!self.src) {
        if (self._auth.hasAuth && !self._auth.sentAuth) {
          self.end()
          return
        }
        if (self.method !== 'GET' && typeof self.method !== 'undefined') {
          self.setHeader('content-length', 0)
        }
        self.end()
      }
    }

    if (self._form && !self.hasHeader('content-length')) {
      // Before ending the request, we had to compute the length of the whole form, asyncly
      self.setHeader(self._form.getHeaders(), true)
      self._form.getLength(function (err, length) {
        if (!err && !isNaN(length)) {
          self.setHeader('content-length', length)
        }
        end()
      })
    } else {
      end()
    }

    self.ntick = true
  })

Request.prototype.start = function () {
  // start() is called once we are ready to send the outgoing HTTP request.
  // this is usually called on the first write(), end() or on nextTick()
  var self = this

  if (self.timing) {
    // All timings will be relative to this request's startTime.  In order to do this,
    // we need to capture the wall-clock start time (via Date), immediately followed
    // by the high-resolution timer (via now()).  While these two won't be set
    // at the _exact_ same time, they should be close enough to be able to calculate
    // high-resolution, monotonically non-decreasing timestamps relative to startTime.
    var startTime = new Date().getTime()
    var startTimeNow = now()
  }

  if (self._aborted) {
    return
  }

  self._started = true
  self.method = self.method || 'GET'
  self.href = self.uri.href

  if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
    self.setHeader('content-length', self.src.stat.size)
  }
  if (self._aws) {
    self.aws(self._aws, true)
  }

  // We have a method named auth, which is completely different from the http.request
  // auth option.  If we don't remove it, we're gonna have a bad time.
  var reqOptions = copy(self)
  delete reqOptions.auth

  debug('make request', self.uri.href)

  // node v6.8.0 now supports a `timeout` value in `http.request()`, but we
  // should delete it for now since we handle timeouts manually for better
  // consistency with node versions before v6.8.0
  delete reqOptions.timeout

  try {
    self.req = self.httpModule.request(reqOptions)
  } catch (err) {
    self.emit('error', err)
    return
  }

  if (self.timing) {
    self.startTime = startTime
    self.startTimeNow = startTimeNow

    // Timing values will all be relative to startTime (by comparing to startTimeNow
    // so we have an accurate clock)
    self.timings = {}
  }

  var timeout
  if (self.timeout && !self.timeoutTimer) {
    if (self.timeout < 0) {
      timeout = 0
    } else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
      timeout = self.timeout
    }
  }

  self.req.on('response', self.onRequestResponse.bind(self))
  self.req.on('error', self.onRequestError.bind(self))
  self.req.on('drain', function () {
    self.emit('drain')
  })

  self.req.on('socket', function (socket) {
    // `._connecting` was the old property which was made public in node v6.1.0
    var isConnecting = socket._connecting || socket.connecting
    if (self.timing) {
      self.timings.socket = now() - self.startTimeNow

      if (isConnecting) {
        var onLookupTiming = function () {
          self.timings.lookup = now() - self.startTimeNow
        }

        var onConnectTiming = function () {
          self.timings.connect = now() - self.startTimeNow
        }

        socket.once('lookup', onLookupTiming)
        socket.once('connect', onConnectTiming)

        // clean up timing event listeners if needed on error
        self.req.once('error', function () {
          socket.removeListener('lookup', onLookupTiming)
          socket.removeListener('connect', onConnectTiming)
        })
      }
    }

    var setReqTimeout = function () {
      // This timeout sets the amount of time to wait *between* bytes sent
      // from the server once connected.
      //
      // In particular, it's useful for erroring if the server fails to send
      // data halfway through streaming a response.
      self.req.setTimeout(timeout, function () {
        if (self.req) {
          self.abort()
          var e = new Error('ESOCKETTIMEDOUT')
          e.code = 'ESOCKETTIMEDOUT'
          e.connect = false
          self.emit('error', e)
        }
      })
    }
    if (timeout !== undefined) {
      // Only start the connection timer if we're actually connecting a new
      // socket, otherwise if we're already connected (because this is a
      // keep-alive connection) do not bother. This is important since we won't
      // get a 'connect' event for an already connected socket.
      if (isConnecting) {
        var onReqSockConnect = function () {
          socket.removeListener('connect', onReqSockConnect)
          clearTimeout(self.timeoutTimer)
          self.timeoutTimer = null
          setReqTimeout()
        }

        socket.on('connect', onReqSockConnect)

        self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
          socket.removeListener('connect', onReqSockConnect)
        })

        // Set a timeout in memory - this block will throw if the server takes more
        // than `timeout` to write the HTTP status and headers (corresponding to
        // the on('response') event on the client). NB: this measures wall-clock
        // time, not the time between bytes sent by the server.
        self.timeoutTimer = setTimeout(function () {
          socket.removeListener('connect', onReqSockConnect)
          self.abort()
          var e = new Error('ETIMEDOUT')
          e.code = 'ETIMEDOUT'
          e.connect = true
          self.emit('error', e)
        }, timeout)
      } else {
        // We're already connected
        setReqTimeout()
      }
    }
    self.emit('socket', socket)
  })

  self.emit('request', self.req)
}

我无法发送HTTP请求来获取图像源。因此,我无法调用img.onload处理程序。

有人可以帮助我解释这个问题吗?

1 个答案:

答案 0 :(得分:0)

最后,我没有找到成功发送图像加载请求的方法。

我的解决方案是:在测试代码中模拟HTMLImageElement的原型:

Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', { get: () => 120 });
Object.defineProperty(HTMLImageElement.prototype, 'complete', { get: () => true });

因此,我不再需要获取真实的图像,同时我可以成功完成测试用例。