Managing Projects with Gantt Charts

Gantt charts have been around for more than 100 years, but these visual project management tools are still among the best ways to manage deadlines, keeping them on time and on budget.

Project managers have options. There are plenty of tools and project management philosophies to choose from.

Gantt charts are among the best for timebound projects. The development team at an ecommerce company might use a Gantt chart in conjunction with other tools to manage a platform migration. Or the operations team at an omnichannel retail enterprise might use a Gantt chart to open a new warehouse or update an existing one.

Gantt charts tend to work best when a project has a clear end, rather than ongoing.

Critical Path

Gantt charts prioritize what tasks must be completed when. The charts show when tasks must be completed and the sequence of lesser tasks that are critical because of their dependencies, complexities, and timing.

This critical path is not a list or series of the most important tasks, but the longest sequence of tasks, the things that will take the most time to complete. A critical path may include some rather mundane items, but they are critical because of when they must be completed, how long they will take, and the order in which they must be done.

Network Diagram

Some managers will use something like a network diagram to define the critical path for a project before building a Gantt chart.

This diagram can even be made with notecards by writing each major task on a card along with an estimate of how long that task will take to complete in hours.

Then arrange the cards based on the dependencies and relationships of tasks, noting when they must be done in some order. The aim is to identify the sequence of tasks that will take the longest time to complete.

Photo of notecard arranged on a desk.

The author has begun to arrange note cards into something resembling a network diagram as part of a web development project.

This same process can be done with project management software.

Adding the Critical Path

Once it has been identified, a critical path can be added to the Gantt chart. This might be done with project management software or in a spreadsheet.

In the spreadsheet format, one would expect to find a list of tasks in the far left column. Dates, which can be captured in days, weeks, or months depending on the project, are often in the first or second row. Each cell should include the number of hours required for a project for a given week.

Screenshot of an Excel spreadsheet

The critical path on the Gantt chart is the sequence of tasks that will take the longest to complete. Each of the tasks in the critical path is dependent on the task that comes before it.

Finally, some project managers will keep all critical-path tasks at the top of the spreadsheet and color the cells in red. Completed paths are shaded gray.

Remember, the critical path represents the longest sequence of tasks. Once you have built a critical path, you can estimate how long the total project will take.

Also, remember your resource constraints. Consider whether the folks working on your project can focus 100 percent of their time on the task for the week, or not.

Floating Tasks

In the Gantt chart context, non-critical-path tasks are said to be “floating,” meaning that while vital for the project, they can be fitted into a “floating” window of time.

If a project manager used a network diagram to define a critical path, she probably also identified several tasks that must be done in sequence but which did not add up to as many hours of work as the critical path.

Taking the longest of these secondary sequences first, add them to the Gantt chart.

There are a couple of ways floating tasks are displayed. First, a black vertical line shows the time frame in which a floating task must be completed.

Diagram of a Gantt chart

Floating tasks are shown lower than the critical path and between sets of black vertical lines. These lines represent time constraints for the floating tasks.

When developing an ecommerce site, for example, a headless content management system must be completed at a specific point in the process, but the associated work could begin much earlier.

Often, project managers color floating tasks, like critical-path tasks, blue or green, and shade them in gray when completed.

Notice that in a spreadsheet, the project manager can sum a column to determine how many total resource hours the project is consuming in a given week. Floating tasks can be shifted to make better use of available hours.

Testing Vue Applications With The Vue Testing Library

In this article, we will look at testing Vue applications using the Vue Testing Library — a lightweight library that emphasizes testing your front-end application from the user’s perspective.

The following assumptions are made throughout this article:

  • The reader is familiar with Vue.
  • The reader is familiar with testing application UI.

Conventionally, in Vue userland, when you want to test your application, you reach out for @vue/test-utils — the official testing library for Vue. @vue/test-utils provides APIs to test instances of rendered Vue components. Like so:

// example.spec.js
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue' describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { propsData: { msg } }) expect(wrapper.text()).toMatch(msg) })
})

You can see we are mounting an instance of the Vue component using the shallowMount function provided by @vue/test-utils.

