New git commands: git switch & git restore!

2020-07-08

For a long time, I was working with SVN as my default version control system. I liked it a lot for its clear syntax.

When I switched to the git, I was surprised how overloaded the checkout sub-command really is:

— You need to switch to another branch? Use git checkout.

— You need to revert modifications made to files? Use git checkout.

In SVN you have separate commands for each of these tasks.

Starting with git 2.23 we have new sub-commands to address this:

  • git switch to switch between branches
  • git restore to undo all modifications made

I'm sure this new syntax will be a great help for newcomers from SVN. Consider this new workflow. Extremely clear to me!

pasha@PG480 MINGW64 ~/go/src/github.com/cybertec-postgresql/pg_timetable (master)
$ git switch docker-tests
Switched to branch 'docker-tests'
Your branch is up to date with 'origin/docker-tests'.

pasha@PG480 MINGW64 ~/go/src/github.com/cybertec-postgresql/pg_timetable (docker-tests)
$ rm README.md

pasha@PG480 MINGW64 ~/go/src/github.com/cybertec-postgresql/pg_timetable (docker-tests)
$ git status
On branch docker-tests
Your branch is up to date with 'origin/docker-tests'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    README.md

no changes added to commit (use "git add" and/or "git commit -a")

pasha@PG480 MINGW64 ~/go/src/github.com/cybertec-postgresql/pg_timetable (docker-tests)
$ git restore README.md

pasha@PG480 MINGW64 ~/go/src/github.com/cybertec-postgresql/pg_timetable (docker-tests)
$ git status
On branch docker-tests
Your branch is up to date with 'origin/docker-tests'.
nothing to commit, working tree clean

Array destructuring with property assignment

2020-03-04

Array destructuring is a nice feature to assign array values to variables.

The default usage is:

const [first, second, third] = [1, 2];

// first:  1
// second: 2
// third:  3

But you can also use already existing variables.

let first, second, third;

let values = [1, 2];

[first, second, third = 3] = values;

// first:  1
// second: 2
// third:  3

This also works for assigning object properties.

let result = {};

[result.first, result.second, result.third = 3] = [1, 2];
// result: { first: 1, second: 2, third: 3 }

Sleeping more conveniently in PostgreSQL

2020-01-28

Postgres' sleep functions are not particularly useful, as they should only be utilized for demonstration purposes (like provoking locking situations and then trying to find out who blocked whom). After years of using the pg_sleep() function, which takes seconds as its input argument, I one day discovered that there are more convenient functions that even accept human readable input!

SELECT now();
SELECT pg_sleep_for('5 minutes');
SELECT  /* then do something …. */
SELECT pg_sleep_until('tomorrow 03:00');

More tips and tricks from our very own Kaarel Moppel!


Storybook with create-react-app and Typescript

2019-11-26

# generate a new app using create-react-app
yarn create react-app my-app --typescript
cd my-app

# install storybook cli
yarn add -D @storybook/cli

# initialize with storybook cli
yarn sb init

yarn sb init should not be called with the --type react flag. Without --type, the cli figures out the correct type on its own.

The correct type for an app created with create-react-app is react_scripts.

Check out the source code for available types.


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 🥳.