One place for hosting & domains

      February 2021

      How To Write Doctests in Python


      Introduction

      Documentation and testing are core components of every productive software development process. Ensuring that code is thoroughly documented and tested not only ensures that a program runs as expected, but also supports collaboration across programmers as well as user adoption. A programmer can be well served by first writing documentation and then tests, before finally writing code. Following a process like this will ensure that the function one is coding (for example) is well thought out and addresses possible edge cases.

      Python’s standard library comes equipped with a test framework module called doctest. The doctest module programmatically searches Python code for pieces of text within comments that look like interactive Python sessions. Then, the module executes those sessions to confirm that the code referenced by a doctest runs as expected.

      Additionally, doctest generates documentation for our code, providing input-output examples. Depending on how you approach writing doctests, this can either be closer to either “‘literate testing’ or ‘executable documentation,’” as the Python Standard Library documentation explains.

      Doctest Structure

      A Python doctest is written as though it is a comment, with a series of three quotation marks in a row — """ — at the top and bottom of the doctest.

      Sometimes, doctests are written with an example of the function and the expected output, but it may be preferable to also include a comment on what the function is intended to do. Including a comment will ensure that you as the programmer have sharpened your goals, and a future person reading the code understands it well. Remember, the future programmer reading the code may very well be you.

      The following is a mathematical example of a doctest for a function such as add(a, b) that adds two numbers together:

      """
      Given two integers, return the sum.
      
      >>> add(2, 3)
      5
      """
      

      In this example we have a line of explanation, and one example of the add() function with two integers for input values. If in the future you want the function to be able to add more than two integers, you would need to revise the doctest to match the function’s inputs.

      So far, this doctest is very readable to a human. You can further iterate on this docstring by including machine-readable parameters and a return description to explicate each variable coming in and out of the function.

      Here, we’ll add docstrings for the two arguments that are passed to the function and the value that is returned. The docstring will note the data types for each of the values — the parameter a, the parameter b, and the returned value — in this case they are all integers.

      """
      Given two integers, return the sum.
      
      :param a: int
      :param b: int
      :return: int
      
      >>> add(2, 3)
      5
      """
      

      This doctest is now ready to be incorporated into a function and tested.

      Incorporating a Doctest into a Function

      Doctests sit within a function after the def statement and before the code of the function. As this follows the initial definition of the function, it will be indented following Python’s conventions.

      This short function indicates how the doctest is incorporated.

      def add(a, b):
          """
          Given two integers, return the sum.
      
          :param a: int
          :param b: int
          :return: int
      
          >>> add(2, 3)
          5
          """
          return a + b
      
      

      In our short example, we only have this one function in our program, so now we will have to import the doctest module and have a call statement for the doctest to run.

      We’ll be adding the following lines before and after our function:

      import doctest 
      ...
      doctest.testmod()
      

      At this point, let’s test it on the Python shell rather than saving it to a program file right now. You can access a Python 3 shell on your command line terminal of choice (including IDE terminal) with the python3 command (or python if you’re using a virtual shell).

      If you go this route, once you press ENTER, you’ll receive output similar to the following:

      Output

      Type "help", "copyright", "credits" or "license" for more information. >>>

      You’ll be able to start typing code after the >>> prompt.

      Our complete example code, including the add() function with a doctest, docstrings, and a call to invoke the doctest follows. You can paste it into your Python interpreter to try it out:

      import doctest
      
      
      def add(a, b):
          """
          Given two integers, return the sum.
      
          :param a: int
          :param b: int
          :return: int
      
          >>> add(2, 3)
          5
          """
          return a + b
      
      doctest.testmod()
      
      

      Once you run the code, you’ll receive the following output:

      Output

      TestResults(failed=0, attempted=1)

      This means that our program ran as expected!

      If you modify the program above so that the return a + b line is instead return a * b, which modifies the function to multiply integers and return their product instead, you’ll receive a failure notice:

      Output

      ********************************************************************** File "__main__", line 9, in __main__.add Failed example: add(2, 3) Expected: 5 Got: 6 ********************************************************************** 1 items had failures: 1 of 1 in __main__.add ***Test Failed*** 1 failures. TestResults(failed=1, attempted=1)

      From the output above, you can begin to understand how useful the doctest module is as it fully describes what happened when a and b were multiplied instead of added, returning the product of 6 in the example case.

      You may want to experiment with more than one example. Let’s try with an example where both variables a and b contain the value of 0, and change the program back to addition with the + operator.

      import doctest
      
      
      def add(a, b):
          """
          Given two integers, return the sum.
      
          :param a: int
          :param b: int
          :return: int
      
          >>> add(2, 3)
          5
          >>> add(0, 0)
          0
          """
          return a + b
      
      doctest.testmod()
      
      

      Once we run this, we’ll receive the following feedback from the Python interpreter:

      Output

      TestResults(failed=0, attempted=2)

      Here, the output indicates that the doctest attempted two tests, on both lines of add(2, 3) and add(0, 0) and that both passed.

      If, again, we change the program to use the * operator for multiplication rather than the + operator, we can learn that edge cases are important when working with the doctest module, because the second example of add(0, 0) will return the same value whether it is addition or multiplication.

      import doctest
      
      
      def add(a, b):
          """
          Given two integers, return the sum.
      
          :param a: int
          :param b: int
          :return: int
      
          >>> add(2, 3)
          5
          >>> add(0, 0)
          0
          """
          return a * b
      
      doctest.testmod()
      
      

      The following output is returned:

      Output

      ********************************************************************** File "__main__", line 9, in __main__.add Failed example: add(2, 3) Expected: 5 Got: 6 ********************************************************************** 1 items had failures: 1 of 2 in __main__.add ***Test Failed*** 1 failures. TestResults(failed=1, attempted=2)

      When we modify the program, only one of the examples fails, but it is fully described as before. If we had started with the add(0, 0) example rather than the add(2, 3) example, we may not have noticed that there were opportunities for failure when small components of our program change.

      Doctests in Programming Files

      So far, we have used an example on the Python interactive terminal. Let’s now use it in a programming file that will count the number of vowels in a single word.

      In a program, we can import and call the doctest module in our if __name__ == "__main__": clause at the bottom of our programming file.

      We’ll create a new file — counting_vowels.py — in our text editor, you can use nano on the command line, like so:

      We can begin with defining our function count_vowels and passing the parameter of word to the function.

      counting_vowels.py

      def count_vowels(word):
      
      

      Before we write the body of the function, let’s explain what we want the function to do in our doctest.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
      

      So far so good, we are being pretty specific. Let’s flesh this out with the data type of the parameter word and the data type we want returned. In the first case it’s a string, in the second case it’s an integer.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      

      Next, let’s find examples. Think of a single word that has vowels, and then type it into the docstring.

      Let’s choose the word 'Cusco' for the city in Peru. How many vowels are in “Cusco”? In English, vowels are often considered to be a, e, i, o, and u. So here we will count u and o as the vowels.

      We’ll add the test for Cusco and the return of 2 as the integer into our program.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      
          >>> count_vowels('Cusco')
          2
      

      Again, it’s a good idea to have more than one example. Let’s have another example with more vowels. We’ll go with 'Manila' for the city in the Philippines.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      
          >>> count_vowels('Cusco')
          2
      
          >>> count_vowels('Manila')
          3
          """
      

      Those doctests look great, now we can code our program.

      We’ll start with initializing a variable — total_vowels to hold the vowel count. Next, we’ll create a for loop to iterate across the letters of the word string, and then include a conditional statement to check whether each letter is a vowel. We’ll increase the vowel count through the loop, then return the total number of vowels in the word to the total_values variable. Our program should be similar to this, without the doctest:

      def count_vowels(word):
          total_vowels = 0
          for letter in word:
              if letter in 'aeiou':
                  total_vowels += 1
          return total_vowels
      

      If you need more guidance on these topics, please check out our How To Code in Python book or complementary series.

      Next, we’ll add our main clause at the bottom of the program and import and run the doctest module:

      if __name__ == "__main__":
          import doctest
          doctest.testmod()
      

      At this point, here is our program:

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      
          >>> count_vowels('Cusco')
          2
      
          >>> count_vowels('Manila')
          3
          """
          total_vowels = 0
          for letter in word:
              if letter in 'aeiou':
                  total_vowels += 1
          return total_vowels
      
      if __name__ == "__main__":
          import doctest
          doctest.testmod()
      
      

      We can run the program by using the python (or python3 depending on your virtual environment) command:

      • python counting_vowels.py

      If your program is identical to the above, all the tests should have passed and you will not receive any output. This means that the tests passed. This silent feature is useful when you are running programs for other purposes. If you are running specifically to test, you may want to use the -v flag, as below.

      • python counting_vowels.py -v

      When you do, you should receive this output:

      Output

      Trying: count_vowels('Cusco') Expecting: 2 ok Trying: count_vowels('Manila') Expecting: 3 ok 1 items had no tests: __main__ 1 items passed all tests: 2 tests in __main__.count_vowels 2 tests in 2 items. 2 passed and 0 failed. Test passed.

      Excellent! The test has passed. Still, our code may not be quite optimized for all edge cases yet. Let’s learn how to use doctests to strengthen our code.

      Using Doctests to Improve Code

      At this point, we have a working program. Maybe it is not the best program it can be yet, so let’s try to find an edge case. What if we add an upper-case vowel?

      Add another example in the doctest, this time let’s try 'Istanbul' for the city in Turkey. Like Manila, Istanbul also has three vowels.

      Below is our updated program with the new example.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      
          >>> count_vowels('Cusco')
          2
      
          >>> count_vowels('Manila')
          3
      
          >>> count_vowels('Istanbul')
          3
          """
          total_vowels = 0
          for letter in word:
              if letter in 'aeiou':
                  total_vowels += 1
          return total_vowels
      
      if __name__ == "__main__":
          import doctest
          doctest.testmod()
      
      

      Let’s run the program again.

      • python counting_vowels.py

      We have identified an edge case! Below is the output we have received:

      Output

      ********************************************************************** File "counting_vowels.py", line 14, in __main__.count_vowels Failed example: count_vowels('Istanbul') Expected: 3 Got: 2 ********************************************************************** 1 items had failures: 1 of 3 in __main__.count_vowels ***Test Failed*** 1 failures.

      The output above indicates that the test on 'Istanbul' is the one that failed. We told the program we were expecting three vowels to be counted, but instead the program counted only two. What went wrong here?

      In our line if letter in 'aeiou': we have only passed in lower-case vowels. We can modify our 'aeiou' string to be 'AEIOUaeiou' to count both upper- and lower-case vowels, or we can do something more elegant, and convert our value stored in word to lower-case with word.lower(). Let’s do the latter.

      counting_vowels.py

      def count_vowels(word):
          """
          Given a single word, return the total number of vowels in that single word.
      
          :param word: str
          :return: int
      
          >>> count_vowels('Cusco')
          2
      
          >>> count_vowels('Manila')
          3
      
          >>> count_vowels('Istanbul')
          3
          """
          total_vowels = 0
          for letter in word.lower():
              if letter in 'aeiou':
                  total_vowels += 1
          return total_vowels
      
      if __name__ == "__main__":
          import doctest
          doctest.testmod()
      
      

      Now, when we run the program, all tests should pass. You can confirm again by running python counting_vowels.py -v with the verbose flag.

      Still, this probably is not the best program it can be, and it may not be considering all edge cases.

      What if we pass the string value 'Sydney' — for the city in Australia — to word? Would we expect three vowels or one? In English, y is sometimes considered to be a vowel. Additionally, what would happen if you use the value 'Würzburg' — for the city in Germany — would the 'ü' count? Should it? How will you handle other non-English words? How will you handle words that use different character encodings, such as those available in UTF-16 or UTF-32?

      As a software developer, you will sometimes need to make tricky decisions like deciding which characters should be counted as vowels in the example program. Sometimes there may not be a right or wrong answer. In many cases, you will not consider the full scope of possibilities. The doctest module is therefore a good tool to start to think through possible edge cases and capture preliminary documentation, but ultimately you will need human user testing — and very likely collaborators — to build robust programs that serve everyone.

      Conclusion

      This tutorial introduced the doctest module as not only a method for testing and documenting software, but also as a way to think through programming before you begin, by first documenting it, then testing it, then writing the code.

      Not writing tests could lead not only to bugs but software failure. Getting in the habit of writing tests prior to writing code can support productive software that serves other developers and end users alike.

      If you would like to learn more about testing and debugging, check out our “Debugging Python Programs” series. We also have a free eBook on How To Code in Python and another on Python Machine Learning Projects.



      Source link

      How To Set Up a Gatsby Project with TypeScript


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript is a superset of JavaScript that adds optional static typing at build time, which cuts down on debugging runtime errors. It has grown into a powerful alternative to JavaScript over the years. At the same time, Gatsby has emerged as a useful front-end framework for creating static websites. TypeScript’s static-typing abilities go well with a static-site generator like Gatsby, and Gatsby has built-in support for coding in TypeScript.

      In this tutorial, you’re going to use Gatsby’s built-in capabilities to configure a Gatsby project for TypeScript. After this tutorial, you will have learned how to integrate TypeScript into your Gatsby project.

      Prerequisites

      • You will need to have both Node and npm installed in order to run a development environment and handle TypeScript- or Gatsby-related packages, respectively. This tutorial was tested with Node.js version 14.13.0 and npm version 6.14.8. To install on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
      • To create a new Gatsby project, you will need the Gatsby CLI command line tool installed on your computer. To set this up, follow Step 1 in How to Set Up Your First Gatsby Site. This step will also show you how to create a new Gatsby project with the gatsby new command.
      • You will need some familiarity with GraphQL queries and using GraphiQL to query for local image data. If you’d like a refresher on the query sandbox in GraphiQL, read How to Handle Images with GraphQL and the Gatsby Image API.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax such as destructuring and imports/exports. You can find more information on these topics in Understanding Destructuring, Rest Parameters, and Spread Syntax in JavaScript and Understanding Modules and Import and Export Statements in JavaScript.
      • Since Gatsby is a React-based framework, you will be refactoring and creating components in this tutorial. You can learn more about this in How to Create Custom Components in React.
      • Additionally, you will need TypeScript installed on your machine. To do this, refer to the official TypeScript website. If you are using an editor besides Visual Studio Code, you may need to go through a few extra steps to make sure you have TypeScript performing type-checks at build time and showing any errors. For example, if you’re using Atom, you’ll need to install the atom-typescript package to be able to achieve a true TypeScript experience. If you would like to download TypeScript only for your project, do so after the Gatsby project folder has been set up.

      Step 1 — Creating a New Gatsby Site and Removing Boilerplate

      To get started, you’re going to create your Gatsby site and make sure that you can run a server and view the site. After that, you will remove some unused boilerplate files and code. This will set your project up for edits in later steps.

      Open your computer’s console/terminal and run the following command:

      • gatsby new gatsby-typescript-tutorial

      This will take a few seconds to run as it sets up the necessary boilerplate files and folders for the Gatsby site. After it is finished, cd into the project’s directory:

      • cd gatsby-typescript-tutorial

      To make sure the site’s development environment can start properly, run the following command:

      After a few seconds, you will receive the following message in the console:

      Output

      ... You can now view gatsby-starter-default in the browser. http://localhost:8000

      Usually, the default port is :8000, but you can change this by running gatsby develop -p another_number instead.

      Head over to your preferred browser and type http://localhost:8000 in the address bar to find the site. It will look like this:

      Gatsby Default Starter Site

      Next, you’ll remove all unnecessary files. This includes gatsby-node.js, gastby-browser.js, and gatsby-ssr.js:

      • rm gatsby-node.js
      • rm gastby-browser.js
      • rm gatsby-ssr.js

      Next, to finish setup, you’re going to remove some boilerplate code from your project’s index page. In your project’s root directory, head to the src directory, followed by pages and then open the index.js file.

      For this tutorial, you are only going to work with an <Image /> component, so you can delete code related to the <Link /> component, along with the h1 and p elements. Your file will then look like the following:

      gatsby-typescript-tutorial/src/pages/index.js

      import React from "react"
      
      import Layout from "../components/layout"
      import Image from "../components/image"
      import SEO from "../components/seo"
      
      const IndexPage = () => (
        <Layout>
          <SEO title="Home" />
          <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
            <Image />
          </div>
        </Layout>
      )
      
      export default IndexPage
      

      Save and close the file.

      Now that you’ve created your project and completed some initial setup, you are ready to install the necessary plugins.

      Step 2 — Installing Dependencies

      In order to set up support for TypeScript in Gatsby, you’ll need some additional plugins and dependencies, which you will install in this step.

      The gatsby-plugin-typescript plugin already comes with a newly created Gatsby site. Unless you want to change any of its default options, you don’t have to add this plugin to your gatsby-config.js file explicitly. This Gatsby plugin makes writing .ts and .tsx files in TypeScript possible.

      Since your app can read TypeScript files, you will now change Gatsby’s JavaScript files to a TypeScript file extension. In particular, change header.js, image.js, layout.js, and seo.js in src/components and index.js in src/pages to header.tsx, image.tsx, layout.tsx, seo.tsx, and index.tsx:

      • mv src/components/header.js src/components/header.tsx
      • mv src/components/image.js src/components/image.tsx
      • mv src/components/layout.js src/components/layout.tsx
      • mv src/components/seo.js src/components/seo.tsx
      • mv src/pages/index.js src/pages/index.tsx

      You are using the mv command to rename the files to the second argument. .tsx is the file extension, since these files use JSX.

      There is one important caveat about the gatsby-plugin-typescript plugin, however: it doesn’t include type-checking at build time (a core function of TypeScript). If you’re using VS Code, this won’t be an issue because TypeScript is a supported language in Visual Studio. But if you’re using another editor, like Atom, you will need to do some extra configurations to achieve a full TypeScript development experience.

      Since Gatsby is a React-based framework, adding some additional React-related typing is also recommended. To add type-checking for types specific to React, run the following command:

      To add type-checking for types related to the React DOM, use this command:

      Now that you’ve become familiar with the plugin gatsby-plugin-typescript, you are ready to configure your Gatsby site for TypeScript in the next step.

      Step 3 — Configuring TypeScript for Gatsby with the tsconfig.json File

      In this step, you will create a tsconfig.json file. A tsconfig.json file has two primary purposes: establishing the root directory of the TypeScript project (include) and overriding the TypeScript compiler’s default configurations (compilerOptions). There are a couple of ways to create this file. If you have the tsc command line tool installed with npm, you could create a new tsconfig file with tsc --init. But the file is then populated with many default options and comments.

      Instead, create a new file at the root of your directory (gatsby-typescript-project/) and name it tsconfig.json.

      Next, create an object with two properties, compilerOptions and include, populated with the following code:

      gatsby-typescript-tutorial/tsconfig.json

       {
        "compilerOptions": {
          "module": "commonjs",
          "target": "es6",
          "jsx": "preserve",
          "lib": ["dom", "es2015", "es2017"],
          "strict": true,
          "noEmit": true,
          "isolatedModules": true,
          "esModuleInterop": true,
          "skipLibCheck": true,
          "noUnusedLocals": true,
          "noUnusedParameters": true,
          "removeComments": false
        },
        "include": ["./src/**/*"]
      }
      

      Note:
      This configuration is partially based on the gatsby-starter-typescript-plus starter.

      Save this file and close it when you are done.

      The include property points to an array of filenames or paths that the compiler knows to convert from TypeScript to JavaScript.

      Here is a brief explanation of each option used in compilerOptions:

      • module - Sets the module system for the project; commonjs is used by default.
      • target - Depending on what version of JavaScript you’re using, this option determines which features to downlevel and which to leave alone. This can be helpful if your project is deployed to older environments vs. newer environments.
      • jsx - Setting for how JSX is treated in .tsx files. The preserve option leaves the JSX unchanged.
      • lib - An array of specified type-definitions of different JS libraries/APIs (dom, es2015, etc.).
      • strict - When set to true, this enables TypeScript’s type-checking abilities at build-time.
      • noEmit - Since Gatsby already uses Babel to compile your code to readable JavaScript, you change this option to true to leave TypeScript out it.
      • isolatedModules - By choosing Babel as your compiler/transpiler, you are opting for compilation one file at a time, which may cause potential problems at runtime. Setting this option to true allows TypeScript to warn you if you are about to run into this problem.
      • esModuleIterop - Enabling this option allows your use of CommonJS (your set module) and ES modules (importing and exporting custom variables and functions) to better work together and allow namespace objects for all imports.
      • noUnusedLocals and noUnusedParamters - Enabling these two options disables the errors TypeScript would normally report if you were to create an unused local variable or parameter.
      • removeComments - Setting this to false (or not setting it at all) allows there to be comments present after any TypeScript files have been converted to JavaScript.

      You can learn more about these different options and many more by visiting TypeScript’s reference guide for tsconfig.

      Now that TypeScript is configured for Gatsby, you are going to complete your TypeScript integration by refactoring some of your boilerplate files in src/components and src/pages.

      Step 4 — Refactoring seo.tsx for TypeScript

      In this step, you’re going to add some TypeScript syntax to the seo.tsx file. This step goes in depth to explain some concepts of TypeScript; the next step will show how to refactor other boilerplate code in a more abbreviated manner.

      One feature of TypeScript is its flexibility with its syntax. If you don’t want to add typing to your variables explicitly, you don’t have to. Gatsby believes that adopting TypeScript in your workflow “can and should be incremental”, and so this step will concentrate on three core TypeScript concepts:

      • basic types
      • defining types and interfaces
      • working with build-time errors

      Basic Types in TypeScript

      TypeScript supports basic datatypes including: boolean, number, and string. The major syntactical difference with TypeScript, compared to JavaScript, is that variables can now be defined with an associated type.

      For example, the following code block shows how to assign the basic types with the highlighted code:

      let num: number;
      num = 0
      
      let str: string;
      str = "TypeScript & Gatsby"
      
      let typeScriptIsAwesome: boolean;
      typeScriptIsAwesome = true;
      

      In this code, num must be a number, str must be a string, and typeScriptIsAwesome must be a boolean.

      Now you will examine the defaultProps and propTypes declarations in the seo.tsx file, found in the src/components directory. Open the file in your editor and look for the following highlighted lines:

      gatsby-typescript-tutorial/src/components/seo.tsx

      ...
      import React from "react"
      import PropTypes from "prop-types"
      import { Helmet } from "react-helmet"
      import { useStaticQuery, graphql } from "gatsby"
      
      ...
            ].concat(meta)}
          />
        )
      }
      
      
      SEO.defaultProps = {
        lang: `en`,
        meta: [],
        description: ``,
      }
      
      SEO.propTypes = {
        description: PropTypes.string,
        lang: PropTypes.string,
        meta: PropTypes.arrayOf(PropTypes.object),
        title: PropTypes.string.isRequired,
      }
      
      export default SEO
      

      By default, a Gatsby site’s SEO component comes with a weak typing system using PropTypes. The defaultProps and propTypes are explicitly declared, using the imported PropsTypes class. For example, in the meta prop (or alias) of the propTypes object, its value is an array of objects, each of which is itself a prop of the PropTypes component. Some props are marked as required (isRequired) while others are not, implying they are optional.

      Since you are using TypeScript, you will be replacing this typing system. Go ahead and delete defaultProps and propTypes (along with the import statement for the PropTypes at the top of the file). Your file will look like the following:

      gatsby-typescript-tutorial/src/components/seo.tsx

       ...
      import React from "react"
      import { Helmet } from "react-helmet"
      import { useStaticQuery, graphql } from "gatsby"
      
      
      ...
            ].concat(meta)}
          />
        )
      }
      
      export default SEO
      

      Now that you’ve removed the default typing, you’ll write out the type aliases with TypeScript.

      Defining TypeScript Interfaces

      In TypeScript, an interface is used to define the “shape” of a custom type. These are used to represent the value type of complex pieces of data like React components and function parameters. In the seo.tsx file, you’re going to build an interface to replace the defaultProps and propTypes definitions that were deleted.

      Add the following highlighted lines:

      gatsby-typescript-tutorial/src/components/seo.ts

       ...
      import React from "react"
      import { Helmet } from "react-helmet"
      import { useStaticQuery, graphql } from "gatsby"
      
      interface SEOProps {
        description?: string,
        lang?: string,
        meta?: Array<{name: string, content: string}>,
        title: string
      }
      
      ...
      
      
      

      The interface SEOProps accomplishes what SEO.propTypes did by setting each of the properties associated data type as well as marking some as required with the ? character.

      Typing a Function

      Just like in JavaScript, functions play an important role in TypeScript applications. You can even type functions by specifying the datatype of the arguments passed into them. In the seo.tsx file, you will now work on the defined SEO function component. Under where the interface for SEOProps was defined, you’re going to explicitly declare the type of the SEO component’s function arguments, along with a return type of SEOProps right after:

      Add the following highlighted code:

      gatsby-typescript-tutorial/src/components/seo.ts

      ...
      interface SEOProps {
        description?: string,
        lang?: string,
        meta?: Array<{name: string, content: string}>,
        title: string
      }
      
      function SEO({ description='', lang='en', meta=[], title }: SEOProps) {
        ...
      }
      

      Here you set defaults for the SEO function arguments so that they adhere to the interface, and added the interface with : SEOProps. Remember that you at least have to include the title in the list of arguments passed to the SEO component because it was defined as a required property in the SEOProps interface you defined earlier.

      Lastly, you can revise the metaDescription and defaultTitle constant declarations by setting their type, which is string in this case:

      gatsby-typescript-tutorial/src/components/seo.tsx

       ...
      function SEO({ description='', lang='en', meta=[], title }: SEOProps) {
        const { site } = useStaticQuery(
          graphql`
            query {
              site {
                siteMetadata {
                  title
                  description
                  author
                }
              }
            }
          `
        )
      
        const metaDescription: string = description || site.siteMetadata.description
        const defaultTitle: string = site.siteMetadata?.title
      ...
      

      Another type in TypeScript is the any type. For situations where you’re dealing with a variable whose type is unclear or difficult to define, use any as a last resort to avoid any build-time errors.

      An example of using the any type is when dealing with data fetched from a third-party, like an API request or a GraphQL query. In the seo.tsx file, where the destructured site property is defined with a GraphQL static query, set its type to any:

      gatsby-typescript-tutorial/src/components/seo.tsx

      ...
      interface SEOProps {
        description?: string,
        lang?: string,
        meta?: Array<{name: string, content: string}>,
        title: string
      }
      
      function SEO({ description='', lang='en', meta=[], title }: Props) {
        const { site }: any = useStaticQuery(
          graphql`
            query {
              site {
                siteMetadata {
                  title
                  description
                  author
                }
              }
            }
          `
        )
        ...
      }
      

      Save and exit the file.

      It’s important to always keep the defined values consistent with their type. Otherwise, you will see build-time errors appear via the TypeScript compiler.

      Build-Time Errors

      It will be helpful to become accustomed to the errors TypeScript will catch and report at build-time. The idea is that TypeScript catches these errors, mostly type-related, at build-time, and this cuts down on the amount of debugging in the long run (in compile-time).

      One example of a build-time error occurring is when you declare a variable of one type but assign it a value that is of another type. If you were to change the value of one of the keyword arguments passed to the SEO component to one of a different type, the TypeScript compiler will detect the inconsistency and report the error. The following is an image of what this looks like in VSCode:

      A build-time error in VSCode when the description variable is set to a number.

      The error says Type 'number' is not assignable to type 'string'. This is because, when you set up your interface, you said the description property would be of type string. The value 0 is of type number. If you change the value of description back into a “string”, the error message will go away.

      Step 5 — Refactoring the Rest of the Boilerplate

      Lastly, you will refactor the remaining boilerplate files with TypeScript: layout.tsx, image.tsx, and header.tsx. Like seo.tsx, these component files are located in the src/components directory.

      Open src/components/layout.tsx. Towards the bottom, is the defined Layout.propTypes. Delete the following highlighted lines:

      gatsby-typescript-tutorial/src/components/layout.tsx

       import React from "react"
      import PropTypes from "prop-types"
      import { useStaticQuery, graphql } from "gatsby"
      ...
      
      Layout.propTypes = {
        children: PropTypes.node.isRequired,
      }
      
      export default Layout
      

      The children prop shows that its value is of type node per the PropTypes class. Plus, it’s a required prop. Since the children in the layout could be anything from simple text to React child components, use ReactNode as the associated type by importing near the top and adding it to the interface:

      Add the following highlighted lines:

      gatsby-typescript-tutorial/src/components/layout.tsx

      ...
      import React, { ReactNode } from "react"
      import { useStaticQuery, graphql } from "gatsby"
      
      import Header from "./header"
      import "./layout.css"
      
      interface LayoutProps {
        children: ReactNode
      }
      
      const Layout = ({ children }: LayoutProps) => {
        ...
      

      Next, add a type to the data variable that stores a GraphQL query that fetches site title data. Since this query object is coming from a third-party entity like GraphQL, give data an any type. Lastly, add the string type to the siteTitle variable that works with that data:

      gatsby-typescript-tutorial/src/components/layout.tsx

       ...
      const Layout = ({ children }: LayoutProps) => {
        const data: any = useStaticQuery(graphql`
        query MyQuery {
          site {
            siteMetadata {
              title
            }
          }
        }
      `)
      
      const siteTitle: string = data.site.siteMetadata?.title || `Title`
      
        return (
          <>
            <Header siteTitle={siteTitle} />
            <div
      ...
      

      Save and close the file.

      Next, open the src/components/image.tsx file.

      Here you are dealing with a similar situation as layout.tsx. There is a data variable that stores a GraphQL query that could have an any type. The image fluid data that is passed into the fluid attribute of the <Img /> component could be separated from the return statement into its own variable. It’s also a complex variable like data, so give this an any type as well:

      gatsby-typescript-tutorial/src/components/image.tsx

      ...
      const Image = () => {
        const data: any = useStaticQuery(graphql`
          query {
            placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
              childImageSharp {
                fluid(maxWidth: 300) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        `)
      
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        const imageFluid: any = data.placeholderImage.childImageSharp.fluid
      
        return <Img fluid={imageFluid} />
      }
      
      export default Image
      

      Save and close the file.

      Now open the src/components/header.tsx file. This file also comes with predefined prop types, using the PropTypes class. Like seo.tsx, image.tsx, and layout.tsx, replace Header.defaultProps and Header.propTypes with an interface using the same prop names:

      gatsby-typescript-tutorial/src/components/header.tsx

      import { Link } from "gatsby"
      import React from "react"
      
      interface HeaderProps {
        siteTitle: string
      }
      
      const Header = ({ siteTitle }: HeaderProps) => (
        <header
          style={{
            background: `rebeccapurple`,
            marginBottom: `1.45rem`,
          }}
        >
          <div
            style={{
              margin: `0 auto`,
              maxWidth: 960,
              padding: `1.45rem 1.0875rem`,
            }}
          >
            <h1 style={{ margin: 0 }}>
              <Link
                to="/"
                style={{
                  color: `white`,
                  textDecoration: `none`,
                }}
              >
                {siteTitle}
              </Link>
            </h1>
          </div>
        </header>
      )
      
      export default Header
      

      Save and close the file.

      With your files refactored for TypeScript, you can now restart the server to make sure everything is working. Run the following command:

      When you navigate to localhost:8000, your browser will render the following:

      Gatsby Default Development page

      Conclusion

      TypeScript’s static-typing capabilities go a long way in keeping debugging at a minimum. It’s also a great language for Gatsby sites since it’s supported by default. Gatsby itself is a useful front-end tool for creating static-sites, such as landing pages.

      You now have two popular tools at your disposal. To learn more about TypeScript and all you can do with it, head over to the official TypeScript handbook.



      Source link

      Understanding Comparison and Logical Operators in JavaScript


      Introduction

      The field of computer science has many foundations in mathematical logic. If you have a familiarity with logic, you know that it involves truth tables, Boolean algebra, and comparisons to determine equality or difference.

      The JavaScript programming language uses operators to evaluate statements that can aid in control flow within programming.

      In this tutorial, we’ll go over logical operators. These are commonly used with conditional statements, and the if, else, and else if keywords, as well as the ternary operator. If you are interested in learning more about conditional statements first, refer to How To Write Conditional Statements in JavaScript.

      Comparison Operators

      In JavaScript, there are a number of comparison operators that you can use to evaluate whether given values are different or equal, as well as if a value is greater than or less than another. Often, these operators are used with stored values in variables.

      Comparison operators all return a Boolean (logical) value of true or false.

      The table below summarizes the comparison operators available in JavaScript.

      OperatorWhat it means
      ==Equal to
      !=Not equal to
      ===Strictly equal to with no type conversion
      ! ==Strictly unequal to with no type conversion
      >Greater than
      >=Greater than or equal to
      <Less than
      <=Less than or equal to

      Let’s go into each operator in detail.

      Equality

      The equality operator measures whether values on either side of the operator are equal.

      Let’s consider the following:

      let x = 3;
      
      x == 3;
      

      Because 3 is equivalent to 3, the output received will be the Boolean value of true.

      Output

      true

      If we instead test whether x is equal to another integer, we’ll receive output stating that the statement is validated to be false.

      let x = 3;
      
      x == 5;
      

      Output

      false

      With this equivalency expression, you can also test other data types such as strings and Booleans.

      We’ll use a string example below.

      let shark = 'sammy';
      
      shark == 'sammy';
      shark == 'taylor';
      

      Output

      true false

      In the first instance, the expression returned true because the strings were equivalent. In the second instance, of shark == 'taylor', the expression returned false because the strings were not equal.

      Worth noting, is that the == operator is not a strict equivalency, so you can mix numbers and strings that evaluate to being equivalent. Consider the following example.

      let x = 3;
      
      x == '3';
      

      Even though the first line uses a number data type, and the second line tests x against a string data type, both values equal 3, and the output you will receive indicates that the expression is true.

      Output

      true

      Because this operator is not strict about data type, it can support users entering strings instead of numbers, for example. There is no need to convert data types to test equivalency.

      There are many cases where you may use comparison operators like the == operator. You may want to test equivalency when grading a test, for example. That way you can validate whether a given answer is correct or not.

      let answer = 10;
      let response = prompt("What is 5 + 5?");
      
      if (answer == response) {
        console.log("You're correct!");
      }
      

      Here, if the student enters 10 in response to the question when prompted, they will receive the feedback that they are correct.

      There are many potential applications of comparison operators in JavaScript, and they will help you control the flow of your program.

      Now that you have a foundation with a few examples for ==, we’ll be a bit briefer going forward.

      Inequality

      The != operator tests inequality, to determine whether the values on either side of the operator are not equal.

      Let’s consider an example.

      let y = 8;
      
      y != 9;
      

      For this example, 8 does not equal 9, so the expression will be evaluated to be true:

      Output

      true

      For a statement of inequality to be considered false, the two values on either side would need to actually be equal, as in the following.

      let y = 8;
      
      y != 8
      

      Output

      false

      In this second example, the two values on either side of the operator are equal, so the expression is not true.

      Identity

      The === operator determines whether two values are both of equal value and of equal type. This is also known as a strict equality operator. This means you cannot mix number and string data types.

      Here’s an example:

      let z = 4;
      
      z === 4;
      
      z === '4'; 
      

      We’ll receive the following output.

      Output

      true false

      The example indicates that z is strictly equal to 4 (as it is assigned the numeric value of 4), but that it is not strictly equal to the string '4'.

      Because this operator is strict, you will need to keep in mind that you may need to convert user-entered data from one data type to another, for instance, when working with the identity operator. This may help you keep data types consistent throughout your program.

      Non Identity

      Like ===, the operator !== evaluates a strict inequality, which considers both the value and the type of the operands on either side of the operator.

      We’ll review the following examples.

      let a = 18;
      
      a !== 18;
      
      a !== '18';
      
      a !== 29;
      

      The output for the above will be as follows.

      Output

      false true true

      In this example, since a does strictly equal 18, the first expression evaluates to false as we are testing inequality. In the next two examples, a is determined to be unequal to the string '18' and the number 29, so those two expressions evaluate to true (since they are not equal).

      Greater than

      The greater than symbol in JavaScript may be familiar to you from math: >. This evaluates whether one value (on the left side of the expression) is greater than another value (on the right side of the expression).

      Like the == operator above, the greater than operator is not strict, and therefore will allow you to mix strings and numbers.

      Let’s consider the following examples.

      let f = 72;
      
      f > 80;
      
      f > '30';
      

      We’ll receive the following output:

      Output

      false true

      In the first instance, 72 is less than 80, so the first expression evaluates to false. In the second instance, 72 is in fact greater than '30', and the operator does not care that the number is a string, so the expression evaluates to true.

      Greater than or equal

      Similarly, the operator for greater than or equal to will evaluate whether one operand meets the threshold of the other. This operator is typed as >= a kind of compound between greater than (>) and the equal sign (=).

      Our examples:

      let g = 102;
      
      g >= 90;
      
      g >= 103;
      

      Output

      true false

      Because 102 is a larger number than 90, it is considered to be greater than or equal to 90. Because 102 is less than 103, it is false to state that 102 >= 103. If either 90 or 103 were a string data type, the expressions would also evaluate the same.

      Less than

      The less than operator appears as the mirror version of the greater than operator: <.

      Consider the following examples as a demonstration.

      let w = 1066;
      
      w < 476;
      
      w < 1945;
      

      Output

      false true

      Here, 1066 is greater than 476, so the expression evaluates to false. However, 1066 is less than 1945, so the second statement evaluates to true. Again, the 476 or 1945 values could also be strings.

      Less than or equal

      The opposite of greater than or equal, the less than or equal operator — <= — will evaluate whether the value on the left side of the operator is less than or equal to the value on the right side.

      Here are a few examples.

      let p = 2001;
      
      p <= 1968;
      
      p <= 2001;
      
      p <= 2020;
      

      Output

      false true true

      The first expression evaluates to false because 2001 is not less than or equal to 1968. In the second expression, because the variable and 2001 are equal values, the output is true. In the third expression, the output is also true because 2001 is less than 2020. Again, these values could also be represented as strings, as in '2001', and would evaluate in the same manner.

      Note: Be sure not to confuse the less than or equal operator (<=) with the arrow function (=>) in JavaScript. Learn more about arrow functions in our tutorial Understanding Arrow Functions in JavaScript.

      To understand how these comparison operators can work together in a program, refer to our grades.js example in our How To Write Conditional Statements in JavaScript tutorial.

      Logical Operators

      In JavaScript, there are three logical operators, which connect two or more programming statements to return a true (also called “truthy”) or false (“falsy”) value. These are most often used with Boolean (logical) types, but can be applied to values of any data type.

      These logical operators are summarized in the table below.

      OperatorSyntaxDescription
      AND&&Returns true if both operands are true
      OR||Returns true if either operand is true
      NOT!Returns true if operand is false

      Let’s review each of these operators in more detail.

      AND

      The AND operator is represented by two ampersands — && — it will return true if the operands to the left and right evaluate to be true.

      For example, with AND we can check if something is both high quality and has a low price.

      // High quality and low price are true
      const highQuality = true;
      const lowPrice = true;
      
      (highQuality && lowPrice);
      

      Output

      true

      Since both variables evaluate to be true, the AND operation within the parentheses returns true. If either one of the variables were initialized as false, the && expression would evaluate to false.

      OR

      The OR operator is represented by two pipes — || — it will return true if one of the operands is true.

      In this example, we’ll check if something is either highQuality or lowPrice.

      // Only low price is true
      const highQuality = false;
      const lowPrice = true;
      
      (highQuality || lowPrice);
      

      Output

      true

      Since one of the two conditions (highQuality or lowPrice) was true, the whole operation returns true. This would only evaluate to false if both conditions were false.

      NOT

      The NOT operator is represented by an exclamation point — ! — it will return true if the operand is set to false, and vice versa.

      const highQuality = true;
      
      !(highQuality);
      

      Output

      false

      In the above statement, highQuality has the value of true. With the NOT operator, we are checking to see if hiqhQuality evaluates to false. If it were false, the output would return true, but since it is true, the output returns false.

      The NOT operator is a bit tricky to understand at first. The important part to remember is that NOT checks whether something evaluates to be false.

      Conclusion

      Logical operators are the building blocks of flow control in JavaScript programming. Using these operators effectively will help you develop programs that evaluate statements and move to the next stage based on whether a statement is true or false.

      To continue learning more about JavaScript, check out our How To Code in JavaScript series, and our JavaScript tag.



      Source link