The problem with the above approach to testing Vue applications is that the end-user will be interacting with the DOM and has no knowledge of how Vue renders the UI. Instead, he/she will be finding UI elements by text content, the label of the input element, and some other visual cues on the page.

A better approach will be writing tests for your Vue applications in such a way that mirrors how an actual user will interact with it e.g looking for a button to increment the quantity of a product in a checkout page, hence Vue Testing Library.

What Is Vue Testing Library?

Vue Testing Library is a lightweight testing library for Vue that provides lightweight utility functions on top of @vue/test-utils. It was created with a simple guiding principle:

The more your tests resemble the way your software is used, the more confidence they can give you.
testing-library.com

Why Use Vue Testing Library

  • You want to write tests that are not focused on implementation details i.e testing how the solution is implemented rather than if it produces the desired output.

  • You want to write tests that focus on the actual DOM nodes and not rendered Vue components.

  • You want to write tests that query the DOM in the same way a user would.

How Vue Testing Library Works

Vue Testing Library functions by providing utilities for querying the DOM in the same way a user would interact with the DOM. These utilities allow you to find elements by their label text, find links and buttons from their text content and assert that your Vue application is fully accessible.

For cases where it doesn’t make sense or is not practical to find elements by their text content or label, Vue testing Library provides a recommended way to find these elements by using data-testid attribute as an escape hatch for finding these elements.

The data-testid attribute is added to the HTML element you plan on querying for in your test. E.g

<button data-testid="checkoutButton">Check Out</button>

Getting Started With Vue Testing Library

Now that you have seen why you should use Vue Testing Library and how it works, let’s proceed by setting it up in a brand new Vue CLI generated Vue project.

First, we will generate a new Vue application by running the below command in the terminal (assuming you have Vue CLI installed on your machine):

vue create vue-testing-library-demo

To run our tests, we will be using Jest — a test runner developed by Facebook. Vue CLI has a plugin that easily sets up Jest. Let’s add that plugin:

vue add unit-jest

You will notice the plugin added a new script in package.json:

 "test:unit": "vue-cli-service test:unit",

This would be used to run the tests. It also added a new tests folder in src and inside the tests folder a unit folder with an example test file called example.spec.js. Based on the configuration of Jest, when we run npm run test:unit Jest will look for files in tests directory and run the test files. Let’s run the example test file:

npm run test:unit

This should run the example.spec.js test file in tests/unit directory. Let’s look at the content of this file:

import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue' describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { propsData: { msg } }) expect(wrapper.text()).toMatch(msg) })
})

By default, installing Jest with the Vue CLI plugin will install @vue/test-utils, hence the above test file is using the shallowMount function from @vue/test-utils. A quick way to get familiar with Vue Testing Library is to quickly modify this same test file to use Vue Testing Library instead of @vue/test-utils.

We would do this by first uninstalling @vue/test-utils as we won’t be needing it.

npm uninstall @vue/test-utils --save-dev

Then we install Vue Testing Library as a development dependency:

npm install @testing-library/vue --save-dev

Then we proceed to modify tests/unit/example.spec.js to this:

import { render } from '@testing-library/vue'
import HelloWorld from '@/components/HelloWorld.vue' describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const { getByText } = render(HelloWorld, { props: { msg } }) getByText(msg) })
})

Run the test again and it should still pass. Let’s look at what we did:

  • We use the render function exposed by Vue Testing Library to render the HelloWorld components. render is the only way of rendering components in Vue Testing Library. When you call render, you pass in the Vue component and an optional options object.

  • We then use the options object to pass in the msg props needed by the HelloWorld component. render will return an object with helper methods to query the DOM and one of those methods is getByText.

  • We then use getByText to assert if an element with the text content of ‘new message’ exist in the DOM.

By now you might have noticed the shift from thinking about testing the rendered Vue component to what the user sees in the DOM. This shift will allow you test your applications from the user perspective as opposed to focusing more on the implementation details.

Our Demo App

Now that we have established how testing is done in Vue using Vue Testing Library, we will proceed to test our demo application. But first, we will flesh out the UI for the app. Our demo app is a simple checkout page for a product. We will be testing if the user can increment the quantity of the product before checkout, he/she can see the product name and price, and so on. Let’s get started.

