Browse Source

add test && fix ajax response in callback (#767)

baiyaaaaa 8 years ago
parent
commit
23d4dbbce4

+ 5 - 0
CHANGELOG.md

@@ -11,6 +11,11 @@
 - 更新 TimePicker 滚动条在 IE10+ 下隐藏
 - 新增 Dropdown 的 command api #432
 - 修复 Slider 在 Form 中的显示问题
+- 修复 Upload 在 onSuccess、onError 钩子无法拿到服务端返回信息的问题
+
+#### 非兼容性更新
+
+- Upload on-error 钩子函数参数变更为 function(err, response, file), on-success 钩子函数参数变更为 function(response, file, fileList)
 
 ### 1.0.0-rc.8
 

+ 3 - 3
examples/docs/zh-cn/upload.md

@@ -145,9 +145,9 @@
 | show-upload-list | 是否显示已上传文件列表 | boolean | — | true |
 | type | 上传控件类型 | string | select,drag | select |
 | accept | 可选参数, 接受上传的[文件类型](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept)(thumbnail-mode 模式下此参数无效)| string | — | — |
-| on-preview | 可选参数, 点击已上传的文件链接时的钩子 | function(file) | — | — |
+| on-preview | 可选参数, 点击已上传的文件链接时的钩子, 可以通过 file.response 拿到服务端返回数据 | function(file) | — | — |
 | on-remove | 可选参数, 文件列表移除文件时的钩子 | function(file, fileList) | — | — |
-| on-success | 可选参数, 文件上传成功时的钩子 | function(file, fileList) | — | — |
-| on-error | 可选参数, 文件上传失败时的钩子 | function(err, file, fileList) | — | — |
+| on-success | 可选参数, 文件上传成功时的钩子 | function(response, file, fileList) | — | — |
+| on-error | 可选参数, 文件上传失败时的钩子 | function(err, response, file) | — | — |
 | before-upload | 可选参数, 上传文件之前的钩子,参数为上传的文件,若返回 false 或者 Promise 则停止上传。 | function(file) | — | — |
 | thumbnail-mode | 是否设置为图片模式,该模式下会显示图片缩略图 | boolean | — | false |

+ 8 - 2
packages/upload/src/ajax.js

@@ -1,5 +1,5 @@
 function getError(action, option, xhr) {
-  const msg = `cannot post ${action} ${xhr.status}'`;
+  const msg = `fail to post ${action} ${xhr.status}'`;
   const err = new Error(msg);
   err.status = xhr.status;
   err.method = 'post';
@@ -20,12 +20,14 @@ function getBody(xhr) {
   }
 }
 
-export default function upload(action, option) {
+export default function upload(option) {
   if (typeof XMLHttpRequest === 'undefined') {
     return;
   }
 
   const xhr = new XMLHttpRequest();
+  const action = option.action;
+
   if (xhr.upload) {
     xhr.upload.onprogress = function progress(e) {
       if (e.total > 0) {
@@ -65,6 +67,10 @@ export default function upload(action, option) {
 
   const headers = option.headers || {};
 
+  if (headers['X-Requested-With'] !== null) {
+    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+  }
+
   for (let item in headers) {
     if (headers.hasOwnProperty(item) && headers[item] !== null) {
       xhr.setRequestHeader(item, headers[item]);

+ 5 - 4
packages/upload/src/index.vue

@@ -111,14 +111,14 @@ export default {
         _file.status = 'finished';
         _file.response = res;
 
-        this.onSuccess(_file, this.fileList);
+        this.onSuccess(res, _file, this.fileList);
 
         setTimeout(() => {
           _file.showProgress = false;
         }, 1000);
       }
     },
-    handleError(err, file) {
+    handleError(err, response, file) {
       var _file = this.getFile(file);
       var fileList = this.fileList;
 
@@ -126,7 +126,7 @@ export default {
 
       fileList.splice(fileList.indexOf(_file), 1);
 
-      this.onError(err, _file, fileList);
+      this.onError(err, response, file);
     },
     handleRemove(file) {
       var fileList = this.fileList;
@@ -182,7 +182,8 @@ export default {
         'on-error': this.handleError,
         'on-preview': this.handlePreview,
         'on-remove': this.handleRemove
-      }
+      },
+      ref: 'upload-inner'
     };
 
     var uploadComponent = typeof FormData !== 'undefined'

+ 4 - 3
packages/upload/src/upload.vue

@@ -130,20 +130,21 @@ export default {
       let formData = new FormData();
       formData.append(this.name, file);
 
-      ajax(this.action, {
+      ajax({
         headers: this.headers,
         withCredentials: this.withCredentials,
         file: file,
         data: this.data,
         filename: this.name,
+        action: this.action,
         onProgress: e => {
           this.onProgress(e, file);
         },
         onSuccess: res => {
           this.onSuccess(res, file);
         },
-        onError: err => {
-          this.onError(err, file);
+        onError: (err, response) => {
+          this.onError(err, response, file);
         }
       });
     },

+ 229 - 0
test/unit/specs/upload.spec.js

@@ -0,0 +1,229 @@
+import { createVue, destroyVM } from '../util.js';
+import ajax from 'packages/upload/src/ajax';
+const noop = () => {
+};
+const option = {
+  onSuccess: noop,
+  data: { a: 'abc', b: 'bcd' },
+  filename: 'file.png',
+  file: 'foo',
+  action: '/upload',
+  headers: { region: 'shanghai' }
+};
+let requests, xhr;
+describe('ajax', () => {
+  beforeEach(() => {
+    xhr = sinon.useFakeXMLHttpRequest();
+    requests = [];
+    xhr.onCreate = req => requests.push(req);
+    option.onError = noop;
+    option.onSuccess = noop;
+  });
+  afterEach(() => {
+    xhr.restore();
+  });
+  it('request success', done => {
+    option.onError = done;
+    option.onSuccess = ret => {
+      expect(ret).to.eql({ success: true });
+      done();
+    };
+    ajax(option);
+    requests[0].respond(200, {}, '{"success": true}');
+  });
+  it('request width header', done => {
+    ajax(option);
+    expect(requests[0].requestHeaders).to.eql({
+      'X-Requested-With': 'XMLHttpRequest',
+      region: 'shanghai'
+    });
+    done();
+  });
+  it('40x code should be error', done => {
+    option.onError = e => {
+      expect(e.toString()).to.contain('404');
+      done();
+    };
+
+    option.onSuccess = () => done('404 should throw error');
+    ajax(option);
+    requests[0].respond(404, {}, 'Not found');
+  });
+  it('2xx code should be success', done => {
+    option.onError = done;
+    option.onSuccess = ret => {
+      expect(ret).to.equal('');
+      done();
+    };
+    ajax(option);
+    requests[0].respond(204, {});
+  });
+});
+describe('Upload', () => {
+  let requests;
+  let xhr;
+
+  beforeEach(() => {
+    xhr = sinon.useFakeXMLHttpRequest();
+    requests = [];
+    xhr.onCreate = req => requests.push(req);
+  });
+
+  afterEach(() => {
+    xhr.restore();
+  });
+
+  describe('ajax upload', () => {
+    if (typeof FormData === 'undefined') {
+      return;
+    }
+
+    let uploader;
+    let handlers = {};
+
+    const props = {
+      props: {
+        action: '/upload',
+        onSuccess(res, file, fileList) {
+          console.log('onSuccess', res);
+          if (handlers.onSuccess) {
+            handlers.onSuccess(res, file, fileList);
+          }
+        },
+        onError(err, response, file) {
+          console.log('onError', err, response);
+          if (handlers.onError) {
+            handlers.onError(err, response, file);
+          }
+        },
+        onPreview(file) {
+          console.log('onPreview', file);
+          if (handlers.onPreview) {
+            handlers.onPreview(file);
+          }
+        }
+      }
+    };
+
+    beforeEach(() => {
+      uploader = createVue({
+        render(h) {
+          return (
+            <el-upload {...props} ref="upload">
+              <el-button size="small" type="primary">点击上传</el-button>
+            </el-upload>
+          );
+        }
+      }, true).$refs.upload;
+    });
+
+    afterEach(() => {
+      destroyVM(uploader);
+      handlers = {};
+    });
+
+    it('upload success', done => {
+      const files = [{
+        name: 'success.png',
+        type: 'xml'
+      }];
+
+      handlers.onSuccess = (res, file, fileList) => {
+        expect(file.name).to.equal('success.png');
+        expect(fileList.length).to.equal(1);
+        expect(res).to.equal('success.png');
+        done();
+      };
+
+      uploader.$refs['upload-inner'].handleChange({ target: { files }});
+
+      setTimeout(() => {
+        requests[0].respond(200, {}, `${files[0].name}`);
+      }, 100);
+    });
+
+    it('upload fail', done => {
+      const files = [{
+        name: 'fail.png',
+        type: 'xml'
+      }];
+
+      handlers.onError = (err, response, file) => {
+        expect(err instanceof Error).to.equal(true);
+        expect(response).to.equal('error 400');
+        done();
+      };
+
+      uploader.$refs['upload-inner'].handleChange({ target: { files }});
+
+      setTimeout(() => {
+        requests[0].respond(400, {}, 'error 400');
+      }, 100);
+    });
+    it('preview file', done => {
+      const files = [{
+        name: 'success.png',
+        type: 'xml'
+      }];
+
+      handlers.onPreview = (file) => {
+        expect(file.response).to.equal('success.png');
+        done();
+      };
+
+      handlers.onSuccess = (res, file, fileList) => {
+        uploader.$nextTick(_ => {
+          uploader.$el.querySelector('.el-upload__files .is-finished a').click();
+        });
+      };
+
+      uploader.$refs['upload-inner'].handleChange({ target: { files }});
+
+      setTimeout(() => {
+        requests[0].respond(200, {}, `${files[0].name}`);
+      }, 100);
+    });
+    it('file remove', done => {
+      const files = [{
+        name: 'success.png',
+        type: 'xml'
+      }];
+
+      handlers.onSuccess = (res, file, fileList) => {
+        uploader.$el.querySelector('.el-upload__files .el-upload__btn-delete').click();
+        uploader.$nextTick(_ => {
+          expect(uploader.fileList.length).to.equal(0);
+          expect(uploader.$el.querySelector('.el-upload__files')).to.not.exist;
+          done();
+        });
+      };
+
+      uploader.$refs['upload-inner'].handleChange({ target: { files }});
+
+      setTimeout(() => {
+        requests[0].respond(200, {}, `${files[0].name}`);
+      }, 100);
+    });
+    it('clear files', done => {
+      const files = [{
+        name: 'success.png',
+        type: 'xml'
+      }];
+
+      handlers.onSuccess = (res, file, fileList) => {
+        uploader.clearFiles();
+        uploader.$nextTick(_ => {
+          expect(uploader.fileList.length).to.equal(0);
+          expect(uploader.$el.querySelector('.el-upload__files')).to.not.exist;
+          done();
+        });
+      };
+
+      uploader.$refs['upload-inner'].handleChange({ target: { files }});
+
+      setTimeout(() => {
+        requests[0].respond(200, {}, `${files[0].name}`);
+      }, 100);
+    });
+  });
+});