Repository: total-typescript/beginners-typescript-tutorial
Branch: main
Commit: e430c2da7ab0
Files: 57
Total size: 29.3 KB
Directory structure:
gitextract_69awz6n2/
├── .github/
│ └── workflows/
│ ├── renovate-checks.yml
│ └── section-repos.yml
├── .gitignore
├── .vscode/
│ └── settings.json
├── README.md
├── package.json
├── renovate.json
├── src/
│ ├── 01-number.problem.ts
│ ├── 01-number.solution.ts
│ ├── 02-object-param.problem.ts
│ ├── 02-object-param.solution.1.ts
│ ├── 02-object-param.solution.2.ts
│ ├── 02-object-param.solution.3.ts
│ ├── 03-optional-properties.problem.ts
│ ├── 03-optional-properties.solution.ts
│ ├── 04-optional-params.problem.ts
│ ├── 04-optional-params.solution.ts
│ ├── 05-assigning-types-to-variables.problem.ts
│ ├── 05-assigning-types-to-variables.solution.ts
│ ├── 06-unions.problem.ts
│ ├── 06-unions.solution.ts
│ ├── 07-arrays.problem.ts
│ ├── 07-arrays.solution.1.ts
│ ├── 07-arrays.solution.2.ts
│ ├── 08-function-return-type-annotations.problem.ts
│ ├── 08-function-return-type-annotations.solution.ts
│ ├── 09-promises.problem.ts
│ ├── 09-promises.solution.1.ts
│ ├── 09-promises.solution.2.ts
│ ├── 09-promises.solution.3.ts
│ ├── 10-set.problem.ts
│ ├── 10-set.solution.ts
│ ├── 11-record.problem.ts
│ ├── 11-record.solution.1.ts
│ ├── 11-record.solution.2.ts
│ ├── 11-record.solution.3.ts
│ ├── 12-typeof-narrowing.problem.ts
│ ├── 12-typeof-narrowing.solution.ts
│ ├── 13-catch-blocks.problem.ts
│ ├── 13-catch-blocks.solution.1.ts
│ ├── 13-catch-blocks.solution.2.ts
│ ├── 13-catch-blocks.solution.3.ts
│ ├── 14-extends.problem.ts
│ ├── 14-extends.solution.ts
│ ├── 15-intersection.problem.ts
│ ├── 15-intersection.solution.ts
│ ├── 16-omit-and-pick.problem.ts
│ ├── 16-omit-and-pick.solution.1.ts
│ ├── 16-omit-and-pick.solution.2.ts
│ ├── 17-function-types.problem.ts
│ ├── 17-function-types.solution.1.ts
│ ├── 17-function-types.solution.2.ts
│ ├── 18-function-types-with-promises.problem.ts
│ ├── 18-function-types-with-promises.solution.ts
│ └── helpers/
│ └── type-utils.ts
├── tsconfig.json
└── vite.config.mts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/renovate-checks.yml
================================================
name: Renovate Checks
on:
push:
branches:
- "renovate/**"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Main
uses: actions/checkout@v4
with:
ref: main
path: repo
- name: Install Dependencies in Main
run: (cd repo && npm install)
- name: Create Snapshot In Main
run: (cd repo && npx tt-cli take-snapshot ./snap.md)
- name: Copy Snapshot To Outer Directory
run: mv repo/snap.md ./snap.md
- name: Delete Main Directory
run: rm -rf repo
- name: Checkout Branch
uses: actions/checkout@v4
with:
path: repo
- name: Install Dependencies in Branch
run: (cd repo && npm install)
- name: Move Snapshot To Branch
run: mv ./snap.md repo/snap.md
- name: Compare Snapshot In Branch
run: (cd repo && npx tt-cli compare-snapshot ./snap.md)
================================================
FILE: .github/workflows/section-repos.yml
================================================
name: Create Section Repos
on:
push:
branches:
- "main"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: git config --global user.email "total-typescript@bot.com"
- run: git config --global user.name "Total TypeScript Bot"
- run: npx @total-typescript/exercise-cli@latest create-section-repos
env:
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules
tsconfig.temp.json
dist
*.tsbuildinfo
*.prompt.*
.vscode/*.code-snippets
================================================
FILE: .vscode/settings.json
================================================
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"github.copilot.enable": {
"*": false,
},
"explorer.sortOrder": "mixed",
}
================================================
FILE: README.md
================================================
## Quickstart
Take the course on [Total TypeScript](https://totaltypescript.com/tutorials/beginners-typescript). There, you'll find:
- Video explanations for each problem and solution
- Transcripts
- Text explanations
- A built-in Stackblitz editor
```sh
# Installs all dependencies
npm install
# Asks you which exercise you'd like to run, and runs it
npm run exercise
```
## How to take the course
You'll notice that the course is split into exercises. Each exercise is split into a `*.problem` and a `*.solution`.
To take an exercise:
1. Run `npm run exercise`
2. Choose which exercise you'd like to run.
This course encourages **active, exploratory learning**. In the video, I'll explain a problem, and **you'll be asked to try to find a solution**. To attempt a solution, you'll need to:
1. Check out [TypeScript's docs](https://www.typescriptlang.org/docs/handbook/intro.html).
1. Try to find something that looks relevant.
1. Give it a go to see if it solves the problem.
You'll know if you've succeeded because the tests will pass.
**If you succeed**, or **if you get stuck**, unpause the video and check out the `*.solution`. You can see if your solution is better or worse than mine!
## Acknowledgements
Say thanks to Matt on [Twitter](https://twitter.com/mattpocockuk) or by joining his [Discord](https://discord.gg/8S5ujhfTB3). Consider signing up to his [Total TypeScript course](https://totaltypescript.com).
## Reference
### `npm run exercise`
Alias: `npm run e`
Open a prompt for choosing which exercise you'd like to run.
================================================
FILE: package.json
================================================
{
"name": "beginners-typescript",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"author": "Matt Pocock ",
"license": "GPL-3.0",
"devDependencies": {
"@total-typescript/exercise-cli": "^0.11.0",
"@total-typescript/tsconfig": "^1.0.3",
"@types/node": "^18.6.5",
"cross-fetch": "^3.1.5",
"jsdom": "^25.0.0",
"typescript": "^5.4.5",
"vite-tsconfig-paths": "^5.0.0",
"vitest": "^2.0.0"
},
"scripts": {
"exercise": "tt-cli run",
"e": "npm run exercise",
"solution": "tt-cli run --solution",
"s": "npm run solution",
"prepare": "tt-cli prepare-stackblitz",
"e-01": "tt-cli run 01",
"s-01": "tt-cli run 01 --solution",
"e-02": "tt-cli run 02",
"s-02": "tt-cli run 02 --solution",
"e-03": "tt-cli run 03",
"s-03": "tt-cli run 03 --solution",
"e-04": "tt-cli run 04",
"s-04": "tt-cli run 04 --solution",
"e-05": "tt-cli run 05",
"s-05": "tt-cli run 05 --solution",
"e-06": "tt-cli run 06",
"s-06": "tt-cli run 06 --solution",
"e-07": "tt-cli run 07",
"s-07": "tt-cli run 07 --solution",
"e-08": "tt-cli run 08",
"s-08": "tt-cli run 08 --solution",
"e-09": "tt-cli run 09",
"s-09": "tt-cli run 09 --solution",
"e-10": "tt-cli run 10",
"s-10": "tt-cli run 10 --solution",
"e-11": "tt-cli run 11",
"s-11": "tt-cli run 11 --solution",
"e-12": "tt-cli run 12",
"s-12": "tt-cli run 12 --solution",
"e-13": "tt-cli run 13",
"s-13": "tt-cli run 13 --solution",
"e-14": "tt-cli run 14",
"s-14": "tt-cli run 14 --solution",
"e-15": "tt-cli run 15",
"s-15": "tt-cli run 15 --solution",
"e-16": "tt-cli run 16",
"s-16": "tt-cli run 16 --solution",
"e-17": "tt-cli run 17",
"s-17": "tt-cli run 17 --solution",
"e-18": "tt-cli run 18",
"s-18": "tt-cli run 18 --solution"
},
"dependencies": {
"@types/express": "^4.17.13",
"express": "^4.18.1",
"zod": "^3.17.10"
}
}
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"packageRules": [
{
"packagePatterns": ["*"],
"excludePackagePatterns": [
"typescript",
"vitest",
"jsdom",
"prettier",
"vite-tsconfig-paths",
"react",
"@types/react",
"@total-typescript/exercise-cli",
"zod"
],
"enabled": false
}
]
}
================================================
FILE: src/01-number.problem.ts
================================================
import { expect, it } from "vitest";
export const addTwoNumbers = (a, b) => {
return a + b;
};
it("Should add the two numbers together", () => {
expect(addTwoNumbers(2, 4)).toEqual(6);
expect(addTwoNumbers(10, 10)).toEqual(20);
});
================================================
FILE: src/01-number.solution.ts
================================================
import { expect, it } from "vitest";
export const addTwoNumbers = (a: number, b: number) => {
return a + b;
};
it("Should add the two numbers together", () => {
expect(addTwoNumbers(2, 4)).toEqual(6);
expect(addTwoNumbers(10, 10)).toEqual(20);
});
================================================
FILE: src/02-object-param.problem.ts
================================================
import { expect, it } from "vitest";
export const addTwoNumbers = (params) => {
return params.first + params.second;
};
it("Should add the two numbers together", () => {
expect(
addTwoNumbers({
first: 2,
second: 4,
}),
).toEqual(6);
expect(
addTwoNumbers({
first: 10,
second: 20,
}),
).toEqual(30);
});
================================================
FILE: src/02-object-param.solution.1.ts
================================================
import { expect, it } from "vitest";
export const addTwoNumbers = (params: { first: number; second: number }) => {
return params.first + params.second;
};
it("Should add the two numbers together", () => {
expect(
addTwoNumbers({
first: 2,
second: 4,
}),
).toEqual(6);
expect(
addTwoNumbers({
first: 10,
second: 20,
}),
).toEqual(30);
});
================================================
FILE: src/02-object-param.solution.2.ts
================================================
import { expect, it } from "vitest";
type AddTwoNumbersArgs = {
first: number;
second: number;
};
export const addTwoNumbers = (params: AddTwoNumbersArgs) => {
return params.first + params.second;
};
it("Should add the two numbers together", () => {
expect(
addTwoNumbers({
first: 2,
second: 4,
}),
).toEqual(6);
expect(
addTwoNumbers({
first: 10,
second: 20,
}),
).toEqual(30);
});
================================================
FILE: src/02-object-param.solution.3.ts
================================================
import { expect, it } from "vitest";
interface AddTwoNumbersArgs {
first: number;
second: number;
}
export const addTwoNumbers = (params: AddTwoNumbersArgs) => {
return params.first + params.second;
};
it("Should add the two numbers together", () => {
expect(
addTwoNumbers({
first: 2,
second: 4,
}),
).toEqual(6);
expect(
addTwoNumbers({
first: 10,
second: 20,
}),
).toEqual(30);
});
================================================
FILE: src/03-optional-properties.problem.ts
================================================
import { expect, it } from "vitest";
export const getName = (params: { first: string; last: string }) => {
if (params.last) {
return `${params.first} ${params.last}`;
}
return params.first;
};
it("Should work with just the first name", () => {
const name = getName({
first: "Matt",
});
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName({
first: "Matt",
last: "Pocock",
});
expect(name).toEqual("Matt Pocock");
});
================================================
FILE: src/03-optional-properties.solution.ts
================================================
import { expect, it } from "vitest";
export const getName = (params: { first: string; last?: string }) => {
if (params.last) {
return `${params.first} ${params.last}`;
}
return params.first;
};
it("Should work with just the first name", () => {
const name = getName({
first: "Matt",
});
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName({
first: "Matt",
last: "Pocock",
});
expect(name).toEqual("Matt Pocock");
});
================================================
FILE: src/04-optional-params.problem.ts
================================================
import { expect, it } from "vitest";
export const getName = (first: string, last: string) => {
if (last) {
return `${first} ${last}`;
}
return first;
};
it("Should work with just the first name", () => {
const name = getName("Matt");
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName("Matt", "Pocock");
expect(name).toEqual("Matt Pocock");
});
================================================
FILE: src/04-optional-params.solution.ts
================================================
import { expect, it } from "vitest";
export const getName = (first: string, last?: string) => {
if (last) {
return `${first} ${last}`;
}
return first;
};
it("Should work with just the first name", () => {
const name = getName("Matt");
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName("Matt", "Pocock");
expect(name).toEqual("Matt Pocock");
});
================================================
FILE: src/05-assigning-types-to-variables.problem.ts
================================================
import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
isAdmin: boolean;
}
/**
* How do we ensure that defaultUser is of type User
* at THIS LINE - not further down in the code?
*/
const defaultUser = {};
const getUserId = (user: User) => {
return user.id;
};
it("Should get the user id", () => {
expect(getUserId(defaultUser)).toEqual(1);
});
================================================
FILE: src/05-assigning-types-to-variables.solution.ts
================================================
import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
isAdmin: boolean;
}
/**
* How do we ensure that defaultUser is of type User
* at THIS LINE - not further down in the code?
*/
const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
isAdmin: true,
};
const getUserId = (user: User) => {
return user.id;
};
it("Should get the user id", () => {
expect(getUserId(defaultUser)).toEqual(1);
});
================================================
FILE: src/06-unions.problem.ts
================================================
interface User {
id: number;
firstName: string;
lastName: string;
/**
* How do we ensure that role is only one of:
* - 'admin'
* - 'user'
* - 'super-admin'
*/
role: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
// @ts-expect-error
role: "I_SHOULD_NOT_BE_ALLOWED",
};
================================================
FILE: src/06-unions.solution.ts
================================================
interface User {
id: number;
firstName: string;
lastName: string;
/**
* How do we ensure that role is only one of:
* - 'admin'
* - 'user'
* - 'super-admin'
*/
role: "admin" | "user" | "super-admin";
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
// @ts-expect-error
role: "I_SHOULD_NOT_BE_ALLOWED",
};
================================================
FILE: src/07-arrays.problem.ts
================================================
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Post;
}
interface Post {
id: number;
title: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
role: "admin",
posts: [
{
id: 1,
title: "How I eat so much cheese",
},
{
id: 2,
title: "Why I don't eat more vegetables",
},
],
};
================================================
FILE: src/07-arrays.solution.1.ts
================================================
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Post[];
}
interface Post {
id: number;
title: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
role: "admin",
posts: [
{
id: 1,
title: "How I eat so much cheese",
},
{
id: 2,
title: "Why I don't eat more vegetables",
},
],
};
================================================
FILE: src/07-arrays.solution.2.ts
================================================
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Array;
}
interface Post {
id: number;
title: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
role: "admin",
posts: [
{
id: 1,
title: "How I eat so much cheese",
},
{
id: 2,
title: "Why I don't eat more vegetables",
},
],
};
================================================
FILE: src/08-function-return-type-annotations.problem.ts
================================================
import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Array;
}
interface Post {
id: number;
title: string;
}
/**
* How do we ensure that makeUser ALWAYS
* returns a user?
*/
const makeUser = () => {
return {};
};
it("Should return a valid user", () => {
const user = makeUser();
expect(user.id).toBeTypeOf("number");
expect(user.firstName).toBeTypeOf("string");
expect(user.lastName).toBeTypeOf("string");
expect(user.role).to.be.oneOf(["super-admin", "admin", "user"]);
expect(user.posts[0].id).toBeTypeOf("number");
expect(user.posts[0].title).toBeTypeOf("string");
});
================================================
FILE: src/08-function-return-type-annotations.solution.ts
================================================
import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Array;
}
interface Post {
id: number;
title: string;
}
/**
* How do we ensure that makeUser ALWAYS
* returns a user?
*/
const makeUser = (): User => {
return {
id: 1,
firstName: "Matt",
lastName: "Pocock",
role: "admin",
posts: [
{
id: 1,
title: "How I eat so much cheese",
},
],
};
};
it("Should return a valid user", () => {
const user = makeUser();
expect(user.id).toBeTypeOf("number");
expect(user.firstName).toBeTypeOf("string");
expect(user.lastName).toBeTypeOf("string");
expect(user.role).to.be.oneOf(["super-admin", "admin", "user"]);
expect(user.posts[0].id).toBeTypeOf("number");
expect(user.posts[0].title).toBeTypeOf("string");
});
================================================
FILE: src/09-promises.problem.ts
================================================
interface LukeSkywalker {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
birth_year: string;
gender: string;
}
export const fetchLukeSkywalker = async (): LukeSkywalker => {
const data = await fetch("https://swapi.py4e.com/api/people/1").then(
(res) => {
return res.json();
}
);
return data;
};
================================================
FILE: src/09-promises.solution.1.ts
================================================
interface LukeSkywalker {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
birth_year: string;
gender: string;
}
export const fetchLukeSkywalker = async (): Promise => {
const data = await fetch("https://swapi.py4e.com/api/people/1").then(
(res) => {
return res.json();
}
);
return data;
};
================================================
FILE: src/09-promises.solution.2.ts
================================================
interface LukeSkywalker {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
birth_year: string;
gender: string;
}
export const fetchLukeSkywalker = async () => {
const data = await fetch("https://swapi.py4e.com/api/people/1").then(
(res) => {
return res.json();
}
);
return data as LukeSkywalker;
};
================================================
FILE: src/09-promises.solution.3.ts
================================================
interface LukeSkywalker {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
birth_year: string;
gender: string;
}
export const fetchLukeSkywalker = async () => {
const data: LukeSkywalker = await fetch(
"https://swapi.py4e.com/api/people/1"
).then((res) => {
return res.json();
});
return data;
};
================================================
FILE: src/10-set.problem.ts
================================================
import { expect, it } from "vitest";
import { Equal, Expect } from "./helpers/type-utils";
const guitarists = new Set();
guitarists.add("Jimi Hendrix");
guitarists.add("Eric Clapton");
it("Should contain Jimi and Eric", () => {
expect(guitarists.has("Jimi Hendrix")).toEqual(true);
expect(guitarists.has("Eric Clapton")).toEqual(true);
});
it("Should give a type error when you try to pass a non-string", () => {
// @ts-expect-error
guitarists.add(2);
});
it("Should be typed as an array of strings", () => {
const guitaristsAsArray = Array.from(guitarists);
type tests = [Expect>];
});
================================================
FILE: src/10-set.solution.ts
================================================
import { expect, it } from "vitest";
import { Equal, Expect } from "./helpers/type-utils";
const guitarists = new Set();
guitarists.add("Jimi Hendrix");
guitarists.add("Eric Clapton");
it("Should contain Jimi and Eric", () => {
expect(guitarists.has("Jimi Hendrix")).toEqual(true);
expect(guitarists.has("Eric Clapton")).toEqual(true);
});
it("Should give a type error when you try to pass a non-string", () => {
// @ts-expect-error
guitarists.add(2);
});
it("Should be typed as an array of strings", () => {
const guitaristsAsArray = Array.from(guitarists);
type tests = [Expect>];
});
================================================
FILE: src/11-record.problem.ts
================================================
import { expect, it } from "vitest";
const createCache = () => {
const cache = {};
const add = (id: string, value: string) => {
cache[id] = value;
};
const remove = (id: string) => {
delete cache[id];
};
return {
cache,
add,
remove,
};
};
it("Should add values to the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
expect(cache.cache["123"]).toEqual("Matt");
});
it("Should remove values from the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
cache.remove("123");
expect(cache.cache["123"]).toEqual(undefined);
});
================================================
FILE: src/11-record.solution.1.ts
================================================
import { expect, it } from "vitest";
const createCache = () => {
const cache: Record = {};
const add = (id: string, value: string) => {
cache[id] = value;
};
const remove = (id: string) => {
delete cache[id];
};
return {
cache,
add,
remove,
};
};
it("Should add values to the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
expect(cache.cache["123"]).toEqual("Matt");
});
it("Should remove values from the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
cache.remove("123");
expect(cache.cache["123"]).toEqual(undefined);
});
================================================
FILE: src/11-record.solution.2.ts
================================================
import { expect, it } from "vitest";
const createCache = () => {
const cache: {
[id: string]: string;
} = {};
const add = (id: string, value: string) => {
cache[id] = value;
};
const remove = (id: string) => {
delete cache[id];
};
return {
cache,
add,
remove,
};
};
it("Should add values to the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
expect(cache.cache["123"]).toEqual("Matt");
});
it("Should remove values from the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
cache.remove("123");
expect(cache.cache["123"]).toEqual(undefined);
});
================================================
FILE: src/11-record.solution.3.ts
================================================
import { expect, it } from "vitest";
interface Cache {
[id: string]: string;
}
const createCache = () => {
const cache: Cache = {};
const add = (id: string, value: string) => {
cache[id] = value;
};
const remove = (id: string) => {
delete cache[id];
};
return {
cache,
add,
remove,
};
};
it("Should add values to the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
expect(cache.cache["123"]).toEqual("Matt");
});
it("Should remove values from the cache", () => {
const cache = createCache();
cache.add("123", "Matt");
cache.remove("123");
expect(cache.cache["123"]).toEqual(undefined);
});
================================================
FILE: src/12-typeof-narrowing.problem.ts
================================================
import { expect, it } from "vitest";
const coerceAmount = (amount: number | { amount: number }) => {};
it("Should return the amount when passed an object", () => {
expect(coerceAmount({ amount: 20 })).toEqual(20);
});
it("Should return the amount when passed a number", () => {
expect(coerceAmount(20)).toEqual(20);
});
================================================
FILE: src/12-typeof-narrowing.solution.ts
================================================
import { expect, it } from "vitest";
const coerceAmount = (amount: number | { amount: number }) => {
if (typeof amount === "number") {
return amount;
}
return amount.amount;
};
it("Should return the amount when passed an object", () => {
expect(coerceAmount({ amount: 20 })).toEqual(20);
});
it("Should return the amount when passed a number", () => {
expect(coerceAmount(20)).toEqual(20);
});
================================================
FILE: src/13-catch-blocks.problem.ts
================================================
import { expect, it } from "vitest";
const tryCatchDemo = (state: "fail" | "succeed") => {
try {
if (state === "fail") {
throw new Error("Failure!");
}
} catch (e) {
return e.message;
}
};
it("Should return the message when it fails", () => {
expect(tryCatchDemo("fail")).toEqual("Failure!");
});
================================================
FILE: src/13-catch-blocks.solution.1.ts
================================================
import { expect, it } from "vitest";
const tryCatchDemo = (state: "fail" | "succeed") => {
try {
if (state === "fail") {
throw new Error("Failure!");
}
} catch (e: any) {
return e.message;
}
};
it("Should return the message when it fails", () => {
expect(tryCatchDemo("fail")).toEqual("Failure!");
});
================================================
FILE: src/13-catch-blocks.solution.2.ts
================================================
import { expect, it } from "vitest";
const tryCatchDemo = (state: "fail" | "succeed") => {
try {
if (state === "fail") {
throw new Error("Failure!");
}
} catch (e) {
return (e as Error).message;
}
};
it("Should return the message when it fails", () => {
expect(tryCatchDemo("fail")).toEqual("Failure!");
});
================================================
FILE: src/13-catch-blocks.solution.3.ts
================================================
import { expect, it } from "vitest";
const tryCatchDemo = (state: "fail" | "succeed") => {
try {
if (state === "fail") {
throw new Error("Failure!");
}
} catch (e) {
if (e instanceof Error) {
return e.message;
}
}
};
it("Should return the message when it fails", () => {
expect(tryCatchDemo("fail")).toEqual("Failure!");
});
================================================
FILE: src/14-extends.problem.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
/**
* Here, the id property is shared between all three
* interfaces. Can you find a way to refactor this to
* make it more DRY?
*/
interface User {
id: string;
firstName: string;
lastName: string;
}
interface Post {
id: string;
title: string;
body: string;
}
interface Comment {
id: string;
comment: string;
}
type tests = [
Expect>,
Expect>,
Expect>,
];
================================================
FILE: src/14-extends.solution.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
/**
* Here, the id property is shared between all three
* interfaces. Can you find a way to refactor this to
* make it more DRY?
*/
interface Base {
id: string;
}
interface User extends Base {
firstName: string;
lastName: string;
}
interface Post extends Base {
title: string;
body: string;
}
interface Comment extends Base {
comment: string;
}
type tests = [
Expect>,
Expect>,
Expect>,
];
================================================
FILE: src/15-intersection.problem.ts
================================================
interface User {
id: string;
firstName: string;
lastName: string;
}
interface Post {
id: string;
title: string;
body: string;
}
/**
* How do we type this return statement so it's both
* User AND { posts: Post[] }
*/
export const getDefaultUserAndPosts = (): unknown => {
return {
id: "1",
firstName: "Matt",
lastName: "Pocock",
posts: [
{
id: "1",
title: "How I eat so much cheese",
body: "It's pretty edam difficult",
},
],
};
};
const userAndPosts = getDefaultUserAndPosts();
console.log(userAndPosts.posts[0]);
================================================
FILE: src/15-intersection.solution.ts
================================================
interface User {
id: string;
firstName: string;
lastName: string;
}
interface Post {
id: string;
title: string;
body: string;
}
export const getDefaultUserAndPosts = (): User & { posts: Post[] } => {
return {
id: "1",
firstName: "Matt",
lastName: "Pocock",
posts: [
{
id: "1",
title: "How I eat so much cheese",
body: "It's pretty edam difficult",
},
],
};
};
const userAndPosts = getDefaultUserAndPosts();
console.log(userAndPosts.posts[0]);
================================================
FILE: src/16-omit-and-pick.problem.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
interface User {
id: string;
firstName: string;
lastName: string;
}
/**
* How do we create a new object type with _only_ the
* firstName and lastName properties of User?
*/
type MyType = unknown;
type tests = [Expect>];
================================================
FILE: src/16-omit-and-pick.solution.1.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
interface User {
id: string;
firstName: string;
lastName: string;
}
/**
* How do we create a new object type with _only_ the
* firstName and lastName properties of User?
*/
type MyType = Omit;
type tests = [Expect>];
================================================
FILE: src/16-omit-and-pick.solution.2.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
interface User {
id: string;
firstName: string;
lastName: string;
}
/**
* How do we create a new object type with _only_ the
* firstName and lastName properties of User?
*/
type MyType = Pick;
type tests = [Expect>];
================================================
FILE: src/17-function-types.problem.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
/**
* How do we type onFocusChange?
*/
const addListener = (onFocusChange: unknown) => {
window.addEventListener("focus", () => {
onFocusChange(true);
});
window.addEventListener("blur", () => {
onFocusChange(false);
});
};
addListener((isFocused) => {
console.log({ isFocused });
type tests = [Expect>];
});
================================================
FILE: src/17-function-types.solution.1.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
/**
* How do we type onFocusChange?
*/
const addListener = (onFocusChange: (isFocused: boolean) => void) => {
window.addEventListener("focus", () => {
onFocusChange(true);
});
window.addEventListener("blur", () => {
onFocusChange(false);
});
};
addListener((isFocused) => {
console.log({ isFocused });
type tests = [Expect>];
});
================================================
FILE: src/17-function-types.solution.2.ts
================================================
import { Equal, Expect } from "./helpers/type-utils";
/**
* How do we type onFocusChange?
*/
type FocusListener = (isFocused: boolean) => void;
const addListener = (onFocusChange: FocusListener) => {
window.addEventListener("focus", () => {
onFocusChange(true);
});
window.addEventListener("blur", () => {
onFocusChange(false);
});
};
addListener((isFocused) => {
console.log({ isFocused });
type tests = [Expect>];
});
================================================
FILE: src/18-function-types-with-promises.problem.ts
================================================
import { expect, it } from "vitest";
interface User {
id: string;
firstName: string;
lastName: string;
}
const createThenGetUser = async (
createUser: unknown,
getUser: unknown,
): Promise => {
const userId: string = await createUser();
const user = await getUser(userId);
return user;
};
it("Should create the user, then get them", async () => {
const user = await createThenGetUser(
async () => "123",
async (id) => ({
id,
firstName: "Matt",
lastName: "Pocock",
}),
);
expect(user).toEqual({
id: "123",
firstName: "Matt",
lastName: "Pocock",
});
});
================================================
FILE: src/18-function-types-with-promises.solution.ts
================================================
import { expect, it } from "vitest";
interface User {
id: string;
firstName: string;
lastName: string;
}
const createThenGetUser = async (
createUser: () => Promise,
getUser: (id: string) => Promise,
): Promise => {
const userId: string = await createUser();
const user = await getUser(userId);
return user;
};
it("Should create the user, then get them", async () => {
const user = await createThenGetUser(
async () => "123",
async (id) => ({
id,
firstName: "Matt",
lastName: "Pocock",
}),
);
expect(user).toEqual({
id: "123",
firstName: "Matt",
lastName: "Pocock",
});
});
================================================
FILE: src/helpers/type-utils.ts
================================================
export type Expect = T;
export type ExpectTrue = T;
export type ExpectFalse = T;
export type IsTrue = T;
export type IsFalse = T;
export type Equal = (() => T extends X ? 1 : 2) extends <
T,
>() => T extends Y ? 1 : 2
? true
: false;
export type NotEqual = true extends Equal ? false : true;
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
export type IsAny = 0 extends 1 & T ? true : false;
export type NotAny = true extends IsAny ? false : true;
export type Debug = { [K in keyof T]: T[K] };
export type MergeInsertions = T extends object
? { [K in keyof T]: MergeInsertions }
: T;
export type Alike = Equal, MergeInsertions>;
export type ExpectExtends = EXPECTED extends VALUE
? true
: false;
export type ExpectValidArgs<
FUNC extends (...args: any[]) => any,
ARGS extends any[],
> = ARGS extends Parameters ? true : false;
export type UnionToIntersection = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;
================================================
FILE: tsconfig.json
================================================
{
"extends": "@total-typescript/tsconfig/bundler",
"compilerOptions": {
"noUncheckedIndexedAccess": false,
"verbatimModuleSyntax": false
},
"include": [
"src"
]
}
================================================
FILE: vite.config.mts
================================================
import { defineConfig } from "vitest/config";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
test: {
include: ["src/**/*{problem,solution,explainer}*.{ts,tsx}"],
passWithNoTests: true,
environment: "jsdom",
},
plugins: [tsconfigPaths()],
});