Coding Journal

Jordan Vidrine

Daily Coding Journal

An experienced business owner + musician with a demonstrated history of management in the Energy sector and an insatiable appetite for learning new and complex skills. Transitioning from an ownership role & seeking employment in Web Development as a Jr Full Stack Developer.

August 26th

This morning I am going through chapter 18 of Eloquent Javascript. (almost done with this book!) The chapter focuses on HTTP requests, html forms, as well as javascript’s fetch function.

I’ve worked with fetch in a couple of my own projects, to make calls to me own API on routes being served up by Express. Let’s dive in.

Fetch

Calling fetch returns a promise that resolves to a response object containing information about the server’s response.

fetch("example/data.txt").then(response => {
  console.log(response.status);
  // → 200
  console.log(response.headers.get("Content-Type"));
  // → text/plain
});

NOTE: The promise returned by fetch resolves even if the server responded with an error code. It may be rejected if there is a network error, or the address can’t be found.

To get the actual content of a response you can use its text method, or a similar method called json (which is what I’ve always used). The json method though only resolves if it can parse as valid JSON.

fetch("example/data.txt")
  .then(resp => resp.text())
  .then(text => console.log(text));
// → This is the content of data.txt

fetch("example/data.txt")
  .then(resp => resp.json())
  .then(data => console.log(data));
  { // data shown here }

By default, fetch uses the GET method, but you can pass an object with extra options as a second argument to use others.

fetch("example/data.txt", {method: "DELETE"}).then(resp => {
  console.log(resp.status);
  // → 405
});

Other than this, this chapter is pretty straight forward when it comes to html forms. I haven’t seen anything I didnt already know.

Except…

File Fields

If a user selects a file for a file input field, the browser interprets that action to mean that the script may read the file. This will log to the console the name of the file chosen, as well as the file type if one is present.

<input type="file">
<script>
  let input = document.querySelector("input");
  input.addEventListener("change", () => {
    if (input.files.length > 0) {
      let file = input.files[0];
      console.log("You chose", file.name);
      if (file.type) console.log("It has type", file.type);
    }
  });
</script>

The file form field though does not have a property that contains the content of the file. Getting that is a little more involved, and must be asynchronous to avoid the document freezing up.

<input type="file" multiple>
<script>
  let input = document.querySelector("input");
  input.addEventListener("change", () => {
    for (let file of Array.from(input.files)) {
      let reader = new FileReader();
      reader.addEventListener("load", () => {
        console.log("File", file.name, "starts with",
                    reader.result.slice(0, 20));
      });
      reader.readAsText(file);
    }
  });
</script>

Reading a file is done by creating a FileReader object, calling a “load” event handler on it, and then calling its readAsText method. Once finished loading, the reader’s result property contains the file’s content.

File readers also throw an error event when reading the file fails for any reason. The error will end up in the objects error property. Since this interface was designed before promises came into the picture, we can wrap these in a promise ourselves.

function readFileText(file) {
  return new Promise((resolve, reject) => {
    let reader = new FileReader();
    reader.addEventListener(
      "load", () => resolve(reader.result));
    reader.addEventListener(
      "error", () => reject(reader.error));
    reader.readAsText(file);
  });
}

Storing data client-side

In this section the book covers the localStorage object available to us in the browser to store information outside of javascript variables in order to survive page reloads.

localStorage.setItem("username", "marijn");
console.log(localStorage.getItem("username"));
// → marijn
localStorage.removeItem("username");

Values in localStorage stick around until overwritten, removed with removeItem or the user clears their local data.

Another important thing is that sites from different domains get different storage compartments. In principle, info stored to the localStorage for a certain website, can only be read by the scripts on that website.

This chapter was pretty straightforward and was a nice summary of thing’s I’ve learned throughout my dev journey… Now onto the Usemy Node Course.

Mocking NPM Modules during testing

When running in a test environment, we may be running code that doesnt need to run in the background. For instance, if we are sending emails with sendGrid, our tests would continuously be sending out emails.

To start, we will create a mocks folder in our test directory. Jest will look here for any modules and use them as mocks if they are present. If I wanted to mock jsonwebtoken, I would then create jsonwebtokens.js in the mock directory.

We will be mocking the sendgrid mail module. To do this, we create a folder called sendgrid inside of mocks, and a file called mail.js inside that folder.

We then need to provide our own versions of what the module is giving us. Not completely, just enough code to where our tests do not fail because methods don’t exist. The functions we provide literally don’t need to do anything as long as that doesn’t affect the actual thing you are trying to test.

Testing file uploads

We will want to test the file upload for our user avatar. The user should be able to upload an avatar image and we need to validate that with supertest. When a file is uploaded, a buffer should be stored in the user file on the db.

To start, we will create a folder in our testing directory called fixtures. Fixtures are items that our tests can use to validate tests. We will place an image here to use as our test image for the avatar.

Here is how that test looks:

test('Should upload avatar image', async () => {
  await request(app)
      .post('/users/me/avatar')
      .set('Authorization', `Bearer ${userOne.tokens[0].token}`)
      .attach('avatar', 'tests/fixtures/profile-pic.jpg')
      .expect(200)

      let user = await User.findById(userOneId)
      // here we check that the data stored at user.avatar IS indeed buffer data
      // expect.any() takes a type that you want to check
      expect(user.avatar).toEqual(expect.any(Buffer))
})

The following two tests test that the user can 1) Update valid user fields, and 2) NOT update invalid user fields.

test('Should update valid user fields', async () => {
  await request(app)
      .patch('/users/me')
      .set('Authorization', `Bearer ${userOne.tokens[0].token}`)
      .send({
        name: 'Jordan'
      })
      .expect(200)

      let user = await User.findById(userOneId)
      expect(user.name).toBe('Jordan')
})

test('Should not update invalid user fields', async () => {
  await request(app)
      .patch('/users/me')
      .set('Authorization', `Bearer ${userOne.tokens[0].token}`)
      .send({
        location: 'Tennessee'
      })
      .expect(400)
})

To move on to the tasks test section of the course, we will have to do some refactoring of the code that creates our test user + test db.

We create a new file called db.js in our fixtures directory. We will move the code that created our user data into here, as we will need this for other testing suites we set up. We will setup the following code in that db file and export it for other testing suites to use, as well as using the function we create in their beforeEach() calls.

const jwt = require('jsonwebtoken')
const mongoose = require('mongoose')
const User = require('../../src/models/user')

const userOneId = new mongoose.Types.ObjectId()
const userOne = {
  _id: userOneId,
  name: 'User One',
  email: 'userOne@user.com',
  password: '1qaz7ujm',
  tokens: [{
    token: jwt.sign({ _id: userOneId }, process.env.JWT_SECRET)
  }]
}

const setupDatabase = async () => {
  await User.deleteMany()
  await new User(userOne).save()
}

module.exports = {
  userOneId,
  userOne,
  setupDatabase
}

Now this isn’t all we have to do. Since Jest runs all of these test suites at the same time, each suite is trying to create and manipulate the database and this causes issues between the suites. To fix this, we just add a simple --runInBand to the test script in our package.json file. This will cause each test suite to run on its own.

Now we are free to create more tests in each suite without their before or after scripts affecting one another.

Back to blog...