Typescript generic with union

2019-11-11

Click here to see the initial setup
enum MessageTypes {
  Success,
  DuplicateEmail,
}

interface ContentMapping {
  [MessageTypes.Success]: {
    data: any;
  };
  [MessageTypes.DuplicateEmail]: {
    email: string;
  };
}

interface Message<Type extends MessageTypes> {
  type: Type;
  content: ContentMapping[Type];
}

We'll compare the following two types:

type Option1 = Message<MessageTypes.DuplicateEmail | MessageTypes.Success>;

type Option2 =
  | Message<MessageTypes.DuplicateEmail>
  | Message<MessageTypes.Success>;
Let's substitute Message and ContentMapping
type Option1 = {
  type: MessageTypes.DuplicateEmail | MessageTypes.Success;
  content:
    | {
        data: any;
      }
    | {
        email: string;
      };
};

type Option2 =
  | {
      type: MessageTypes.DuplicateEmail;
      content: {
        email: string;
      };
    }
  | {
      type: MessageTypes.Success;
      content: {
        data: any;
      };
    };

In Option1, type and content are not connected. A malformed Message can be created:

const message: Option1 = {
  type: MessageTypes.DuplicateEmail,
  content: {
    data: "This should not be allowed!",
  },
};

On the other hand, Option2 throws an error:

const message2: Option2 = {
  type: MessageTypes.DuplicateEmail,
  content: {
    data: "This *is* not allowed!",
  },
};

Property 'email' is missing in type '{ data: string; }' but required in type '{ email: string; }'.

Check out this TypeScript playground for a live demo.


Fix toString error in Elm 0.19

2019-11-08

Many initial tutorials of Elm show us how we can display an Integer as String using the util function called toString.

Example

module Main exposing (main)

import Html


add a b =
    a + b


result =
    add 1 2 |> add 3


main =
    Html.text (toString result)

But with the new version, precisely v0.19 this shows an error.

Cannot find a 'toString' variable

To solve this error, simply replace toString with Debug.toString.

module Main exposing (main)

import Html


add a b =
    a + b


result =
    add 1 2 |> add 3


main =
    Html.text (Debug.toString result)

See the upgrade instructions for more information.


How to turn off test caching for golang!

2019-11-07

When testing Golang projects you'll notice that test results are cached for as long as their corresponding source files remain unchanged. This is generally advantageous, unless your test cases behave differently depending on the environment that they are running in, just like our pg_timetable scheduler depends on a PostgreSQL database.

Before Go 1.12 the known solution was to use the GOCACHE=off environment variable, e.g.

$ GOCACHE=off go test ./internal/pgengine -v

However starting from Go 1.12 this leads to the error:

$ GOCACHE=off go test ./internal/pgengine -v
build cache is disabled by GOCACHE=off, but required as of Go 1.12

As a solution one may clear the build cache explicitly before running tests:

$ go clean -cache

Or only clean the test cache:

$ go clean -testcache

Another approach is to use environmental variables:

$ GOFLAGS="-count=1" go test ./internal/pgengine -v

docker-compose for postgres with db init script

2019-11-06

Single script

postgres:
  image: postgres
  volumes:
    - ./init.sql:/docker-entrypoint-initdb.d/init.sql

Multiple scripts

Multiple scripts run in alphabetical order, thus it's good practice to prefix them with a number.

volumes:
  - ./schema.sql:/docker-entrypoint-initdb.d/1-schema.sql
  - ./data.sql:/docker-entrypoint-initdb.d/2-data.sql

Directory containing scripts

volumes:
  - ./init-scripts:/docker-entrypoint-initdb.d

Typescript module declarations are ignored

2019-11-04

You can write custom type definitions for a module in .d.ts files, for example:

// some-name.d.ts

// my-module will have the type `any`
declare module "my-module";

// specify the exports
declare module "my-other-module" {
  export function foo(bar: number): string;
}

.d.ts files should be in the src directory.

With tsc, this file is included globally.

Starting with version 7 of ts-node, .d.ts files are not included automatically.

Injecting TS_NODE_FILES=true tells ts-node to include the files as well.

See here for more information.


Testing Frontend applications

2019-10-31

We know that testing is hard. Imagine testing a frontend applications 🤯.

So for that, we are going to cover different approaches and review several libraries to test a frontend application.

Static Testing

