So you have a component in your application that uses a file input to return a FileList and now you want to write some tests for it, there is a problem however, JavaScript doesn’t provide a constructor for FileList’s meaning you can’t do anything along the lines of:

const fileList = new FileList()

Now you can create mock files using the new File() constructor and for simple use cases a FileList looks a lot like an array so you could just do something like this:

function test() {
  // Note that 'File' will not exist in Node.js
  // The below is inteneded to run in an environment that understands frontend browser functions (such as Jest)

  const mockJpg = new File(['1234'], 'test.jpg', { type: 'image/jpeg' })
  const mockPng = new File(['1234'], 'test.png', { type: 'image/png' })

  const mockFileList = [ mockJpg, mockPng ]

  console.log(mockFileList)
}

test()

However despite having a .length property a FileList is not actually an Array, it’s closer to an Object, if you want to prove this to yourself you can run the following up in a browser:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload Example</title>
  </head>
  <body>
    <input type="file" id="files" name="files" multiple>
    <script>
      const filesInput = document.getElementById('files')
      filesInput.addEventListener('change', function(e) {
        const files = e.target.files
        console.log(typeof files)
        // object
        console.log(Array.isArray(files))
        // false
        console.log(files)
        // FileList {0: File, 1: File, 2: File, length: 3}
      })
    </script>
  </body>
</html>

If this difference is important to us for the purposes of our test (or if you just want to mimic a FileList as closely as possible) then we can amend our code as follows:

function test() {
  // Note that 'File' will not exist in Node.js
  // The below is inteneded to run in an environment that understands frontend browser functions (such as Jest)

  const mockJpg = new File(['1234'], 'test.jpg', { type: 'image/jpeg' })
  const mockPng = new File(['1234'], 'test.png', { type: 'image/png' })

  const mockFileList = {
    0: mockJpg,
    1: mockPng,
    length: 2,
  }

  console.log(mockFileList)
}

test()

The problem here however, is that a FileList should be iterable so that you can use it in a for loop:

function test() {
  // Note that 'File' will not exist in Node.js
  // The below is inteneded to run in an environment that understands frontend browser functions (such as Jest)

  const mockJpg = new File(['1234'], 'test.jpg', { type: 'image/jpeg' })
  const mockPng = new File(['1234'], 'test.png', { type: 'image/png' })

  const mockFileList = {
    0: mockJpg,
    1: mockPng,
    length: 2,
  }

  // Uncaught TypeError: mockFileList is not iterable
  for (mockFile of mockFileList) {
    console.log(mockFile)
  }
}

test()

If you need to fix this you can use a Well-known Symbol to make the object iterable. If you’re not familiar with JavaScript Symbols I’ve done a quick primer that you can use to get up to speed.

Here is my implementation, the code could probably be cleaner but hopefully someone finds it helpful regardless of whether you’re trying to simulate a FileList or just figuring out how to make another type of Object iterable:

function iterator() {
  let i = 0
  const keys = Object.keys(this)
  return {
    next: () => {
      // The -1 is to account for our length property
      if (i >= Object.keys(this).length - 1) {
        i = 0
        return {
          value: undefined,
          done: true,
        }
      }
      const val = {
        value: this[keys[i]],
        done: false,
      }
      i += 1
      return val
    },
  }
}

function test() {
  // Note that 'File' will not exist in Node.js
  // The below is inteneded to run in an environment that understands frontend browser functions (such as Jest)

  const mockJpg = new File(['1234'], 'test.jpg', { type: 'image/jpeg' })
  const mockPng = new File(['1234'], 'test.png', { type: 'image/png' })

  const mockFileList = {
    0: mockJpg,
    1: mockPng,
    [Symbol.iterator]: iterator,
    length: 2,
  }

  // File {name: 'test.jpg', lastModified: .., lastModifiedDate: ..., webkitRelativePath: '', size: 4, ...}
  // File {name: 'test.png', lastModified: ..., lastModifiedDate: ..., webkitRelativePath: '', size: 4, ...}
  for (mockFile of mockFileList) {
    console.log(mockFile)
  }
}

test()


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.