import React from 'react';
import { act } from 'react-dom/test-utils';
import Upload from '..';
import { fireEvent, render, sleep, waitFor } from '../../../tests/utils';
import Form from '../../form';
import UploadList from '../UploadList';
import { previewImage } from '../utils';
import { setup, teardown } from './mock';
import { errorRequest, successRequest } from './requests';

const fileList = [
  {
    uid: '-1',
    name: 'xxx.png',
    status: 'done',
    url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
  },
  {
    uid: '-2',
    name: 'yyy.png',
    status: 'done',
    url: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
    thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
  },
];

describe('Upload List', () => {
  // Mock for rc-util raf
  window.requestAnimationFrame = callback => {
    window.setTimeout(callback, 16);
  };
  window.cancelAnimationFrame = id => {
    window.clearTimeout(id);
  };

  // jsdom not support `createObjectURL` yet. Let's handle this.
  const originCreateObjectURL = window.URL.createObjectURL;
  window.URL.createObjectURL = jest.fn(() => '');

  // Mock dom
  let size = { width: 0, height: 0 };
  function setSize(width, height) {
    size = { width, height };
  }
  const mockWidthGet = jest.spyOn(Image.prototype, 'width', 'get');
  const mockHeightGet = jest.spyOn(Image.prototype, 'height', 'get');
  const mockSrcSet = jest.spyOn(Image.prototype, 'src', 'set');

  let drawImageCallback = null;
  function hookDrawImageCall(callback) {
    drawImageCallback = callback;
  }
  const mockGetCanvasContext = jest.spyOn(HTMLCanvasElement.prototype, 'getContext');
  const mockToDataURL = jest.spyOn(HTMLCanvasElement.prototype, 'toDataURL');

  // HTMLCanvasElement.prototype

  beforeEach(() => setup());
  afterEach(() => {
    teardown();
    drawImageCallback = null;
  });

  let open;
  beforeAll(() => {
    open = jest.spyOn(window, 'open').mockImplementation(() => {});
    mockWidthGet.mockImplementation(() => size.width);
    mockHeightGet.mockImplementation(() => size.height);
    mockSrcSet.mockImplementation(function fn() {
      if (this.onload) {
        this.onload();
      }
    });

    mockGetCanvasContext.mockReturnValue({
      drawImage: (...args) => {
        if (drawImageCallback) drawImageCallback(...args);
      },
    });
    mockToDataURL.mockReturnValue('data:image/png;base64,');
  });

  afterAll(() => {
    window.URL.createObjectURL = originCreateObjectURL;
    mockWidthGet.mockRestore();
    mockHeightGet.mockRestore();
    mockSrcSet.mockRestore();
    mockGetCanvasContext.mockRestore();
    mockToDataURL.mockRestore();
    open.mockRestore();
  });

  // https://github.com/ant-design/ant-design/issues/4653
  it('should use file.thumbUrl for <img /> in priority', () => {
    const { container: wrapper, unmount } = render(
      <Upload defaultFileList={fileList} listType="picture">
        <button type="button">upload</button>
      </Upload>,
    );
    fileList.forEach((file, i) => {
      const linkNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail')[i];
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i];
      expect(linkNode.getAttribute('href')).toBe(file.url);
      expect(imgNode.getAttribute('src')).toBe(file.thumbUrl);
    });
    unmount();
  });

  // https://github.com/ant-design/ant-design/issues/7269
  it('should remove correct item when uid is 0', async () => {
    jest.useFakeTimers();

    const list = [
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
      },
      {
        uid: '1',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
      },
    ];
    const { container, unmount } = render(
      <Upload defaultFileList={list}>
        <button type="button">upload</button>
      </Upload>,
    );
    expect(container.querySelectorAll('.ant-upload-list-item').length).toBe(2);
    fireEvent.click(
      container.querySelectorAll('.ant-upload-list-item')[0].querySelector('.anticon-delete'),
    );

    // Upload use Promise to wait remove action. Let's wait this also.
    await act(async () => {
      for (let i = 0; i < 10; i += 1) {
        // eslint-disable-next-line no-await-in-loop
        await Promise.resolve();
      }
    });

    // Progress motion to active
    act(() => {
      jest.runAllTimers();
    });

    // Progress motion to done
    // React 17 will reach deadline, so we need check if already done
    if (container.querySelector('.ant-upload-animate-leave-active')) {
      fireEvent.animationEnd(container.querySelector('.ant-upload-animate-leave-active'));
    }
    act(() => {
      jest.runAllTimers();
    });

    expect(container.querySelectorAll('.ant-upload-list-text-container')).toHaveLength(1);

    unmount();

    jest.useRealTimers();
  });

  it('should be uploading when upload a file', async () => {
    jest.useFakeTimers();
    const done = jest.fn();
    let wrapper;
    let latestFileList = null;
    const onChange = async ({ file, fileList: eventFileList }) => {
      expect(eventFileList === latestFileList).toBeFalsy();
      if (file.status === 'uploading') {
        await Promise.resolve();
        expect(wrapper.container.firstChild).toMatchSnapshot();
      }
      if (file.status === 'done') {
        done();
      }

      latestFileList = eventFileList;
    };
    wrapper = render(
      <Upload
        action="http://jsonplaceholder.typicode.com/posts/"
        onChange={onChange}
        customRequest={successRequest}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.change(wrapper.container.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });
    await act(async () => {
      await Promise.resolve();
      await Promise.resolve();
    });
    act(() => {
      jest.runAllTimers();
    });

    expect(done).toHaveBeenCalled();

    wrapper.unmount();

    jest.useRealTimers();
  });

  it('handle error', async () => {
    jest.useFakeTimers();
    const onChange = jest.fn();

    const {
      container: wrapper,
      unmount,
      baseElement,
    } = render(
      <Upload
        action="http://jsonplaceholder.typicode.com/posts/"
        onChange={onChange}
        customRequest={errorRequest}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.change(wrapper.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });

    await act(async () => {
      await Promise.resolve();
    });

    // Wait twice since `errorRequest` also use timeout for mock
    act(() => {
      jest.runAllTimers();
    });

    expect(onChange).toHaveBeenLastCalledWith(
      expect.objectContaining({
        file: expect.objectContaining({
          status: 'error',
        }),
      }),
    );
    if (wrapper.querySelector('.ant-upload-animate-appear-active')) {
      fireEvent.animationEnd(wrapper.querySelector('.ant-upload-animate-appear-active'));
    }

    act(() => {
      jest.runAllTimers();
    });
    expect(wrapper.firstChild).toMatchSnapshot();

    // Error message
    fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item'));

    await act(() => {
      jest.runAllTimers();
    });

    expect(baseElement.querySelector('.ant-tooltip')).not.toHaveClass('.ant-tooltip-hidden');

    jest.useRealTimers();
    unmount();
  });

  it('does concat fileList when beforeUpload returns false', async () => {
    const handleChange = jest.fn();
    const ref = React.createRef();
    const { container: wrapper, unmount } = render(
      <Upload
        ref={ref}
        listType="picture"
        defaultFileList={fileList}
        onChange={handleChange}
        beforeUpload={() => false}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.change(wrapper.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });

    await sleep();

    expect(ref.current.fileList.length).toBe(fileList.length + 1);
    expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);

    unmount();
  });

  it('In the case of listType=picture, the error status does not show the download.', () => {
    global.testName =
      'In the case of listType=picture, the error status does not show the download.';
    const file = { status: 'error', uid: 'file' };
    const { container: wrapper, unmount } = render(
      <Upload listType="picture" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
        <button type="button">upload</button>
      </Upload>,
    );

    // Has error item className
    fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item-error'));

    expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0);

    unmount();
  });

  it('In the case of listType=picture-card, the error status does not show the download.', () => {
    global.testName =
      'In the case of listType=picture-card, the error status does not show the download.';
    const file = { status: 'error', uid: 'file' };
    const { container: wrapper, unmount } = render(
      <Upload listType="picture-card" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0);

    unmount();
  });

  it('In the case of listType=text, the error status does not show the download.', () => {
    const file = { status: 'error', uid: 'file' };
    const { container: wrapper, unmount } = render(
      <Upload listType="text" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper.querySelectorAll('div.ant-upload-list-item i.anticon-download').length).toBe(0);

    unmount();
  });

  it('should support onPreview', () => {
    const handlePreview = jest.fn();
    const { container: wrapper, unmount } = render(
      <Upload listType="picture-card" defaultFileList={fileList} onPreview={handlePreview}>
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.click(wrapper.querySelectorAll('.anticon-eye')[0]);
    expect(handlePreview).toHaveBeenCalledWith(fileList[0]);
    fireEvent.click(wrapper.querySelectorAll('.anticon-eye')[1]);
    expect(handlePreview).toHaveBeenCalledWith(fileList[1]);

    unmount();
  });

  it('should support onRemove', async () => {
    const handleRemove = jest.fn();
    const handleChange = jest.fn();
    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture-card"
        defaultFileList={fileList}
        onRemove={handleRemove}
        onChange={handleChange}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.click(wrapper.querySelectorAll('.anticon-delete')[0]);
    expect(handleRemove).toHaveBeenCalledWith(fileList[0]);
    fireEvent.click(wrapper.querySelectorAll('.anticon-delete')[1]);
    expect(handleRemove).toHaveBeenCalledWith(fileList[1]);
    await sleep();
    expect(handleChange.mock.calls.length).toBe(2);

    unmount();
  });

  it('should support onDownload', async () => {
    const handleDownload = jest.fn();
    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture-card"
        defaultFileList={[
          {
            uid: '0',
            name: 'xxx.png',
            status: 'done',
            url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
          },
        ]}
        onDownload={handleDownload}
        showUploadList={{
          showDownloadIcon: true,
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.click(wrapper.querySelectorAll('.anticon-download')[0]);

    unmount();
  });

  it('should support no onDownload', async () => {
    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture-card"
        defaultFileList={[
          {
            uid: '0',
            name: 'xxx.png',
            status: 'done',
            url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
          },
        ]}
        showUploadList={{
          showDownloadIcon: true,
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.click(wrapper.querySelectorAll('.anticon-download')[0]);

    unmount();
  });

  describe('should generate thumbUrl from file', () => {
    [
      { width: 100, height: 200, name: 'height large than width' },
      { width: 200, height: 100, name: 'width large than height' },
    ].forEach(({ width, height, name }) => {
      it(name, async () => {
        setSize(width, height);
        const onDrawImage = jest.fn();
        hookDrawImageCall(onDrawImage);

        const handlePreview = jest.fn();
        const newFileList = [...fileList];
        const newFile = {
          ...fileList[0],
          uid: '-3',
          originFileObj: new File([], 'xxx.png', { type: 'image/png' }),
        };
        delete newFile.thumbUrl;
        newFileList.push(newFile);
        const ref = React.createRef();
        const { unmount } = render(
          <Upload
            ref={ref}
            listType="picture-card"
            defaultFileList={newFileList}
            onPreview={handlePreview}
          >
            <button type="button">upload</button>
          </Upload>,
        );
        await sleep();

        expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined);
        expect(onDrawImage).toHaveBeenCalled();

        // Offset check
        const [, offsetX, offsetY] = onDrawImage.mock.calls[0];
        if (width > height) {
          expect(offsetX === 0).toBeTruthy();
        } else {
          expect(offsetY === 0).toBeTruthy();
        }

        unmount();
      });
    });
  });

  it('should non-image format file preview', () => {
    const list = [
      {
        name: 'not-image',
        status: 'done',
        uid: '-3',
        url: 'https://cdn.xxx.com/aaa.zip',
        thumbUrl: 'data:application/zip;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk',
        originFileObj: new File([], 'aaa.zip'),
      },
      {
        name: 'image',
        status: 'done',
        uid: '-4',
        url: 'https://cdn.xxx.com/aaa',
      },
      {
        name: 'not-image',
        status: 'done',
        uid: '-5',
        url: 'https://cdn.xxx.com/aaa.xx',
      },
      {
        name: 'not-image',
        status: 'done',
        uid: '-6',
        url: 'https://cdn.xxx.com/aaa.png/xx.xx',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-7',
        url: 'https://cdn.xxx.com/xx.xx/aaa.png',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-8',
        url: 'https://cdn.xxx.com/xx.xx/aaa.png',
        thumbUrl: 'data:image/png;base64,UEsDBAoAAAAAADYZYkwAAAAAAAAAAAAAAAAdAAk',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-9',
        url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=123',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-10',
        url: 'https://cdn.xxx.com/xx.xx/aaa.png#anchor',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-11',
        url: 'https://cdn.xxx.com/xx.xx/aaa.png?query=some.query.with.dot',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-12',
        url: 'https://publish-pic-cpu.baidu.com/1296beb3-50d9-4276-885f-52645cbb378e.jpeg@w_228%2ch_152',
        type: 'image/png',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload listType="picture" defaultFileList={list}>
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper.firstChild).toMatchSnapshot();

    unmount();
  });

  it('not crash when uploading not provides percent', async () => {
    jest.useFakeTimers();

    const { unmount } = render(
      <Upload
        listType="picture"
        defaultFileList={[
          {
            name: 'bamboo.png',
            status: 'uploading',
          },
        ]}
      />,
    );

    jest.runAllTimers();

    unmount();

    jest.useRealTimers();
  });

  it('should support showRemoveIcon and showPreviewIcon', () => {
    const list = [
      {
        name: 'image',
        status: 'uploading',
        uid: '-4',
        url: 'https://cdn.xxx.com/aaa',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-5',
        url: 'https://cdn.xxx.com/aaa',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture"
        defaultFileList={list}
        showUploadList={{
          showRemoveIcon: false,
          showPreviewIcon: false,
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper.firstChild).toMatchSnapshot();

    unmount();
  });

  it('should support custom onClick in custom icon', async () => {
    const handleRemove = jest.fn();
    const handleChange = jest.fn();
    const myClick = jest.fn();
    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture-card"
        defaultFileList={fileList}
        onRemove={handleRemove}
        onChange={handleChange}
        showUploadList={{
          showRemoveIcon: true,
          removeIcon: (
            <i className="custom-delete" onClick={myClick}>
              RM
            </i>
          ),
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.click(wrapper.querySelectorAll('.custom-delete')[0]);
    expect(handleRemove).toHaveBeenCalledWith(fileList[0]);
    expect(myClick).toHaveBeenCalled();
    fireEvent.click(wrapper.querySelectorAll('.custom-delete')[1]);
    expect(handleRemove).toHaveBeenCalledWith(fileList[1]);
    expect(myClick).toHaveBeenCalled();
    await sleep();
    expect(handleChange.mock.calls.length).toBe(2);

    unmount();
  });

  it('should support removeIcon and downloadIcon', () => {
    const list = [
      {
        name: 'image',
        status: 'uploading',
        uid: '-4',
        url: 'https://cdn.xxx.com/aaa',
      },
      {
        name: 'image',
        status: 'done',
        uid: '-5',
        url: 'https://cdn.xxx.com/aaa',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload
        listType="picture"
        defaultFileList={list}
        showUploadList={{
          showRemoveIcon: true,
          showDownloadIcon: true,
          showPreviewIcon: true,
          removeIcon: <i>RM</i>,
          downloadIcon: <i>DL</i>,
          previewIcon: <i>PV</i>,
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper.firstChild).toMatchSnapshot();
    unmount();

    const { container: wrapper2, unmount: unmount2 } = render(
      <Upload
        listType="picture"
        defaultFileList={list}
        showUploadList={{
          showRemoveIcon: true,
          showDownloadIcon: true,
          showPreviewIcon: true,
          removeIcon: () => <i>RM</i>,
          downloadIcon: () => <i>DL</i>,
          previewIcon: () => <i>PV</i>,
        }}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    expect(wrapper2.firstChild).toMatchSnapshot();
    unmount2();
  });

  // https://github.com/ant-design/ant-design/issues/7762
  it('work with form validation', async () => {
    let formRef;

    const TestForm = () => {
      const [form] = Form.useForm();
      formRef = form;

      return (
        <Form form={form}>
          <Form.Item
            name="file"
            valuePropName="fileList"
            getValueFromEvent={e => e.fileList}
            rules={[
              {
                required: true,
                validator: (rule, value, callback) => {
                  if (!value || value.length === 0) {
                    callback('file required');
                  } else {
                    callback();
                  }
                },
              },
            ]}
          >
            <Upload beforeUpload={() => false}>
              <button type="button">upload</button>
            </Upload>
          </Form.Item>
        </Form>
      );
    };

    const { container, unmount } = render(<TestForm />);

    fireEvent.submit(container.querySelector('form'));
    await sleep();
    expect(formRef.getFieldError(['file'])).toEqual(['file required']);

    fireEvent.change(container.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });

    fireEvent.submit(container.querySelector('form'));
    await sleep();
    expect(formRef.getFieldError(['file'])).toEqual([]);

    unmount();
  });

  it('return when prop onPreview not exists', () => {
    const ref = React.createRef();
    const { unmount } = render(<UploadList ref={ref} />);
    expect(ref.current.handlePreview()).toBe(undefined);
    unmount();
  });

  it('return when prop onDownload not exists', () => {
    const file = new File([''], 'test.txt', { type: 'text/plain' });
    const items = [{ uid: 'upload-list-item', url: '' }];
    const ref = React.createRef();
    const { unmount } = render(
      <UploadList
        ref={ref}
        items={items}
        locale={{ downloadFile: '' }}
        showUploadList={{ showDownloadIcon: true }}
      />,
    );
    expect(ref.current.handleDownload(file)).toBe(undefined);
    unmount();
  });

  it('previewFile should work correctly', async () => {
    const items = [{ uid: 'upload-list-item', url: '' }];
    const previewFunc = jest.fn(previewImage);
    const { container: wrapper, unmount } = render(
      <Upload
        fileList={items}
        previewFile={previewFunc}
        locale={{ previewFile: '' }}
        listType="picture-card"
      />,
    );

    fireEvent.change(wrapper.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });

    await sleep();
    expect(wrapper.querySelector('.ant-upload-list-item-thumbnail').getAttribute('href')).toBe(
      null,
    );

    unmount();
  });

  it('downloadFile should work correctly', async () => {
    const downloadFunc = jest.fn();
    const items = [{ uid: 'upload-list-item', name: 'test', url: '', status: 'done' }];
    const { container: wrapper, unmount } = render(
      <UploadList
        showDownloadIcon
        listType="picture-card"
        items={items}
        onDownload={downloadFunc}
        locale={{ downloadFile: 'Download file' }}
      />,
    );

    // Not throw
    const btn = wrapper.querySelector('.ant-btn');
    expect(btn.getAttribute('title')).toBe('Download file');
    fireEvent.click(btn);
    expect(downloadFunc).toBeCalled();

    unmount();
  });

  it('extname should work correctly when url not exists', () => {
    const items = [{ uid: 'upload-list-item', url: '' }];
    const { container: wrapper, unmount } = render(
      <UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
    );
    expect(wrapper.querySelectorAll('.ant-upload-list-item-thumbnail').length).toBe(1);
    unmount();
  });

  it('extname should work correctly when url exists', done => {
    const items = [{ status: 'done', uid: 'upload-list-item', url: '/example' }];
    const { container: wrapper, unmount } = render(
      <UploadList
        listType="picture"
        onDownload={file => {
          expect(file.url).toBe('/example');
          unmount();
          done();
        }}
        items={items}
        locale={{ downloadFile: '' }}
        showDownloadIcon
      />,
    );
    fireEvent.click(wrapper.querySelector('div.ant-upload-list-item .anticon-download'));
  });

  it('when picture-card is loading, icon should render correctly', () => {
    const items = [{ status: 'uploading', uid: 'upload-list-item' }];
    const { container: wrapper, unmount } = render(
      <UploadList listType="picture-card" items={items} locale={{ uploading: 'uploading' }} />,
    );
    expect(wrapper.querySelectorAll('.ant-upload-list-item-thumbnail').length).toBe(1);
    expect(wrapper.querySelector('.ant-upload-list-item-thumbnail').textContent).toBe('uploading');

    unmount();
  });

  it('onPreview should be called, when url exists', () => {
    const onPreview = jest.fn();
    const items = [{ thumbUrl: 'thumbUrl', url: 'url', uid: 'upload-list-item' }];
    const {
      container: wrapper,
      rerender,
      unmount,
    } = render(
      <UploadList
        listType="picture-card"
        items={items}
        locale={{ uploading: 'uploading' }}
        onPreview={onPreview}
      />,
    );
    fireEvent.click(wrapper.querySelector('.ant-upload-list-item-thumbnail'));
    expect(onPreview).toHaveBeenCalled();
    fireEvent.click(wrapper.querySelector('.ant-upload-list-item-name'));
    expect(onPreview).toHaveBeenCalled();
    rerender(
      <UploadList
        listType="picture-card"
        items={[{ thumbUrl: 'thumbUrl', uid: 'upload-list-item' }]}
        locale={{ uploading: 'uploading' }}
        onPreview={onPreview}
      />,
    );
    fireEvent.click(wrapper.querySelector('.ant-upload-list-item-name'));
    expect(onPreview).toHaveBeenCalled();

    unmount();
  });

  it('upload image file should be converted to the base64', async () => {
    const mockFile = new File([''], 'foo.png', {
      type: 'image/png',
    });

    const previewFunc = jest.fn(previewImage);

    const { unmount } = render(
      <Upload
        fileList={[
          {
            originFileObj: mockFile,
          },
        ]}
        previewFile={previewFunc}
        locale={{ uploading: 'uploading' }}
        listType="picture-card"
      />,
    );

    await waitFor(() => {
      expect(previewFunc).toHaveBeenCalled();
    });
    await previewFunc(mockFile).then(dataUrl => {
      expect(dataUrl).toEqual('data:image/png;base64,');
    });
    unmount();
  });

  it("upload non image file shouldn't be converted to the base64", async () => {
    const mockFile = new File([''], 'foo.7z', {
      type: 'application/x-7z-compressed',
    });
    const previewFunc = jest.fn(previewImage);

    const { unmount } = render(
      <Upload
        fileList={[
          {
            originFileObj: mockFile,
          },
        ]}
        previewFile={previewFunc}
        locale={{ uploading: 'uploading' }}
        listType="picture-card"
      />,
    );

    await waitFor(() => {
      expect(previewFunc).toHaveBeenCalled();
    });
    await previewFunc(mockFile).then(dataUrl => {
      expect(dataUrl).toBe('');
    });

    unmount();
  });

  describe('customize previewFile support', () => {
    function test(name, renderInstance) {
      it(name, async () => {
        const mockThumbnail = 'mock-image';
        const previewFile = jest.fn(() => Promise.resolve(mockThumbnail));
        const file = {
          ...fileList[0],
          originFileObj: renderInstance(),
        };
        delete file.thumbUrl;
        const ref = React.createRef();
        const { container: wrapper, unmount } = render(
          <Upload ref={ref} listType="picture" defaultFileList={[file]} previewFile={previewFile}>
            <button type="button">button</button>
          </Upload>,
        );
        expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
        await sleep(100);

        expect(
          wrapper.querySelector('.ant-upload-list-item-thumbnail img').getAttribute('src'),
        ).toBe(mockThumbnail);

        unmount();
      });
    }
    test('File', () => new File([], 'xxx.png'));
    test('Blob', () => new Blob());
  });

  // https://github.com/ant-design/ant-design/issues/22958
  describe('customize isImageUrl support', () => {
    const list = [
      ...fileList,
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl:
          'http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg@!panda_style?spm=a2c4g.11186623.2.17.4dc56b29BHokyg&file=example.jpg@!panda_style',
      },
    ];
    it('should not render <img /> when file.thumbUrl use "!" as separator', () => {
      const { container: wrapper, unmount } = render(
        <Upload listType="picture-card" fileList={list}>
          <button type="button">button</button>
        </Upload>,
      );
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img');
      expect(imgNode.length).toBe(2);

      unmount();
    });
    it('should render <img /> when custom imageUrl return true', () => {
      const isImageUrl = jest.fn(() => true);
      const { container: wrapper, unmount } = render(
        <Upload listType="picture-card" fileList={list} isImageUrl={isImageUrl}>
          <button type="button">button</button>
        </Upload>,
      );
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img');
      expect(isImageUrl).toHaveBeenCalled();
      expect(imgNode.length).toBe(3);

      unmount();
    });
    it('should not render <img /> when custom imageUrl return false', () => {
      const isImageUrl = jest.fn(() => false);
      const { container: wrapper, unmount } = render(
        <Upload listType="picture-card" fileList={list} isImageUrl={isImageUrl}>
          <button type="button">button</button>
        </Upload>,
      );
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img');
      expect(isImageUrl).toHaveBeenCalled();
      expect(imgNode.length).toBe(0);

      unmount();
    });
  });

  describe('thumbUrl support for non-image', () => {
    beforeEach(() => {
      jest.useFakeTimers();
    });

    afterEach(() => {
      jest.useRealTimers();
    });

    const nonImageFile = new File([''], 'foo.7z', { type: 'application/x-7z-compressed' });

    /** Wait for a long promise since `rc-util` internal has at least 3 promise wait */
    async function waitPromise() {
      /* eslint-disable no-await-in-loop */
      for (let i = 0; i < 10; i += 1) {
        await Promise.resolve();
      }
      /* eslint-enable */
    }

    it('should render <img /> when upload non-image file and configure thumbUrl in onChange', async () => {
      let wrapper;
      const onChange = jest.fn(({ fileList: files }) => {
        const newFileList = files.map(item => ({
          ...item,
          thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        }));

        wrapper.rerender(
          <Upload
            action="http://jsonplaceholder.typicode.com/posts/"
            listType="picture-card"
            fileList={newFileList}
            onChange={onChange}
            customRequest={successRequest}
          >
            <button type="button">upload</button>
          </Upload>,
        );
      });

      wrapper = render(
        <Upload
          action="http://jsonplaceholder.typicode.com/posts/"
          listType="picture-card"
          fileList={[]}
          onChange={onChange}
          customRequest={successRequest}
        >
          <button type="button">upload</button>
        </Upload>,
      );
      const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img');
      expect(imgNode.length).toBe(0);

      // Simulate change is a timeout change
      fireEvent.change(wrapper.container.querySelector('input'), {
        target: { files: [nonImageFile] },
      });

      // Wait for `rc-upload` process file
      await waitPromise();

      // Wait for mock request finish request
      jest.runAllTimers();

      // Basic called times
      expect(onChange).toHaveBeenCalled();

      // Check for images
      await act(() => {
        jest.runAllTimers();
      });
      const afterImgNode = wrapper.container.querySelectorAll(
        '.ant-upload-list-item-thumbnail img',
      );
      expect(afterImgNode.length).toBeTruthy();

      wrapper.unmount();
    });

    it('should not render <img /> when upload non-image file without thumbUrl in onChange', done => {
      global.testName =
        'should not render <img /> when upload non-image file without thumbUrl in onChange';
      let wrapper;
      const onChange = async ({ fileList: files }) => {
        wrapper.rerender(
          <Upload
            action="http://jsonplaceholder.typicode.com/posts/"
            listType="picture-card"
            fileList={files}
            onChange={onChange}
            customRequest={successRequest}
          >
            <button type="button">upload</button>
          </Upload>,
        );

        await sleep();
        const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img');
        expect(imgNode.length).toBe(0);

        done();
      };
      wrapper = render(
        <Upload
          action="http://jsonplaceholder.typicode.com/posts/"
          listType="picture-card"
          fileList={[]}
          onChange={onChange}
          customRequest={successRequest}
        >
          <button type="button">upload</button>
        </Upload>,
      );
      const imgNode = wrapper.container.querySelectorAll('.ant-upload-list-item-thumbnail img');
      expect(imgNode.length).toBe(0);
      fireEvent.change(wrapper.container.querySelector('input'), {
        target: { files: [nonImageFile] },
      });
    });
  });

  it('[deprecated] should support transformFile', done => {
    let wrapper;
    let lastFile;

    const handleTransformFile = jest.fn();
    const onChange = ({ file }) => {
      if (file.status === 'done') {
        expect(file).not.toBe(lastFile);
        expect(handleTransformFile).toHaveBeenCalled();
        wrapper.unmount();
        done();
      }

      lastFile = file;
    };
    wrapper = render(
      <Upload
        action="http://jsonplaceholder.typicode.com/posts/"
        transformFile={handleTransformFile}
        onChange={onChange}
        customRequest={successRequest}
      >
        <button type="button">upload</button>
      </Upload>,
    );
    fireEvent.change(wrapper.container.querySelector('input'), {
      target: {
        files: [{ name: 'foo.png' }],
      },
    });
  });

  it('should render button inside UploadList when listStyle is picture-card', () => {
    const {
      container: wrapper,
      rerender,
      unmount,
    } = render(
      <Upload
        action="http://jsonplaceholder.typicode.com/posts/"
        listType="picture-card"
        fileList={[
          {
            uid: '0',
            name: 'xxx.png',
          },
        ]}
        showUploadList
      >
        <button className="trigger" type="button">
          upload
        </button>
      </Upload>,
    );
    expect(wrapper.querySelectorAll('.ant-upload-list button.trigger').length).toBeGreaterThan(0);
    rerender(
      <Upload
        action="http://jsonplaceholder.typicode.com/posts/"
        listType="picture-card"
        fileList={[
          {
            uid: '0',
            name: 'xxx.png',
          },
        ]}
        showUploadList={false}
      >
        <button className="trigger" type="button">
          upload
        </button>
      </Upload>,
    );
    expect(wrapper.querySelectorAll('.ant-upload-list button.trigger').length).toBe(0);

    unmount();
  });

  // https://github.com/ant-design/ant-design/issues/26536
  it('multiple file upload should keep the internal fileList async', async () => {
    jest.useFakeTimers();

    const uploadRef = React.createRef();

    const MyUpload = () => {
      const [testFileList, setTestFileList] = React.useState([]);

      return (
        <Upload
          ref={uploadRef}
          fileList={testFileList}
          action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
          multiple
          onChange={info => {
            setTestFileList([...info.fileList]);
          }}
        >
          <button type="button">Upload</button>
        </Upload>
      );
    };

    const { unmount } = render(<MyUpload />);

    // Mock async update in a frame
    const fileNames = ['light', 'bamboo', 'little'];

    await act(() => {
      uploadRef.current.onBatchStart(
        fileNames.map(fileName => {
          const file = new File([], fileName);
          file.uid = fileName;

          return {
            file,
            parsedFile: file,
          };
        }),
      );
    });

    expect(uploadRef.current.fileList).toHaveLength(fileNames.length);

    await act(() => {
      jest.runAllTimers();
    });
    expect(uploadRef.current.fileList).toHaveLength(fileNames.length);

    unmount();

    jest.useRealTimers();
  });

  it('itemRender', () => {
    const onDownload = jest.fn();
    const onRemove = jest.fn();
    const onPreview = jest.fn();
    const itemRender = (originNode, file, currFileList, actions) => {
      const { name, status, uid, url } = file;
      const index = currFileList.indexOf(file);
      return (
        <div className="custom-item-render">
          <span>
            {`uid:${uid} name: ${name} status: ${status} url: ${url}  ${index + 1}/${
              currFileList.length
            }`}
          </span>
          <span onClick={actions.remove} className="custom-item-render-action-remove">
            remove
          </span>
          <span onClick={actions.download} className="custom-item-render-action-download">
            download
          </span>
          <span onClick={actions.preview} className="custom-item-render-action-preview">
            preview
          </span>
        </div>
      );
    };
    const { container: wrapper, unmount } = render(
      <UploadList
        onDownload={onDownload}
        onPreview={onPreview}
        onRemove={onRemove}
        locale={{}}
        items={fileList}
        itemRender={itemRender}
      />,
    );
    expect(wrapper.firstChild).toMatchSnapshot();

    fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-remove')[0]);
    expect(onRemove.mock.calls[0][0]).toEqual(fileList[0]);

    fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-download')[0]);
    expect(onDownload.mock.calls[0][0]).toEqual(fileList[0]);

    fireEvent.click(wrapper.querySelectorAll('.custom-item-render-action-preview')[0]);
    expect(onPreview.mock.calls[0][0]).toEqual(fileList[0]);

    unmount();
  });

  it('LIST_IGNORE should not add in list', async () => {
    const beforeUpload = jest.fn(() => Upload.LIST_IGNORE);
    const { container: wrapper, unmount } = render(<Upload beforeUpload={beforeUpload} />);

    await act(() => {
      fireEvent.change(wrapper.querySelector('input'), {
        target: {
          files: [{ file: 'foo.png' }],
        },
      });
    });

    await sleep();

    expect(beforeUpload).toHaveBeenCalled();
    expect(wrapper.querySelectorAll('.ant-upload-list-text-container')).toHaveLength(0);

    unmount();
  });

  it('Not crash when fileList is null', () => {
    const defaultWrapper = render(<Upload defaultFileList={null} />);
    defaultWrapper.unmount();

    const wrapper = render(<Upload fileList={null} />);
    wrapper.unmount();
  });

  it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture"', () => {
    const list = [
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload fileList={list} listType="picture">
        <button type="button">upload</button>
      </Upload>,
    );
    list.forEach((file, i) => {
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i];
      expect(imgNode.getAttribute('crossOrigin')).toBe(null);
    });
    unmount();
  });

  it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture"', () => {
    const list = [
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: '',
      },
      {
        uid: '1',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: 'anonymous',
      },
      {
        uid: '2',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: 'use-credentials',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload fileList={list} listType="picture">
        <button type="button">upload</button>
      </Upload>,
    );
    list.forEach((file, i) => {
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i];
      expect(imgNode.getAttribute('crossOrigin')).not.toBe(undefined);
      expect(imgNode.getAttribute('crossOrigin')).toBe(file.crossOrigin);
    });
    unmount();
  });

  it('should not exist crossorigin attribute when does not set file.crossorigin in case of listType="picture-card"', () => {
    const list = [
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload fileList={list} listType="picture">
        <button type="button">upload</button>
      </Upload>,
    );
    list.forEach((file, i) => {
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i];
      expect(imgNode.getAttribute('crossOrigin')).toBe(null);
    });
    unmount();
  });

  it('should exist crossorigin attribute when set file.crossorigin in case of listType="picture-card"', () => {
    const list = [
      {
        uid: '0',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: '',
      },
      {
        uid: '1',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: 'anonymous',
      },
      {
        uid: '2',
        name: 'xxx.png',
        status: 'done',
        url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
        thumbUrl: 'https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png',
        crossOrigin: 'use-credentials',
      },
    ];

    const { container: wrapper, unmount } = render(
      <Upload fileList={list} listType="picture">
        <button type="button">upload</button>
      </Upload>,
    );
    list.forEach((file, i) => {
      const imgNode = wrapper.querySelectorAll('.ant-upload-list-item-thumbnail img')[i];
      expect(imgNode.getAttribute('crossOrigin')).not.toBe(undefined);
      expect(imgNode.getAttribute('crossOrigin')).toBe(file.crossOrigin);
    });
    unmount();
  });
});