First, create a new Vue component called checkout in components/ directory and add the snippet below to it:

<template> <div class="checkout"> <h1>{{ product.name }} - <span data-testid="finalPrice">${{ product.price }}</span></h1> <div class="quantity-wrapper"> <div> <label for="quanity">Quantity</label> <input type="number" v-model="quantity" name="quantity" class="quantity-input" /> </div> <div> <button @click="incrementQuantity" class="quantity-btn">+</button> <button @click="decrementQuantity" class="quantity-btn">-</button> </div> </div> <p>final price - $<span data-testId="finalPrice">{{ finalPrice }}</span></p> <button @click="checkout" class="checkout-btn">Checkout</button> </div>
</template>
<script>
export default { data() { return { quantity: 1, } }, props: { product: { required: true } }, computed: { finalPrice() { return this.product.price * this.quantity } }, methods: { incrementQuantity() { this.quantity++; }, decrementQuantity() { if (this.quantity == 1) return; this.quantity--; }, checkout() { } }
}
</script> <style scoped>
.quantity-wrapper { margin: 2em auto; width: 50%; display: flex; justify-content: center;
} .quantity-wrapper div { margin-right: 2em;
}
.quantity-input { margin-left: 0.5em;
}
.quantity-wrapper button { margin-right: 1em;
}
button { cursor: pointer;
}
</style>

Then modify App.vue to:

<template> <div id="app"> <check-out :product="product" /> </div>
</template> <script>
import CheckOut from './components/CheckOut.vue' export default { name: 'App', data() { return { product: { name: 'Shure Mic SM7B', price: 200, } } }, components: { CheckOut }
}
</script> <style>
#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px;
}
</style>

For our test case we will be testing the following scenarios:

  1. Can the user see the product name?
  2. Can the user see the product price?
  3. Can the user increment product quantity?
  4. Can the user decrement product quantity?
  5. Can the user see the updated total price in real-time as the quantity changes?

Our UI is pretty minimalistic as the emphasis is on testing with Vue Testing Library. Let’s proceed to test the Checkout component. Create a new test file in tests/unit/ called checkout.spec.js.

We will then proceed to scaffold the test file:

import { render, fireEvent } from '@testing-library/vue'
import CheckOut from '@/components/CheckOut.vue' const product = { name: 'Korg Kronos', price: 1200
}
describe('Checkout.vue', () => { // tests goes here
})

Our very first test case will be to check if the product name is rendered. We would do so like so:

 it('renders product name', () => { const { getByText } = render(CheckOut, { props: { product } }) getByText(product.name) })

Then we will check if the product price is rendered:

it('renders product price', () => { const { getByText } = render(CheckOut, { props: { product } }) getByText("$" + product.price) })

Going forward with testing the Checkout component, we will test if the initial quantity the user sees is 1 using the getByDisplayValue helper method:

it('renders initial quantity as 1', () => { const { getByDisplayValue, getByText } = render(CheckOut, { props: { product } }) getByDisplayValue(1) })

Next up, we will be checking if when the user clicks the button to increment product quantity, the quantity is incremented. We will do this by firing the click event using the fireEvent utility from Vue Testing Library. Here is the complete implementation:

it('increments product quantity', async () => { const { getByDisplayValue, getByText } = render(CheckOut, { props: { product } }) const incrementQuantityButton = getByText('+') await fireEvent.click(incrementQuantityButton) getByDisplayValue(2)
})

We will do the same for decrement when the quantity is 1 — in this case, we don’t decrement the quantity. And also when the quantity is 2. Let’s write both test cases.

it('does not decrement quantity when quanty is 1', async () => { const { getByDisplayValue, getByText } = render(CheckOut, { props: { product } }) const decrementQuantityButton = getByText('-') await fireEvent.click(decrementQuantityButton) getByDisplayValue(1) }) it('decrement quantity when quantity greater than 1', async () => { const { getByDisplayValue, getByText } = render(CheckOut, { props: { product } }) const incrementQuantityButton = getByText('+') const decrementQuantityButton = getByText('-') await fireEvent.click(incrementQuantityButton) await fireEvent.click(decrementQuantityButton) getByDisplayValue(1) })

