diff --git a/projects/using-ide-features/typearium/01-favorite-animals/README.md b/projects/using-ide-features/typearium/01-favorite-animals/README.md new file mode 100644 index 00000000..9c062f55 --- /dev/null +++ b/projects/using-ide-features/typearium/01-favorite-animals/README.md @@ -0,0 +1,13 @@ +# Step 1: Favorite Animals + +We keep track of our visitors' favorite animals by a combination of informal polling and park ticket data. +The program we use to work with that data stores that list of favorites as an array of strings... duplicated in multiple places. +We have to modify all those places whenever the list needs updating. + +Could you please use your IDE to extract the array into a shared constant, and re-use that constant? + +## Files + +- `index.ts`: Refactor the functions here +- `index.test.ts`: Tests verifying the refactored functions +- `solution.ts`: Solution code diff --git a/projects/using-ide-features/typearium/01-favorite-animals/index.test.ts b/projects/using-ide-features/typearium/01-favorite-animals/index.test.ts new file mode 100644 index 00000000..b6710490 --- /dev/null +++ b/projects/using-ide-features/typearium/01-favorite-animals/index.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "@jest/globals"; + +import * as index from "./index"; +import * as solution from "./solution"; + +const { checkIsAnyAnimalFavorite, getFavoriteAnimals, logFavoriteAnimals } = + process.env.TEST_SOLUTIONS ? solution : index; + +describe(checkIsAnyAnimalFavorite, () => { + it("returns true for a favorite animal", () => { + expect(checkIsAnyAnimalFavorite("parakeet")).toBe(true); + }); + + it("returns true for a favorite animal then a non-favorite animal", () => { + expect(checkIsAnyAnimalFavorite("parakeet", "snake")).toBe(true); + }); + + it("returns true for a non-favorite animal then a favorite animal", () => { + expect(checkIsAnyAnimalFavorite("snake", "parakeet")).toBe(true); + }); + + it("returns false for a non-favorite animal", () => { + expect(checkIsAnyAnimalFavorite("snake")).toBe(false); + }); + + it("does not include its own list of animals", () => { + expect(checkIsAnyAnimalFavorite.toString()).not.toMatch(/parakeet/); + }); +}); + +describe(getFavoriteAnimals, () => { + it("returns all favorite animals by default", () => { + expect(getFavoriteAnimals()).toEqual([ + "parakeet", + "macaw", + "cat", + "monkey", + "elephant", + "alpaca", + "fox", + ]); + }); + + it("returns a limited quantity of favorite animals when a quantity is provided", () => { + expect(getFavoriteAnimals(3)).toEqual(["parakeet", "macaw", "cat"]); + }); + + it("does not include its own list of animals", () => { + expect(getFavoriteAnimals.toString()).not.toMatch(/parakeet/); + }); +}); + +describe(logFavoriteAnimals, () => { + it("does not include its own list of animals", () => { + expect(logFavoriteAnimals.toString()).not.toMatch(/parakeet/); + }); +}); diff --git a/projects/using-ide-features/typearium/01-favorite-animals/index.ts b/projects/using-ide-features/typearium/01-favorite-animals/index.ts new file mode 100644 index 00000000..e5beb06a --- /dev/null +++ b/projects/using-ide-features/typearium/01-favorite-animals/index.ts @@ -0,0 +1,35 @@ +// Refactor here! ✨ + +export function checkIsAnyAnimalFavorite(...animals: string[]) { + const favoriteAnimalsUnique = new Set([ + "parakeet", + "macaw", + "cat", + "monkey", + "elephant", + "alpaca", + "fox", + ]); + + return animals.some((animal) => favoriteAnimalsUnique.has(animal)); +} + +export function getFavoriteAnimals(max = Infinity) { + return [ + "parakeet", + "macaw", + "cat", + "monkey", + "elephant", + "alpaca", + "fox", + ].slice(0, max); +} + +export function logFavoriteAnimals() { + ["parakeet", "macaw", "cat", "monkey", "elephant", "alpaca", "fox"].forEach( + (animal, i) => { + console.log(`I like ${animal} number ${i}!`); + } + ); +} diff --git a/projects/using-ide-features/typearium/01-favorite-animals/solution.ts b/projects/using-ide-features/typearium/01-favorite-animals/solution.ts new file mode 100644 index 00000000..d15470e6 --- /dev/null +++ b/projects/using-ide-features/typearium/01-favorite-animals/solution.ts @@ -0,0 +1,27 @@ +// Refactor here! ✨ + +const favoriteAnimals = [ + "parakeet", + "macaw", + "cat", + "monkey", + "elephant", + "alpaca", + "fox", +]; + +export function checkIsAnyAnimalFavorite(...animals: string[]) { + const favoriteAnimalsUnique = new Set(favoriteAnimals); + + return animals.some((animal) => favoriteAnimalsUnique.has(animal)); +} + +export function getFavoriteAnimals(quantity = Infinity) { + return favoriteAnimals.slice(0, quantity); +} + +export function logFavoriteAnimals() { + favoriteAnimals.forEach((animal, i) => { + console.log(`I like ${animal} number ${i}!`); + }); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/README.md b/projects/using-ide-features/typearium/02-species-collections/README.md new file mode 100644 index 00000000..c0afd822 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/README.md @@ -0,0 +1,24 @@ +# Step 2: Species Collections + +Lovely, now we can log our favorite animals much more easily! +Thank you! + +... + +right click to move to new file + +add nesting for fauna/\*, flora/\* + +typescript names the files like "MammalsSettings.ts" - change name to just "fauna/mammals.ts" + +... + +:::tip +once you're done, use find-all-references on `onlyTruthy` and the various settings interfaces to practice navigating with IDE features. +::: + +## Files + +- `index.ts`: Refactor the functions here +- `index.test.ts`: Tests verifying the refactored functions +- `solution.ts`: Solution code diff --git a/projects/using-ide-features/typearium/02-species-collections/fauna.solution.ts b/projects/using-ide-features/typearium/02-species-collections/fauna.solution.ts new file mode 100644 index 00000000..91560219 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/fauna.solution.ts @@ -0,0 +1,11 @@ +import { getMammals, MammalsSettings } from "./fauna/mammals.solution"; +import { getReptiles, ReptilesSettings } from "./fauna/reptiles.solution"; + +export interface FaunaSettings { + mammals?: MammalsSettings; + reptiles?: ReptilesSettings; +} + +export function getFauna(settings?: FaunaSettings) { + return [getMammals(settings?.mammals), getReptiles(settings?.reptiles)]; +} diff --git a/projects/using-ide-features/typearium/02-species-collections/fauna/mammals.solution.ts b/projects/using-ide-features/typearium/02-species-collections/fauna/mammals.solution.ts new file mode 100644 index 00000000..db22f5c3 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/fauna/mammals.solution.ts @@ -0,0 +1,17 @@ +import { onlyTruthy } from "../utils/onlyTruthy.solution"; + +export interface MammalsSettings { + cute?: boolean; + deadly?: boolean; +} + +export function getMammals(settings?: MammalsSettings) { + return onlyTruthy( + settings?.cute && [ + "cats", + "dogs", + settings?.deadly && "monty python rabbit", + ], + settings?.deadly && ["lion", "tiger"] + ); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/fauna/reptiles.solution.ts b/projects/using-ide-features/typearium/02-species-collections/fauna/reptiles.solution.ts new file mode 100644 index 00000000..4c29c56b --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/fauna/reptiles.solution.ts @@ -0,0 +1,13 @@ +import { onlyTruthy } from "../utils/onlyTruthy.solution"; + +export interface ReptilesSettings { + ferocious?: boolean; + small?: boolean; +} + +export function getReptiles(settings?: ReptilesSettings) { + return onlyTruthy( + settings?.ferocious && "dragon", + settings?.small && ["frog", "gecko"] + ); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/flora.solution.ts b/projects/using-ide-features/typearium/02-species-collections/flora.solution.ts new file mode 100644 index 00000000..2dc403d8 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/flora.solution.ts @@ -0,0 +1,11 @@ +import { FlowersSettings, getFlowers } from "./flora/flowers.solution"; +import { TreesSettings, getTrees } from "./flora/trees.solution"; + +export interface FloraSettings { + flowers?: FlowersSettings; + trees?: TreesSettings; +} + +export function getFlora(settings?: FloraSettings) { + return [getFlowers(settings?.flowers), getTrees(settings?.trees)]; +} diff --git a/projects/using-ide-features/typearium/02-species-collections/flora/flowers.solution.ts b/projects/using-ide-features/typearium/02-species-collections/flora/flowers.solution.ts new file mode 100644 index 00000000..4f65a71e --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/flora/flowers.solution.ts @@ -0,0 +1,13 @@ +import { onlyTruthy } from "../utils/onlyTruthy.solution"; + +export interface FlowersSettings { + colorful?: boolean; + prickly?: boolean; +} + +export function getFlowers(settings?: FlowersSettings) { + return onlyTruthy( + settings?.colorful && ["carnation", "lilac", "tulip"], + settings?.colorful && settings?.prickly && "rose" + ); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/flora/trees.solution.ts b/projects/using-ide-features/typearium/02-species-collections/flora/trees.solution.ts new file mode 100644 index 00000000..26ca2967 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/flora/trees.solution.ts @@ -0,0 +1,13 @@ +import { onlyTruthy } from "../utils/onlyTruthy.solution"; + +export interface TreesSettings { + evergreen?: boolean; + fruitBearing?: boolean; +} + +export function getTrees(settings?: TreesSettings) { + return onlyTruthy( + settings?.evergreen && "pine", + settings?.fruitBearing && ["apple", "pear"] + ); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/index.solution.ts b/projects/using-ide-features/typearium/02-species-collections/index.solution.ts new file mode 100644 index 00000000..58f4137c --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/index.solution.ts @@ -0,0 +1,14 @@ +// Refactor here! ✨ + +import { FaunaSettings, getFauna } from "./fauna.solution"; +import { FloraSettings, getFlora } from "./flora.solution"; +import { onlyTruthy } from "./utils/onlyTruthy.solution"; + +export interface EverythingSettings { + fauna?: FaunaSettings; + flora?: FloraSettings; +} + +export function getEverything(settings?: EverythingSettings) { + return onlyTruthy(getFauna(settings?.fauna), getFlora(settings?.flora)); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/index.test.ts b/projects/using-ide-features/typearium/02-species-collections/index.test.ts new file mode 100644 index 00000000..b99ccf33 --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/index.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it, test } from "@jest/globals"; + +import * as index from "./index"; +import * as solution from "./index.solution"; + +const { getEverything } = process.env.TEST_SOLUTIONS ? solution : index; + +describe(getEverything, () => { + it("returns nothing with no settings", () => { + expect(getEverything()).toMatchInlineSnapshot(`Array []`); + }); + + it("returns everything with all settings", () => { + expect( + getEverything({ + fauna: { + mammals: { + cute: true, + deadly: true, + }, + + reptiles: { + ferocious: true, + small: true, + }, + }, + + flora: { + flowers: { + colorful: true, + prickly: true, + }, + + trees: { + evergreen: true, + fruitBearing: true, + }, + }, + }) + ).toMatchInlineSnapshot(` + Array [ + "cats", + "dogs", + "monty python rabbit", + "lion", + "tiger", + "dragon", + "frog", + "gecko", + "carnation", + "lilac", + "tulip", + "rose", + "pine", + "apple", + "pear", + ] + `); + }); +}); diff --git a/projects/using-ide-features/typearium/02-species-collections/index.ts b/projects/using-ide-features/typearium/02-species-collections/index.ts new file mode 100644 index 00000000..00af396e --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/index.ts @@ -0,0 +1,100 @@ +// Refactor here! ✨ + +export type DeepStringsMaybe = + | string + | boolean + | undefined + | DeepStringsMaybe[]; + +function onlyTruthy(...items: DeepStringsMaybe[]): string[] { + return items.flatMap((item) => { + if (typeof item === "string") { + return item; + } + + if (item instanceof Array) { + return item.flatMap((subItem) => onlyTruthy(subItem)); + } + + return []; + }); +} + +interface MammalsSettings { + cute?: boolean; + deadly?: boolean; +} + +function getMammals(settings?: MammalsSettings) { + return onlyTruthy( + settings?.cute && [ + "cats", + "dogs", + settings?.deadly && "monty python rabbit", + ], + settings?.deadly && ["lion", "tiger"] + ); +} + +interface ReptilesSettings { + ferocious?: boolean; + small?: boolean; +} + +function getReptiles(settings?: ReptilesSettings) { + return onlyTruthy( + settings?.ferocious && "dragon", + settings?.small && ["frog", "gecko"] + ); +} + +interface FaunaSettings { + mammals?: MammalsSettings; + reptiles?: ReptilesSettings; +} + +function getFauna(settings?: FaunaSettings) { + return [getMammals(settings?.mammals), getReptiles(settings?.reptiles)]; +} + +interface FlowersSettings { + colorful?: boolean; + prickly?: boolean; +} + +function getFlowers(settings?: FlowersSettings) { + return onlyTruthy( + settings?.colorful && ["carnation", "lilac", "tulip"], + settings?.colorful && settings?.prickly && "rose" + ); +} + +interface TreesSettings { + evergreen?: boolean; + fruitBearing?: boolean; +} + +function getTrees(settings?: TreesSettings) { + return onlyTruthy( + settings?.evergreen && "pine", + settings?.fruitBearing && ["apple", "pear"] + ); +} + +interface FloraSettings { + flowers?: FlowersSettings; + trees?: TreesSettings; +} + +function getFlora(settings?: FloraSettings) { + return [getFlowers(settings?.flowers), getTrees(settings?.trees)]; +} + +export interface EverythingSettings { + fauna?: FaunaSettings; + flora?: FloraSettings; +} + +export function getEverything(settings?: EverythingSettings) { + return onlyTruthy(getFauna(settings?.fauna), getFlora(settings?.flora)); +} diff --git a/projects/using-ide-features/typearium/02-species-collections/utils/onlyTruthy.solution.ts b/projects/using-ide-features/typearium/02-species-collections/utils/onlyTruthy.solution.ts new file mode 100644 index 00000000..5a83288d --- /dev/null +++ b/projects/using-ide-features/typearium/02-species-collections/utils/onlyTruthy.solution.ts @@ -0,0 +1,19 @@ +export type DeepStringsMaybe = + | string + | boolean + | undefined + | DeepStringsMaybe[]; + +export function onlyTruthy(...items: DeepStringsMaybe[]): string[] { + return items.flatMap((item) => { + if (typeof item === "string") { + return item; + } + + if (item instanceof Array) { + return item.flatMap((subItem) => onlyTruthy(subItem)); + } + + return []; + }); +} diff --git a/projects/using-ide-features/typearium/README.md b/projects/using-ide-features/typearium/README.md new file mode 100644 index 00000000..bba10076 --- /dev/null +++ b/projects/using-ide-features/typearium/README.md @@ -0,0 +1,34 @@ +# Typearium + +> A [Learning TypeScript > Using IDE Features](https://learning-typescript.com/using-ide-features) 🥗 appetizer project. + +Welcome to the Typearium: a combination acquarium/terrarium/vivarium that keeps animals in the form of TypeScript types. +We're quite niche. + +Because we're so niche, we haven't had the budget to invest in updating our (somewhat legacy) code. +It's a good thing we originally wrote it in TypeScript, because we're hoping you can use IDE features powered by TypeScript to help with some refactors! + +## Setup + +In one terminal, start the TypeScript compiler in watch mode: + +```shell +tsc --watch +``` + +In another terminal, run Jest on whichever step you're working on. +For example, to run tests for the first step in watch mode: + +```shell +npm test -- 1 --watch +``` + +## Steps + +- [1. Favorite Animals](./01-favorite-animals) +- [2. Species Trees](./02-species-trees) + +## Notes + +- Remember, this is meant to exercise your IDE refactoring skills. +- Don't import code from one step into another. diff --git a/projects/using-ide-features/typearium/_category_.json b/projects/using-ide-features/typearium/_category_.json new file mode 100644 index 00000000..ed4e395c --- /dev/null +++ b/projects/using-ide-features/typearium/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "🥗 Typearium", + "position": 1 +} diff --git a/projects/using-ide-features/typearium/jest.config.js b/projects/using-ide-features/typearium/jest.config.js new file mode 100644 index 00000000..0bcb2272 --- /dev/null +++ b/projects/using-ide-features/typearium/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + transform: { + "^.+\\.(t|j)sx?$": [ + "@swc/jest", + { + jsc: { + target: "es2021", + }, + }, + ], + }, +}; diff --git a/projects/using-ide-features/typearium/package.json b/projects/using-ide-features/typearium/package.json new file mode 100644 index 00000000..9e928345 --- /dev/null +++ b/projects/using-ide-features/typearium/package.json @@ -0,0 +1,7 @@ +{ + "name": "analyzing-dna", + "scripts": { + "test": "jest", + "test:solutions": "cross-env TEST_SOLUTIONS=1 jest" + } +}