Use ESLint to detect bugs, e.g. typos, and codebase improvements on build time. Use Typescript to detect type bugs on build time.

    uploader.tsx
        44:2    error     Expected '===' and instead saw '=='    eqeqeq
    greeter.ts(8,21): error TS2345: Argument of type ‘number[]’ is not assignable to parameter of type ‘string’.

Logic Unit Testing

Use Jest to unit test business logic decoupled from UI.

import React from "react";
import renderer from "react-test-renderer";
import App, { Counter, reducer } from "./App";

const list = ["a", "b", "c"];

describe("App", () => {
  describe("Reducer", () => {
    it("should set a list", () => {
      const state = { list: [], error: null };
      const newState = reducer(state, {
        type: "SET_LIST",
        list,
      });
      expect(newState).toEqual({ list, error: null });
    });
  });
  ...
});

Component Unit Testing

Use Jest to unit tests the component using different possible approaches.

Regression Testing with Snapshots

Use Jest Snapshots for regression testing of the output.

import React from "react";
import { render } from "@testing-library/react";
import LoginForm from "./LoginForm";

test("LoginForm should generate the correct HTML and CSS", () => {
  const onSubmit = jest.fn();

  const { asFragment } = render(<LoginForm onSubmit={onSubmit} />);
  expect(asFragment()).toMatchSnapshot();
});

Use Storybook with Storyshoots or Jest Image Snapshot for visual regression testing.

Behavior Testing with Testing Library

Use Testing Library to test the behavior of the components from the user point of view.

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import LoginForm from "./LoginForm";

test("LoginForm should call the onSubmit callback", () => {
  const onSubmit = jest.fn();

  const { getByText } = render(<LoginForm onSubmit={onSubmit} />);
  fireEvent.click(getByText(/send/));
  expect(onSubmit).toHaveBeenCalled();
});

Integration Test for Complex Component

Use Cypress or TestCafe to test complex workflows as a real user, in a real browser with a fake backend and faking any HTTP call.

import { RequestMock } from "testcafe";
import { within, addTestCafeTestingLibrary } from "@testing-library/testcafe";

const loginAPIMock = RequestMock.onRequestTo(
  "http://localhost:3000/api",
).respond(null, 200);

fixture`Login`.beforeEach(addTestCafeTestingLibrary)
  .page`http://localhost:3000`.requestHooks(loginAPIMock);

test("fill login", async t => {
  const { getByLabelText, getByText } = await within("body");

  await t.typeText(getByLabelText(/email/), "[email protected]");
  await t.typeText(getByLabelText(/password/), "abc123");
  await t.click(getByText(/submit/));
  await t.expect(getByText(/success/).exists).ok();
});

End to End Tests for Complex Workflows

Use Cypress or TestCafe to test complex workflows as a real user, in a real browser with a real working backend.

import { within, addTestCafeTestingLibrary } from "@testing-library/testcafe";

fixture`Login`.beforeEach(addTestCafeTestingLibrary)
  .page`http://localhost:3000`;

test("fill login", async t => {
  const { getByLabelText, getByText } = await within("body");

  await t.typeText(getByLabelText(/email/), "[email protected]");
  await t.typeText(getByLabelText(/password/), "abc123");
  await t.click(getByText(/submit/));
  await t.expect(getByText(/success/).exists).ok();
});

Using tests, we don't have to be scared about deploying on Friday anymore 🥳.


Change default shell for new users

2019-10-24

If you have a system with lots of new users, you probably want to set the default shell to the one that most users prefer.

If you're using the useradd command, you can set default options in the file /etc/default/useradd.

Editing this file can be done by using useradd -D ...:

# set default shell for new users to bash
useradd -D -s /bin/bash

Check here for more information.


Convert JSON to table type

2019-10-23

The function json_populate_record can be used to cast a JSON object to a table type:

CREATE TABLE foo(bar int);

SELECT  *
FROM    json_populate_record(NULL::foo, '{"bar": 42}')
+-------+
| bar   |
|-------|
| 42    |
+-------+

This can also be used to insert into the table:

INSERT INTO foo
SELECT *
FROM   json_populate_record(NULL::foo, '{"bar": 42}')

Use multiple files for docker-compose config

2019-10-18

You can specify one or multiple config files for docker-compose with the -f flag:

# one file
docker-compose -f docker-compose.yml up -d
# multiple files
docker-compose -f docker-compose.yml -f docker-compose.tests.yml up -d

Be sure to specify the config files before the sub-command:

# invalid
docker-compose ps -f docker-compose.yml
# valid
docker-compose -f docker-compose.yml ps