Lastly, we will test if the final price is being calculated accordingly and displayed to the user when both the increment and decrement quantity buttons are clicked.

it('displays correct final price when increment button is clicked', async () => { const { getByText, getByTestId } = render(CheckOut, { props: { product } }) const incrementQuantityButton = getByText('+') await fireEvent.click(incrementQuantityButton) getByText(product.price * 2) }) it('displays correct final price when decrement button is clicked', async () => { const { getByText} = render(CheckOut, { props: { product } }) const incrementQuantityButton = getByText('+') const decrementQuantityButton = getByText('-') await fireEvent.click(incrementQuantityButton) await fireEvent.click(decrementQuantityButton) getByText(product.price) })

All throughout our test cases, you will notice that we were more focused on writing our tests from the perspective of what the user will see and interact with. Writing tests this way ensures that we are testing what matters to the users of the application.

Conclusion

This article introduces an alternative library and approach for testing Vue applications called Vue Testing Library, we see how to set it up and write tests for Vue components with it.

Resources

You can find the demo project on GitHub.

6 Tweaks to Streamline the Checkout Process

Ecommerce should see significant spikes this holiday season. However, merchants must consider the need for speedy, no-hassle checkout processes to lower abandons, especially from shoppers on mobile devices.

What follows are six tweaks to streamline an ecommerce checkout.

6 Tweaks to Streamline the Checkout Process

Address payment error messages. According to Paysafe, an electronic payments provider, about 10 percent of initial credit and debit card transactions fail. The most common reasons are:

  • Wrong card number,
  • Too many charges or charge attempts in a short time,
  • Limit reached on the credit card.

What occurs after a decline determines if the consumer finalizes the purchase. By default, most shopping carts display a simple “decline” message. But that’s neither clear enough nor encouraging for the shopper to try again.

While decline codes may not indicate the actual issue, a positive message can ease concerns, such as:

Oops, there’s a problem with your card number. Please re-enter your number, expiration date, and security code.

However, this doesn’t provide motivation to continue should the card decline again. That’s where offering an alternative payment method comes into play. Since many consumers have preferred payment methods, it’s ideal to offer at least two non-credit card options, such as PayPal and Apple Pay.

Thus, a better message could state:

Oops, there’s a problem with your card number. Please re-enter your number, expiration date, and security code. Alternatively, you can pay via PayPal or Apple Pay.

Baymard Institute, a research firm, says 2 to 5 percent of would-be customers receive a decline message. By modifying the alert, it was able to capture about 30 percent of those orders.

Checkout page with detailed payment decline message and alternative method.

Baymard Institute’s test provides a more detailed decline notice and link to an alternate payment method.

Ease the surprise of address verification fails. Pop-up messages from real-time address verification can be alarming. By explaining the reason for wanting the shopper to accept the suggested address, you can eliminate confusion.

For example:

We want to make sure your order reaches you. According to the USPS, this is your properly formatted address. Please confirm.

This verbiage changes the message from an alert to a helpful tip. It can also assist shoppers to identify typos that could route packages to the wrong destination.

Append delivery days to shipping methods. Customers want to know when they can expect delivery. By displaying transit times, you help them choose the best method and decrease order status requests. When disclosing “arrives in X days,” take into consideration processing time. Most shoppers consider the countdown to start once they place the order.

Eliminate all unnecessary fields. The trick to decreasing cart abandonment during checkout is to remove all distractions, including collecting info that’s unnecessary for payment or fulfillment. For example, most shoppers would prefer not to provide a phone number or details about how they found the store. Beyond unnecessary info, make as many fields optional as possible.

Define every error. Red asterisks can be hard to catch. Rather than displaying a top of page error, display messages next to or beneath each problematic field. This combats two common issues:

  • The top-of-page error may not appear on the screen, or the error that displays further down the page may be missed.
  • What’s wrong isn’t always apparent. Shoppers get frustrated if an entry appears correct and there’s no defined issue.

Include a discrete link to edit the cart. While you don’t want to promote leaving the checkout, making it easy for shoppers to edit the cart can save a sale. A simple link within the cart contents section will suffice.